How to implement message passing callbacks in an all-in-one (Edge/Firefox/Chrome) browser extension's content script? - firefox

Development Environment OS: Windows 7 Enterprise LTS
Browser compatibility minimum requirements: Should support all Edge, Firefox, Chrome browsers, as of 2018.
Current ongoing issue: Unable to run VM on dev workstation; Cannot run Windows 10 VMs to debug Microsoft Edge extensions.
To explain:
An "all-in-one browser extension" refers to a browser extension code that uses the same code with minor differences to work on various WebExtensions / Chrome Extensions supported browsers. At bare minimum, the same codebase should work and run on Edge, Firefox, and Chrome with very minor changes.
Callbacks on the content scripts for Edge/Firefox/Chrome extensions are handled differently.
For unknown reasons, I cannot run VM on my workstation machine. When VM is running, VM client is black. This is a localized issue on my end that I cannot resolve, so I'm forced to find a different solution/alternative.
How are they handled differently on the content scripts:
Edge: browser.runtime.sendMessage uses callbacks, and returns undefined.
Firefox: browser.runtime.sendMessage uses Promises, and returns a Promise.
Chrome: chrome.runtime.sendMessage uses callbacks, and returns undefined.
According to various references:
Firefox / Chrome / MS Edge extensions using chrome.* or browser.*
https://www.smashingmagazine.com/2017/04/browser-extension-edge-chrome-firefox-opera-brave-vivaldi/
On the content scripts, you can declare the following JavaScript snippet at the top in order to create a global variable that can be referenced everywhere else:
//Global "browser" namespace definition.
window.browser = (function() {
return window.msBrowser || window.browser || window.chrome;
})();
Unfortunately, because of the issue I'm experiencing (VM not running), I cannot tell if window.msBrowser is still being used. And this solution is not helpful for me when handling message callbacks when using namespace.runtime.sendMessage.
With all that said, my main question is: How to write a message passing function that can handle callbacks properly?
Currently, I'm using the following code:
function sendGlobalMessage(messageRequest, callback) {
if (chrome && window.openDatabase) {
//This is Chrome browser
chrome.runtime.sendMessage(messageRequest, callback);
}
else if (browser) {
try {
//Edge will error out because of a quirk in Edge IndexedDB implementation.
//See https://gist.github.com/nolanlawson/a841ee23436410f37168
let db = window.indexedDB.open("edge", (Math.pow(2, 30) + 1));
db.onerror = function(e) {
throw new Error("edge is found");
};
db.onsuccess = function(e) {
//This is Firefox browser.
browser.runtime.sendMessage(messageRequest).then(callback);
};
}
catch (e) {
//This is Edge browser
browser.runtime.sendMessage(messageRequest, callback);
}
}
}
I truly felt this is a hacky solution, because the code is based off of browser platform exclusive quirks in order to separate chrome.runtime.sendMessage and browser.runtime.sendMessage API calls, so as to handle callbacks in their respective platforms. I really wanted to change this.
So I'm asking what better ways are there, out there, that is useful to detect the different platforms, and handle message passing callbacks properly at the same time?
Thanks in advance.

