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

Delta Between Two Patch Sets: lib/content/elemHideEmulation.js

Issue 29559743: Issue 5650 - Apply emulation filters to shadow DOMs Base URL: https://hg.adblockplus.org/adblockpluscore/
Left Patch Set: Remove unused node argument Created Oct. 18, 2017, 11:08 p.m.
Right Patch Set: Use pure generator function to extract added subtrees Created Oct. 22, 2017, 7:51 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | test/browser/elemHideEmulation.js » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-present eyeo GmbH 3 * Copyright (C) 2006-present eyeo GmbH
4 * 4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify 5 * Adblock Plus is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 3 as 6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation. 7 * published by the Free Software Foundation.
8 * 8 *
9 * Adblock Plus is distributed in the hope that it will be useful, 9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
(...skipping 18 matching lines...) Expand all
29 */ 29 */
30 function positionInParent(node) 30 function positionInParent(node)
31 { 31 {
32 let {children} = node.parentNode; 32 let {children} = node.parentNode;
33 for (let i = 0; i < children.length; i++) 33 for (let i = 0; i < children.length; i++)
34 if (children[i] == node) 34 if (children[i] == node)
35 return i + 1; 35 return i + 1;
36 return 0; 36 return 0;
37 } 37 }
38 38
39 function isShadowRoot(node)
40 {
41 return typeof ShadowRoot != "undefined" && node instanceof ShadowRoot;
42 }
43
39 function makeSelector(node, selector) 44 function makeSelector(node, selector)
40 { 45 {
41 if (node == null) 46 if (node == null)
42 return null; 47 return null;
43 48
44 // If this is the topmost element in a shadow DOM, climb up one more level 49 // If this is the topmost element in a shadow DOM, climb up one more level
45 // and then use a ":host" prefix. 50 // and then use a ":host" prefix.
46 if (!node.parentElement && !(node.parentNode instanceof ShadowRoot)) 51 if (!node.parentElement && !isShadowRoot(node.parentNode))
47 { 52 {
48 let newSelector = node instanceof ShadowRoot ? ":host" : ":root"; 53 let newSelector = isShadowRoot(node) ? ":host" : ":root";
49 if (selector) 54 if (selector)
50 newSelector += " > " + selector; 55 newSelector += " > " + selector;
51 return newSelector; 56 return newSelector;
52 } 57 }
53 let idx = positionInParent(node); 58 let idx = positionInParent(node);
54 if (idx > 0) 59 if (idx > 0)
55 { 60 {
56 let newSelector = `${node.tagName}:nth-child(${idx})`; 61 let newSelector = `${node.tagName}:nth-child(${idx})`;
57 if (selector) 62 if (selector)
58 newSelector += " > " + selector; 63 newSelector += " > " + selector;
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
139 yield null; 144 yield null;
140 else 145 else
141 yield* evaluate(chain, index + 1, selector, element, styles); 146 yield* evaluate(chain, index + 1, selector, element, styles);
142 } 147 }
143 // Just in case the getSelectors() generator above had to run some heavy 148 // Just in case the getSelectors() generator above had to run some heavy
144 // document.querySelectorAll() call which didn't produce any results, make 149 // document.querySelectorAll() call which didn't produce any results, make
145 // sure there is at least one point where execution can pause. 150 // sure there is at least one point where execution can pause.
146 yield null; 151 yield null;
147 } 152 }
148 153
149 function removeRedundantNodes(nodes) 154 function isDescendantOf(node, subtrees)
150 { 155 {
151 let nodesInfo = []; 156 return subtrees.some(subtree => subtree.contains(node));
152 for (let node of nodes) 157 }
153 { 158
154 let nodeInfo = {node}; 159 function* extractAddedSubtrees(mutations)
155 nodesInfo.push(nodeInfo); 160 {
156 161 let knownSubtrees = [];
157 let link = node; 162
158 while (link = link.parentNode) 163 for (let mutation of mutations)
159 { 164 {
160 // Since a node's ancestors are always added to the DOM before it, they 165 for (let node of mutation.addedNodes)
161 // will likely also appear before it in the list (at least on Chromium). 166 {
162 // If we encounter an ancestor here that has already been marked 167 if (node instanceof Element && !isDescendantOf(node, knownSubtrees))
163 // redundant, we can mark this node redundant too. This is an 168 {
164 // optimization that saves us having to do a lookup in the set and keep 169 knownSubtrees.push(node);
165 // walking up the tree. 170 yield node;
166 if (link.redundant || nodes.has(link))
167 {
168 nodeInfo.redundant = true;
Sebastian Noack 2017/10/19 00:43:08 Why don't you just keep track of redundant nodes i
Manish Jethani 2017/10/19 02:13:44 You mean "node.redundant = true" rather than "node
Manish Jethani 2017/10/19 02:19:06 By setting a redundant flag we don't just avoid wa
Sebastian Noack 2017/10/19 03:51:01 How about using a Set() object containing only red
Manish Jethani 2017/10/19 09:57:14 How do you do lookups efficiently in an array? Pe
Sebastian Noack 2017/10/19 18:35:39 I was basically talking about what lainverse is su
Manish Jethani 2017/10/19 23:14:58 OK, I did not realize Node.contains was so efficie
169 break;
170 } 171 }
171 } 172 }
172 } 173 }
173
174 for (let nodeInfo of nodesInfo)
175 {
176 if (nodeInfo.redundant)
177 nodes.delete(nodeInfo.node);
178 }
179
180 return nodes;
181 } 174 }
182 175
183 function* traverse(nodes) 176 function* traverse(nodes)
184 { 177 {
185 for (let node of nodes) 178 for (let node of nodes)
186 { 179 {
187 yield* traverse(node.children); 180 yield* traverse(node.children);
188 yield node; 181 yield node;
189 } 182 }
190 } 183 }
191 184
192 function niceLoop(iterator, callback) 185 function niceLoop(iterator, callback)
193 { 186 {
194 let loop = () => 187 let loop = () =>
195 { 188 {
196 let cycleStart = performance.now(); 189 let cycleStart = performance.now();
197 190
198 for (let next = iterator.next(); !next.done; next = iterator.next()) 191 for (let next = iterator.next(); !next.done; next = iterator.next())
199 { 192 {
200 callback(next.value); 193 callback(next.value);
201 194
202 if (performance.now() - cycleStart > MAX_SYNCHRONOUS_PROCESSING_TIME) 195 if (performance.now() - cycleStart > MAX_SYNCHRONOUS_PROCESSING_TIME)
196 {
203 setTimeout(loop, 0); 197 setTimeout(loop, 0);
Sebastian Noack 2017/10/19 00:43:08 Don't you have to terminate the loop here?
Manish Jethani 2017/10/19 02:13:44 Right, I forgot to terminate it. Done.
198 return;
199 }
204 } 200 }
205 }; 201 };
206 202
207 loop(); 203 loop();
208 } 204 }
209 205
210 function PlainSelector(selector) 206 function PlainSelector(selector)
211 { 207 {
212 this._selector = selector; 208 this._selector = selector;
213 } 209 }
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after
359 yield [selector, subtree]; 355 yield [selector, subtree];
360 } 356 }
361 }; 357 };
362 358
363 function isSelectorHidingOnlyPattern(pattern) 359 function isSelectorHidingOnlyPattern(pattern)
364 { 360 {
365 return pattern.selectors.some(s => s.preferHideWithSelector) && 361 return pattern.selectors.some(s => s.preferHideWithSelector) &&
366 !pattern.selectors.some(s => s.requiresHiding); 362 !pattern.selectors.some(s => s.requiresHiding);
367 } 363 }
368 364
369 function ElemHideEmulation(document, root, addSelectorsFunc, hideElemsFunc) 365 function ElemHideEmulation(document, root, addSelectorsFunc, hideElemsFunc,
366 shadowAttachedEventType)
370 { 367 {
371 this.document = document; 368 this.document = document;
372 this.root = root || document; 369 this.root = root || document;
373 this.addSelectorsFunc = addSelectorsFunc; 370 this.addSelectorsFunc = addSelectorsFunc;
374 this.hideElemsFunc = hideElemsFunc; 371 this.hideElemsFunc = hideElemsFunc;
372 this.shadowAttachedEventType = shadowAttachedEventType;
375 this.patterns = []; 373 this.patterns = [];
376 this.observer = new MutationObserver(this.observe.bind(this)); 374 this.observer = new MutationObserver(this.observe.bind(this));
377 this.shadowEmulations = new WeakMap(); 375 this.shadowEmulations = new WeakMap();
378 376
377 if (shadowAttachedEventType)
378 {
379 this.root.addEventListener(shadowAttachedEventType,
380 this.onShadowAttached.bind(this), true);
381 }
382
379 if (this.root == this.document) 383 if (this.root == this.document)
380 {
381 this.document.addEventListener("load", this.onLoad.bind(this), true); 384 this.document.addEventListener("load", this.onLoad.bind(this), true);
382 this.document.addEventListener("shadowAttached", 385 else
Sebastian Noack 2017/10/19 00:43:08 So I assume you still intend to patch attachShadow
Manish Jethani 2017/10/19 02:13:44 Ack. Web pages won't get the event because we're
383 this.onShadowAttached.bind(this), true); 386 this.findShadowRoots(this.root.children);
384 }
385 } 387 }
386 388
387 ElemHideEmulation.prototype = { 389 ElemHideEmulation.prototype = {
388 isSameOrigin(stylesheet) 390 isSameOrigin(stylesheet)
389 { 391 {
390 try 392 try
391 { 393 {
392 return new URL(stylesheet.href).origin == this.document.location.origin; 394 return new URL(stylesheet.href).origin == this.document.location.origin;
393 } 395 }
394 catch (e) 396 catch (e)
(...skipping 232 matching lines...) Expand 10 before | Expand all | Expand 10 after
627 } 629 }
628 else 630 else
629 { 631 {
630 this._filteringInProgress = true; 632 this._filteringInProgress = true;
631 this._addSelectors(stylesheets, completion); 633 this._addSelectors(stylesheets, completion);
632 } 634 }
633 }, 635 },
634 636
635 onLoad(event) 637 onLoad(event)
636 { 638 {
639 this.findShadowRoots(this.root.children);
640
637 if (this.patterns.length == 0) 641 if (this.patterns.length == 0)
638 return; 642 return;
639 643
640 let stylesheet = event.target.sheet; 644 let stylesheet = event.target.sheet;
641 if (stylesheet) 645 if (stylesheet)
642 this.queueFiltering([stylesheet]); 646 this.queueFiltering([stylesheet]);
643 }, 647 },
644 648
645 onShadowAttached(event) 649 onShadowAttached(event)
646 { 650 {
(...skipping 10 matching lines...) Expand all
657 this.addShadowRoot(shadowRoot); 661 this.addShadowRoot(shadowRoot);
658 }, 662 },
659 663
660 addShadowRoot(shadowRoot) 664 addShadowRoot(shadowRoot)
661 { 665 {
662 if (!this.shadowEmulations.has(shadowRoot)) 666 if (!this.shadowEmulations.has(shadowRoot))
663 { 667 {
664 let emulation = new ElemHideEmulation(this.document, 668 let emulation = new ElemHideEmulation(this.document,
665 shadowRoot, 669 shadowRoot,
666 this.addSelectorsFunc, 670 this.addSelectorsFunc,
667 this.hideElemsFunc); 671 this.hideElemsFunc,
672 this.shadowAttachedEventType);
668 this.shadowEmulations.set(shadowRoot, emulation); 673 this.shadowEmulations.set(shadowRoot, emulation);
669 emulation.apply(this.patterns, true); 674 emulation.apply(this.patterns, true);
670 } 675 }
671 }, 676 },
672 677
673 observe(mutations) 678 findShadowRoots(nodes)
674 { 679 {
675 let allAddedElements = new Set(); 680 niceLoop(traverse(nodes), node =>
676 for (let mutation of mutations)
677 {
678 for (let node of mutation.addedNodes)
679 {
680 if (node instanceof Element)
681 allAddedElements.add(node);
682 }
683 }
684
685 // Find any preattached shadows.
686 niceLoop(traverse(removeRedundantNodes(allAddedElements)), node =>
687 { 681 {
688 let shadowRoot = node.shadowRoot; 682 let shadowRoot = node.shadowRoot;
689 if (shadowRoot) 683 if (shadowRoot)
690 this.addShadowRoot(shadowRoot); 684 this.addShadowRoot(shadowRoot);
691 }); 685 });
686 },
687
688 observe(mutations)
689 {
690 if (typeof ShadowRoot != "undefined")
691 {
692 // Find any preattached shadows.
693 this.findShadowRoots(extractAddedSubtrees(mutations));
694 }
692 695
693 this.queueFiltering(); 696 this.queueFiltering();
694 }, 697 },
695 698
696 apply(patterns, parsed) 699 apply(patterns, parsed)
697 { 700 {
698 if (parsed) 701 if (parsed)
699 { 702 {
700 this.patterns = patterns; 703 this.patterns = patterns;
701 } 704 }
(...skipping 18 matching lines...) Expand all
720 attributes: true, 723 attributes: true,
721 characterData: true, 724 characterData: true,
722 subtree: true 725 subtree: true
723 } 726 }
724 ); 727 );
725 } 728 }
726 } 729 }
727 }; 730 };
728 731
729 exports.ElemHideEmulation = ElemHideEmulation; 732 exports.ElemHideEmulation = ElemHideEmulation;
LEFTRIGHT

Powered by Google App Engine
This is Rietveld