Calling add-on from web page in new multiprocess Firefox - javascript

dear all.
We have crypto signing extensions implemented for few browsers in our application, everything went fine, but now we faced problem with new Mozilla's multiprocess API migration (E10S aka Electrolysis).
Our web part interacts with extension which collaborates with native library written in C (we utilize c-types lib for this part).
Now Firefox is moving to multiprocess model that requires code adaptation. The most significant and complicated part for now is content-to-extension communication reimplementation. It was implemented according to related official documentation
We used bootstrap extension initialization in following manner:
function startup(params, reason) {
include("chrome/content/extmain.js");
mainWindow = winMediator.getMostRecentWindow("navigator:browser");
if (null == mainWindow) {
var windowListenerWidget = {
onOpenWindow: function (aWindow) {
winMediator.removeListener(windowListenerWidget);
var mainWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
mainWindow.addEventListener("load", function onWindowLoad() {
mainWindow.removeEventListener("load", onWindowLoad);
addAddonListener(mainWindow);
});
},
onCloseWindow: function (aWindow) {
},
onWindowTitleChange: function (aWindow, aTitle) {
}
};
winMediator.addListener(windowListenerWidget);
} else {
addAddonListener(mainWindow);
}
}
function addAddonListener(win) {
win.document.addEventListener(
"CryptoApiExtension_HandleMsg",
function (event) {
var node = event.target;
if (!node || node.nodeType != 3) {
return;
}
var response = CryptoApiExtension.handleMessage(JSON.parse(node.nodeValue));
var doc = node.ownerDocument;
node.nodeValue = JSON.stringify(response);
var event = doc.createEvent("HTMLEvents");
event.initEvent("CryptoApiExtension_response", true, false);
return node.dispatchEvent(event);
}, false, true);
}
This code above was broken with new multiprocess architecture. There are lot of documentation we have read, but still there's no way we could handle this issue.
The question is: how to adapt this code to make extension accept web page invocations?

You now need to use messageManagers and frame scripts for inter-process communication:
// bootstrap.js
function addAddonListener(win) {
win.messageManager.addEventListener(
"CryptoApiExtension_request",
function (event) {
var response = CryptoApiExtension.handleRequest(event.json);
var childMM = event.target.messageManager;
childMM.sendAsyncMessage("CryptoApiExtension_response", response);
}
);
// <...>
win.messageManager.loadFrameScript("chrome://myaddon/content/frame-script.js", true);
}
// frame-script.js
sendAsyncMessage("CryptoApiExtension_request", request);
addMessageListener(
"CryptoApiExtension_response",
function(event) {
handleResponse(event.json);
}
);

Related

How to give fake GPU info to site

