如何在Chrome扩展程序的内容脚本中避免继承样式

9
我正在编写一个Google Chrome扩展程序,它会在每个页面上运行内容脚本。在我的内容脚本中,我注入了一个带有一些
  • 子元素的
    。我在样式表中为这些元素指定了一些样式。
    但是我发现,在某些随机页面上,我的元素将继承自定义在网页上的样式,因为我没有为我的div指定每一个样式属性。
    我应该如何阻止我的注入元素继承这些样式呢?
    对我来说,我可以选择:
    1. 在我的样式表中指定每一个样式(例如,查看没有干扰时的计算样式),或者 2. 我可以将我的
    放在一个

2
从技术上讲,还有第三种方法:Shadow DOM。但是我不知道是否有一个完整的工作示例适用于Chrome扩展程序。有一个使用它的display-anchors扩展程序,但它不是一个与扩展程序通信的GUI界面。 - Xan
CSS文件在页面中的加载顺序是怎样的?是先加载页面CSS,然后再加载内容脚本CSS文件吗?还是反过来? - Aryan Firouzian
1
答案现在已经过时了,你现在可以在CSS中使用 all: initial; - Muhammad Umer
3个回答

1

我会选择第一种方式——完全指定您使用的元素的样式。但这比我想象的要复杂一些。

首先,您必须完全指定容器元素。然后,对于其后代,您必须说明它们也应该使用默认值或从其父级继承(直到容器)。最后,您必须指定每个其他元素的外观,以便它们不都是普通的跨度。

相关的API是DOM Level 2 Style中的getComputedStyleCSSStyleSheet接口。您可以使用那里的所有值,除了widthheight,默认情况下应为auto。您还需要下载一个默认样式表,例如Webkit用户代理样式表。然后,您可以调用以下函数创建完整的样式表,然后将其注入文档中。

请注意,在将样式表插入目标文档时,您必须使容器选择器尽可能具体,因为网页可能会给出比您的规则更高 specificity 的规则。例如,在 <html id=a><head id=b><style>#a #b * {weird overrides}</style></head> 中,#a #b * 的特异性高于 #yourId div,但我想这是不常见的。
注意:由于某种原因,Chrome 在加载 CSS 时给我报错 "无法加载资源",除非它已经在当前文档的 <link> 中了。因此,您应该在调用此函数的页面中包含 html.css。
// 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。关于 CSS Chrome 错误,我在扩展程序的 manifest.json 文件中包含了我的样式表引用:"content_scripts": [{"matches": ["http://*/*"],"css": ["my.css"]}]每当我需要在我的内容脚本中引用本地资源时,我会通过 chrome.extension.getURL("..."); 来获取它。 - mark
我在上面的代码中遇到了一个问题。当我尝试执行iframe.contentDocument.defaultView.getComputedStyle(span)时,问题在于 iframe.contentDocument.defaultView 是未定义的。 - mark
啊,我遇到的问题与Chrome的一个bug相关http://code.google.com/p/chromium/issues/detail?id=49001。当我使用file://协议时,问题就表现出来了。我搭建了一个本地Web服务器,然后使用http://协议就能够访问所需的cssRules属性了。 - mark

1
.my-extension-frame {
   all: initial;
   /* style... */
}

更新:最近我不得不重新访问这个问题,如果网页样式元素直接作用于元素类型,那么这将是一场噩梦。我的新方法大致如下:

  1. 有一个基本的 div,所有内容都是它的子元素。
  2. 使用通配符样式并执行 all:revert。
  3. 排除 SVG,因为 "all:revert" 会破坏 SVG。
  4. 同样重置 contenteditable。

示例 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;
   }
}

更多文章:https://blog.mukunda.com/cat/2023/getting-around-existing-css-for-overlays.txt

请不要仅仅发布代码作为答案,还要提供解释您的代码是如何解决问题的。带有解释的答案通常更有帮助和更高质量,并且更有可能吸引赞同。 - Mark Rotteveel

0

最近我创建了一个名为Boundary的CSS+JS库来解决类似这样的问题。Boundary可以创建与现有网页CSS完全分离的元素。

以创建对话框为例。安装Boundary后,您可以在内容脚本中执行以下操作:

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.
});

#yourDialogID内的元素不会受到现有网页的影响。

希望这能帮到你。如果有任何问题,请随时告诉我。

https://github.com/liviavinci/Boundary


1
停下来阅读我的评论,关于你之前的回答。如果你继续复制粘贴这个回答而不做出改进,尽管它很有用,我将不得不标记它。 - Xan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接