I believed I solved it.
EDIT: The FINAL final version (updated and more stable, less message passing):
//Global "browser" namespace definition, defined as "namespace". Can be renamed to anything else.
window.namespace = (function() {
return window.browser || window.chrome;
})();
function sendGlobalResponse(message, callback){
if (window.namespace === window.chrome) {
//Chrome
window.namespace.runtime.sendMessage(message, callback);
}
else if (window.namespace === window.browser) {
//Using instanceof to check for object type, and use the returned evaluation as a truthy value.
let supportPromises = false;
try {
supportPromises = window.namespace.runtime.getPlatformInfo() instanceof Promise;
}
catch(e) { }
if (supportPromises){
//Firefox
window.namespace.runtime.sendMessage(message).then(callback);
}
else {
//Edge
window.namespace.runtime.sendMessage(message, callback);
}
}
}
(Original Post):
The final version (Now obsoleted):
//Global "browser" namespace definition.
window.namespace = (function() {
return window.browser || window.chrome;
})();
function sendGlobalResponse(message, callback){
if (window.namespace === window.chrome) {
//Chrome
window.namespace.runtime.sendMessage(message, callback);
}
else if (window.namespace === window.browser) {
let returnValue = window.namespace.runtime.sendMessage({});
if (typeof returnValue === "undefined"){
//Edge
window.namespace.runtime.sendMessage(message, callback);
}
else {
//Firefox
window.namespace.runtime.sendMessage(message).then(callback);
}
}
}
In the second if statement, by checking to see if the return value of a window.browser.runtime.sendMessage is a Promise or undefined, we can detect if the platform is Firefox or Edge.
I think this is the only solution to handle message passing callbacks/message responses on the content scripts.
I really couldn't think of a better solution than this. So I'll be using this from now on.
But if anyone else knows a better way, a way where you don't need to send out 1 extra dummy message for Firefox and Edge per function call, that would be great!
It sucks that anything inside the content script is not persistent, and even if you store information about what platform the code is being run on, you still have to fetch the information from the background script before filtering out which runtime.sendMessage function to call on, so it doesn't really save much time.

Related

How do you get any useful call stack information when debugging react native?