I need to create web browser using CefSharp.Wpf with ability to give fake data to site for example CPU cores, browser plugins, platform name etc.
There are site that can retrieve all this info: https://www.deviceinfo.me/
My quesiton is: How to hide GPU info from this site? Using javascript or CefSharp functionality
I have tried to redefine WebGLRenderingContext.getParameter method, which gives an info about GPU renderer and vendor:
var canvas = document.createElement('canvas');
var gl;
try {
gl = canvas.getContext("webgl2") || canvas.getContext("webgl") || canvas.getContext("experimental-webgl2") || canvas.getContext("experimental-webgl");
} catch (e) {
}
var oldParam = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter){
console.log("we have guests");
if(parameter == debugInfo.UNMASKED_RENDERER_WEBGL){
return "GTX 1080";
}
if(parameter == gl.getExtension("WEBGL_debug_renderer_info").UNMASKED_RENDERER_WEBGL){
return "GTX 1080";
}
if(parameter == debugInfo.UNMASKED_RENDERER_WEBGL){
return "NVidia";
}
if(parameter == gl.VERSION){
return "GTX 1080";
}
return oldParam(parameter);
};
I expected to completely redefine this method and return some fake info, but when i called gl.getParameter(param) again, it still gave me an old gpu info
If you still want Canvas2D and WebGL to still work then you can't hide since they can finger print by actually rendering.
You could disable them with
HTMLCanvasElement.prototype.getContext = function() {
return null;
};
Though the fact they don't exist is also a data point.
Otherwise your wrapper appears to have some issues.
First you really should set the function before creating the context.
Second your last line should be
oldParam.call(this, parameter);
Also you didn't show debugInfo but you can use WebGLRenderingContext instead or you can just hard code the numbers
As for http://www.deviceinfo.me you need to make sure your patch runs in all iframes and workers before any other JavaScript.
WebGLRenderingContext.prototype.getParameter = function(origFn) {
const paramMap = {};
paramMap[0x9245] = "Foo"; // UNMASKED_VENDOR_WEBGL
paramMap[0x9246] = "Bar"; // UNMASKED_RENDERER_WEBGL
paramMap[0x1F00] = "Nobody"; // VENDOR
paramMap[0x1F01] = "Jim"; // RENDERER
paramMap[0x1F02] = "Version 1.0"; // VERSION
return function(parameter) {
return paramMap[parameter] || origFn.call(this, parameter);
};
}(WebGLRenderingContext.prototype.getParameter);
// --- test
const gl = document.createElement('canvas').getContext('webgl');
const ext = gl.getExtension('WEBGL_debug_renderer_info');
show(gl, gl, [
'VENDOR',
'RENDERER',
'VERSION',
]);
if (ext) {
show(gl, ext, [
'UNMASKED_VENDOR_WEBGL',
'UNMASKED_RENDERER_WEBGL',
]);
}
function show(gl, base, params) {
for (const param of params) {
console.log(param, ':', gl.getParameter(base[param]));
}
}
There is WebGLRenderingContext and WebGL2RenderingContext

Unable to make collaborative editing work using Prosemirror

I am using prosemirror to build a collaborative editor, where multiple people can edit one document. I wrote the following code, based on the example given here - http://prosemirror.net/docs/guides/collab/
Here is the code-
const { EditorState } = require('prosemirror-state');
const { EditorView } = require('prosemirror-view');
const { DOMParser } = require("prosemirror-model");
const {schema} = require("./schema");
var collab = require("prosemirror-collab");
function Authority(doc) {
this.doc = doc
this.steps = []
this.stepClientIDs = []
this.onNewSteps = []
}
Authority.prototype.receiveSteps = function(version, steps, clientID) {
if (version != this.steps.length) return
var self = this
// Apply and accumulate new steps
steps.forEach(function(step) {
self.doc = step.apply(self.doc).doc
self.steps.push(step)
self.stepClientIDs.push(clientID)
})
// Signal listeners
this.onNewSteps.forEach(function(f) { f() })
}
Authority.prototype.stepsSince = function(version) {
return {
steps: this.steps.slice(version),
clientIDs: this.stepClientIDs.slice(version)
}
}
var auth = new Authority('');
collabEditor(auth)
function collabEditor(authority) {
var view = new EditorView(document.querySelector("#editor"), {
state: EditorState.create({schema: schema, plugins: [collab.collab()]}),
dispatchTransaction: function(transaction) {
var newState = view.state.apply(transaction)
view.updateState(newState)
var sendable = collab.sendableSteps(newState)
if (sendable)
authority.receiveSteps(sendable.version, sendable.steps,
sendable.clientID)
}
})
authority.onNewSteps.push(function() {
var newData = authority.stepsSince(collab.getVersion(view.state))
view.dispatch(
collab.receiveTransaction(view.state, newData.steps, newData.clientIDs))
})
return view
}
When i run this code (after installing all the dependencies and setting up a simple server in nodejs) I am basically able to edit a text box but I am not able to open two tabs in chrome and see the collaboration happen. What am i doing wrong?
Will love some feedback.
This is the example code for a simple, single-page, no-external-communication setup. As such, no, it won't communicate to other tabs. For that, you'd have to move the authority somewhere else and set up pages to actually communicate with it over HTTP or websockets. (See for example this demo.)

How does jQuery do async:false in its $.ajax method?

