如何使用 XUL、SDK 或 WebExtensions 技术从 Firefox 附加组件中的 JavaScript 动态修改 CSS 规则集(例如使用 class 选择器)?

How to dynamically modify CSS rule set (e.g. with a class selector) from JavaScript within Firefox Add-on using XUL, SDK or WebExtensions techniques?

如何使用 XUL、SDK 或 WebExtensions 技术从 Firefox 附加组件中的 JavaScript 动态修改 CSS 规则集(例如使用 class 选择器)?尝试通过 49.0a1 支持 Firefox 29.0b1。

我想解决的问题(来自 Firefox 附加组件中的 XUL 文档,有时与网页不同):

file.xul:(这一行在最上面)

<?xml-stylesheet href="chrome://{GUID}/skin/text.css" type="text/css"?>

text.css:(文件内相关class)

.myTextClass {
    max-width: 800px;
}

code:

// Modify the class selector rule set to add another rule,
// i.e. font-family: DejaVu Sans Mono

// Later on, after user input, change or remove this font in this one class

这可能是 #20927075 的部分重复,没有令人满意的答案,但我对 SDK 或 WebExtensions 技术也很感兴趣,以确保我想要添加的功能现有的基于 XUL 的附加组件可以使用较新的 API 来实现。

我想实现一个基本的 "font selector" UI 功能,并将新字体应用到 class。我有一个简单的 CSS 规则集和一个 class 选择器,它定义了一个 class 和一个规则(现在,但可能添加了其他规则集 manually/statically),其中我想添加另一条规则,并更新插件的界面。

我知道的选项,但似乎都太笨拙了。

1) document.getElementById,手动设置 style 属性。我为演示区做这个,没问题。

2) 和上面类似,但是getElementByClassName。但是,这不是使用 CLASS!这是使用 class 来查找节点,我将每个元素的属性更改为样式或 class。这似乎相当缓慢。我可以用 class 每种可能的字体创建一个巨大的 CSS 文件,然后更改 class 属性,但这是愚蠢的。比仅仅改变样式属性更糟糕。

3) 有样式 sheet 服务,但我不知道它是否仍然存在或将被废弃或删除(它是基于 XPCOM 的?),它不是 Services.jsm 的一部分,它是一种非常生硬的工具,当我只想修改一个 class 定义中的一个规则时,它只能加载或卸载整个样式 sheet。

4) 有 jQuery,但是,没有。就是不行。甚至不建议那样做。对于一个小的附加组件来说开销太大,并且不知道该技术是否适用于 XUL 接口代码。

5) 像 document.styleSheets 这样的东西看起来是对的,但没有得到我想要的(除非我弄错了?)。一切似乎都是只读的。更多内容在下方。

6) 通过摆弄 document.getElementsByTagName('head') 手动破坏了整个样式 sheet,这再次破坏了整个 css sheet而不是一个规则 class.

如果真的没有别的办法,我可能只好用上面的方法2了。但我希望有一个选项允许使用 JavaScript.

从 XUL 中对 CSS 进行细粒度控制

我正在尝试了解如何做到这一点:

for ( i = 0; i < document.styleSheets.length; i++ ) {
    styleSheet = document.styleSheets[ i ];

    console.log( 'style sheet: ' + i + ' ' + styleSheet.href );

    if ( styleSheet.href === 'chrome://{GUID}/skin/text.css' ) {
        for ( j = 0; j < styleSheet.cssRules.length; j++ ) {
            styleRule = styleSheet.cssRules[ j ];
            console.log( 'style sheet: ' + i + ' ' + 'styleRule: ' + styleRule.cssText );
        }
    }
}

在这次编辑中,我现在得到代码来显示定义 class 的规则,而昨晚我得到了一个无效的 属性 错误。我混淆了我的术语,认为规则是 class 中的行项目。但无论如何,从那时起,我不确定如何进行。

上面的代码(在 file.js 中,从 file.xul <script> 标签引用)输出 console.log 类似于此(在 Firefox 29.0b1 中):

"style sheet: 0 chrome://git-addonbar/skin/addonbar.css"                              file.js:189
09:49:47.101 "style sheet: 1 chrome://browser/content/browser.css"                    file.js:189
09:49:47.101 "style sheet: 2 chrome://browser/content/places/places.css"              file.js:189
09:49:47.101 "style sheet: 3 chrome://browser/skin/devtools/common.css"               file.js:189
09:49:47.101 "style sheet: 4 chrome://browser/skin/customizableui/panelUIOverlay.css" file.js:189
09:49:47.101 "style sheet: 5 chrome://browser/skin/browser.css"                       file.js:189
09:49:47.101 "style sheet: 6 chrome://browser/skin/browser-lightweightTheme.css"      file.js:189
09:49:47.101 "style sheet: 7 chrome://global/skin/global.css"                         file.js:189
09:49:47.101 "style sheet: 8 chrome://{GUID}/skin/text.css"                           file.js:189
09:49:47.102 "style sheet: 8 styleRule: .myTextClass { max-width: 800px; }"           file.js:194
09:49:47.102 "style sheet: 9 null"                                                    file.js:189

