Stylish chrome extension solved this problem using the following steps:
- Cache the state in a background page script variable and message it back to the content script which asks for the data on its start.
- Optionally send a message to the content script from chrome.webNavigation.onCommitted which occurs before the page started loading, sometimes even before a content script runs so this method is just an additional measure. You can make it the sole method, though, by sending the same message several times using e.g. setInterval in the background page script.
- Use a
"persistent": true
background page. Arguably, it's the only method to avoid FOUC reliably in this communication scenario as non-persistent event pages need some time to load.
- Declare the content script to be injected at "document_start".
When the content script executes the document is empty, no head, no body. At this point Stylish extension, its function being style injection, simply adds a style element directly under <html>
.
In your case an additional step is needed:
- Use MutationObserver to process the page as it's being loaded (example, performance info).
manifest.json:
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"js": ["contents.js"]
"matches": ["<all_urls>"],
"run_at": "document_start",
"all_frames": true,
}
],
Content script:
var gotData = false;
chrome.runtime.sendMessage({action: 'whatDo'}, doSomething);
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action == 'doSomething') {
doSomething(msg);
}
});
function doSomething(msg) {
if (gotData || !msg || !msg.data)
return;
gotData = true;
new MutationObserver(onMutation).observe(document, {
childList: true, // report added/removed nodes
subtree: true, // observe any descendant elements
});
function onMutation(mutations, observer) {
// use the insanely fast getElementById instead of enumeration of all added nodes
var elem = document.getElementById('targetId');
if (!elem)
return;
// do something with elem
.............
// disconnect the observer if no longer needed
observer.disconnect();
}
}
Background page script:
var state;
chrome.storage.sync.get({state: true}, function(data) {
state = data.state;
});
chrome.storage.onChanged.addListener(function(changes, namespace) {
if (namespace == 'sync' && 'state' in changes) {
state = changes.state.newValue;
}
});
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action == 'whatDo') {
sendResponse({action: 'doSomething', data: state});
}
});
chrome.webNavigation.onCommitted.addListener(function(navDetails) {
chrome.tabs.sendMessage(
navDetails.tabId,
{action: 'doSomething', data: state},
{frameId: navDetails.frameId}
);
});
Repeated messaging, a simple example that doesn't check if the message was processed:
chrome.webNavigation.onCommitted.addListener(function(navDetails) {
var repetitions = 10;
var delayMs = 10;
send();
function send() {
chrome.tabs.sendMessage(
navDetails.tabId,
{action: 'doSomething', data: state},
{frameId: navDetails.frameId}
);
if (--repetitions)
setTimeout(send, delayMs);
}
});