I have a similar question here, but I thought I'd ask it a different way to cast a wider net. I haven't come across a workable solution yet (that I know of).
I'd like for XCode to issue a JavaScript command and get a return value back from an executeSql callback.
From the research that I've been reading, I can't issue a synchronous executeSql command. The closest I came was trying to Spin Lock until I got the callback. But that hasn't worked yet either. Maybe my spinning isn't giving the callback chance to come back (See code below).
Q: How can jQuery have an async=false argument when it comes to Ajax? Is there something different about XHR than there is about the executeSql command?
Here is my proof-of-concept so far: (Please don't laugh)
// First define any dom elements that are referenced more than once.
var dom = {};
dom.TestID = $('#TestID'); // <input id="TestID">
dom.msg = $('#msg'); // <div id="msg"></div>
window.dbo = openDatabase('POC','1.0','Proof-Of-Concept', 1024*1024); // 1MB
!function($, window, undefined) {
var Variables = {}; // Variables that are to be passed from one function to another.
Variables.Ready = new $.Deferred();
Variables.DropTableDeferred = new $.Deferred();
Variables.CreateTableDeferred = new $.Deferred();
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'drop table Test;',
[],
Variables.DropTableDeferred.resolve()
// ,WebSqlError
);
});
$.when(Variables.DropTableDeferred).done(function() {
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'CREATE TABLE IF NOT EXISTS Test'
+ '(TestID Integer NOT NULL PRIMARY KEY'
+ ',TestSort Int'
+ ');',
[],
Variables.CreateTableDeferred.resolve(),
WebSqlError
);
});
});
$.when(Variables.CreateTableDeferred).done(function() {
for (var i=0;i < 10;i++) {
myFunction(i);
};
Variables.Ready.resolve();
function myFunction(i) {
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'INSERT INTO Test(TestID,TestSort) VALUES(?,?)',
[
i
,i+100000
]
,function() {}
,WebSqlError
)
});
};
});
$.when(Variables.Ready).done(function() {
$('#Save').removeAttr('disabled');
});
}(jQuery, window);
!function($, window, undefined) {
var Variables = {};
$(document).on('click','#Save',function() {
var local = {};
local.result = barcode.Scan(dom.TestID.val());
console.log(local.result);
});
var mySuccess = function(transaction, argument) {
var local = {};
for (local.i=0; local.i < argument.rows.length; local.i++) {
local.qry = argument.rows.item(local.i);
Variables.result = local.qry.TestSort;
}
Variables.Return = true;
};
var myError = function(transaction, argument) {
dom.msg.text(argument.message);
Variables.result = '';
Variables.Return = true;
}
var barcode = {};
barcode.Scan = function(argument) {
var local = {};
Variables.result = '';
Variables.Return = false;
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'SELECT * FROM Test WHERE TestID=?'
,[argument]
,mySuccess
,myError
)
});
for (local.I = 0;local.I < 3; local.I++) { // Try a bunch of times.
if (Variables.Return) break; // Gets set in mySuccess and myError
SpinLock(250);
}
return Variables.result;
}
var SpinLock = function(milliseconds) {
var local = {};
local.StartTime = Date.now();
do {
} while (Date.now() < local.StartTime + milliseconds);
}
function WebSqlError(tx,result) {
if (dom.msg.text()) {
dom.msg.append('<br>');
}
dom.msg.append(result.message);
}
}(jQuery, window);
Is there something different about XHR than there is about the executeSql command?
Kind of.
How can jQuery have an async=false argument when it comes to Ajax?
Ajax, or rather XMLHttpRequest, isn't strictly limited to being asynchronous -- though, as the original acronym suggested, it is preferred.
jQuery.ajax()'s async option is tied to the boolean async argument of xhr.open():
void open(
DOMString method,
DOMString url,
optional boolean async, // <---
optional DOMString user,
optional DOMString password
);
The Web SQL Database spec does also define a Synchronous database API. However, it's only available to implementations of the WorkerUtils interface, defined primarily for Web Workers:
window.dbo = openDatabaseSync('POC','1.0','Proof-Of-Concept', 1024*1024);
var results;
window.dbo.transaction(function (trans) {
results = trans.executeSql('...');
});
If the environment running the script hasn't implemented this interface, then you're stuck with the asynchronous API and returning the result will not be feasible. You can't force blocking/waiting of asynchronous tasks for the reason you suspected:
Maybe my spinning isn't giving the callback chance to come back (See code below).

