9

I'm writing a Google Chrome extension that runs a content script on every page. In my content script, I inject a <div> with some <ul> and <li> children into the page. I specify a few styles for these elements in a stylesheet.

But I've found that on some random pages my elements will inherit styles from those defined on the webpage since I haven't specified every single style property for my divs.

What's the best way I can stop my injected elements from inheriting these styles?

It seems to me I could either:

  • specify every single style in my stylesheet (eg. by looking at what the computed styles are when there is no interference), or
  • I could put my <div> inside an <iframe>. However, then I'll have to do hella message passing between my content script's iframe and the source page since the chrome:// URL of my iframe src and the http:// urls of the source pages would be considered cross-origin.
Kas Elvirov
  • 7,394
  • 4
  • 40
  • 62
mark
  • 4,678
  • 7
  • 36
  • 46
  • 2
    Technically, there's a third route: [Shadow DOM](http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/). But I'm not aware of a full working example for a Chrome extension. There's [display-anchors](https://github.com/Rob--W/display-anchors) extension that uses it, but it's not a GUI that communicates with the extension. – Xan Jun 23 '14 at 12:57
  • How is the order of css files loading in the page. is it like first page css is loaded and then content script css files? or vice versa? – Aryan Firouzian Feb 21 '17 at 10:18
  • 1
    answers are outdated now, you can use `all: initial;` in css now – Muhammad Umer Oct 16 '17 at 00:02

3 Answers3

1

I would go with the first choice--to fully specify the style of the elements you use. But this is a bit more involved than I thought.

First, you have to completely specify the container element. Then, for its descendants, you have to say that they should also use the default values or inherit from their parent (up to the container). Finally, you have to specify the look of every other element so that they're not all plain spans.

The relevant APIs are getComputedStyle and the CSSStyleSheet interface from DOM Level 2 Style. You can use all the values there except width and height, which should be auto by default. You also need to download a default stylesheet, such as the Webkit user agent stylesheet. Then you can call the following function to create a complete stylesheet that you can inject into the document.

Note that when you insert the stylesheet into the target document, you'll have to make the container selector as specific as possible because the webpage could conceivably give rules that have a higher specificity than your rules. For example, in <html id=a><head id=b><style>#a #b * {weird overrides}</style></head>, #a #b * has a higher specificity than #yourId div would. But I imagine that this is uncommon.

Note: for some reason, Chrome is giving me error "Failed to load resource" when I load the CSS, unless it is already in a <link> of the current document. So you should include html.css in the page that calls this function too.

// CSS 2.1 inherited prpoerties
var inheritedProperties = [
    'azimuth', 'border-collapse', 'border-spacing', 'caption-side',
    'color', 'cursor', 'direction', 'elevation', 'empty-cells',
    'font-family', 'font-size', 'font-style', 'font-variant',
    'font-weight', 'font', 'letter-spacing', 'line-height',
    'list-style-image', 'list-style-position', 'list-style-type',
    'list-style', 'orphans', 'pitch-range', 'pitch', 'quotes',
    'richness', 'speak-header', 'speak-numeral', 'speak-punctuation',
    'speak', 'speech-rate', 'stress', 'text-align', 'text-indent',
    'text-transform', 'visibility', 'voice-family', 'volume',
    'white-space', 'widows', 'word-spacing'];
// CSS Text Level 3 properties that inherit http://www.w3.org/TR/css3-text/
inheritedProperties.push(
    'hanging-punctuation', 'line-break', 'punctuation-trim',
    'text-align-last', 'text-autospace', 'text-decoration-skip',
    'text-emphasis', 'text-emphasis-color', 'text-emphasis-position',
    'text-emphasis-style', 'text-justify', 'text-outline',
    'text-shadow', 'text-underline-position', 'text-wrap',
    'white-space-collapsing', 'word-break', 'word-wrap');
/**
 * Example usage:
       var fullStylesheet = completeStylesheet('#container', 'html.css').map(
           function(ruleInfo) {
               return ruleInfo.selectorText + ' {' + ruleInfo.cssText + '}';
           }).join('\n');
 * @param {string} containerSelector The most specific selector you can think
 *     of for the container element; e.g. #container. It had better be more
 *     specific than any other selector that might affect the elements inside.
 * @param {string=} defaultStylesheetLocation If specified, the location of the
 *     default stylesheet. Note that this script must be able to access that
 *     locatoin under same-origin policy.
 * @return {Array.<{selectorText: string, cssText: string}>} rules
 */
var completeStylesheet = function(containerSelector,
                                  defaultStylesheetLocation) {
  var rules = [];
  var iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  document.body.appendChild(iframe);  // initializes contentDocument
  try {
    var span = iframe.contentDocument.createElement('span');
    iframe.contentDocument.body.appendChild(span);
    /** @type {CSSStyleDeclaration} */
    var basicStyle = iframe.contentDocument.defaultView.getComputedStyle(span);
    var allPropertyValues = {};
    Array.prototype.forEach.call(basicStyle, function(property) {
      allPropertyValues[property] = basicStyle[property];
    });
    // Properties whose used value differs from computed value, and that
    // don't have a default value of 0, should stay at 'auto'.
    allPropertyValues['width'] = allPropertyValues['height'] = 'auto';
    var declarations = [];
    for (var property in allPropertyValues) {
      var declaration = property + ': ' + allPropertyValues[property] + ';';
      declarations.push(declaration);
    }
    // Initial values of all properties for the container element and
    // its descendants
    rules.push({selectorText: containerSelector + ', ' +
                              containerSelector + ' *',
                cssText: declarations.join(' ')});

    // For descendants, some of the properties should inherit instead
    // (mostly dealing with text).
    rules.push({selectorText: containerSelector + ' *',
                cssText: inheritedProperties.map(
                    function(property) {
                      return property + ': inherit;'
                    }).join(' ')});

    if (defaultStylesheetLocation) {
      var link = iframe.contentDocument.createElement('link');
      link.rel = 'stylesheet';
      link.href = defaultStylesheetLocation;
      iframe.contentDocument.head.appendChild(link);
      /** @type {CSSStyleSheet} */
      var sheet = link.sheet;
      Array.prototype.forEach.call(
          sheet.cssRules,
          /** @param {CSSStyleRule} cssRule */
          function(cssRule) {
        rules.push({
            selectorText: containerSelector + ' ' + cssRule.selectorText,
            cssText: cssRule.style.cssText});
      });
    }
    return rules;
  } finally {
    document.body.removeChild(iframe);
  }
};
yonran
  • 18,156
  • 8
  • 72
  • 97
  • Great, thanks for the detailed response @yonran. Re: the CSS Chrome error, I include my stylesheet reference in the extension manifest.json: `"content_scripts": [{"matches": ["http://*/*"],"css": ["my.css"]}]` and whenever I need to reference a local resource in my content script I get it via `chrome.extension.getURL("...");` – mark Feb 13 '11 at 00:01
  • I'm having one problem with the above. When I try `iframe.contentDocument.defaultView.getComputedStyle(span)` the problem is that `iframe.contentDocument.defaultView` is undefined. – mark Feb 15 '11 at 22:09
  • Ah, the problems I was having were related to a Chrome bug http://code.google.com/p/chromium/issues/detail?id=49001. The problem manifested itself when I was using the file:// protocol. I set up a local web server, and was then able to access the desired cssRules property using the http:// protocol. – mark Feb 21 '11 at 00:54
1
.my-extension-frame {
   all: initial;
   /* style... */
}

UPDATE: I had to revisit this recently and it is quite the nightmare if the web page styles element types directly. My new method is something like this:

  1. Have a base div that everything is a child of.
  2. Use a wildcard style and do all:revert
  3. Exclude svg as "all:revert" will kill SVGs
  4. Reset contenteditable as well.

Example scss:

@namespace svg "http://www.w3.org/2000/svg";

#mypanel {
   *:not(svg|*) {
      all: revert;
   }

   // tested on chrome and not firefox
   div[contenteditable] {
      -webkit-user-modify: read-write;
      overflow-wrap: break-word;
      -webkit-line-break: after-white-space;
      line-break: after-white-space;
   }
}

More writing: https://blog.mukunda.com/cat/2023/getting-around-existing-css-for-overlays.txt

mukunda
  • 2,908
  • 15
  • 21
  • Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Mark Rotteveel May 03 '22 at 10:12
0

I recently created Boundary, a CSS+JS library to solve problems just like this. Boundary creates elements that are completely separate from the existing webpage's CSS.

Take creating a dialog for example. After installing Boundary, you can do this in your content script

var dialog = Boundary.createBox("yourDialogID", "yourDialogClassName");

Boundary.loadBoxCSS("#yourDialogID", "style-for-elems-in-dialog.css");

Boundary.appendToBox(
    "#yourDialogID",
    "<button id='submit_button'>submit</button>"
);

Boundary.find("#submit_button").click(function() {
  // find() function returns a regular jQuery DOM element
  // so you can do whatever you want with it.
  // some js after button is clicked.
});

Elements within #yourDialogID will not be affected by the existing webpage.

Hope this helps. Please let me know if you have any question.

https://github.com/liviavinci/Boundary

Livia Zhang
  • 193
  • 1
  • 7
  • 1
    Please **stop and read my comments** on [your previous answer](http://stackoverflow.com/a/26431160/934239). If you continue to copy-paste this answer without improvement, I'll have to flag it, despite its usefulness. – Xan Oct 17 '14 at 18:29