5) There's things like document.styleSheets which doesn't seem to get what I want (unless I am mistaken?). Everything seems read-only.

这是正确的选项。虽然 styleSheets 属性 是只读的(这意味着您不能像这样分配:document.styleSheets = val),但您获得的样式表对象确实允许修改。

由于您只关心一个(现代!)浏览器,这比 Changing a CSS rule-set from Javascript 以跨浏览器的方式更容易。一个简单的例子:

function changeCSS() {
  let myClassRule = document.styleSheets[0].cssRules[0];
  if (myClassRule.style.color == "red")
    myClassRule.style.color = "blue";
  else
    myClassRule.style.color = "red";
}
.my-class {
  color: red;
}
<p class="my-class">First paragraph</p>
<p class="my-class">Second paragraph</p>
<button onclick="changeCSS()">Change the stylesheet!</button>

您必须扩展它才能找到您关心的样式表和规则 - 上面链接的问题有 a minimal example of doing that

3) There's the style sheet service, but I don't know if it's still in existence or will be obsoleted or removed (it's XPCOM based?), it's not part of Services.jsm, and it is a very blunt instrument that can merely load or unload an entire style sheet, when I merely want to modify one rule from one class definition.

目前其他插件 API 是围绕 sheet 服务或 windowutils.loadSheet 的抽象。当然,mozilla 最终可能会删除它,同时通过其他方式维护抽象。但从现在的情况来看,未来还有一年。

addon-sdk 提供了使用其 page-mod 模块加载样式的选项

Webextensions 通过 tabs.insertCSS or declaratively in the manifest

强制支持它

但这些都是针对内容框架的。如果您想修改浏览器 chrome 本身,您无论如何都必须使用 xpcom API,因为没有针对该浏览器的专用插件 API。

为了回答我自己的问题,这是最终有效并解决了我的问题的代码。关键位信息如下:

document.styleSheets[ i ].href
document.styleSheets[ i ].cssRules[ j ].selectorText
document.styleSheets[ i ].cssRules[ j ].style.fontFamily

这一切都从document.styleSheets类数组对象开始(类数组,因为它实际上不是JavaScript数组),剩下的就很简单了DOM。可以创建一个通用函数。 href 可以是可选的,具体取决于您是否知道外部 sheet 的加载位置并且您知道您可以放心地只担心一个唯一的选择器,然后您可以避免搜索其他 10 个 sheet由 Firefox 加载。这是最简单的用例。

// Load font and apply style sheet class

// Am using the .href to find the sheet faster because location is known and rule is unique
var myCSShref = 'chrome://{GUID}/skin/text.css';
var mySelector = '.myTextClass';

// fonts stored as a string in a user pref, later split to array inline
// fonts could be stored as array during runtime, remove splits accordingly
// fonts get appended to a XUL menulist element
// list index 0 is for default, 1+ for fonts below
var fonts = 'monospace|Anonymous Pro|Apple Menlo|Bitstream Vera Sans Mono|Consolas|DejaVu Sans Mono|Droid Sans Mono|Everson Mono|Liberation Mono|Lucida Console|Source Code Pro';
var font_index = 2; // Anonymous Pro, the 2 is pulled from a user pref

// for loop vars
var i, styleSheet, j, cssRule, oldFontFamily, newFontFamily;

// console.logs for demonstration purposes only

// The following code only needed to be run once for me,
// but it can easily be changed to a reusable function.

if ( document.styleSheets ) {
    for ( i = 0; i < document.styleSheets.length; i++ ) {
        styleSheet = document.styleSheets[ i ];

        console.log( 'style sheet: ' + i + ' ' + styleSheet.href );

        if ( styleSheet.href === myCSShref ) {
            for ( j = 0; j < styleSheet.cssRules.length; j++ ) {
                cssRule = styleSheet.cssRules[ j ];
                console.log( 'style sheet: ' + i + ' cssRule.selectorText: ' + cssRule.selectorText );

                if ( cssRule && cssRule.selectorText === mySelector ) {
                    oldFontFamily = ( cssRule.style.fontFamily ) ? cssRule.style.fontFamily : '';
                    // font_index 0 is the menu default option, so we have 1 extra
                    newFontFamily = ( font_index === 0 ) ? 'inherit' : fonts.split( '|' )[ font_index - 1 ];
                    console.log( 'style sheet: ' + i + ' cssRule.style.fontFamily: ' + oldFontFamily + ' (before)' );
                    console.log( 'style sheet: ' + i + ' cssRule.style.fontFamily: ' + newFontFamily + ' (after)' );
                    cssRule.style.fontFamily = newFontFamily;
                }
            }
        }
    }
}