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

Delta Between Two Patch Sets: include.preload.js

Issue 6393086494113792: Issue 154 - Added devtools panel showing blocked and blockable items (Closed)
Left Patch Set: Moved UI to adblockplusui repo Created Jan. 7, 2015, 1:46 p.m.
Right Patch Set: Adapt for UI changes generating domain specific filters when necessary Created Feb. 3, 2016, 10:40 a.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 | « dependencies ('k') | lib/devtools.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 <http://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2014 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
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 var SELECTOR_GROUP_SIZE = 20; 18 var SELECTOR_GROUP_SIZE = 20;
19 19
20 var typeMap = { 20 var typeMap = {
21 "img": "IMAGE", 21 "img": "IMAGE",
22 "input": "IMAGE", 22 "input": "IMAGE",
23 "picture": "IMAGE",
23 "audio": "MEDIA", 24 "audio": "MEDIA",
24 "video": "MEDIA", 25 "video": "MEDIA",
25 "frame": "SUBDOCUMENT", 26 "frame": "SUBDOCUMENT",
26 "iframe": "SUBDOCUMENT" 27 "iframe": "SUBDOCUMENT",
28 "object": "OBJECT",
29 "embed": "OBJECT"
27 }; 30 };
31
32 function getURLsFromObjectElement(element)
33 {
34 var url = element.getAttribute("data");
35 if (url)
36 return [url];
37
38 for (var i = 0; i < element.children.length; i++)
39 {
40 var child = element.children[i];
41 if (child.localName != "param")
42 continue;
43
44 var name = child.getAttribute("name");
45 if (name != "movie" && // Adobe Flash
46 name != "source" && // Silverlight
47 name != "src" && // Real Media + Quicktime
48 name != "FileName") // Windows Media
49 continue;
50
51 var value = child.getAttribute("value");
52 if (!value)
53 continue;
54
55 return [value];
56 }
57
58 return [];
59 }
60
61 function getURLsFromAttributes(element)
62 {
63 var urls = [];
64
65 if (element.src)
66 urls.push(element.src);
67
68 if (element.srcset)
69 {
70 var candidates = element.srcset.split(",");
71 for (var i = 0; i < candidates.length; i++)
72 {
73 var url = candidates[i].trim().replace(/\s+\S+$/, "");
74 if (url)
75 urls.push(url);
76 }
77 }
78
79 return urls;
80 }
81
82 function getURLsFromMediaElement(element)
83 {
84 var urls = getURLsFromAttributes(element);
85
86 for (var i = 0; i < element.children.length; i++)
87 {
88 var child = element.children[i];
89 if (child.localName == "source" || child.localName == "track")
90 urls.push.apply(urls, getURLsFromAttributes(child));
91 }
92
93 if (element.poster)
94 urls.push(element.poster);
95
96 return urls;
97 }
98
99 function getURLsFromElement(element)
100 {
101 var urls;
102 switch (element.localName)
103 {
104 case "object":
105 urls = getURLsFromObjectElement(element);
106 break;
107
108 case "video":
109 case "audio":
110 case "picture":
111 urls = getURLsFromMediaElement(element);
112 break;
113
114 default:
115 urls = getURLsFromAttributes(element);
116 break;
117 }
118
119 for (var i = 0; i < urls.length; i++)
120 {
121 if (/^(?!https?:)[\w-]+:/i.test(urls[i]))
122 urls.splice(i--, 1);
123 }
124
125 return urls;
126 }
28 127
29 function checkCollapse(element) 128 function checkCollapse(element)
30 { 129 {
31 var tag = element.localName; 130 var tag = element.localName;
32 if (tag in typeMap) 131 if (tag in typeMap)
33 { 132 {
34 // This element failed loading, did we block it? 133 // This element failed loading, did we block it?
35 var url = element.src; 134 var urls = getURLsFromElement(element);
36 if (!url || !/^https?:/i.test(url)) 135 if (urls.length == 0)
37 return; 136 return;
38 137
39 ext.backgroundPage.sendMessage( 138 ext.backgroundPage.sendMessage(
40 { 139 {
41 type: "should-collapse", 140 type: "should-collapse",
42 url: url, 141 urls: urls,
43 mediatype: typeMap[tag] 142 mediatype: typeMap[tag],
143 baseURL: document.location.href
44 }, 144 },
45 145
46 function(response) 146 function(response)
47 { 147 {
48 if (response && element.parentNode) 148 if (response && element.parentNode)
49 { 149 {
50 var property = "display"; 150 var property = "display";
51 var value = "none"; 151 var value = "none";
52 152
53 // <frame> cannot be removed, doing that will mess up the frameset 153 // <frame> cannot be removed, doing that will mess up the frameset
54 if (tag == "frame") 154 if (tag == "frame")
55 { 155 {
56 property = "visibility"; 156 property = "visibility";
57 value = "hidden"; 157 value = "hidden";
58 } 158 }
59 159
60 // <input type="image"> elements try to load their image again 160 // <input type="image"> elements try to load their image again
61 // when the "display" CSS property is set. So we have to check 161 // when the "display" CSS property is set. So we have to check
62 // that it isn't already collapsed to avoid an infinite recursion. 162 // that it isn't already collapsed to avoid an infinite recursion.
63 if (element.style.getPropertyValue(property) != value || 163 if (element.style.getPropertyValue(property) != value ||
64 element.style.getPropertyPriority(property) != "important") 164 element.style.getPropertyPriority(property) != "important")
65 element.style.setProperty(property, value, "important"); 165 element.style.setProperty(property, value, "important");
66 } 166 }
67 } 167 }
68 ); 168 );
69 } 169 }
170
171 window.collapsing = true;
70 } 172 }
71 173
72 function checkSitekey() 174 function checkSitekey()
73 { 175 {
74 var attr = document.documentElement.getAttribute("data-adblockkey"); 176 var attr = document.documentElement.getAttribute("data-adblockkey");
75 if (attr) 177 if (attr)
76 ext.backgroundPage.sendMessage({type: "add-sitekey", token: attr}); 178 ext.backgroundPage.sendMessage({type: "add-sitekey", token: attr});
77 } 179 }
78 180
79 function hasInlineURL(element, attribute) 181 function getContentDocument(element)
80 { 182 {
81 var value = element.getAttribute(attribute); 183 try
82 return value == null || /^\s*(javascript:|about:|$)/i.test(value); 184 {
83 } 185 return element.contentDocument;
84 186 }
85 function isInlineFrame(element) 187 catch (e)
86 { 188 {
87 switch (element.localName) 189 return null;
88 { 190 }
89 case "iframe": 191 }
90 return hasInlineURL(element, "src") || element.hasAttribute("srcdoc"); 192
91 case "frame": 193 function ElementHidingTracer(document, selectors)
92 return hasInlineURL(element, "src"); 194 {
93 case "object": 195 this.document = document;
94 return hasInlineURL(element, "data") && element.contentDocument; 196 this.selectors = selectors;
95 default: 197
96 return false; 198 this.changedNodes = [];
97 } 199 this.timeout = null;
98 } 200
99 201 this.observer = new MutationObserver(this.observe.bind(this));
100 function resolveURL(url) 202 this.trace = this.trace.bind(this);
101 { 203
102 var a = document.createElement("a"); 204 if (document.readyState == "loading")
103 a.href = url; 205 document.addEventListener("DOMContentLoaded", this.trace);
104 return a.href; 206 else
105 } 207 this.trace();
208 }
209 ElementHidingTracer.prototype = {
210 checkNodes: function(nodes)
211 {
212 var matchedSelectors = [];
213
214 // Find all selectors that match any hidden element inside the given nodes.
215 for (var i = 0; i < this.selectors.length; i++)
216 {
217 var selector = this.selectors[i];
218
219 for (var j = 0; j < nodes.length; j++)
220 {
221 var elements = nodes[j].querySelectorAll(selector);
222 var matched = false;
223
224 for (var k = 0; k < elements.length; k++)
225 {
226 // Only consider selectors that actually have an effect on the
227 // computed styles, and aren't overridden by rules with higher
228 // priority, or haven't been circumvented in a different way.
229 if (getComputedStyle(elements[k]).display == "none")
230 {
231 matchedSelectors.push(selector);
232 matched = true;
233 break;
234 }
235 }
236
237 if (matched)
238 break;
239 }
240 }
241
242 if (matchedSelectors.length > 0)
243 ext.backgroundPage.sendMessage({
244 type: "trace-elemhide",
245 selectors: matchedSelectors
246 });
247 },
248
249 onTimeout: function()
250 {
251 this.checkNodes(this.changedNodes);
252 this.changedNodes = [];
253 this.timeout = null;
254 },
255
256 observe: function(mutations)
257 {
258 // Forget previously changed nodes that are no longer in the DOM.
259 for (var i = 0; i < this.changedNodes.length; i++)
260 {
261 if (!this.document.contains(this.changedNodes[i]))
262 this.changedNodes.splice(i--, 1);
263 }
264
265 for (var j = 0; j < mutations.length; j++)
266 {
267 var mutation = mutations[j];
268 var node = mutation.target;
269
270 // Ignore mutations of nodes that aren't in the DOM anymore.
271 if (!this.document.contains(node))
272 continue;
273
274 // Since querySelectorAll() doesn't consider the root itself
275 // and since CSS selectors can also match siblings, we have
276 // to consider the parent node for attribute mutations.
277 if (mutation.type == "attributes")
278 node = node.parentNode;
279
280 var addNode = true;
281 for (var k = 0; k < this.changedNodes.length; k++)
282 {
283 var previouslyChangedNode = this.changedNodes[k];
284
285 // If we are already going to check an ancestor of this node,
286 // we can ignore this node, since it will be considered anyway
287 // when checking one of its ancestors.
288 if (previouslyChangedNode.contains(node))
289 {
290 addNode = false;
291 break;
292 }
293
294 // If this node is an ancestor of a node that previously changed,
295 // we can ignore that node, since it will be considered anyway
296 // when checking one of its ancestors.
297 if (node.contains(previouslyChangedNode))
298 this.changedNodes.splice(k--, 1);
299 }
300
301 if (addNode)
302 this.changedNodes.push(node);
303 }
304
305 // Check only nodes whose descendants have changed, and not more often
306 // than once a second. Otherwise large pages with a lot of DOM mutations
307 // (like YouTube) freeze when the devtools panel is active.
308 if (this.timeout == null)
309 this.timeout = setTimeout(this.onTimeout.bind(this), 1000);
310 },
311
312 trace: function()
313 {
314 this.checkNodes([this.document]);
315
316 this.observer.observe(
317 this.document,
318 {
319 childList: true,
320 attributes: true,
321 subtree: true
322 }
323 );
324 },
325
326 disconnect: function()
327 {
328 this.document.removeEventListener("DOMContentLoaded", this.trace);
329 this.observer.disconnect();
330 clearTimeout(this.timeout);
331 }
332 };
106 333
107 function reinjectRulesWhenRemoved(document, style) 334 function reinjectRulesWhenRemoved(document, style)
108 { 335 {
109 var MutationObserver = window.MutationObserver || window.WebKitMutationObserve r; 336 var MutationObserver = window.MutationObserver || window.WebKitMutationObserve r;
110 if (!MutationObserver) 337 if (!MutationObserver)
111 return; 338 return;
112 339
113 var observer = new MutationObserver(function(mutations) 340 var observer = new MutationObserver(function(mutations)
114 { 341 {
115 var isStyleRemoved = false; 342 var isStyleRemoved = false;
(...skipping 11 matching lines...) Expand all
127 observer.disconnect(); 354 observer.disconnect();
128 355
129 var n = document.styleSheets.length; 356 var n = document.styleSheets.length;
130 if (n == 0) 357 if (n == 0)
131 return; 358 return;
132 359
133 var stylesheet = document.styleSheets[n - 1]; 360 var stylesheet = document.styleSheets[n - 1];
134 ext.backgroundPage.sendMessage( 361 ext.backgroundPage.sendMessage(
135 {type: "get-selectors"}, 362 {type: "get-selectors"},
136 363
137 function(selectors) 364 function(response)
138 { 365 {
366 var selectors = response.selectors;
139 while (selectors.length > 0) 367 while (selectors.length > 0)
140 { 368 {
141 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); 369 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", ");
142 370
143 // Using non-standard addRule() here. This is the only way 371 // Using non-standard addRule() here. This is the only way
144 // to add rules at the end of a cross-origin stylesheet 372 // to add rules at the end of a cross-origin stylesheet
145 // because we don't know how many rules are already in there 373 // because we don't know how many rules are already in there
146 stylesheet.addRule(selector, "display: none !important;"); 374 stylesheet.addRule(selector, "display: none !important;");
147 } 375 }
148 } 376 }
149 ); 377 );
150 }); 378 });
151 379
152 observer.observe(style.parentNode, {childList: true}); 380 observer.observe(style.parentNode, {childList: true});
153 } 381 return observer;
154 382 }
155 function traceHiddenElements(document, selectors) 383
156 { 384 function convertSelectorsForShadowDOM(selectors)
157 function check(element) 385 {
158 { 386 var result = [];
159 var matchedSelectors = []; 387 var prefix = "::content ";
160 388
161 for (var i = 0; i < selectors.length; i++) 389 for (var i = 0; i < selectors.length; i++)
162 { 390 {
163 var selector = selectors[i]; 391 var selector = selectors[i];
164 var elements = document.querySelectorAll(selector); 392 if (selector.indexOf(",") == -1)
165 393 {
166 for (var j = 0; j < elements.length; j++) 394 result.push(prefix + selector);
167 { 395 continue;
168 if (getComputedStyle(elements[j]).display == "none") 396 }
397
398 var start = 0;
399 var sep = "";
400 for (var j = 0; j < selector.length; j++)
401 {
402 var chr = selector[j];
403 if (chr == "\\")
404 j++;
405 else if (chr == sep)
406 sep = "";
407 else if (sep == "")
408 {
409 if (chr == '"' || chr == "'")
410 sep = chr;
411 else if (chr == ",")
169 { 412 {
170 matchedSelectors.push(selector); 413 result.push(prefix + selector.substring(start, j));
171 break; 414 start = j + 1;
172 } 415 }
173 } 416 }
174 } 417 }
175 418
176 if (matchedSelectors.length > 0) 419 result.push(prefix + selector.substring(start));
177 ext.backgroundPage.sendMessage({type: "trace-elemhide", selectors: matched Selectors}); 420 }
178 } 421
179 422 return result;
180 function trace()
181 {
182 check();
183
184 var MutationObserver = window.MutationObserver || window.WebKitMutationObser ver;
185 if (MutationObserver)
186 {
187 new MutationObserver(check).observe(document,
188 {
189 childList: true,
190 attributes: true,
191 subtree: true
192 });
193 }
194 }
195
196 if (document.readyState == "loading")
197 document.addEventListener("DOMContentLoaded", trace);
198 else
199 trace();
200 } 423 }
201 424
202 function init(document) 425 function init(document)
203 { 426 {
204 // use Shadow DOM if available to don't mess with web pages that
205 // rely on the order of their own <style> tags (#309). However we
206 // must not create the shadow root in the response callback passed
207 // to sendMessage(), otherwise Chrome breaks some websites (#450).
208 var shadow = null; 427 var shadow = null;
209 if ("createShadowRoot" in document.documentElement) 428 var style = null;
429 var observer = null;
430 var tracer = null;
431 var propertyFilters = new CSSPropertyFilters(window, addElemHideSelectors);
432
433 // Use Shadow DOM if available to don't mess with web pages that rely on
434 // the order of their own <style> tags (#309).
435 //
436 // However, creating a shadow root breaks running CSS transitions. So we
437 // have to create the shadow root before transistions might start (#452).
438 //
439 // Also, using shadow DOM causes issues on some Google websites,
440 // including Google Docs and Gmail (#1770, #2602).
441 if ("createShadowRoot" in document.documentElement && !/\.google\.com$/.test(d ocument.domain))
210 { 442 {
211 shadow = document.documentElement.createShadowRoot(); 443 shadow = document.documentElement.createShadowRoot();
212 shadow.appendChild(document.createElement("shadow")); 444 shadow.appendChild(document.createElement("shadow"));
213 } 445 }
214 446
215 // Sets the currently used CSS rules for elemhide filters 447 function addElemHideSelectors(selectors)
216 var setElemhideCSSRules = function(response) 448 {
217 { 449 if (selectors.length == 0)
218 if (response.selectors.length == 0)
219 return; 450 return;
220 451
221 var selectors = response.selectors.slice(0); 452 if (!style)
222 var style = document.createElement("style"); 453 {
223 style.setAttribute("type", "text/css"); 454 // Create <style> element lazily, only if we add styles. Add it to
224 455 // the shadow DOM if possible. Otherwise fallback to the <head> or
456 // <html> element. If we have injected a style element before that
457 // has been removed (the sheet property is null), create a new one.
458 style = document.createElement("style");
459 (shadow || document.head || document.documentElement).appendChild(style);
460
461 // It can happen that the frame already navigated to a different
462 // document while we were waiting for the background page to respond.
463 // In that case the sheet property will stay null, after addind the
464 // <style> element to the shadow DOM.
465 if (!style.sheet)
466 return;
467
468 observer = reinjectRulesWhenRemoved(document, style);
469 }
470
471 // If using shadow DOM, we have to add the ::content pseudo-element
472 // before each selector, in order to match elements within the
473 // insertion point.
225 if (shadow) 474 if (shadow)
226 { 475 selectors = convertSelectorsForShadowDOM(selectors);
227 shadow.appendChild(style); 476
228 477 // WebKit (and Blink?) apparently chokes when the selector list in a
229 for (var i = 0; i < selectors.length; i++) 478 // CSS rule is huge. So we split the elemhide selectors into groups.
230 selectors[i] = "::content " + selectors[i]; 479 while (selectors.length > 0)
231 } 480 {
232 else 481 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", ");
233 { 482 style.sheet.addRule(selector, "display: none !important;");
234 // Try to insert the style into the <head> tag, inserting directly under t he 483 }
235 // document root breaks dev tools functionality: 484 };
236 // http://code.google.com/p/chromium/issues/detail?id=178109 485
237 (document.head || document.documentElement).appendChild(style); 486 var updateStylesheet = function()
238 } 487 {
239 488 var selectors = null;
240 var setRules = function() 489 var CSSPropertyFiltersLoaded = false;
241 { 490
242 // The sheet property might not exist yet if the 491 var checkLoaded = function()
243 // <style> element was created for a sub frame 492 {
244 if (!style.sheet) 493 if (!selectors || !CSSPropertyFiltersLoaded)
245 {
246 setTimeout(setRules, 0);
247 return; 494 return;
248 } 495
249 496 if (observer)
250 // WebKit apparently chokes when the selector list in a CSS rule is huge. 497 observer.disconnect();
251 // So we split the elemhide selectors into groups. 498 observer = null;
252 for (var i = 0; selectors.length > 0; i++) 499
253 { 500 if (tracer)
254 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); 501 tracer.disconnect();
255 style.sheet.insertRule(selector + " { display: none !important; }", i); 502 tracer = null;
256 } 503
257 504 if (style && style.parentElement)
258 if (response.trace) 505 style.parentElement.removeChild(style);
259 traceHiddenElements(document, response.selectors); 506 style = null;
507
508 addElemHideSelectors(selectors.selectors);
509 propertyFilters.apply();
510
511 if (selectors.trace)
512 tracer = new ElementHidingTracer(document, selectors.selectors);
260 }; 513 };
261 514
262 setRules(); 515 ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response)
263 reinjectRulesWhenRemoved(document, style); 516 {
517 selectors = response;
518 checkLoaded();
519 });
520
521 propertyFilters.load(function()
522 {
523 CSSPropertyFiltersLoaded = true;
524 checkLoaded();
525 });
264 }; 526 };
527
528 updateStylesheet();
265 529
266 document.addEventListener("error", function(event) 530 document.addEventListener("error", function(event)
267 { 531 {
268 checkCollapse(event.target); 532 checkCollapse(event.target);
269 }, true); 533 }, true);
270 534
271 document.addEventListener("load", function(event) 535 document.addEventListener("load", function(event)
272 { 536 {
273 var element = event.target; 537 var element = event.target;
274 538
275 if (/^i?frame$/.test(element.localName)) 539 if (/^i?frame$/.test(element.localName))
276 checkCollapse(element); 540 checkCollapse(element);
277 541
278 // prior to Chrome 37, content scripts cannot run on about:blank, 542 if (/\bChrome\//.test(navigator.userAgent))
279 // about:srcdoc and javascript: URLs. Moreover, as of Chrome 40 543 {
280 // "load" and "error" events aren't dispatched there. So we have 544 var contentDocument = getContentDocument(element);
281 // to apply element hiding and collapsing from the parent frame. 545 if (contentDocument)
282 if (/\bChrome\//.test(navigator.userAgent) && isInlineFrame(element)) 546 {
283 { 547 var contentWindow = contentDocument.defaultView;
284 init(element.contentDocument); 548 if (contentDocument instanceof contentWindow.HTMLDocument)
285 549 {
286 for (var tagName in typeMap) 550 // Prior to Chrome 37, content scripts cannot run in
287 Array.prototype.forEach.call(element.contentDocument.getElementsByTagNam e(tagName), checkCollapse); 551 // dynamically created frames. Also on Chrome 37-40
552 // document_start content scripts (like this one) don't
553 // run either in those frames due to https://crbug.com/416907.
554 // So we have to apply element hiding from the parent frame.
555 if (!("init" in contentWindow))
556 init(contentDocument);
557
558 // Moreover, "load" and "error" events aren't dispatched for elements
559 // in dynamically created frames due to https://crbug.com/442107.
560 // So we also have to apply element collpasing from the parent frame.
561 if (!contentWindow.collapsing)
562 Array.prototype.forEach.call(
563 contentDocument.querySelectorAll(Object.keys(typeMap).join(",")),
564 checkCollapse
565 );
566 }
567 }
288 } 568 }
289 }, true); 569 }, true);
290 570
291 ext.backgroundPage.sendMessage({type: "get-selectors"}, setElemhideCSSRules); 571 return updateStylesheet;
292 } 572 }
293 573
294 if (document instanceof HTMLDocument) 574 if (document instanceof HTMLDocument)
295 { 575 {
296 checkSitekey(); 576 checkSitekey();
297 init(document); 577 window.updateStylesheet = init(document);
298 } 578 }
LEFTRIGHT

Powered by Google App Engine
This is Rietveld