4

I have an addon that changes the look of a website completely by injecting a CSS file. It works but for a split second before the new look appear the old look is there, thus causing the "flicker" each time a page loads. The new look changes the background color etc. so the switch is very noticeable. (I only notice this on my desktop, not on my laptop. I don't know why but other users report it as well, perhaps the faster computer makes the page show faster than it takes to get the CSS injected?). The CSS injection need to happen so the injected CSS is the most important (as in last).

What I've tried that cause this issue (code afterwards):

  1. Manifest: Made sure css is in web_accessible_resources
  2. Manifest: Doing the injection directly in content_scripts
  3. Manifest: Doing it through a javascrip run from the content_scripts
  4. Manifest: Making sure all content-scripts are run at document_start
  5. Manifest: Tried running the injection from a script running on the background page
  6. JS Injection Timing: Added eventlistener for DOMSubtreeModified
  7. JS injection Timing: Added eventlistener for chrome.webNavigation.onCommitted
  8. JS Injection Timing: Waiting for document.head / document.body
  9. JS Injection Method: appendChild
  10. JS Injection Method: chrome.tabs.executeScript()
  11. JS Injection Code: Link element linking to css file in extension
  12. JS Injection Code: Directly executed javascript

Code Examples:

Manifest:

{
  "manifest_version": 2,
  "name": "foo",
  "short_name": "bar",
  "description": "baz",
  "options_page": "options.html",
  "version": "2.1.1",
  "homepage_url": "http://google.com/",
  "permissions": ["storage", "*://google.com/*", "webNavigation", "tabs", "activeTab"],
  "browser_action": {
    "default_icon": "icon16.png",
    "default_title": "title",
    "default_popup": "popup.html"
  },
  "icons": {
    "16": "icon16.png",
    "48": "icon48.png",
    "128": "icon128.png"
  },
  "content_scripts": [
    {
      "matches": ["*://google.com/*"],
      "js": ["carbonicEditionScript.js"],
      "all_frames": true,
      "run_at": "document_start"
    }
  ],
  "background": {
    "page": "popup.html"
  },
  "web_accessible_resources": ["carbonicEditionStyle.css"]
}

carbonicEditionScript.js

document.addEventListener('DOMSubtreeModified', injectStyle, false);
function injectStyle(){
    document.removeEventListener('DOMSubtreeModified', injectStyle, false);
    var style = document.createElement('style');
    style.setAttribute("id", "CarbonicEditionStyle");
    style.setAttribute("class", "CarbonicEditionStyle");
    style.setAttribute("type", "text/css");
    style.appendChild(document.createTextNode(css));
    document.getElementsByTagName("html")[0].appendChild(style);
}

carbonicEditionScriptAlternative.js

document.addEventListener('DOMSubtreeModified', injectCSS, false);
function injectCSS(){
var style = document.createElement('link');
style.rel = 'stylesheet';
style.type = 'text/css';
style.href = chrome.extension.getURL('carbonicEditionStyle.css');
if(document.head){
  document.removeEventListener('DOMSubtreeModified', injectCSS, false);
  (document.head||document.documentElement).appendChild(style);
}}

background.js

chrome.webNavigation.onCommitted.addListener(function(o) {
  chrome.tabs.executeScript(o.tabId, {
        code: "var css = 'body{background-color: green !important;}'; var style = document.createElement('style'); style.setAttribute('id', 'CarbonicEditionStyle'); style.setAttribute('class', 'CarbonicEditionStyle'); style.setAttribute('type', 'text/css'); style.appendChild(document.createTextNode(css)); document.getElementsByTagName('html')[0].appendChild(style);"
  });
}, {
  url: [{hostContains: 'google.com'}]
});

Does anyone know what's going on? All the solutions above work but the flicker is still happening.

1 Answer 1

4

Stylish-chrome extension fixed the flicker just by using webNavigation.onCommited event so you should've been already able to fix the issue. However, the problem you experience might be caused by asynchronous reading of the css code from the extension package since you mention web_accessible_resources. In this case cache it in chrome.storage.local or sessionStorage or localStorage. Or consider embedding the CSS inside your content script.

Some [probably redundant] notes on the code you've posted:

  1. Don't use DOMSubtreeModified event because it's a) not actually needed - (you can inject elements even before the webpage is parsed) and b) it's ancient/deprecated/slow/bad.

    So the entire content script might be:

    var style = document.createElement('style');
    style.id = "CarbonicEditionStyle";
    style.className = "CarbonicEditionStyle";
    style.type = "text/css";
    style.textContent = "body{background-color: green !important;}";
    (document.body || document.head || document.documentElement).appendChild(style);
    
  2. Use runAt: "document_start" in executeScript as by default it's document_idle which occurs usually only when the DOM is loaded and parsed:

    chrome.tabs.executeScript(o.tabId, {runAt: "document_start", code: "......"});
    
  3. Consider injecting the CSS directly via insertCSS (not really needed to eliminate the flicker and it won't allow you to disable the injected style but for the sake of completeness):

    chrome.tabs.insertCSS(o.tabId, {runAt: "document_start", code: "body{background....."});
    
  4. "matches": ["*://google.com/*"], won't match www.google.com which is used by default so it should be "matches": ["*://*.google.com/*"],
  5. google.com isn't the only domain, there are also lots of international domains.
Sign up to request clarification or add additional context in comments.

5 Comments

I already tried embedding the css in the content script. Will see if local storeage makes a difference.
1. The mentioned code does not produce the flicker, it's however get inserted so fast that it's the first CSS to be read and thus the least important making most of the CSS injections useless. Changing the injected CSS with !important etc. cannot be done due to the site already doing this and I have no control over that. The injected CSS should be added/read last so it takes precedence. 2. Seems reasonable, I will see if I can get it to avoid the problems in (1) 4+5. Changed the url in the manifest, this can be ignored
Well, problem 1 sounds like something essentially unsolvable, unless you block the actual CSS file with webRequest.
@user1800592, that's a separate issue. I think it can be solved by moving the injected STYLE element to the end of HEAD right when the first elements are added to BODY. See an example. I still recommend making a new question for that.
@wOxxOm your 2nd/3rd post about forcing executescript to runAt: "document_start" have done the trick.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.