如何真正隔离Google Chrome扩展程序中的样式表?

38

我编写了一个Google Chrome扩展程序,弹出一个带有自动完成字段和其自己的样式的对话框,但是有些网站我的CSS完全被破坏了,看起来不太好。

我知道使用iFrames隔离样式的方法,但是在Google Chrome扩展程序中,没有办法以这种方式隔离我的HTML和CSS。另一种方法是将所有东西都包装到一个具有自己id和相对样式的分离div中,我也这样做了,但是似乎在一些具有“硬”标签样式重载或CSS代码中的“!important”指令的网站上无效。

所以,我想知道是否有一种真正便捷的方法来隔离我的样式,还是我的运气不好,在每个网站上超载每一个小的CSS属性以修复一个或另一个样式问题?

顺便说一句:我设置了我的清单以在“document_end”加载所有内容,但我发现它并未应用于每当DOM准备就绪时每次加载的样式表。


1
你已经尝试过这些方法了吗?两种方法的结合应该足够了。确保你的选择器足够具体。 - Rob W
谢谢Rob!是的,我尝试过这个方法,但似乎并不适用于每个网站。我知道有些页面即使在GC DevTools中看到“计算样式”显示所有CSS设置都正确,例如“list-style: none !important;”,但仍然继续以'圆点'标记显示列表。是的 - 我的样式像您的建议一样在清单中设置,并且在DOM创建后通过嵌入到头部动态加载。也许在Google Chrome扩展中有其他隔离样式的方法?我的意思是清单中是否有某种秘密的“disable-inheritance:on”选项或类似的东西? - smileart
我的最后一个建议怎么样?增加选择器的特异性。如果您控制要样式化的元素,则添加style="prop:val!important"将具有最高优先级 - 例如,请参见http://jsfiddle.net/rytkL/。 - Rob W
请看我在这里回答类似问题的答案:http://stackoverflow.com/questions/10608924/how-can-i-efficiently-overwrite-content-script/31128196#31128196。有几种方法,但我详细介绍的可能是最简单和最可靠的... :) - Joseph Kahn
5个回答

23
在提问时,你唯一的选择是使用iframes或具有非常高specificity的样式表,并明确设置可能影响样式的所有属性。最后一种方法非常麻烦,因为总会有一些属性被你忽略了。因此,隔离样式表的唯一可用方法是使用iframes。
这个问题的解决方案 - 在没有 iframe 的情况下隔离样式 - 是使用 Shadow DOM从 Chrome 25 开始)。您可以在 HTML5 Rocks 找到教程。有一个真实的 Chrome 扩展程序,它使用 Shadow DOM 隔离样式,请参见 Display #Anchors源代码在这里)。

“Display #Anchors”这个扩展真的非常方便(例如,用于链接到Chrome扩展文档的特定部分;) - gkalpak
使用Sass,例如:#myid { .all-my-extension-styles ... }可能会解决这个问题? - bboydflo
@rob-w,你的回答看起来有点复杂。你能否提供一个简化的代码来注入一个元素并将其覆盖在其他元素的顶部? - user17085802
我能够创建一个shadowDOM元素,但一旦加载,它就被页面覆盖了。例如,在Medium编辑器中,它会显示shadow元素一秒钟,然后隐藏起来。我可以使用开发工具检查该元素。 - user17085802

16

最近我经历了这个问题的磨难,我想分享一些我认为有价值的信息。

首先,Rob W的答案是正确的。影子DOM是解决这个问题的正确方法。然而,在我的情况下,我不仅需要CSS隔离,还需要JavaScript事件。例如,如果用户单击存在于隔离HTML内部的按钮会发生什么?只使用Shadow DOM会变得非常丑陋,但是我们还有另一种Web组件技术——自定义元素来拯救。然而,截至本文撰写时,Chrome中存在一个错误,防止自定义元素在Chrome扩展程序中使用。请参见我在此处此处以及这里的问题。

那么我们该怎么办呢?我认为今天最好的解决方案是IFrames,这也是我采用的方法。shahalpk链接的文章很棒,但它只描述了过程的一部分。以下是我的做法:

首先,为您的隔离小部件创建一个HTML文件和JS文件。这些文件内部的所有内容都将在IFrame中运行。请确保从HTML文件引用JS文件。

//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> 

接下来,在你的内容脚本中使用JavaScript创建一个iframe元素。你需要在JavaScript中这样做,因为你必须使用chrome.extension.getURL来获取你的iframe HTML文件:

接下來,在你的內容腳本中用JavaScript創建一個iframe元素。你需要在JavaScript中這樣做,因為你必須使用chrome.extension.getURL來獲取你的iframe HTML文件:

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

就这样了。

需要记住的一件事:如果你需要在iframe和其他内容脚本之间通信,你需要使用chrome.runtime.sendMessage()发送消息到后台页面,然后从后台页面使用chrome.tabs.sendMessage向选项卡发送消息。它们不能直接通信。

编辑:我写了一篇博客文章,详细介绍了我通过这个过程学到的所有内容,包括一个完整的示例chrome扩展程序和许多链接到不同信息的网页:

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

如果我的博客消失了,这里是原始帖子的来源:

博客文章

示例源代码


顺便提一下,在那篇博客文章中提到,您可能想要查看我的chromeps库来处理消息传递。这篇博客文章对此进行了解释。 - anderspitman

9

使用all参数


.some-selector {
    all: initial;
}

.some-selector * {
    all: unset;
}

或使用影子DOM

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

使用说明

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

您可以通过 myWidget.inner 访问小部件内容,通过 myWidget.outer 访问外部内容。

样式

/* 
 * 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;
}

当涉及到事件处理时,Shadow DOM非常棘手。我正在尝试使用React + Shadow DOM来隔离扩展,但仍然无法将事件处理程序绑定到内部DOM。 - Yuanfei Zhu
我也曾经尝试过同样的事情。最终我意识到React和Shadow DOM在一起使用效果不佳。当涉及到封装时,老式的iframe仍然是最好的选择。 - HaNdTriX
很高兴听到有人和我一样。但是如果你正在使用iframe,主机和iframe内容之间的消息传递可能会非常困难。你是如何管理完成这个的? - Yuanfei Zhu

5

我最近创建了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() {
  // some js after button is clicked.
});

#yourDialogID中的元素不会受到现有网页的影响。find()函数返回一个常规的jQuery DOM元素,因此您可以对其进行任何操作。

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

https://github.com/liviavinci/Boundary


很好!现在这是一个更好的答案。(虽然你应该删除片段标签,它没有用) - Xan
@Xan 这是我第一次在stackoverflow上发布答案,非常感谢你的指导! - Livia Zhang
这个对用户事件的响应良好吗? - fsljfke

2

使用 iframe。这是一个变通方法,但可行。

Maxime 写了一篇关于此的 文章


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