From febb18147a268999fb5ca339bab741d254cf766b Mon Sep 17 00:00:00 2001 From: gorhill Date: Mon, 6 Jul 2015 07:48:56 -0400 Subject: [PATCH] #435: using shadow nodes instead of modifying directly nodes' style attr --- src/js/contentscript-end.js | 28 ++++++- src/js/contentscript-start.js | 36 ++++++++- src/js/logger-ui-inspector.js | 4 +- src/js/scriptlets/cosmetic-off.js | 29 ++++--- src/js/scriptlets/cosmetic-on.js | 31 ++++---- src/js/scriptlets/dom-inspector.js | 120 +++++++++++++++++++++-------- 6 files changed, 188 insertions(+), 60 deletions(-) diff --git a/src/js/contentscript-end.js b/src/js/contentscript-end.js index 5d413ba83..4bcbaca45 100644 --- a/src/js/contentscript-end.js +++ b/src/js/contentscript-end.js @@ -446,12 +446,34 @@ var uBlockCollapser = (function() { if ( document.body === null ) { return; } - // https://github.com/chrisaljoudi/uBlock/issues/158 - // Using CSSStyleDeclaration.setProperty is more reliable var elems = document.querySelectorAll(selectors); var i = elems.length; + if ( i === 0 ) { + return; + } + // https://github.com/chrisaljoudi/uBlock/issues/158 + // Using CSSStyleDeclaration.setProperty is more reliable + var template = vAPI.perNodeShadowTemplate; + if ( template === null ) { + while ( i-- ) { + elems[i].style.setProperty('display', 'none', 'important'); + } + return; + } + // https://github.com/gorhill/uBlock/issues/435 + // Using shadow content so that we do not have to modify style + // attribute. + var sessionId = vAPI.sessionId; + var elem, shadow; while ( i-- ) { - elems[i].style.setProperty('display', 'none', 'important'); + elem = elems[i]; + shadow = elem.shadowRoot; + if ( shadow !== null && shadow.className === sessionId ) { + continue; + } + 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 255f075ed..6a88d5bcc 100644 --- a/src/js/contentscript-start.js +++ b/src/js/contentscript-start.js @@ -54,6 +54,14 @@ 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; +})(); /******************************************************************************/ @@ -157,12 +165,34 @@ var hideElements = function(selectors) { if ( document.body === null ) { return; } - // https://github.com/chrisaljoudi/uBlock/issues/158 - // Using CSSStyleDeclaration.setProperty is more reliable var elems = document.querySelectorAll(selectors); var i = elems.length; + if ( i === 0 ) { + return; + } + // https://github.com/chrisaljoudi/uBlock/issues/158 + // Using CSSStyleDeclaration.setProperty is more reliable + var template = vAPI.perNodeShadowTemplate; + if ( template === null ) { + while ( i-- ) { + elems[i].style.setProperty('display', 'none', 'important'); + } + return; + } + // https://github.com/gorhill/uBlock/issues/435 + // Using shadow content so that we do not have to modify style + // attribute. + var sessionId = vAPI.sessionId; + var elem, shadow; while ( i-- ) { - elems[i].style.setProperty('display', 'none', 'important'); + elem = elems[i]; + shadow = elem.shadowRoot; + if ( shadow !== null && shadow.className === sessionId ) { + continue; + } + shadow = elem.createShadowRoot(); + shadow.className = sessionId; + shadow.appendChild(template.cloneNode(true)); } }; diff --git a/src/js/logger-ui-inspector.js b/src/js/logger-ui-inspector.js index bd43fdfa0..f70357168 100644 --- a/src/js/logger-ui-inspector.js +++ b/src/js/logger-ui-inspector.js @@ -29,8 +29,11 @@ /******************************************************************************/ +var showdomButton = uDom.nodeFromId('showdom'); + // Don't bother if the browser is not modern enough. if ( typeof Map === undefined || typeof WeakMap === undefined ) { + showdomButton.classList.add('disabled'); return; } @@ -44,7 +47,6 @@ var inspectedURL = ''; var inspectedHostname = ''; var pollTimer = null; var fingerprint = null; -var showdomButton = uDom.nodeFromId('showdom'); var inspector = uDom.nodeFromId('domInspector'); var domTree = uDom.nodeFromId('domTree'); var tabSelector = uDom.nodeFromId('pageSelector'); diff --git a/src/js/scriptlets/cosmetic-off.js b/src/js/scriptlets/cosmetic-off.js index 1983e9174..e0f1bfe50 100644 --- a/src/js/scriptlets/cosmetic-off.js +++ b/src/js/scriptlets/cosmetic-off.js @@ -49,6 +49,8 @@ if ( Array.isArray(styles) === false ) { return; } +var sessionId = vAPI.sessionId; + /******************************************************************************/ // Remove all cosmetic filtering-related styles from the DOM @@ -60,15 +62,12 @@ var style, i; i = styles.length; while ( i-- ) { style = styles[i]; - if ( style.parentElement === null ) { - continue; - } - style.parentElement.removeChild(style); selectors.push(style.textContent.replace(reProperties, '')); + if ( style.sheet !== null ) { + style.sheet.disabled = true; + } } -// Remove `display: none !important` attribute - if ( selectors.length === 0 ) { return; } @@ -78,13 +77,23 @@ try { elems = document.querySelectorAll(selectors.join(',')); } catch (e) { } - i = elems.length; + +var elem, shadow; while ( i-- ) { - style = elems[i].style; - if ( typeof style === 'object' || typeof style.removeProperty === 'function' ) { - style.removeProperty('display'); + elem = elems[i]; + shadow = elem.shadowRoot; + if ( shadow === undefined ) { + style = elem.style; + if ( typeof style === 'object' || typeof style.removeProperty === 'function' ) { + style.removeProperty('display'); + } + continue; } + if ( shadow === null || shadow.className !== sessionId ) { + continue; + } + shadow.children[0].select = ''; } /******************************************************************************/ diff --git a/src/js/scriptlets/cosmetic-on.js b/src/js/scriptlets/cosmetic-on.js index be9f4c142..9a903fe6e 100644 --- a/src/js/scriptlets/cosmetic-on.js +++ b/src/js/scriptlets/cosmetic-on.js @@ -49,6 +49,8 @@ if ( Array.isArray(styles) === false ) { return; } +var sessionId = vAPI.sessionId; + /******************************************************************************/ // Insert all cosmetic filtering-related style tags in the DOM @@ -56,23 +58,16 @@ if ( Array.isArray(styles) === false ) { var selectors = []; var reProperties = /\s*\{[^}]+\}\s*/; var style, i; -var parent = document.head || document.body || document.documentElement; i = styles.length; while ( i-- ) { style = styles[i]; - if ( style.parentElement !== null ) { - continue; - } - if ( parent === null ) { - continue; - } selectors.push(style.textContent.replace(reProperties, '')); - parent.appendChild(style); + if ( style.sheet !== null ) { + style.sheet.disabled = false; + } } -// Add `display: none !important` attribute - if ( selectors.length === 0 ) { return; } @@ -83,12 +78,22 @@ try { } catch (e) { } +var elem, shadow, selector = '#' + sessionId; i = elems.length; while ( i-- ) { - style = elems[i].style; - if ( typeof style === 'object' || typeof style.removeProperty === 'function' ) { - style.setProperty('display', 'none', 'important'); + elem = elems[i]; + shadow = elem.shadowRoot; + if ( shadow === undefined ) { + style = elems[i].style; + if ( typeof style === 'object' || typeof style.removeProperty === 'function' ) { + style.setProperty('display', 'none', 'important'); + } + continue; } + if ( shadow === null || shadow.className !== sessionId ) { + continue; + } + shadow.children[0].select = selector; } /******************************************************************************/ diff --git a/src/js/scriptlets/dom-inspector.js b/src/js/scriptlets/dom-inspector.js index 6eb4ea8d1..fc8f72e96 100644 --- a/src/js/scriptlets/dom-inspector.js +++ b/src/js/scriptlets/dom-inspector.js @@ -42,7 +42,9 @@ if ( typeof vAPI !== 'object' ) { /******************************************************************************/ -if ( document.querySelector('iframe.dom-inspector.' + vAPI.sessionId) !== null ) { +var sessionId = vAPI.sessionId; + +if ( document.querySelector('iframe.dom-inspector.' + sessionId) !== null ) { return; } @@ -159,7 +161,7 @@ var toggledNodes = new Map(); // Some kind of fingerprint for the DOM, without incurring too much overhead. var domFingerprint = function() { - return vAPI.sessionId; + return sessionId; }; /******************************************************************************/ @@ -274,7 +276,7 @@ var domLayout = (function() { return null; } // skip uBlock's own nodes - if ( node.classList.contains(vAPI.sessionId) ) { + if ( node.classList.contains(sessionId) ) { return null; } if ( level === 0 && localName === 'body' ) { @@ -807,14 +809,14 @@ var onScrolled = function() { /******************************************************************************/ var resetToggledNodes = function() { - var value; + var details; // Chromium does not support destructuring as of v43. for ( var node of toggledNodes.keys() ) { - value = toggledNodes.get(node); - if ( value !== null ) { - node.style.removeProperty('display'); + details = toggledNodes.get(node); + if ( details.show ) { + showNode(node, details.v1, details.v2); } else { - node.style.setProperty('display', value); + hideNode(node); } } toggledNodes.clear(); @@ -845,6 +847,7 @@ var selectNodes = function(selector, nid) { /******************************************************************************/ var shutdown = function() { + toggleStylesVisibility(true); resetToggledNodes(); domLayout.shutdown(); localMessager.removeAllListeners(); @@ -869,32 +872,38 @@ var toggleNodes = function(nodes, originalState, targetState) { if ( i === 0 ) { return; } - var node, value; + var node, details; while ( i-- ) { node = nodes[i]; - if ( originalState ) { // any, ? - if ( targetState ) { // any, any - value = toggledNodes.get(node); - if ( value === undefined ) { - continue; - } - if ( value !== null ) { - node.style.removeProperty('display'); - } else { - node.style.setProperty('display', value); - } + // originally visible node + if ( originalState ) { + // unhide visible node + if ( targetState ) { + details = toggledNodes.get(node) || {}; + showNode(node, details.v1, details.v2); toggledNodes.delete(node); - } else { // any, hidden - toggledNodes.set(node, node.style.getPropertyValue('display') || null); - node.style.setProperty('display', 'none'); } - } else { // hidden, ? - if ( targetState ) { // hidden, any - toggledNodes.set(node, 'none'); - node.style.setProperty('display', 'initial', 'important'); - } else { // hidden, hidden + // hide visible node + else { + toggledNodes.set(node, { + show: true, + v1: node.style.getPropertyValue('display') || '', + v2: node.style.getPropertyPriority('display') || '' + }); + hideNode(node); + } + } + // originally hidden node + else { + // show hidden node + if ( targetState ) { + toggledNodes.set(node, { show: false }); + showNode(node, 'initial', 'important'); + } + // hide hidden node + else { + hideNode(node); toggledNodes.delete(node); - node.style.setProperty('display', 'none', 'important'); } } } @@ -902,6 +911,56 @@ var toggleNodes = function(nodes, originalState, targetState) { // https://www.youtube.com/watch?v=L5jRewnxSBY +/******************************************************************************/ + +var showNode = function(node, v1, v2) { + var shadow = node.shadowRoot; + if ( shadow === undefined ) { + if ( !v1 ) { + node.style.removeProperty('display'); + } else { + node.style.setProperty('display', v1, v2); + } + } else if ( shadow !== null && shadow.className === sessionId ) { + shadow.children[0].select = ''; + } +}; + +/******************************************************************************/ + +var hideNode = function(node) { + var shadow = node.shadowRoot; + if ( shadow === undefined ) { + node.style.setProperty('display', 'none', 'important'); + return; + } + if ( shadow !== null && shadow.className === sessionId ) { + shadow.children[0].select = '#' + sessionId; + return; + } + // not all node 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; + while ( i-- ) { + sheet = styleTags[i].sheet; + if ( sheet !== null ) { + sheet.disabled = !state; + } + } +}; + /******************************************************************************/ /******************************************************************************/ @@ -978,7 +1037,7 @@ var onMessage = function(request) { // Install DOM inspector widget pickerRoot = document.createElement('iframe'); -pickerRoot.classList.add(vAPI.sessionId); +pickerRoot.classList.add(sessionId); pickerRoot.classList.add('dom-inspector'); pickerRoot.style.cssText = [ 'background: transparent', @@ -1048,6 +1107,7 @@ pickerRoot.onload = function() { window.addEventListener('scroll', onScrolled, true); highlightElements(); + toggleStylesVisibility(false); localMessager.addListener(onMessage); };