Routing knockout.js app with Sammy.js and history with html4 support

I just started playing around with sammy.js and first thing I want to do is to test how history changes works. And it works as expected, even better, but once I open IE10 and switched to IE9 browser mode, everything felled apart. If I'm not setting the links with hash, IE9 just keeps following the links. Same problem with IE8 of course.
At this moment I only have this bit of code related with sammy
App.sm = $.sammy('#content', function() {
this.get('/', function(context) {
console.log('Yo yo yo')
});
this.get('/landing', function(context) {
console.log('landing page')
});
this.get('/:user', function(context) {
console.log(context)
});
});
And initiator
$(function() {
App.sm.run('/');
});
I also looked at this example which contains three types of the links, normal ones, hash and again normal ones, but working properly on IE9 and IE8. that makes me think that somehow it should be possible to make sammy.js support html5 history and html4 at the same time.
So my question would be, how I can do achieve that?
Update
I found the way to make it work on IE
I just added this snippet:
this.bind('run', function(e) {
var ctx = this;
$('body').on('click', 'a', function(e) {
e.preventDefault();
ctx.redirect($(e.target).attr('href'));
return false;
});
});
Anyway, I'm still having a problem with entry to the website, html5 history supporting browsers is always redirected to the domain.com, no matter what was initial url.
So I wonder how I should configure sammy.js to work peroperly. Or maybe anyone could recommend
some other router which would work nicely with knockout.js.
For a number of reasons, including search engine spiders and link sharing; your site should work without the History API. If a user sees http://example.org/poodles/red and wants to show someone else the red poodles on your web site, they copy the link. The other visitor needs to be able to see the same content at the same URL; even if they don't start at the homepage.
For this reason, I suggest using the History API as a progressive enhancement. Where it's available, you should use it to provide a better UX. Where it's not available, links should function as normal.
Here's an example Router (like Sammy) which simply allows the default browser navigation if history.pushState isn't available.
And about the Knockout part; I have used this in a KnockoutJS project and it works well.
(function($){
function Route(path, callback) {
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
// replace "/:something" with a regular expression fragment
var expression = escapeRegExp(path).replace(/\/:(\w+)+/g, "/(\\w+)*");
this.regex = new RegExp(expression);
this.callback = callback;
}
Route.prototype.test = function (path) {
this.regex.lastIndex = 0;
var match = this.regex.exec(path);
if (match !== null && match[0].length === path.length) {
// call it, passing any matching groups
this.callback.apply(this, match.slice(1));
return false;
}
};
function Router(paths) {
var self = this;
self.routes = [];
$.each(paths, function (path, callback) {
self.routes.push(new Route(path, callback));
});
self.listen();
self.doCallbacks(location.pathname);
}
Router.prototype.listen = function () {
var self = this, $document = $(document);
// watch for clicks on links
// does AJAX when ctrl is not down
// nor the href ends in .html
// nor the href is blank
// nor the href is /
$document.ready(function(e){
$document.on("click", "[href]", function(e){
var href = this.getAttribute("href");
if ( !e.ctrlKey && (href.indexOf(".html") !== href.length - 5) && (href.indexOf(".zip") !== href.length - 4) && href.length > 0 && href !== "/") {
e.preventDefault();
self.navigate(href);
}
});
});
window.addEventListener("popstate", function(e) {
self.doCallbacks(location.pathname);
});
};
Router.prototype.navigate = function(url) {
if (window.history && window.history.pushState) {
history.pushState(null, null, url);
this.doCallbacks(location.pathname);
}
};
Router.prototype.doCallbacks = function(url) {
var routes = this.routes;
for (var i=0; i<routes.length; i++){
var route = routes[i];
// it returns false when there's a match
if (route.test(url) === false) {
console.log("nav matched " + route.regex);
return;
}
}
if (typeof this.fourOhFour === "function") {
this.fourOhFour(url);
} else {
console.log("404 at ", url);
}
};
window.Router = Router;
}).call(this, jQuery);
Example usage:
router = new Router({
"/": function () {
},
"/category/:which": function (category) {
},
"/search/:query": function(query) {
},
"/search/:category/:query": function(category, query) {
},
"/:foo/:bar": function(foo, bar) {
}
});
router.fourOhFour = function(requestURL){
};

