Index: include.preload.js |
=================================================================== |
--- a/include.preload.js |
+++ b/include.preload.js |
@@ -190,6 +190,147 @@ |
} |
} |
+function ElementHidingTracer(document, selectors) |
+{ |
+ this.document = document; |
+ this.selectors = selectors; |
+ |
+ this.changedNodes = []; |
+ this.timeout = null; |
+ |
+ this.observer = new MutationObserver(this.observe.bind(this)); |
+ this.trace = this.trace.bind(this); |
+ |
+ if (document.readyState == "loading") |
+ document.addEventListener("DOMContentLoaded", this.trace); |
+ else |
+ this.trace(); |
+} |
+ElementHidingTracer.prototype = { |
+ checkNodes: function(nodes) |
+ { |
+ var matchedSelectors = []; |
+ |
+ // Find all selectors that match any hidden element inside the given nodes. |
+ for (var i = 0; i < this.selectors.length; i++) |
+ { |
+ var selector = this.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 |
+ }); |
+ }, |
+ |
+ onTimeout: function() |
+ { |
+ this.checkNodes(this.changedNodes); |
+ this.changedNodes = []; |
+ this.timeout = null; |
+ }, |
+ |
+ observe: function(mutations) |
+ { |
+ // Forget previously changed nodes that are no longer in the DOM. |
+ for (var i = 0; i < this.changedNodes.length; i++) |
+ { |
+ if (!this.document.contains(this.changedNodes[i])) |
+ this.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 (!this.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 < this.changedNodes.length; k++) |
+ { |
+ var previouslyChangedNode = this.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)) |
+ this.changedNodes.splice(k--, 1); |
+ } |
+ |
+ if (addNode) |
+ this.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 (this.timeout == null) |
+ this.timeout = setTimeout(this.onTimeout.bind(this), 1000); |
+ }, |
+ |
+ trace: function() |
+ { |
+ this.checkNodes([this.document]); |
+ |
+ this.observer.observe( |
+ this.document, |
+ { |
+ childList: true, |
+ attributes: true, |
+ subtree: true |
+ } |
+ ); |
+ }, |
+ |
+ disconnect: function() |
+ { |
+ this.document.removeEventListener("DOMContentLoaded", this.trace); |
+ this.observer.disconnect(); |
+ clearTimeout(this.timeout); |
+ } |
+}; |
+ |
function reinjectRulesWhenRemoved(document, style) |
{ |
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; |
@@ -220,8 +361,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(", "); |
@@ -285,6 +427,7 @@ |
var shadow = null; |
var style = null; |
var observer = null; |
+ var tracer = null; |
var propertyFilters = new CSSPropertyFilters(window, addElemHideSelectors); |
// Use Shadow DOM if available to don't mess with web pages that rely on |
@@ -354,12 +497,19 @@ |
observer.disconnect(); |
observer = null; |
+ if (tracer) |
+ tracer.disconnect(); |
+ tracer = null; |
+ |
if (style && style.parentElement) |
style.parentElement.removeChild(style); |
style = null; |
- addElemHideSelectors(selectors); |
+ addElemHideSelectors(selectors.selectors); |
propertyFilters.apply(); |
+ |
+ if (selectors.trace) |
+ tracer = new ElementHidingTracer(document, selectors.selectors); |
}; |
ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response) |