I'm working on a Browser extension/add-on. We have it working in Chrome, so I'm trying to get it working in Firefox.
I've gotten my add-on to load in Firefox Developer Edition 49.0a2 (2016-07-25).
My extension involves a content_script set to run_at: document_start
, so it can inject a script tag before other page scripts run, so it can make an object globally available to websites.
This has seemed to work fine in Chrome, but in Firefox it has proven to be a bit of a race condition, with other page resources loading first most of the time.
Is there a strategy to load a content script in a way that it can inject & load a script before any other page scripts run?
When I add logs, I can isolate what is happening pretty nicely. In this example content-script:
// inject in-page script
console.log('STEP 1, this always happens first')
var scriptTag = document.createElement('script')
scriptTag.src = chrome.extension.getURL('scripts/inpage.js')
scriptTag.onload = function () { this.parentNode.removeChild(this) }
var container = document.head || document.documentElement
// append as first child
container.insertBefore(scriptTag, container.children[0])
Now if the file scripts/inpage.js
simply runs a log, like
console.log('STEP 2, this should always run second')
And I visit a page with a script like this:
console.log('Step 3, the page itself, should run last')
In practice, Step 2 and Step 3 run in a non-deterministic order.
Thanks a lot!
I have Firefox-compatible version of the script in a public repository on a special branch if you dare to try it yourself: https://github.com/MetaMask/metamask-plugin/tree/FirefoxCompatibility
An dynamically inserted script with an external source (<script src>
) does not block the execution of scripts, so there is no guarantee that your script would load. If your extension worked in Chrome, it was just by sheer luck.
If you really want to run some script before the rest, you have to run it inline:
var actualCode = `
// Content of scripts/inpage.js here
`;
var s = document.createElement('script');
s.textContent = actualCode;
(document.head || document.documentElement).appendChild(s);
s.remove();
Ideally, your build script would read scripts/inpage.js
, serialize it to a string and put it in the actualCode
variable. But if inpage.js
is just a few lines of code, then the above can be used.
Note that you should not inject code in the web page unless it is absolutely necessary. The reason for that is that the execution environment of the page is untrusted. If you inject at document_start
, then you can save functions and (prototype) methods that use for later (in a closure), but very careful coding is required.
If your content script is not generated by a build script and you still want to keep the scripts separate, then you can also use synchronous XMLHttpRequest
to fetch the script. Synchronous XHR is deprecated for performance reasons, so use it at your own risk. Extension code is typically bundled with your extension, so the use of sync xhr should be low-risk:
// Note: do not use synchronous XHR in production!
var x = new XMLHttpRequest();
x.open('GET', chrome.runtime.getURL('scripts/inpage.js'), false);
x.send();
var actualCode = x.responseText;
var s = document.createElement('script');
s.textContent = actualCode;
(document.head || document.documentElement).appendChild(s);
s.remove();