26

Can I inject a CSS file programmatically using a content script js file?

It is possible for me to inject the css when the js file is linked to my popup.html. The problem is I have to click on the button to open the popup to inject the css. I want it to happen automatically and in the background.

What happens in my code...

  1. Get a variable from a MySQL database through a XMLHttpRequest
  2. Call the function, "processdata()"
  3. "processdata" Will process the data from the XMLHttpRequest. More specifically, split the variable, put it into 2 different variables and make them global
  4. I call the function, "click()"
  5. "click" Then will set the css after 1250 milliseconds using setTimeout
  6. I use chrome.tabs.insertCSS to insert the css. The css name is the variable, "currenttheme"

As I mentioned before it does work using the popup. But the popup has to be opened before the CSS gets injected.

How do I make this all happen automatically, without any user interaction?

Here is my code:

    function getData() {
if (window.XMLHttpRequest)
      {// code for IE7+, Firefox, Chrome, Opera, Safari
      xmlhttp=new XMLHttpRequest();
      }
    else
      {// code for IE6, IE5
      xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
      }
    xmlhttp.onreadystatechange=function()
      {
      if (xmlhttp.readyState==4 && xmlhttp.status==200)
        {
        user_data = xmlhttp.responseText;
        window.user_data = user_data;
        processdata();
        }
      }
    xmlhttp.open("GET","http://localhost:8888/g_dta.php",true);
    xmlhttp.send();
}

getData();

function processdata() {
  var downdata = user_data.split('|||||');
  var installedthemes = downdata[0];
  var currenttheme = downdata[1].toString();
  window.currenttheme = currenttheme;
  click();
  }

function click(e) { 
      function setCSS() {
          chrome.tabs.insertCSS(null, {file:currenttheme + ".css"});
          }
      setTimeout(function(){setCSS()}, 1250);
      document.getElementById('iFrameLocation').innerHTML = "<iframe src='http://localhost:8888/wr_dta.php?newid="+e.target.id+"'></iframe>";
      getData();
}

document.addEventListener('DOMContentLoaded', function () {
  var divs = document.querySelectorAll('div');
  for (var i = 0; i < divs.length; i++) {
    divs[i].addEventListener('click', click);
  }
});
Jacques Blom
  • 1,794
  • 5
  • 24
  • 42

3 Answers3

75

You can programmatically create a new <link> tag and add it to the <head> section just like JS files are loaded dynamically.

var link = document.createElement("link");
link.href = "http://example.com/mystyle.css";
link.type = "text/css";
link.rel = "stylesheet";
document.getElementsByTagName("head")[0].appendChild(link);

Here's an article on the topic.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 2
    i have the impression you did that so easy...like make an answer with a ref without much effort. good answers come easy to experts... – Cris Stringfellow Feb 18 '12 at 23:29
  • @CrisStringfellow - Thanks Cris. I knew the code to add a script tag dynamically and figured it was probably the same for a CSS file. I did one Google search to confirm that and then wrote the answer. – jfriend00 Feb 18 '12 at 23:57
  • Works for me. Good method to test external CSS on single page – Maksym Kozlenko Jul 16 '13 at 03:21
  • Would this not create a flicker or FOUC since we are waiting for the document head (and thus presumably the entire document) to be rendered on screen before linking the external stylesheet? – zr0gravity7 Aug 20 '21 at 12:40
  • 1
    @zr0gravity7 - Yes, it could cause a change in rendering when the new CSS loads. Depending upon exactly why it's being dynamically loaded in the first place and what it contains, there are various strategies to protect against that. The simplest idea is that the content defaults to not visible until this style sheet is loaded. – jfriend00 Aug 20 '21 at 14:30
  • Update! Manifest V3 will not allow injecting files from external sources as shown in this answer. Only files contained in your extension package can be injected into the webpage. See @Divyesh [answer below for how to inject css](https://stackoverflow.com/a/37388005/1843673) – Moses Machua May 30 '22 at 00:26
  • Note that you also need to add the css file in `web_accessible_resources` in yout `manifest.json` file. Reference: https://stackoverflow.com/a/11554116/6908282 – Gangula Sep 17 '22 at 16:42
12

Extension flicker

A trauma to the ecosystem that isn't hard to fix

Preface:

Among many ways to get your css added to a page, most of them cause the screen to flicker - initially showing the page without your css applied, and then later applying it. I've tried several ways, below are my findings.

The gist

You need to append a CSS <link> or a <style> tag in a Content script injected on document_start

That code looks like this:

var link = document.createElement('link');
link.href =  chrome.runtime.getURL('main.css');
  //chrome-extension://<extension id>/main.css
link.rel = 'stylesheet';
document.documentElement.insertBefore(link);

For some dynamic CSS code:

var customStyles = document.createElement('style'); 
customStyles.appendChild(document.createTextNode(
   'body { background-color: ' + localStorage.getItem('background-color') + '}'
));
document.documentElement.insertBefore(customStyles); 

Doing this in a script on document_start ensures there is no flicker!

The reasoning

In this JavaScript file you can handle any number of programmatic things. The URL pattern matching offered in manifest.json is very limited, but here you can actually look at all the ?query_string and #hash of a url, and use full regular expressions and string operations to decide whether or not to inject CSS. That CSS can also be based on settings, and/or coordinate with messaging to the background page.

Parting words

  • Don't use .insertCSS, you will notice a flicker.
zr0gravity7
  • 2,917
  • 1
  • 12
  • 33
Devin Rhode
  • 23,026
  • 8
  • 58
  • 72
  • 2
    I recommend [`chrome.extension.getURL('/mod/style.css')`](http://code.google.com/chrome/extensions/extension.html#method-getURL) over a hard-coded `chrome-extension://...` protocol + host. – Rob W Mar 13 '12 at 21:21
  • I know, this is more of a blog post, but tell me what you guys think! – Devin Rhode Mar 14 '12 at 05:05
  • `chrome.extension.getURL()` is deprecated since Chrome 58. https://developer.chrome.com/extensions/extension#method-getURL – Yonggoo Noh Mar 19 '18 at 14:27
  • 1
    ```document.documentElement.insertBefore(link, null)``` otherwise you would get Uncaught (in promise) TypeError: Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 1 present. – goodhyun May 06 '20 at 22:10
  • Why the use of `insertBefore`, rather than appending after? – zr0gravity7 Aug 20 '21 at 15:21
1

It's to simple you could add to the manifest's permissions field : See web_accessible_resources

 , "web_accessible_resources": [
        "mystyle.css"
    ]

and also add this in content_scripts

"content_scripts": [
    {
        "matches": ["<all_urls>"],
        "css": ["mystyle.css"],
        "js": ["jquery-1.10.2.min.js","content.js","my.min.js"]
    }],

you also add same thing using Programmatic injection and insertCSS().

chrome.tabs.insertCSS(integer tabId, object details, function callback)
Divyesh Kanzariya
  • 3,629
  • 3
  • 43
  • 44