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); |
} |
}); |
}; |