2

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.

tom_mai78101
  • 2,383
  • 2
  • 32
  • 59
  • Why can't you simply use Mozilla's web-extension polyfill like everyone else? – wOxxOm May 30 '18 at 15:15
  • @wOxxOm It's not for Edge. https://github.com/mozilla/webextension-polyfill Also, there are some crucial checks missing. https://github.com/mozilla/webextension-polyfill/issues/68 – tom_mai78101 May 30 '18 at 15:32
  • In Edge/Firefox it'll simply do nothing because they already have `browser` API. As for that issue, it was fixed already in the github repo, the fix itself was extremely trivial btw. – wOxxOm May 30 '18 at 19:52
  • @wOxxOm Why is the issue still open? :/ https://github.com/mozilla/webextension-polyfill/issues/3 – tom_mai78101 May 30 '18 at 20:38
  • I'm not a maintainer so I don't know why. Looks like Edge is a complete mess though (quite expectedly) so this polyfill won't help it. – wOxxOm May 31 '18 at 04:02
  • @wOxxOm Thanks. Looks like I'll be sticking to my answer for now. – tom_mai78101 May 31 '18 at 11:29

1 Answers1

1

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.

tom_mai78101
  • 2,383
  • 2
  • 32
  • 59