How to really isolate stylesheets in the Google Chrome extension?

At the time of asking the question, your only option was to either use iframes, or stylesheets with a very high specificity and explicitly set all properties that might affect styles. The last method is very cumbersome, because there will always be some property that is overlooked by you. Consequently, the only usable method for isolating stylesheets was to use iframes.

The solution to this problem -isolation of styles without iframes- is Shadow DOM (since Chrome 25). You can find a tutorial at HTML5 Rocks. For a real-world Chrome extension that uses Shadow DOM to isolate styles, see Display #Anchors (source code here).


As I've recently gone through the gauntlet of this issue, I want to share some information I think is valuable.

First, Rob W's answer is correct. Shadow DOM is the correct solution to this problem. However, in my case not only did I need CSS isolation, I also needed JavaScript events. For example, what happens if the user clicks a button that lives within the isolated HTML? This gets really ugly with just Shadow DOM, but we have another Web Components technology, Custom Elements, to the rescue. Except that as of this writing there is a bug in chrome that prevents custom element in chrome extensions. See my questions here and here and the bug here.

So where does that leave us? I believe the best solution today is IFrames, which is what I went with. The article shahalpk linked is great but it only describes part of the process. Here's how I did it:

First, create an html file and js file for your isolated widget. Everything inside these files will run in an isolated environment in an iframe. Be sure to source your js file from the html file.

//iframe.js
var button = document.querySelector('.my-button');
button.addEventListener('click', function() {
    // do useful things
});

//iframe.html
<style>
/* css */
</style>
<button class='my-button'>Hi there</button>
<script src='iframe.js'></script> 

Next, inside your content script create an iframe element in javascript. You need to do it in javascript because you have to use chrome.extension.getURL in order to grab your iframe html file:

var iframe = document.createElement('iframe');
iframe.src = chrome.extension.getURL("iframe.html");
document.body.appendChild(iframe);

And that's it.

One thing to keep in mind: If you need to communicated between the iframe and the rest of the content script, you need to chrome.runtime.sendMessage() to the background page, and then chrome.tabs.sendMessage from the background page back to the tab. They can't communicate directly.

EDIT: I wrote a blog post detailing everything I learned through my process, including a complete example chrome extension and lots of links to different information:

https://apitman.com/3/#chrome-extension-content-script-stylesheet-isolation

In case my blog goes down, here's the sources to the original post:

Blog post

Example source


Either use all

.some-selector {
    all: initial;
}

.some-selector * {
    all: unset;
}

or use Shadow DOM

Library

function Widget(nodeName, appendTo){
  this.outer = document.createElement(nodeName || 'DIV');
  this.outer.className = 'extension-widget-' + chrome.runtime.id;
  this.inner = this.outer.createShadowRoot();
  (appendTo || document.body).appendChild(this.outer);
}

Widget.prototype.show = function(){
  this.outer.style.display = 'block';
  return this;
};

Widget.prototype.hide = function(){
  this.outer.style.display = 'none';
  return this;
};

Usage

var myWidget = new Widget();
myWidget.inner.innerHTML = '<h1>myWidget</h1>';

You can access the widget contents via myWidget.inner and the outer via myWidget.outer.

Styles

/* 
 * Reset Widget Wrapper Element 
 */
.extension-widget-__MSG_@@extension_id__ {
  background: none;
  border: none;
  bottom: auto;
  box-shadow: none;
  color: black;
  cursor: auto;
  display: inline;
  float: none;
  font-family : "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  font-size: inherit;
  font-style: normal;
  font-variant: normal;
  font-weight: normal;
  height: auto;
  left: auto;
  letter-spacing: 0;
  line-height: 100%;
  margin: 0;
  max-height: none;
  max-width: none;
  min-height: 0;
  min-width: 0;
  opacity: 1;
  padding: 0;
  position: static;
  right: auto;
  text-align: left;
  text-decoration: none;
  text-indent: 0;
  text-shadow: none;
  text-transform: none;
  top: auto;
  vertical-align: baseline;
  white-space: normal;
  width: auto;
  z-index: 2147483648;
}

/* 
 * Add your own styles here 
 * but always prefix them with:
 * 
 *   .extension-widget-__MSG_@@extension_id__ 
 *   
 */

.extension-widget-__MSG_@@extension_id__{
  position: fixed;
  top: 100px;
  margin: 0 auto;
  left: 0;
  right: 0;
  width: 500px;
}

.extension-widget-__MSG_@@extension_id__::shadow h1 {
  display: block;
  margin: 0 auto;
  padding: 20px;
  background-color: yellow;
  border: 10px solid green;
  font-size: 20px;
  text-align: center;
}