When I try to debug react native, it usually looks like this.
at ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext (ReactCompositeComponent.js:785)
at ReactCompositeComponentWrapper._renderValidatedComponent (ReactCompositeComponent.js:811)
at ReactCompositeComponentWrapper._updateRenderedComponent (ReactCompositeComponent.js:735)
at ReactCompositeComponentWrapper._performComponentUpdate (ReactCompositeComponent.js:715)
at ReactCompositeComponentWrapper.updateComponent (ReactCompositeComponent.js:634)
at ReactCompositeComponentWrapper.receiveComponent (ReactCompositeComponent.js:534)
at Object.receiveComponent (ReactReconciler.js:131)
at ReactCompositeComponentWrapper._updateRenderedComponent (ReactCompositeComponent.js:737)
There are no useful information and if I use debugger command to see what initiated the 50-depth call stack, then it almost always comes down to the onmessage method in `debuggerWorker.js', and it's almost zero useful.
How do you really debug your react native app?
onmessage = function(message) {
var object = message.data;
var sendReply = function(result) {
postMessage({replyID: object.id, result: result});
};
var handler = messageHandlers[object.method];
if (handler) {
// Special cased handlers
handler(object, sendReply);
} else {
// Other methods get called on the bridge
var returnValue = [[], [], [], 0];
try {
if (typeof __fbBatchedBridge === 'object') {
returnValue = __fbBatchedBridge[object.method].apply(null, object.arguments);
}
} finally {
sendReply(JSON.stringify(returnValue));
}
}
};
I tend to use the stack traces to figure out the general area where the error occurred and then use the web debugger to actually find out what when wrong.
https://facebook.github.io/react-native/docs/debugging.html#chrome-developer-tools
Its actually pretty nice, you can open all your js files in the sources tab of the chrome dev tools and then put debug points in and just step through your file and view your variables.

How to send message or establish 2 way communication between two XUL Overlay Firefox extensions/add-ons

I have an XUL Overlay Firefox extension, I need to develop a dummy XUL extension that establishes connection with the original extension and sends a set of parameters (message) to the original extension. In short, I have to trigger my original extension with my dummy extension.
Probably the easiest way to do this is to have the original extension listening for a custom event on the base browser window. The dummy extension can then create and dispatch the event with whatever custom data is desired.
Creating and dispatching the event from the dummy:
function sendDataToMainExtension(data) {
if (typeof window === "undefined") {
//If there is no window defined, get the most recent.
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
}
//This assumes that this event is being both sent from
// and received by privileged (main add-on) code.
var event = new CustomEvent('MyExtensionName-From-Dummy', { 'detail': data });
window.dispatchEvent(event);
}
You may need to take the same steps for making sure the data is visible on the receiving end as would be necessary when firing from privileged code to non-privileged code.
Listening for the event in main:
Components.utils.import("resource://gre/modules/Services.jsm");
const Ci = Components.interfaces;
//Listen for the event on all windows as it is unknown on which one
// the event will be sent.
function loadIntoWindow(myWindow) {
myWindow.addEventListener("MyExtensionName-From-Dummy",
receiveMessageFromDummy, false);
}
function unloadFromWindow(myWindow) {
myWindow.removeEventListener("MyExtensionName-From-Dummy",
receiveMessageFromDummy, false);
}
function forEachOpenWindow(fn) {
// Apply a function to all open browser windows
var windows = Services.wm.getEnumerator("navigator:browser");
let windowCount =0;
while (windows.hasMoreElements()) {
windowCount++;
fn(windows.getNext().QueryInterface(Ci.nsIDOMWindow));
}
}
function receiveMessageFromDummy(event) {
var dataFromDummy = event.detail;
//Do whatever was desired with the data.
}
var WindowListener = {
onOpenWindow: function(aWindow)
{
let domWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
function onWindowLoad()
{
domWindow.removeEventListener("load",onWindowLoad);
if (domWindow.document.documentElement.getAttribute("windowtype")
== "navigator:browser") {
loadIntoWindow(domWindow);
}
}
domWindow.addEventListener("load",onWindowLoad);
},
onCloseWindow: function(xulWindow) { }, // Each window has an unload event handler.
onWindowTitleChange: function(xulWindow, newTitle) { }
};
//Listen for the custom event on all current browser windows.
forEachOpenWindow(loadIntoWindow);
//Listen for the custom event on any new browser window.
Services.wm.addListener(WindowListener);
The data sent should be available as event.detail within the receiveMessageFromDummy() function.
The code above provides one way communication. Two way communication is obtained just duplicating the code to communicate in the other direction with a different custom event. In other words, by having the main extension dispatching a different custom event called something like MyExtensionName-From-Main and having the dummy extension listening for that event. The code is exactly the same as above, but with the event name changed and the function called being receiveMessageFromMain().
Alternately, you could use Window.postMessage(). Doing so sends a "message" event for which you can listen. However, doing so leads to complications which are easier to avoid by using a custom event (e.g. you have to account for the fact that any code (i.e. some other random extension) could be using this event for their own purpose).
Note: The code to loop through windows was originally taken from Converting an old overlay-based Firefox extension into a restartless addon which that author re-wrote as the initial part of How to convert an overlay extension to restartless on MDN. It has been modified multiple times from that code. It may have even earlier versions from other sources.

Getting nsIChannel request's nsIDOMWindow always returns null on e10s

I have an observer on "http-on-modify-request" and I need to get the DOMWindow that request is associated with.
The following code was taken from AdBlock Plus and is based on this article.
function getRequestWindow(/**nsIChannel*/ channel) /**nsIDOMWindow*/
{
try
{
if (channel.notificationCallbacks)
return channel.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
} catch(e) {}
try
{
if (channel.loadGroup && channel.loadGroup.notificationCallbacks)
return channel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
} catch(e) {}
return null;
}
However, this code is no longer working on multiprocess Firefox (v36+, right now on Firefox Nightly).
Any ideas?
I haven't tried that myself, so take with a grain of salt:
Instead of getting the nsIDOMWindow itself from the channel you can get the innerWindowId from the nsIChannel.loadInfo. The window id can then be resolved inside a frame script by QueryInterfaceing to nsIDOMWindowUtils.
To talk to content, the recommended path for this sort of use case is probably Frame Scripts, see this documentation on MDN for more.

Load an external JS library into a page using Greasemonkey

I want a translator in my Firefox. I find some code from internet. but it doesn't run in my Firefox. I have installed Greasemonkey.
function loadBingTranslator() {
script = document.createElement('script');
script.src = 'http://dict.bing.com.cn/cloudwidget/Scripts/Generated/BingTranslate_Selection_ShowIcon.js';
script.onload = initBingTranslator;
document.body.appendChild(script);
};
function initBingTranslator() {
BingCW.Init({
MachineTranslation: true,
WebDefinition: true
});
}
loadBingTranslator();
Such a script must account for the GM sandbox, and also (usually) allow time for the library to load and initialize.   See Avoid Common Pitfalls (in Greasemonkey).
So, you would use this library like so:
//--- Load the library.
var D = document;
var appTarg = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
var jsNode = D.createElement ('script');
jsNode.src = 'http://dict.bing.com.cn/cloudwidget/Scripts/Generated/BingTranslate_Selection_ShowIcon.js';
jsNode.addEventListener ("load", initBingTranslatorOnDelay, false);
appTarg.appendChild (jsNode);
//--- Allow some time for the library to initialize after loading.
function initBingTranslatorOnDelay () {
setTimeout (initBingTranslator, 666);
}
//--- Call the library's start-up function, if any. Note needed use of unsafeWindow.
function initBingTranslator () {
unsafeWindow.BingCW.Init ( {
AppID: "GM Foo",
MachineTranslation: true,
WebDefinition: true
} );
}
Issues, some specific to this question:
onload is not available; See the pitfalls. Event handlers cannot be set this way in GM. Also, addEventListener() is the best practice anyway.
Accessing JS (including libraries we load) in the page scope, requires unsafeWindow.
That app appears to want an AppID.
Sometimes, libraries like this can be loaded in the GM scope instead of the page scope, using the // #require directive.
I did not try that with this library, but with others, it may be possible.   Do not try this with untrusted libraries, as they gain extra abilities to infect your machine, once inside the GM scope.
Don't use reserved words, like "script", for variable names.
My JavaScript Console is outputting a "Component is not available"
line 10: script.onload = initBingTranslator;
So I fixed changed it to ... = initBingTranslator() because it is a function.
Now it says "BingCW is not definded"
Line 15: BingCW.Init({
MachineTranslation: true,
WebDefinition: true
});
And it is right, not sure if something is missing or this is supposed to only work in IE, I would find a Google translator solution personally (or just use an existing add-on).
Bing Dictionary hasd published a Firefox addon.
You can use it directly.

Ajax call: What is the difference between new ActiveXObject("Msxml2.XMLHTTP") and new ActiveXObject("Microsoft.XMLHTTP")?

I hope both the object invocations are referring to the ActiveXObject.
But why are we passing two different parameters to work in IE.
1. Msxml2.XMLHTTP and
2. Microsoft.XMLHTTP
Are they both same ? Or Are they browser dependent(IE7 and IE8) ?
I used both. I did not get any exception. Both are looking same for me. I am using IE 8.
Both are actually outdated. There are various versions of Microsoft's venerable MSXML ActiveX object (I believe the last one was version 5.0 and came with some version of Office.) The versions have minor differences in behavior, and bug fixes that usually don't come into play in AJAX scenarios.
Starting with IE7, Microsoft supported the standardized "XmlHttpRequest" object that other modern browsers had adopted. See http://msdn.microsoft.com/en-us/library/ms537505(VS.85).aspx. You should definitely be using that as IE7 is now the de-facto lowest common denominator. IE6 has been declared dead by most major organizations, so there's no reason to support the old Microsoft-specific ActiveX ProgIDs.
And of course, there is very little reason these days to roll your own AJAX calls, as libraries like jQuery and ASP.NET Ajax do it for you, abstracting away these obscure browser quirks. I would strongly suggest learning one of those libraries.
Jordan Rieger
jquery (at least 1.4.2) has problem on $.ajax() calling. It makes great memory leakage (like fountain)
tragedy code:
if ( window.ActiveXObject ) {
jQuery.ajaxSettings.xhr = function() {
if ( window.location.protocol !== "file:" ) {
try {
return new window.XMLHttpRequest();
} catch(xhrError) {}
}
try {
return new window.ActiveXObject("Microsoft.XMLHTTP");
} catch(activeError) {}
};
}
resolution:
if ( window.ActiveXObject ) {
jQuery.ajaxSettings.xhr = function() {
if ( window.location.protocol !== "file:" ) {
if ( window.ActiveXObject ) {
try {
return new window.ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {}
}
try {
return new window.XMLHttpRequest();
} catch(xhrError) {}
}
try {
return new window.ActiveXObject("Microsoft.XMLHTTP");
} catch(activeError) {}
};
}

Resources