How to make a Firefox addon listen to xmlhttprequests from a page?

Background
I have an existing extension designed to accompany a browser-based game (The extension is mine, the game is not). The extension had been scraping the pages as they came in for the data it needed and making ajax requests for taking any actions.
Problem
The game developers recently changed a number of actions on the site to use ajax requests and I am thus far unable to get the data from those requests.
What I have so far
function TracingListener() {
}
TracingListener.prototype =
{
originalListener: null,
receivedData: [], // array for incoming data.
onDataAvailable: function(request, context, inputStream, offset, count)
{
var binaryInputStream = CCIN("#mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream");
var storageStream = CCIN("#mozilla.org/storagestream;1", "nsIStorageStream");
binaryInputStream.setInputStream(inputStream);
storageStream.init(8192, count, null);
var binaryOutputStream = CCIN("#mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream");
binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));
// Copy received data as they come.
var data = binaryInputStream.readBytes(count);
this.receivedData.push(data);
binaryOutputStream.writeBytes(data, count);
this.originalListener.onDataAvailable(request, context,storageStream.newInputStream(0), offset, count);
},
onStartRequest: function(request, context) {
this.originalListener.onStartRequest(request, context);
},
onStopRequest: function(request, context, statusCode)
{
try {
if (request.originalURI && piratequesting.baseURL == request.originalURI.prePath && request.originalURI.path.indexOf("/index.php?ajax=") == 0) {
dump("\nProcessing: " + request.originalURI.spec + "\n");
var date = request.getResponseHeader("Date");
var responseSource = this.receivedData.join();
dump("\nResponse: " + responseSource + "\n");
piratequesting.ProcessRawResponse(request.originalURI.spec, responseSource, date);
}
} catch(e) { dumpError(e);}
this.originalListener.onStopRequest(request, context, statusCode);
},
QueryInterface: function (aIID) {
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
}
}
hRO = {
observe: function(aSubject, aTopic, aData){
try {
if (aTopic == "http-on-examine-response") {
if (aSubject.originalURI && piratequesting.baseURL == aSubject.originalURI.prePath && aSubject.originalURI.path.indexOf("/index.php?ajax=") == 0) {
var newListener = new TracingListener();
aSubject.QueryInterface(Ci.nsITraceableChannel);
newListener.originalListener = aSubject.setNewListener(newListener);
dump("\n\nObserver Processing: " + aSubject.originalURI.spec + "\n");
for (var i in aSubject) {
dump("\n\trequest." + i);
}
}
}
} catch (e) {
dumpError(e);
}
},
QueryInterface: function(aIID){
if (aIID.equals(Ci.nsIObserver) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
}
};
var observerService = Cc["#mozilla.org/observer-service;1"] .getService(Ci.nsIObserverService);
observerService.addObserver(hRO, "http-on-examine-response", false);
What's happening
The above code is notified properly when an http request is processed. The uri is also available and is correct (it passes the domain/path check) but the responseSource that gets dumped is, as far as I can tell, always the contents of the first http request made after the browser opened and, obviously, not what I was expecting.
The code above comes in large part from http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/. I'm really hoping that it's just something small that I've overlooked but I've been banging my head against the desk for days on this one, and so now I turn to the wisdom of SO. Any ideas?
but the responseSource that gets
dumped is, as far as I can tell,
always the contents of the first http
request made after the browser opened
and, obviously, not what I was
expecting.
There is a problem with the code above. The "receivedData" member is declared on prototype object and have empty array assigned. This leads to every instantiation of the TracingListener class to be using the same object in memory for receivedData. Changing your code to might solve he problem:
function TracingListener() {
this.receivedData = [];
}
TracingListener.prototype =
{
originalListener: null,
receivedData: null, // array for incoming data.
/* skipped */
}
Not sure though if this will solve your original problem.

Categories