Reputation: 477
I'm trying to create my first Chrome browser extension. It should use content scripts to manipulate the DOM on every page of the specified domain. Initially I created a style rule in style.css
using only selectors that already existed in the page I was manipulating—this method worked as expected.
I then decided to extend the functionality by adding options, allowing the user to choose from 3 states which relate to 3 different style rules. I added scripts.js
to set a class based on the chosen option that I would use as a selector to apply the appropriate style rule. The problem is that now I have to wait for the state to be read from chrome storage before my custom class is applied which means there's a flash of the default styles on the page before my styles take effect.
What method should I use to prevent the delay before my styles load?
manifest.json (partial)
"content_scripts": [
{
"js": [ "scripts.js" ],
"css": [ "style.css" ],
"matches": [ "https://example.com/*" ]
}
]
scripts.js
chrome.storage.sync.get("state", function (obj) {
var elem = document.getElementById('targetId');
if (obj.state === 'targetState') {
elem.className += ' myClass';
}
});
style.css
.myClass {
/* do something */
}
Upvotes: 4
Views: 751
Reputation: 73506
Stylish chrome extension solved this problem using the following steps:
"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.<html>
.In your case an additional step is needed:
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);
}
});
Upvotes: 4