Stylish must use Shadow DOM (some sites forcing their ads so much that they remove extra styles)


I tried to write a style for an site (not sure if it available in your country, may redirect to or something different, available from Ukrainian IP) and encountered a problem. Apparently they are removing all extra styles added into the page and Stylish uses just that.

Here is a userscript to detect such behaviour:

// ==UserScript==
// @name Test
// @namespace
// @version 0.1
// @match*
// @grant none
// @run-at document-start
// ==/UserScript==
/* jshint -W097 */
'use strict';
var obs = new MutationObserver(function(e){
e.forEach(function(e){if (e.removedNodes.length) console.log(e.removedNodes);});

As I know Adblock Plus and a couple of other extensions are using shadow DOM and impossible to detect from the JS on the page. Is it possible to do the same with Stylish?


  • Yep, I'm using Firefox to login here since Google Chrome logged in to the different Google account.
  • Adblock Plus does some magic for this purpose in their code:
    Look for init function in particular.

    It might be necessary to contact them to make it compatible with case when shadow root already exists since creating multiple shadow roots were deprecated in Chrome 45.

    When ABP is active I can do following from console:
    var shadow = document.documentElement.shadowRoot;
    var style = shadow.styleSheets[0];
    style.insertRule('::content * {position:fixed!important}',0);
    As the result everything on the page will fall into the top-left corner.
  • As I understood the trick is to add shadowRoot with element "shadow" in it and then append styles with ::content pseudo-selector before the actual selector.

    var shadow=document.documentElement.createShadowRoot();
    var style = document.createElement('style');
    style.sheet.insertRule('::content h2 {display:none!important}',0);

    BTW, looks like it's still possible to attach multiple shadow roots this way, even though it's marked as deprecated.

  • edited February 2016 Firefox
    You've misunderstood. ABP is not adding its stylesheets to shadow DOM to evade anti-ABP code, it's doing it to avoid breaking websites which manage their own stylesheets by dead-reckoning.
    (Which you should already know if you did indeed read the page that you linked.)

    ABP still simply replaces its stylesheets when they're removed.
    (Which you should also already know.)

    There's nothing to prevent a site from removing shadow DOM stylesheets.
    function removeStylesheet(styleSheet) {
    function removeAllStylesheets(styleSheets) {
    while (styleSheets.length > 0) removeStylesheet(styleSheets[0]);
    function removeAllShadowStylesheets(node) {
    var shadowRoot = node.shadowRoot;
    while (shadowRoot) {
    shadowRoot = shadowRoot.olderShadowRoot;
    just as there's nothing to prevent ABP from detecting the removal of shadow stylesheets
    observer = new MutationObserver(function (mutations) { console.log(mutations); });
    observer.observe(document.documentElement.shadowRoot, {childList:true, subtree:true, attributes:true});
    and replacing them as it does its light stylesheets.

    So, no magic. (Unless it's the magic of shadow restoring the light DOM - that shouldn't happen, shadow is an insertion point for another shadow tree, in this case the olderShadowRoot. A bug?) And, it's not a solution - it only works until someone notices that it works.

    The best thing for you to do is add your site to the Baidu blocker script.

    And maybe file a bug report asking Chrome to add a chrome.tabs.removeCSS method to its extensions API, complementing chrome.tabs.insertCSS, so that extensions like Stylish can both add and remove user stylesheets without interference from the website.
    (And by user stylesheets I mean our own author stylesheets, of course.)

    Oops, errata - fixed.
  • edited February 2016 Firefox
    I've found another way to limit the use of userstyles:
    Content-Security-Policy property in HTML responce header. Using CSP they may prohibit images and fonts (and could be something else) embedded in CSS. see
  • edited February 2016 Firefox
    Not an issue in Stylish for Firefox, only in Stylish for Chrome and Frankenstylish for Firefox.
    W3C said:

    Note: User agents may allow users to modify or bypass policy enforcement through user preferences, bookmarklets, third-party additions to the user agent, and other such mechanisms.

  • Not an issue in Stylish for Firefox

    I checked it out with my style github octicons, userscipt Github_Comment_Enhancer and any github issue post.

    When security.csp.enable = true, Firefox reports:
    Content Security Policy: The page's settings blocked the loading of a resource at data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAEIoAAoAAAAA...

    This occurs because GitHub 'Content-Security-Policy' header contains the rule 'font-src;', which efficently forbids any font from userstyle.
  • edited February 2016 Firefox
    Hmm, that was unexpected. Firefox had been using the browser context to fetch resources for stylesheets registered with the stylesheet service, so CSP wasn't relevant. Now it's only using the browser context for User and User Agent stylesheets; it's using the page context for Author sheets. So, CSP isn't a problem in Firefox with /* AGENT_SHEET */, but it can be a problem otherwise. Happy now?

    Here's a test jig for Scratchpad. I simplified your example to eliminate the script and the data: URI, chose a distinctive font so it was clear when the font was active, and added Userstyles as a non-CSP site for comparison.
    // Scratchpad, in Browser context ([menu] > Environment > Browser): 

    // Change the sheet's origin from AUTHOR_SHEET to AGENT_SHEET if necessary
    // Select 'Run' from the menu to add the style. 'Run' it again to remove the style.

    var sheet = 'data:text/css,\
    @import url(;\
    @-moz-document domain(""), domain("") {\
    * { font-family: "Kaushan Script", serif !important; }\
    var sss = Components.classes[";1"]
    var ios = Components.classes[";1"]
    var uri = ios.newURI(sheet, null, null);
    if(sss.sheetRegistered(uri, origin)) {
    sss.unregisterSheet(uri, origin);
    } else {
    sss.loadAndRegisterSheet(uri, origin);
  • /* AGENT_SHEET */

    This spell works :|
Sign In or Register to comment.