| Index: include.preload.js |
| =================================================================== |
| --- a/include.preload.js |
| +++ b/include.preload.js |
| @@ -104,6 +104,139 @@ |
| return !("isFrameWithoutContentScript" in contentDocument.defaultView); |
| } |
| +function traceHiddenElements(document, selectors) |
| +{ |
| + var changedNodes = []; |
| + var timeout = null; |
| + |
| + var checkNodes = function(nodes) |
| + { |
| + var matchedSelectors = []; |
| + |
| + // Find all selectors that match any hidden element inside the given nodes. |
| + for (var i = 0; i < selectors.length; i++) |
| + { |
| + var selector = selectors[i]; |
| + |
| + for (var j = 0; j < nodes.length; j++) |
| + { |
| + var elements = nodes[j].querySelectorAll(selector); |
| + var matched = false; |
| + |
| + for (var k = 0; k < elements.length; k++) |
| + { |
| + // Only consider selectors that actually have an effect on the |
| + // computed styles, and aren't overridden by rules with higher |
| + // priority, or haven't been circumvented in a different way. |
| + if (getComputedStyle(elements[k]).display == "none") |
| + { |
| + matchedSelectors.push(selector); |
| + matched = true; |
| + break; |
| + } |
| + } |
| + |
| + if (matched) |
| + break; |
| + } |
| + } |
| + |
| + if (matchedSelectors.length > 0) |
| + ext.backgroundPage.sendMessage({type: "trace-elemhide", selectors: matchedSelectors}); |
| + }; |
| + |
| + var observer = new MutationObserver(function(mutations) |
| + { |
| + // Forget previously changed nodes that are no longer in the DOM. |
| + for (var i = 0; i < changedNodes.length; i++) |
| + { |
| + if (!document.contains(changedNodes[i])) |
| + changedNodes.splice(i--, 1); |
| + } |
| + |
| + for (var j = 0; j < mutations.length; j++) |
| + { |
| + var mutation = mutations[j]; |
| + var node = mutation.target; |
| + |
| + // Ignore mutations of nodes that aren't in the DOM anymore. |
| + if (!document.contains(node)) |
| + continue; |
| + |
| + // Since querySelectorAll() doesn't consider the root itself |
| + // and since CSS selectors can also match siblings, we have |
| + // to consider the parent node for attribute mutations. |
| + if (mutation.type == "attributes") |
| + node = node.parentNode; |
| + |
| + var addNode = true; |
| + for (var k = 0; k < changedNodes.length; k++) |
| + { |
| + var previouslyChangedNode = changedNodes[k]; |
| + |
| + // If we are already going to check an ancestor of this node, |
| + // we can ignore this node, since it will be considered anyway |
| + // when checking one of its ancestors. |
| + if (previouslyChangedNode.contains(node)) |
| + { |
| + addNode = false; |
| + break; |
| + } |
| + |
| + // If this node is an ancestor of a node that previously changed, |
| + // we can ignore that node, since it will be considered anyway |
| + // when checking one of its ancestors. |
| + if (node.contains(previouslyChangedNode)) |
| + changedNodes.splice(k--, 1); |
| + } |
| + |
| + if (addNode) |
| + changedNodes.push(node); |
| + } |
| + |
| + // Check only nodes whose descendants have changed, and not more often |
| + // than once a second. Otherwise large pages with a lot of DOM mutations |
| + // (like YouTube) freeze when the devtools panel is active. |
| + if (!timeout) |
| + { |
| + timeout = setTimeout(function() |
| + { |
| + checkNodes(changedNodes); |
| + changedNodes = []; |
| + timeout = null; |
| + }, 1000); |
| + } |
| + }); |
| + |
| + var startTracing = function() |
| + { |
| + checkNodes([document]); |
| + |
| + observer.observe( |
| + document, |
| + { |
| + childList: true, |
| + attributes: true, |
| + subtree: true |
| + } |
| + ); |
| + }; |
| + |
| + var stopTracing = function() |
| + { |
| + document.removeEventListener("DOMContentLoaded", startTracing); |
| + observer.disconnect(); |
| + clearTimeout(timeout); |
| + }; |
| + |
| + if (document.readyState == "loading") |
| + document.addEventListener("DOMContentLoaded", startTracing); |
| + else |
| + startTracing(); |
| + |
| + return stopTracing; |
| +} |
| + |
| function reinjectRulesWhenRemoved(document, style) |
| { |
| var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; |
| @@ -134,8 +267,9 @@ |
| ext.backgroundPage.sendMessage( |
| {type: "get-selectors"}, |
| - function(selectors) |
| + function(response) |
| { |
| + var selectors = response.selectors; |
| while (selectors.length > 0) |
| { |
| var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); |
| @@ -190,7 +324,9 @@ |
| { |
| var shadow = null; |
| var style = null; |
| - var observer = null; |
| + |
| + var reinjectObserver = null; |
| + var stopTracing = null; |
| // Use Shadow DOM if available to don't mess with web pages that rely on |
| // the order of their own <style> tags (#309). |
| @@ -208,12 +344,18 @@ |
| var updateStylesheet = function(reinject) |
| { |
| - ext.backgroundPage.sendMessage({type: "get-selectors"}, function(selectors) |
| + ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response) |
| { |
| - if (observer) |
| + if (reinjectObserver) |
| { |
| - observer.disconnect(); |
| - observer = null; |
| + reinjectObserver.disconnect(); |
| + reinjectObserver = null; |
| + } |
| + |
| + if (stopTracing) |
| + { |
| + stopTracing(); |
| + stopTracing = null; |
| } |
| if (style && style.parentElement) |
| @@ -222,6 +364,7 @@ |
| style = null; |
| } |
| + var selectors = response.selectors; |
| if (selectors.length > 0) |
| { |
| // Create <style> element lazily, only if we add styles. Add it to |
| @@ -250,9 +393,12 @@ |
| var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); |
| style.sheet.insertRule(selector + " { display: none !important; }", i); |
| } |
| + |
| + reinjectObserver = reinjectRulesWhenRemoved(document, style); |
| + |
| + if (response.trace) |
| + stopTracing = traceHiddenElements(document, response.selectors); |
| } |
| - |
| - observer = reinjectRulesWhenRemoved(document, style); |
| } |
| }); |
| }; |