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

Side by Side Diff: include.preload.js

Issue 29370970: [adblockpluschrome] Issue 3596 - Added support for CSS property filters to devtools panel (Closed)
Patch Set: Addressed review comments Created Feb. 18, 2017, 12:56 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | lib/devtools.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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-2016 Eyeo GmbH 3 * Copyright (C) 2006-2016 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 174 matching lines...) Expand 10 before | Expand all | Expand 10 after
185 try 185 try
186 { 186 {
187 return element.contentDocument; 187 return element.contentDocument;
188 } 188 }
189 catch (e) 189 catch (e)
190 { 190 {
191 return null; 191 return null;
192 } 192 }
193 } 193 }
194 194
195 function ElementHidingTracer(selectors) 195 function ElementHidingTracer()
196 { 196 {
197 this.selectors = selectors; 197 this.selectors = [];
198 this.filters = [];
198 199
199 this.changedNodes = []; 200 this.changedNodes = [];
200 this.timeout = null; 201 this.timeout = null;
201 202
202 this.observer = new MutationObserver(this.observe.bind(this)); 203 this.observer = new MutationObserver(this.observe.bind(this));
203 this.trace = this.trace.bind(this); 204 this.trace = this.trace.bind(this);
204 205
205 if (document.readyState == "loading") 206 if (document.readyState == "loading")
206 document.addEventListener("DOMContentLoaded", this.trace); 207 document.addEventListener("DOMContentLoaded", this.trace);
207 else 208 else
208 this.trace(); 209 this.trace();
209 } 210 }
210 ElementHidingTracer.prototype = { 211 ElementHidingTracer.prototype = {
211 checkNodes: function(nodes) 212 addSelectors: function(selectors, filters)
212 { 213 {
213 var matchedSelectors = []; 214 if (!filters)
215 filters = new Array(selectors.length);
Sebastian Noack 2017/02/20 12:19:55 In the end, it turns out the logic would be somewh
wspee 2017/02/23 10:29:39 NP, Done.
214 216
215 // Find all selectors that match any hidden element inside the given nodes. 217 if (document.readyState != "loading")
216 for (var i = 0; i < this.selectors.length; i++) 218 this.checkNodes([document], selectors, filters);
219
220 this.selectors.push(...selectors);
221 this.filters.push(...filters);
222 },
223
224 checkNodes: function(nodes, selectors, filters)
225 {
226 let matchedSelectors = [];
227
228 for (let i = 0; i < selectors.length; i++)
217 { 229 {
218 var selector = this.selectors[i]; 230 nodes: for (let node of nodes)
231 {
232 let elements = node.querySelectorAll(selectors[i]);
219 233
220 for (var j = 0; j < nodes.length; j++) 234 for (let element of elements)
221 {
222 var elements = nodes[j].querySelectorAll(selector);
223 var matched = false;
224
225 for (var k = 0; k < elements.length; k++)
226 { 235 {
227 // Only consider selectors that actually have an effect on the 236 // Only consider selectors that actually have an effect on the
228 // computed styles, and aren't overridden by rules with higher 237 // computed styles, and aren't overridden by rules with higher
229 // priority, or haven't been circumvented in a different way. 238 // priority, or haven't been circumvented in a different way.
230 if (getComputedStyle(elements[k]).display == "none") 239 if (getComputedStyle(element).display == "none")
231 { 240 {
232 matchedSelectors.push(selector); 241 let filter = filters[i] || selectors[i];
233 matched = true; 242 matchedSelectors.push(filter.replace(/^.*?##/, ""));
234 break; 243 break nodes;
235 } 244 }
236 } 245 }
237
238 if (matched)
239 break;
240 } 246 }
241 } 247 }
242 248
243 if (matchedSelectors.length > 0) 249 if (matchedSelectors.length > 0)
244 ext.backgroundPage.sendMessage({ 250 ext.backgroundPage.sendMessage({
245 type: "devtools.traceElemHide", 251 type: "devtools.traceElemHide",
246 selectors: matchedSelectors 252 selectors: matchedSelectors
247 }); 253 });
248 }, 254 },
249 255
250 onTimeout: function() 256 onTimeout: function()
251 { 257 {
252 this.checkNodes(this.changedNodes); 258 this.checkNodes(this.changedNodes, this.selectors, this.filters);
253 this.changedNodes = []; 259 this.changedNodes = [];
254 this.timeout = null; 260 this.timeout = null;
255 }, 261 },
256 262
257 observe: function(mutations) 263 observe: function(mutations)
258 { 264 {
259 // Forget previously changed nodes that are no longer in the DOM. 265 // Forget previously changed nodes that are no longer in the DOM.
260 for (var i = 0; i < this.changedNodes.length; i++) 266 for (var i = 0; i < this.changedNodes.length; i++)
261 { 267 {
262 if (!document.contains(this.changedNodes[i])) 268 if (!document.contains(this.changedNodes[i]))
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
305 311
306 // Check only nodes whose descendants have changed, and not more often 312 // Check only nodes whose descendants have changed, and not more often
307 // than once a second. Otherwise large pages with a lot of DOM mutations 313 // than once a second. Otherwise large pages with a lot of DOM mutations
308 // (like YouTube) freeze when the devtools panel is active. 314 // (like YouTube) freeze when the devtools panel is active.
309 if (this.timeout == null) 315 if (this.timeout == null)
310 this.timeout = setTimeout(this.onTimeout.bind(this), 1000); 316 this.timeout = setTimeout(this.onTimeout.bind(this), 1000);
311 }, 317 },
312 318
313 trace: function() 319 trace: function()
314 { 320 {
315 this.checkNodes([document]); 321 this.checkNodes([document], this.selectors, this.filters);
316 322
317 this.observer.observe( 323 this.observer.observe(
318 document, 324 document,
319 { 325 {
320 childList: true, 326 childList: true,
321 attributes: true, 327 attributes: true,
322 subtree: true 328 subtree: true
323 } 329 }
324 ); 330 );
325 }, 331 },
(...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after
483 var shadow = shadowRoot(this); 489 var shadow = shadowRoot(this);
484 return shadow == ourShadowRoot ? null : shadow; 490 return shadow == ourShadowRoot ? null : shadow;
485 } 491 }
486 }); 492 });
487 }, null); 493 }, null);
488 } 494 }
489 495
490 return shadow; 496 return shadow;
491 }, 497 },
492 498
493 addSelectors: function(selectors) 499 addSelectors: function(selectors, filters)
494 { 500 {
495 if (selectors.length == 0) 501 if (selectors.length == 0)
496 return; 502 return;
497 503
498 if (!this.style) 504 if (!this.style)
499 { 505 {
500 // Create <style> element lazily, only if we add styles. Add it to 506 // Create <style> element lazily, only if we add styles. Add it to
501 // the shadow DOM if possible. Otherwise fallback to the <head> or 507 // the shadow DOM if possible. Otherwise fallback to the <head> or
502 // <html> element. If we have injected a style element before that 508 // <html> element. If we have injected a style element before that
503 // has been removed (the sheet property is null), create a new one. 509 // has been removed (the sheet property is null), create a new one.
504 this.style = document.createElement("style"); 510 this.style = document.createElement("style");
505 (this.shadow || document.head 511 (this.shadow || document.head
506 || document.documentElement).appendChild(this.style); 512 || document.documentElement).appendChild(this.style);
507 513
508 // It can happen that the frame already navigated to a different 514 // It can happen that the frame already navigated to a different
509 // document while we were waiting for the background page to respond. 515 // document while we were waiting for the background page to respond.
510 // In that case the sheet property will stay null, after addind the 516 // In that case the sheet property will stay null, after addind the
511 // <style> element to the shadow DOM. 517 // <style> element to the shadow DOM.
512 if (!this.style.sheet) 518 if (!this.style.sheet)
513 return; 519 return;
514 } 520 }
515 521
516 // If using shadow DOM, we have to add the ::content pseudo-element 522 // If using shadow DOM, we have to add the ::content pseudo-element
517 // before each selector, in order to match elements within the 523 // before each selector, in order to match elements within the
518 // insertion point. 524 // insertion point.
525 let preparedSelectors = [];
519 if (this.shadow) 526 if (this.shadow)
520 { 527 {
521 var preparedSelectors = [];
522 for (var i = 0; i < selectors.length; i++) 528 for (var i = 0; i < selectors.length; i++)
523 { 529 {
524 var subSelectors = splitSelector(selectors[i]); 530 var subSelectors = splitSelector(selectors[i]);
525 for (var j = 0; j < subSelectors.length; j++) 531 for (var j = 0; j < subSelectors.length; j++)
526 preparedSelectors.push("::content " + subSelectors[j]); 532 preparedSelectors.push("::content " + subSelectors[j]);
527 } 533 }
528 selectors = preparedSelectors; 534 }
535 else
536 {
537 preparedSelectors = selectors;
529 } 538 }
530 539
531 // Safari only allows 8192 primitive selectors to be injected at once[1], we 540 // Safari only allows 8192 primitive selectors to be injected at once[1], we
532 // therefore chunk the inserted selectors into groups of 200 to be safe. 541 // therefore chunk the inserted selectors into groups of 200 to be safe.
533 // (Chrome also has a limit, larger... but we're not certain exactly what it 542 // (Chrome also has a limit, larger... but we're not certain exactly what it
534 // is! Edge apparently has no such limit.) 543 // is! Edge apparently has no such limit.)
535 // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69 debb75fc1de/Source/WebCore/css/RuleSet.h#L68 544 // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69 debb75fc1de/Source/WebCore/css/RuleSet.h#L68
536 for (var i = 0; i < selectors.length; i += this.selectorGroupSize) 545 for (var i = 0; i < preparedSelectors.length; i += this.selectorGroupSize)
537 { 546 {
538 var selector = selectors.slice(i, i + this.selectorGroupSize).join(", "); 547 var selector = preparedSelectors.slice(i, i + this.selectorGroupSize).join (", ");
539 this.style.sheet.insertRule(selector + "{display: none !important;}", 548 this.style.sheet.insertRule(selector + "{display: none !important;}",
540 this.style.sheet.cssRules.length); 549 this.style.sheet.cssRules.length);
541 } 550 }
551
552 if (this.tracer)
553 this.tracer.addSelectors(selectors, filters);
542 }, 554 },
543 555
544 apply: function() 556 apply: function()
545 { 557 {
546 var selectors = null; 558 ext.backgroundPage.sendMessage({type: "get-selectors"}, response =>
547 var elemHideEmulationLoaded = false;
548
549 var checkLoaded = function()
550 { 559 {
551 if (!selectors || !elemHideEmulationLoaded)
552 return;
553
554 if (this.tracer) 560 if (this.tracer)
555 this.tracer.disconnect(); 561 this.tracer.disconnect();
556 this.tracer = null; 562 this.tracer = null;
557 563
558 if (this.style && this.style.parentElement) 564 if (this.style && this.style.parentElement)
559 this.style.parentElement.removeChild(this.style); 565 this.style.parentElement.removeChild(this.style);
560 this.style = null; 566 this.style = null;
561 567
562 this.addSelectors(selectors.selectors); 568 if (response.trace)
569 this.tracer = new ElementHidingTracer();
570
571 this.addSelectors(response.selectors);
572
563 this.elemHideEmulation.apply(); 573 this.elemHideEmulation.apply();
564
565 if (selectors.trace)
566 this.tracer = new ElementHidingTracer(selectors.selectors);
567 }.bind(this);
568
569 ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response)
570 {
571 selectors = response;
572 checkLoaded();
573 });
574
Sebastian Noack 2017/02/18 13:11:01 Nit: I'd remove the blank line here (as it was bef
575 this.elemHideEmulation.load(function()
576 {
577 elemHideEmulationLoaded = true;
578 checkLoaded();
579 }); 574 });
580 } 575 }
581 }; 576 };
582 577
583 if (document instanceof HTMLDocument) 578 if (document instanceof HTMLDocument)
584 { 579 {
585 checkSitekey(); 580 checkSitekey();
586 wrapWebSocket(); 581 wrapWebSocket();
587 582
588 var elemhide = new ElemHide(); 583 var elemhide = new ElemHide();
589 elemhide.apply(); 584 elemhide.apply();
590 585
591 document.addEventListener("error", function(event) 586 document.addEventListener("error", function(event)
592 { 587 {
593 checkCollapse(event.target); 588 checkCollapse(event.target);
594 }, true); 589 }, true);
595 590
596 document.addEventListener("load", function(event) 591 document.addEventListener("load", function(event)
597 { 592 {
598 var element = event.target; 593 var element = event.target;
599 if (/^i?frame$/.test(element.localName)) 594 if (/^i?frame$/.test(element.localName))
600 checkCollapse(element); 595 checkCollapse(element);
601 }, true); 596 }, true);
602 } 597 }
OLDNEW
« no previous file with comments | « no previous file | lib/devtools.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld