Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: lib/content/elemHideEmulation.js

Issue 29559743: Issue 5650 - Apply emulation filters to shadow DOMs Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Prevent author code from intercepting shadowAttached event Created Sept. 30, 2017, 12:20 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | test/browser/elemHideEmulation.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/content/elemHideEmulation.js
===================================================================
--- a/lib/content/elemHideEmulation.js
+++ b/lib/content/elemHideEmulation.js
@@ -35,30 +35,33 @@
return i + 1;
return 0;
}
function makeSelector(node, selector)
{
if (node == null)
return null;
- if (!node.parentElement)
+
+ // If this is the topmost element in a shadow DOM, climb up one more level
+ // and then use a ":host" prefix.
+ if (!node.parentElement && !(node.parentNode instanceof ShadowRoot))
{
- let newSelector = ":root";
+ let newSelector = node instanceof ShadowRoot ? ":host" : ":root";
if (selector)
newSelector += " > " + selector;
return newSelector;
}
let idx = positionInParent(node);
if (idx > 0)
{
let newSelector = `${node.tagName}:nth-child(${idx})`;
if (selector)
newSelector += " > " + selector;
- return makeSelector(node.parentElement, newSelector);
+ return makeSelector(node.parentElement || node.parentNode, newSelector);
}
return selector;
}
function parseSelectorContent(content, startIndex)
{
let parens = 1;
@@ -297,22 +300,31 @@
};
function isSelectorHidingOnlyPattern(pattern)
{
return pattern.selectors.some(s => s.preferHideWithSelector) &&
!pattern.selectors.some(s => s.requiresHiding);
}
-function ElemHideEmulation(addSelectorsFunc, hideElemsFunc)
+function ElemHideEmulation(document, root, addSelectorsFunc, hideElemsFunc)
Manish Jethani 2017/09/30 12:29:01 It's best to pass these in the constructor as we n
{
this.document = document;
+ this.root = root || document;
this.addSelectorsFunc = addSelectorsFunc;
this.hideElemsFunc = hideElemsFunc;
this.observer = new MutationObserver(this.observe.bind(this));
+ this.shadowEmulations = new WeakMap();
+
+ if (this.root == this.document)
Manish Jethani 2017/09/30 12:29:01 We have to listen for these events as early as pos
+ {
+ this.document.addEventListener("load", this.onLoad.bind(this), true);
+ this.document.addEventListener("shadowAttached",
+ this.onShadowAttached.bind(this), true);
+ }
}
ElemHideEmulation.prototype = {
isSameOrigin(stylesheet)
{
try
{
return new URL(stylesheet.href).origin == this.document.location.origin;
@@ -404,17 +416,17 @@
let elements = [];
let elementFilters = [];
let cssStyles = [];
let stylesheetOnlyChange = !!stylesheets;
if (!stylesheets)
- stylesheets = this.document.styleSheets;
+ stylesheets = this.root.styleSheets;
for (let stylesheet of stylesheets)
{
// Explicitly ignore third-party stylesheets to ensure consistent behavior
// between Firefox and Chrome.
if (!this.isSameOrigin(stylesheet))
continue;
@@ -454,30 +466,30 @@
if (stylesheetOnlyChange &&
!pattern.selectors.some(selector => selector.dependsOnStyles))
{
pattern = null;
return processPatterns();
}
generator = evaluate(pattern.selectors, 0, "",
- this.document, cssStyles);
+ this.root, cssStyles);
}
for (let selector of generator)
{
if (selector != null)
{
if (isSelectorHidingOnlyPattern(pattern))
{
selectors.push(selector);
selectorFilters.push(pattern.text);
}
else
{
- for (let element of this.document.querySelectorAll(selector))
+ for (let element of this.root.querySelectorAll(selector))
{
elements.push(element);
elementFilters.push(pattern.text);
}
}
}
if (performance.now() - cycleStart > MAX_SYNCHRONOUS_PROCESSING_TIME)
{
@@ -555,46 +567,103 @@
{
this._filteringInProgress = true;
this._addSelectors(stylesheets, completion);
}
},
onLoad(event)
{
+ if (this.patterns.length == 0)
+ return;
+
let stylesheet = event.target.sheet;
if (stylesheet)
this.queueFiltering([stylesheet]);
},
+ onShadowAttached(event)
+ {
+ event.stopImmediatePropagation();
Manish Jethani 2017/09/30 12:29:01 Prevent any author code from detecting even the pr
lainverse 2017/10/02 14:11:55 What will stop page from sending own events with t
Manish Jethani 2017/10/02 14:28:11 That's a good point, but the event would have to b
lainverse 2017/10/02 16:05:39 What about a proper DOM element with Proxy with ge
Manish Jethani 2017/10/02 16:12:31 A content script has its own copy of these APIs, a
+
+ if (this.patterns.length == 0)
+ return;
+
+ // The shadow root won't be available if it's a closed shadow root. Even
+ // though we override Element.attachShadow to create an open shadow root,
+ // it is possible that some other extension has overridden it before us to
+ // create a closed shadow root.
+ let shadowRoot = event.target.shadowRoot;
+ if (!shadowRoot)
+ return;
+
+ this.addShadowRoot(shadowRoot);
+ },
+
+ addShadowRoot(shadowRoot)
+ {
+ if (!this.shadowEmulations.has(shadowRoot))
+ {
+ let emulation = new ElemHideEmulation(this.document,
+ shadowRoot,
+ this.addSelectorsFunc,
+ this.hideElemsFunc);
+ this.shadowEmulations.set(shadowRoot, emulation);
+ emulation.apply(this.patterns, true);
+ }
+ },
+
+ findShadowRoots(node)
+ {
+ let shadowRoot = node.shadowRoot;
+ if (shadowRoot)
+ this.addShadowRoot(shadowRoot);
+
+ for (let child of node.children)
+ this.findShadowRoots(child);
+ },
+
observe(mutations)
{
+ for (let mutation of mutations)
+ {
+ // Find any preattached shadows.
+ for (let node of mutation.addedNodes)
+ this.findShadowRoots(node);
+ }
+
this.queueFiltering();
},
- apply(patterns)
+ apply(patterns, parsed)
{
- this.patterns = [];
- for (let pattern of patterns)
+ if (parsed)
+ {
+ this.patterns = patterns;
+ }
+ else
{
- let selectors = this.parseSelector(pattern.selector);
- if (selectors != null && selectors.length > 0)
- this.patterns.push({selectors, text: pattern.text});
+ this.patterns = [];
+ for (let pattern of patterns)
+ {
+ let selectors = this.parseSelector(pattern.selector);
+ if (selectors != null && selectors.length > 0)
+ this.patterns.push({selectors, text: pattern.text});
+ }
}
if (this.patterns.length > 0)
{
this.queueFiltering();
this.observer.observe(
- this.document,
+ this.root,
{
childList: true,
attributes: true,
characterData: true,
subtree: true
}
);
- this.document.addEventListener("load", this.onLoad.bind(this), true);
}
}
};
exports.ElemHideEmulation = ElemHideEmulation;
« no previous file with comments | « no previous file | test/browser/elemHideEmulation.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld