From e3e4d577eee89983e0c2b2642df6893bb017051d Mon Sep 17 00:00:00 2001 From: gorhill Date: Mon, 6 Jul 2015 13:53:39 -0400 Subject: [PATCH] this makes shadow DOM work on Firefox --- src/js/contentscript-end.js | 4 +- src/js/contentscript-start.js | 12 +---- src/js/scriptlets/cosmetic-off.js | 5 +- src/js/scriptlets/cosmetic-on.js | 5 +- src/js/scriptlets/dom-inspector.js | 74 +++++++++++++++++------------- 5 files changed, 47 insertions(+), 53 deletions(-) diff --git a/src/js/contentscript-end.js b/src/js/contentscript-end.js index 4bcbaca45..5a3f99dcd 100644 --- a/src/js/contentscript-end.js +++ b/src/js/contentscript-end.js @@ -453,8 +453,7 @@ var uBlockCollapser = (function() { } // https://github.com/chrisaljoudi/uBlock/issues/158 // Using CSSStyleDeclaration.setProperty is more reliable - var template = vAPI.perNodeShadowTemplate; - if ( template === null ) { + if ( document.body.shadowRoot === undefined ) { while ( i-- ) { elems[i].style.setProperty('display', 'none', 'important'); } @@ -473,7 +472,6 @@ var uBlockCollapser = (function() { } shadow = elem.createShadowRoot(); shadow.className = sessionId; - shadow.appendChild(template.cloneNode(true)); } }; diff --git a/src/js/contentscript-start.js b/src/js/contentscript-start.js index 6a88d5bcc..ec30d0568 100644 --- a/src/js/contentscript-start.js +++ b/src/js/contentscript-start.js @@ -54,14 +54,6 @@ if ( vAPI.contentscriptStartInjected ) { } vAPI.contentscriptStartInjected = true; vAPI.styles = vAPI.styles || []; -vAPI.perNodeShadowTemplate = (function() { - if ( typeof document.documentElement.createShadowRoot !== 'function' ) { - return null; - } - var content = document.createElement('content'); - content.select = '#' + vAPI.sessionId; - return content; -})(); /******************************************************************************/ @@ -172,8 +164,7 @@ var hideElements = function(selectors) { } // https://github.com/chrisaljoudi/uBlock/issues/158 // Using CSSStyleDeclaration.setProperty is more reliable - var template = vAPI.perNodeShadowTemplate; - if ( template === null ) { + if ( document.body.shadowRoot === undefined ) { while ( i-- ) { elems[i].style.setProperty('display', 'none', 'important'); } @@ -192,7 +183,6 @@ var hideElements = function(selectors) { } shadow = elem.createShadowRoot(); shadow.className = sessionId; - shadow.appendChild(template.cloneNode(true)); } }; diff --git a/src/js/scriptlets/cosmetic-off.js b/src/js/scriptlets/cosmetic-off.js index e0f1bfe50..e8d46eb11 100644 --- a/src/js/scriptlets/cosmetic-off.js +++ b/src/js/scriptlets/cosmetic-off.js @@ -90,10 +90,9 @@ while ( i-- ) { } continue; } - if ( shadow === null || shadow.className !== sessionId ) { - continue; + if ( shadow !== null && shadow.className === sessionId && shadow.firstElementChild === null ) { + shadow.appendChild(document.createElement('content')); } - shadow.children[0].select = ''; } /******************************************************************************/ diff --git a/src/js/scriptlets/cosmetic-on.js b/src/js/scriptlets/cosmetic-on.js index 9a903fe6e..bedb95358 100644 --- a/src/js/scriptlets/cosmetic-on.js +++ b/src/js/scriptlets/cosmetic-on.js @@ -90,10 +90,9 @@ while ( i-- ) { } continue; } - if ( shadow === null || shadow.className !== sessionId ) { - continue; + if ( shadow !== null && shadow.className === sessionId && shadow.firstElementChild !== null ) { + shadow.removeChild(shadow.firstElementChild); } - shadow.children[0].select = selector; } /******************************************************************************/ diff --git a/src/js/scriptlets/dom-inspector.js b/src/js/scriptlets/dom-inspector.js index e9bfc2de3..a147e18e0 100644 --- a/src/js/scriptlets/dom-inspector.js +++ b/src/js/scriptlets/dom-inspector.js @@ -154,6 +154,7 @@ var pickerRoot = null; var highlightedElementLists = [ [], [], [] ]; var nodeToIdMap = new WeakMap(); // No need to iterate +var nodeToCosmeticFilterMap = new WeakMap(); var toggledNodes = new Map(); /******************************************************************************/ @@ -192,31 +193,6 @@ var domLayout = (function() { return nid; }; - // Collect all nodes which are directly affected by cosmetic filters: these - // will be reported in the layout data. - // TODO: take into account cosmetic filters added after the map is build. - - var nodeToCosmeticFilterMap = (function() { - var out = new WeakMap(); - var styleTags = vAPI.styles || []; - var i = styleTags.length; - var selectors, styleText, j, selector, nodes, k; - while ( i-- ) { - styleText = styleTags[i].textContent; - selectors = styleText.slice(0, styleText.lastIndexOf('\n')).split(/,\n/); - j = selectors.length; - while ( j-- ) { - selector = selectors[j]; - nodes = document.querySelectorAll(selector); - k = nodes.length; - while ( k-- ) { - out.set(nodes[k], selector); - } - } - } - return out; - })(); - var selectorFromNode = function(node) { var str, attr, pos, sw, i; var tag = node.localName; @@ -708,6 +684,29 @@ var cosmeticFilterFromTarget = function(nid, coarseSelector) { /******************************************************************************/ +var cosmeticFilterMapFromStyleTag = function(styleTag) { + var filterMap = nodeToCosmeticFilterMap; + var styleText = styleTag.textContent; + var selectors = styleText.slice(0, styleText.lastIndexOf('\n')).split(/,\n/); + var i = selectors.length; + var selector, nodes, j, node; + while ( i-- ) { + selector = selectors[i]; + nodes = document.querySelectorAll(selector); + j = nodes.length; + while ( j-- ) { + node = nodes[j]; + filterMap.set(node, selector); + // Not all target nodes have necessarily been force-hidden, + // do it now so that the inspector does not unhide these + // nodes. + hideNode(node); + } + } +}; + +/******************************************************************************/ + var elementsFromSelector = function(selector, context) { if ( !context ) { context = document; @@ -772,7 +771,7 @@ var highlightElements = function(scrollTo) { svgRoot.children[i+1].setAttribute('d', islands.join('') || 'M0 0'); } - svgRoot.children[0].setAttribute('d', ocean.join('')); + svgRoot.firstElementChild.setAttribute('d', ocean.join('')); if ( !scrollTo ) { return; @@ -926,8 +925,8 @@ var showNode = function(node, v1, v2) { } else { node.style.setProperty('display', v1, v2); } - } else if ( shadow !== null && shadow.className === sessionId ) { - shadow.children[0].select = ''; + } else if ( shadow !== null && shadow.className === sessionId && shadow.firstElementChild === null ) { + shadow.appendChild(document.createElement('content')); } }; @@ -940,26 +939,35 @@ var hideNode = function(node) { return; } if ( shadow !== null && shadow.className === sessionId ) { - shadow.children[0].select = '#' + sessionId; + if ( shadow.firstElementChild !== null ) { + shadow.removeChild(shadow.firstElementChild); + } return; } - // not all node can be shadowed + // not all nodes can be shadowed try { shadow = node.createShadowRoot(); } catch (ex) { return; } shadow.className = sessionId; - shadow.appendChild(vAPI.perNodeShadowTemplate.cloneNode(true)); }; /******************************************************************************/ var toggleStylesVisibility = function(state) { var styleTags = vAPI.styles || []; - var i = styleTags.length, sheet; + var styleTag, sheet; + var i = styleTags.length; while ( i-- ) { - sheet = styleTags[i].sheet; + styleTag = styleTags[i]; + // Collect all nodes which are directly affected by cosmetic filters: these + // will be reported in the layout data. + // TODO: take into account cosmetic filters added after the map is build. + if ( state === false ) { + cosmeticFilterMapFromStyleTag(styleTag); + } + sheet = styleTag.sheet; if ( sheet !== null ) { sheet.disabled = !state; }