Left: | ||
Right: |
LEFT | RIGHT |
---|---|
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 |
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 "use strict"; | 18 "use strict"; |
19 | 19 |
20 const {textToRegExp, filterToRegExp, splitSelector} = require("../common"); | 20 const {textToRegExp, filterToRegExp, splitSelector} = require("../common"); |
21 const {indexOf} = require("../coreUtils"); | |
21 | 22 |
22 let MIN_INVOCATION_INTERVAL = 3000; | 23 let MIN_INVOCATION_INTERVAL = 3000; |
23 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50; | 24 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50; |
24 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; | 25 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; |
26 | |
27 let testInfo = null; | |
28 | |
29 function setTestMode() | |
30 { | |
31 testInfo = { | |
32 lastProcessedElements: new Set() | |
33 }; | |
34 } | |
35 | |
36 exports.setTestMode = setTestMode; | |
37 | |
38 function getTestInfo() | |
39 { | |
40 return testInfo; | |
41 } | |
42 | |
43 exports.getTestInfo = getTestInfo; | |
hub
2018/05/24 19:19:09
I'm a bit skeptical with that way or test, includi
Manish Jethani
2018/05/25 07:21:25
We can work on improving our test framework, but f
| |
25 | 44 |
26 function getCachedPropertyValue(object, name, defaultValueFunc = () => {}) | 45 function getCachedPropertyValue(object, name, defaultValueFunc = () => {}) |
27 { | 46 { |
28 let value = object[name]; | 47 let value = object[name]; |
29 if (typeof value == "undefined") | 48 if (typeof value == "undefined") |
30 Object.defineProperty(object, name, {value: value = defaultValueFunc()}); | 49 Object.defineProperty(object, name, {value: value = defaultValueFunc()}); |
31 return value; | 50 return value; |
32 } | 51 } |
33 | 52 |
34 /** Return position of node from parent. | 53 /** Return position of node from parent. |
35 * @param {Node} node the node to find the position of. | 54 * @param {Node} node the node to find the position of. |
36 * @return {number} One-based index like for :nth-child(), or 0 on error. | 55 * @return {number} One-based index like for :nth-child(), or 0 on error. |
37 */ | 56 */ |
38 function positionInParent(node) | 57 function positionInParent(node) |
39 { | 58 { |
40 let {children} = node.parentNode; | 59 return indexOf(node.parentNode.children, node) + 1; |
41 for (let i = 0; i < children.length; i++) | 60 } |
42 if (children[i] == node) | 61 |
43 return i + 1; | 62 function makeSelector(node, selector = "") |
44 return 0; | |
45 } | |
46 | |
47 function makeSelector(node, selector) | |
48 { | 63 { |
49 if (node == null) | 64 if (node == null) |
50 return null; | 65 return null; |
51 if (!node.parentElement) | 66 if (!node.parentElement) |
52 { | 67 { |
53 let newSelector = ":root"; | 68 let newSelector = ":root"; |
54 if (selector) | 69 if (selector) |
55 newSelector += " > " + selector; | 70 newSelector += " > " + selector; |
56 return newSelector; | 71 return newSelector; |
57 } | 72 } |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
172 } | 187 } |
173 return all ? subtree.querySelectorAll(selector) : | 188 return all ? subtree.querySelectorAll(selector) : |
174 subtree.querySelector(selector); | 189 subtree.querySelector(selector); |
175 } | 190 } |
176 | 191 |
177 function scopedQuerySelectorAll(subtree, selector) | 192 function scopedQuerySelectorAll(subtree, selector) |
178 { | 193 { |
179 return scopedQuerySelector(subtree, selector, true); | 194 return scopedQuerySelector(subtree, selector, true); |
180 } | 195 } |
181 | 196 |
182 const regexpRegexp = /^\/(.*)\/([im]*)$/; | 197 const regexpRegexp = /^\/(.*)\/([imu]*)$/; |
183 | 198 |
184 /** | 199 /** |
185 * Make a regular expression from a text argument. If it can be parsed as a | 200 * Make a regular expression from a text argument. If it can be parsed as a |
186 * regular expression, parse it and the flags. | 201 * regular expression, parse it and the flags. |
187 * @param {string} text the text argument. | 202 * @param {string} text the text argument. |
188 * @return {?RegExp} a RegExp object or null in case of error. | 203 * @return {?RegExp} a RegExp object or null in case of error. |
189 */ | 204 */ |
190 function makeRegExpParameter(text) | 205 function makeRegExpParameter(text) |
191 { | 206 { |
192 let [, pattern, flags] = | 207 let [, pattern, flags] = |
(...skipping 27 matching lines...) Expand all Loading... | |
220 // Just in case the getSelectors() generator above had to run some heavy | 235 // Just in case the getSelectors() generator above had to run some heavy |
221 // document.querySelectorAll() call which didn't produce any results, make | 236 // document.querySelectorAll() call which didn't produce any results, make |
222 // sure there is at least one point where execution can pause. | 237 // sure there is at least one point where execution can pause. |
223 yield null; | 238 yield null; |
224 } | 239 } |
225 | 240 |
226 function PlainSelector(selector) | 241 function PlainSelector(selector) |
227 { | 242 { |
228 this._selector = selector; | 243 this._selector = selector; |
229 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); | 244 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); |
245 this.dependsOnDOM = this.maybeDependsOnAttributes; | |
246 this.maybeContainsSiblingCombinators = /[~+]/.test(selector); | |
230 } | 247 } |
231 | 248 |
232 PlainSelector.prototype = { | 249 PlainSelector.prototype = { |
233 /** | 250 /** |
234 * Generator function returning a pair of selector | 251 * Generator function returning a pair of selector |
235 * string and subtree. | 252 * string and subtree. |
236 * @param {string} prefix the prefix for the selector. | 253 * @param {string} prefix the prefix for the selector. |
237 * @param {Node} subtree the subtree we work on. | 254 * @param {Node} subtree the subtree we work on. |
238 * @param {StringifiedStyle[]} styles the stringified style objects. | 255 * @param {StringifiedStyle[]} styles the stringified style objects. |
239 * @param {Node[]} [targets] the nodes we are interested in. | 256 * @param {Node[]} [targets] the nodes we are interested in. |
240 */ | 257 */ |
241 *getSelectors(prefix, subtree, styles, targets) | 258 *getSelectors(prefix, subtree, styles, targets) |
242 { | 259 { |
243 yield [prefix + this._selector, subtree]; | 260 yield [prefix + this._selector, subtree]; |
244 } | 261 } |
245 }; | 262 }; |
246 | 263 |
247 const incompletePrefixRegexp = /[\s>+~]$/; | 264 const incompletePrefixRegexp = /[\s>+~]$/; |
248 | 265 |
249 function HasSelector(selectors) | 266 function HasSelector(selectors) |
250 { | 267 { |
251 this._innerSelectors = selectors; | 268 this._innerSelectors = selectors; |
252 } | 269 } |
253 | 270 |
254 HasSelector.prototype = { | 271 HasSelector.prototype = { |
255 requiresHiding: true, | |
256 dependsOnDOM: true, | 272 dependsOnDOM: true, |
257 | 273 |
258 get dependsOnStyles() | 274 get dependsOnStyles() |
259 { | 275 { |
260 return this._innerSelectors.some(selector => selector.dependsOnStyles); | 276 return this._innerSelectors.some(selector => selector.dependsOnStyles); |
261 }, | 277 }, |
262 | 278 |
263 get dependsOnCharacterData() | 279 get dependsOnCharacterData() |
264 { | 280 { |
265 return this._innerSelectors.some( | 281 return this._innerSelectors.some( |
266 selector => selector.dependsOnCharacterData | 282 selector => selector.dependsOnCharacterData |
267 ); | 283 ); |
268 }, | 284 }, |
269 | 285 |
270 get maybeDependsOnAttributes() | 286 get maybeDependsOnAttributes() |
271 { | 287 { |
272 return this._innerSelectors.some( | 288 return this._innerSelectors.some( |
273 selector => selector.maybeDependsOnAttributes | 289 selector => selector.maybeDependsOnAttributes |
274 ); | 290 ); |
275 }, | 291 }, |
276 | 292 |
277 *getSelectors(prefix, subtree, styles, targets) | 293 *getSelectors(prefix, subtree, styles, targets) |
278 { | 294 { |
279 for (let element of this.getElements(prefix, subtree, styles, targets)) | 295 for (let element of this.getElements(prefix, subtree, styles, targets)) |
280 yield [makeSelector(element, ""), element]; | 296 yield [makeSelector(element), element]; |
281 }, | 297 }, |
282 | 298 |
283 /** | 299 /** |
284 * Generator function returning selected elements. | 300 * Generator function returning selected elements. |
285 * @param {string} prefix the prefix for the selector. | 301 * @param {string} prefix the prefix for the selector. |
286 * @param {Node} subtree the subtree we work on. | 302 * @param {Node} subtree the subtree we work on. |
287 * @param {StringifiedStyle[]} styles the stringified style objects. | 303 * @param {StringifiedStyle[]} styles the stringified style objects. |
288 * @param {Node[]} [targets] the nodes we are interested in. | 304 * @param {Node[]} [targets] the nodes we are interested in. |
289 */ | 305 */ |
290 *getElements(prefix, subtree, styles, targets) | 306 *getElements(prefix, subtree, styles, targets) |
(...skipping 17 matching lines...) Expand all Loading... | |
308 let iter = evaluate(this._innerSelectors, 0, "", element, styles, | 324 let iter = evaluate(this._innerSelectors, 0, "", element, styles, |
309 targets); | 325 targets); |
310 for (let selector of iter) | 326 for (let selector of iter) |
311 { | 327 { |
312 if (selector == null) | 328 if (selector == null) |
313 yield null; | 329 yield null; |
314 else if (scopedQuerySelector(element, selector)) | 330 else if (scopedQuerySelector(element, selector)) |
315 yield element; | 331 yield element; |
316 } | 332 } |
317 yield null; | 333 yield null; |
334 | |
335 if (testInfo) | |
336 testInfo.lastProcessedElements.add(element); | |
318 } | 337 } |
319 } | 338 } |
320 } | 339 } |
321 }; | 340 }; |
322 | 341 |
323 function ContainsSelector(textContent) | 342 function ContainsSelector(textContent) |
324 { | 343 { |
325 this._regexp = makeRegExpParameter(textContent); | 344 this._regexp = makeRegExpParameter(textContent); |
326 } | 345 } |
327 | 346 |
328 ContainsSelector.prototype = { | 347 ContainsSelector.prototype = { |
329 requiresHiding: true, | |
330 dependsOnDOM: true, | 348 dependsOnDOM: true, |
331 dependsOnCharacterData: true, | 349 dependsOnCharacterData: true, |
332 | 350 |
333 *getSelectors(prefix, subtree, styles, targets) | 351 *getSelectors(prefix, subtree, styles, targets) |
334 { | 352 { |
335 for (let element of this.getElements(prefix, subtree, styles, targets)) | 353 for (let element of this.getElements(prefix, subtree, styles, targets)) |
336 yield [makeSelector(element, ""), subtree]; | 354 yield [makeSelector(element), subtree]; |
337 }, | 355 }, |
338 | 356 |
339 *getElements(prefix, subtree, styles, targets) | 357 *getElements(prefix, subtree, styles, targets) |
340 { | 358 { |
341 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 359 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? |
342 prefix + "*" : prefix; | 360 prefix + "*" : prefix; |
343 | 361 |
344 let elements = scopedQuerySelectorAll(subtree, actualPrefix); | 362 let elements = scopedQuerySelectorAll(subtree, actualPrefix); |
363 | |
345 if (elements) | 364 if (elements) |
346 { | 365 { |
366 let lastRoot = null; | |
347 for (let element of elements) | 367 for (let element of elements) |
348 { | 368 { |
369 // For a filter like div:-abp-contains(Hello) and a subtree like | |
370 // <div id="a"><div id="b"><div id="c">Hello</div></div></div> | |
371 // we're only interested in div#a | |
372 if (lastRoot && lastRoot.contains(element)) | |
373 { | |
374 yield null; | |
375 continue; | |
376 } | |
377 | |
378 lastRoot = element; | |
379 | |
349 if (targets && !targets.some(target => element.contains(target) || | 380 if (targets && !targets.some(target => element.contains(target) || |
Manish Jethani
2018/03/20 10:21:57
By the way this may not seem like such a great ide
| |
350 target.contains(element))) | 381 target.contains(element))) |
351 { | 382 { |
352 yield null; | 383 yield null; |
353 continue; | 384 continue; |
354 } | 385 } |
355 | 386 |
356 if (this._regexp && this._regexp.test(element.textContent)) | 387 if (this._regexp && this._regexp.test(element.textContent)) |
357 yield element; | 388 yield element; |
358 else | 389 else |
359 yield null; | 390 yield null; |
391 | |
392 if (testInfo) | |
393 testInfo.lastProcessedElements.add(element); | |
360 } | 394 } |
361 } | 395 } |
362 } | 396 } |
363 }; | 397 }; |
364 | 398 |
365 function PropsSelector(propertyExpression) | 399 function PropsSelector(propertyExpression) |
366 { | 400 { |
367 let regexpString; | 401 let regexpString; |
368 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && | 402 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && |
369 propertyExpression[propertyExpression.length - 1] == "/") | 403 propertyExpression[propertyExpression.length - 1] == "/") |
370 { | 404 { |
371 regexpString = propertyExpression.slice(1, -1) | 405 regexpString = propertyExpression.slice(1, -1) |
372 .replace("\\7B ", "{").replace("\\7D ", "}"); | 406 .replace("\\7B ", "{").replace("\\7D ", "}"); |
373 } | 407 } |
374 else | 408 else |
375 regexpString = filterToRegExp(propertyExpression); | 409 regexpString = filterToRegExp(propertyExpression); |
376 | 410 |
377 this._regexp = new RegExp(regexpString, "i"); | 411 this._regexp = new RegExp(regexpString, "i"); |
378 } | 412 } |
379 | 413 |
380 PropsSelector.prototype = { | 414 PropsSelector.prototype = { |
381 preferHideWithSelector: true, | |
382 dependsOnStyles: true, | 415 dependsOnStyles: true, |
383 | 416 |
384 *findPropsSelectors(styles, prefix, regexp) | 417 *findPropsSelectors(styles, prefix, regexp) |
385 { | 418 { |
386 for (let style of styles) | 419 for (let style of styles) |
387 if (regexp.test(style.style)) | 420 if (regexp.test(style.style)) |
388 for (let subSelector of style.subSelectors) | 421 for (let subSelector of style.subSelectors) |
389 { | 422 { |
390 if (subSelector.startsWith("*") && | 423 if (subSelector.startsWith("*") && |
391 !incompletePrefixRegexp.test(prefix)) | 424 !incompletePrefixRegexp.test(prefix)) |
(...skipping 14 matching lines...) Expand all Loading... | |
406 } | 439 } |
407 }; | 440 }; |
408 | 441 |
409 function Pattern(selectors, text) | 442 function Pattern(selectors, text) |
410 { | 443 { |
411 this.selectors = selectors; | 444 this.selectors = selectors; |
412 this.text = text; | 445 this.text = text; |
413 } | 446 } |
414 | 447 |
415 Pattern.prototype = { | 448 Pattern.prototype = { |
416 isSelectorHidingOnlyPattern() | |
417 { | |
418 return getCachedPropertyValue( | |
419 this, "_selectorHidingOnlyPattern", | |
420 () => this.selectors.some(selector => selector.preferHideWithSelector) && | |
421 !this.selectors.some(selector => selector.requiresHiding) | |
422 ); | |
423 }, | |
424 | |
425 get dependsOnStyles() | 449 get dependsOnStyles() |
426 { | 450 { |
427 return getCachedPropertyValue( | 451 return getCachedPropertyValue( |
428 this, "_dependsOnStyles", | 452 this, "_dependsOnStyles", |
429 () => this.selectors.some(selector => selector.dependsOnStyles) | 453 () => this.selectors.some(selector => selector.dependsOnStyles) |
430 ); | 454 ); |
431 }, | 455 }, |
432 | 456 |
433 get dependsOnDOM() | 457 get dependsOnDOM() |
434 { | 458 { |
(...skipping 30 matching lines...) Expand all Loading... | |
465 }, | 489 }, |
466 | 490 |
467 get dependsOnCharacterData() | 491 get dependsOnCharacterData() |
468 { | 492 { |
469 // Observe changes to character data only if there's a contains selector in | 493 // Observe changes to character data only if there's a contains selector in |
470 // one of the patterns. | 494 // one of the patterns. |
471 return getCachedPropertyValue( | 495 return getCachedPropertyValue( |
472 this, "_dependsOnCharacterData", | 496 this, "_dependsOnCharacterData", |
473 () => this.selectors.some(selector => selector.dependsOnCharacterData) | 497 () => this.selectors.some(selector => selector.dependsOnCharacterData) |
474 ); | 498 ); |
499 }, | |
500 | |
501 get maybeContainsSiblingCombinators() | |
502 { | |
503 return getCachedPropertyValue( | |
504 this, "_maybeContainsSiblingCombinators", | |
505 () => this.selectors.some(selector => | |
506 selector.maybeContainsSiblingCombinators) | |
507 ); | |
508 }, | |
509 | |
510 matchesMutationTypes(mutationTypes) | |
511 { | |
512 let mutationTypeMatchMap = getCachedPropertyValue( | |
513 this, "_mutationTypeMatchMap", | |
514 () => new Map([ | |
515 // All types of DOM-dependent patterns are affected by mutations of | |
516 // type "childList". | |
517 ["childList", true], | |
518 ["attributes", this.maybeDependsOnAttributes], | |
519 ["characterData", this.dependsOnCharacterData] | |
520 ]) | |
521 ); | |
522 | |
523 for (let mutationType of mutationTypes) | |
524 { | |
525 if (mutationTypeMatchMap.get(mutationType)) | |
526 return true; | |
527 } | |
528 | |
529 return false; | |
475 } | 530 } |
476 }; | 531 }; |
532 | |
533 function extractMutationTypes(mutations) | |
534 { | |
535 let types = new Set(); | |
536 | |
537 for (let mutation of mutations) | |
538 { | |
539 types.add(mutation.type); | |
540 | |
541 // There are only 3 types of mutations: "attributes", "characterData", and | |
542 // "childList". | |
543 if (types.size == 3) | |
544 break; | |
545 } | |
546 | |
547 return types; | |
548 } | |
477 | 549 |
478 function extractMutationTargets(mutations) | 550 function extractMutationTargets(mutations) |
479 { | 551 { |
480 if (!mutations) | 552 if (!mutations) |
481 return null; | 553 return null; |
482 | 554 |
483 let targets = new Set(); | 555 let targets = new Set(); |
484 | 556 |
485 for (let mutation of mutations) | 557 for (let mutation of mutations) |
486 { | 558 { |
487 if (mutation.type == "childList") | 559 if (mutation.type == "childList") |
488 { | 560 { |
489 // When new nodes are added, we're interested in the added nodes rather | 561 // When new nodes are added, we're interested in the added nodes rather |
490 // than the parent. | 562 // than the parent. |
491 for (let node of mutation.addedNodes) | 563 for (let node of mutation.addedNodes) |
492 targets.add(node); | 564 targets.add(node); |
493 | |
494 // Ideally we would also be interested in removed nodes, but since we | |
495 // never unhide an element once hidden we can simply ignore any removed | |
496 // nodes. Note that this will change once we start using CSS selectors | |
497 // for -abp-has and -abp-contains, i.e. we'll have to remove the | |
498 // selectors for any removed nodes. | |
499 } | 565 } |
500 else | 566 else |
501 { | 567 { |
502 targets.add(mutation.target); | 568 targets.add(mutation.target); |
503 } | 569 } |
504 } | 570 } |
505 | 571 |
506 return [...targets]; | 572 return [...targets]; |
507 } | 573 } |
508 | 574 |
509 function filterPatterns(patterns, {stylesheets, mutations}) | 575 function filterPatterns(patterns, {stylesheets, mutations}) |
510 { | 576 { |
511 if (!stylesheets && !mutations) | 577 if (!stylesheets && !mutations) |
512 return patterns.slice(); | 578 return patterns.slice(); |
513 | 579 |
580 let mutationTypes = mutations ? extractMutationTypes(mutations) : null; | |
581 | |
514 return patterns.filter( | 582 return patterns.filter( |
515 pattern => (stylesheets && pattern.dependsOnStyles) || | 583 pattern => (stylesheets && pattern.dependsOnStyles) || |
516 (mutations && pattern.dependsOnDOM) | 584 (mutations && pattern.dependsOnDOM && |
585 pattern.matchesMutationTypes(mutationTypes)) | |
517 ); | 586 ); |
518 } | 587 } |
519 | 588 |
520 function shouldObserveAttributes(patterns) | 589 function shouldObserveAttributes(patterns) |
521 { | 590 { |
522 return patterns.some(pattern => pattern.maybeDependsOnAttributes); | 591 return patterns.some(pattern => pattern.maybeDependsOnAttributes); |
523 } | 592 } |
524 | 593 |
525 function shouldObserveCharacterData(patterns) | 594 function shouldObserveCharacterData(patterns) |
526 { | 595 { |
527 return patterns.some(pattern => pattern.dependsOnCharacterData); | 596 return patterns.some(pattern => pattern.dependsOnCharacterData); |
528 } | 597 } |
529 | 598 |
530 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) | 599 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) |
531 { | 600 { |
532 this.document = document; | 601 this.document = document; |
533 this.addSelectorsFunc = addSelectorsFunc; | 602 this.addSelectorsFunc = addSelectorsFunc; |
534 this.hideElemsFunc = hideElemsFunc; | 603 this.hideElemsFunc = hideElemsFunc; |
535 this.observer = new MutationObserver(this.observe.bind(this)); | 604 this.observer = new MutationObserver(this.observe.bind(this)); |
605 this.useInlineStyles = true; | |
536 } | 606 } |
537 | 607 |
538 ElemHideEmulation.prototype = { | 608 ElemHideEmulation.prototype = { |
539 isSameOrigin(stylesheet) | 609 isSameOrigin(stylesheet) |
540 { | 610 { |
541 try | 611 try |
542 { | 612 { |
543 return new URL(stylesheet.href).origin == this.document.location.origin; | 613 return new URL(stylesheet.href).origin == this.document.location.origin; |
544 } | 614 } |
545 catch (e) | 615 catch (e) |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
622 * @param {MutationRecord[]} [mutations] | 692 * @param {MutationRecord[]} [mutations] |
623 * The list of DOM mutations that have been applied to the document and | 693 * The list of DOM mutations that have been applied to the document and |
624 * made reprocessing necessary. This parameter shouldn't be passed in for | 694 * made reprocessing necessary. This parameter shouldn't be passed in for |
625 * the initial processing, the entire document will be considered | 695 * the initial processing, the entire document will be considered |
626 * then and all rules, including the ones not dependent on the DOM. | 696 * then and all rules, including the ones not dependent on the DOM. |
627 * @param {function} [done] | 697 * @param {function} [done] |
628 * Callback to call when done. | 698 * Callback to call when done. |
629 */ | 699 */ |
630 _addSelectors(stylesheets, mutations, done) | 700 _addSelectors(stylesheets, mutations, done) |
631 { | 701 { |
702 if (testInfo) | |
703 testInfo.lastProcessedElements.clear(); | |
704 | |
632 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); | 705 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); |
633 | 706 |
634 let selectors = []; | 707 let selectors = []; |
635 let selectorFilters = []; | 708 let selectorFilters = []; |
636 | 709 |
637 let elements = []; | 710 let elements = []; |
638 let elementFilters = []; | 711 let elementFilters = []; |
639 | 712 |
640 let cssStyles = []; | 713 let cssStyles = []; |
641 | 714 |
(...skipping 11 matching lines...) Expand all Loading... | |
653 if (mutations && patterns.some(pattern => pattern.dependsOnStylesAndDOM)) | 726 if (mutations && patterns.some(pattern => pattern.dependsOnStylesAndDOM)) |
654 stylesheets = this.document.styleSheets; | 727 stylesheets = this.document.styleSheets; |
655 | 728 |
656 for (let stylesheet of stylesheets || []) | 729 for (let stylesheet of stylesheets || []) |
657 { | 730 { |
658 // Explicitly ignore third-party stylesheets to ensure consistent behavior | 731 // Explicitly ignore third-party stylesheets to ensure consistent behavior |
659 // between Firefox and Chrome. | 732 // between Firefox and Chrome. |
660 if (!this.isSameOrigin(stylesheet)) | 733 if (!this.isSameOrigin(stylesheet)) |
661 continue; | 734 continue; |
662 | 735 |
663 let rules = stylesheet.cssRules; | 736 let rules; |
737 try | |
738 { | |
739 rules = stylesheet.cssRules; | |
740 } | |
741 catch (e) | |
742 { | |
743 // On Firefox, there is a chance that an InvalidAccessError | |
744 // get thrown when accessing cssRules. Just skip the stylesheet | |
745 // in that case. | |
746 // See https://searchfox.org/mozilla-central/rev/f65d7528e34ef1a7665b4a1 a7b7cdb1388fcd3aa/layout/style/StyleSheet.cpp#699 | |
747 continue; | |
748 } | |
749 | |
664 if (!rules) | 750 if (!rules) |
665 continue; | 751 continue; |
666 | 752 |
667 for (let rule of rules) | 753 for (let rule of rules) |
668 { | 754 { |
669 if (rule.type != rule.STYLE_RULE) | 755 if (rule.type != rule.STYLE_RULE) |
670 continue; | 756 continue; |
671 | 757 |
672 cssStyles.push(stringifyStyle(rule)); | 758 cssStyles.push(stringifyStyle(rule)); |
673 } | 759 } |
(...skipping 16 matching lines...) Expand all Loading... | |
690 this.addSelectorsFunc(selectors, selectorFilters); | 776 this.addSelectorsFunc(selectors, selectorFilters); |
691 if (elements.length > 0) | 777 if (elements.length > 0) |
692 this.hideElemsFunc(elements, elementFilters); | 778 this.hideElemsFunc(elements, elementFilters); |
693 if (typeof done == "function") | 779 if (typeof done == "function") |
694 done(); | 780 done(); |
695 return; | 781 return; |
696 } | 782 } |
697 | 783 |
698 pattern = patterns.shift(); | 784 pattern = patterns.shift(); |
699 | 785 |
786 let evaluationTargets = targets; | |
787 | |
788 // If the pattern appears to contain any sibling combinators, we can't | |
789 // easily optimize based on the mutation targets. Since this is a | |
790 // special case, skip the optimization. By setting it to null here we | |
791 // make sure we process the entire DOM. | |
792 if (pattern.maybeContainsSiblingCombinators) | |
793 evaluationTargets = null; | |
794 | |
795 // Ignore mutation targets when using style sheets, because we may have | |
796 // to update all the CSS selectors. | |
797 if (!this.useInlineStyles) | |
798 evaluationTargets = null; | |
799 | |
700 generator = evaluate(pattern.selectors, 0, "", | 800 generator = evaluate(pattern.selectors, 0, "", |
701 this.document, cssStyles, targets); | 801 this.document, cssStyles, evaluationTargets); |
702 } | 802 } |
703 for (let selector of generator) | 803 for (let selector of generator) |
704 { | 804 { |
705 if (selector != null) | 805 if (selector != null) |
706 { | 806 { |
707 if (pattern.isSelectorHidingOnlyPattern()) | 807 if (!this.useInlineStyles) |
708 { | 808 { |
709 selectors.push(selector); | 809 selectors.push(selector); |
710 selectorFilters.push(pattern.text); | 810 selectorFilters.push(pattern.text); |
711 } | 811 } |
712 else | 812 else |
713 { | 813 { |
714 for (let element of this.document.querySelectorAll(selector)) | 814 for (let element of this.document.querySelectorAll(selector)) |
715 { | 815 { |
716 elements.push(element); | 816 elements.push(element); |
717 elementFilters.push(pattern.text); | 817 elementFilters.push(pattern.text); |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
769 this.queueFiltering(params.stylesheets, params.mutations); | 869 this.queueFiltering(params.stylesheets, params.mutations); |
770 } | 870 } |
771 }; | 871 }; |
772 | 872 |
773 if (this._scheduledProcessing) | 873 if (this._scheduledProcessing) |
774 { | 874 { |
775 if (!stylesheets && !mutations) | 875 if (!stylesheets && !mutations) |
776 { | 876 { |
777 this._scheduledProcessing = {}; | 877 this._scheduledProcessing = {}; |
778 } | 878 } |
779 else | 879 else if (this._scheduledProcessing.stylesheets || |
880 this._scheduledProcessing.mutations) | |
780 { | 881 { |
781 if (stylesheets) | 882 if (stylesheets) |
782 { | 883 { |
783 if (!this._scheduledProcessing.stylesheets) | 884 if (!this._scheduledProcessing.stylesheets) |
784 this._scheduledProcessing.stylesheets = []; | 885 this._scheduledProcessing.stylesheets = []; |
785 this._scheduledProcessing.stylesheets.push(...stylesheets); | 886 this._scheduledProcessing.stylesheets.push(...stylesheets); |
786 } | 887 } |
787 if (mutations) | 888 if (mutations) |
788 { | 889 { |
789 if (!this._scheduledProcessing.mutations) | 890 if (!this._scheduledProcessing.mutations) |
(...skipping 16 matching lines...) Expand all Loading... | |
806 this._scheduledProcessing = null; | 907 this._scheduledProcessing = null; |
807 this._addSelectors(params.stylesheets, params.mutations, completion); | 908 this._addSelectors(params.stylesheets, params.mutations, completion); |
808 }, | 909 }, |
809 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); | 910 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); |
810 } | 911 } |
811 else if (this.document.readyState == "loading") | 912 else if (this.document.readyState == "loading") |
812 { | 913 { |
813 this._scheduledProcessing = {stylesheets, mutations}; | 914 this._scheduledProcessing = {stylesheets, mutations}; |
814 let handler = () => | 915 let handler = () => |
815 { | 916 { |
816 document.removeEventListener("DOMContentLoaded", handler); | 917 this.document.removeEventListener("DOMContentLoaded", handler); |
817 let params = Object.assign({}, this._scheduledProcessing); | 918 let params = Object.assign({}, this._scheduledProcessing); |
818 this._filteringInProgress = true; | 919 this._filteringInProgress = true; |
819 this._scheduledProcessing = null; | 920 this._scheduledProcessing = null; |
820 this._addSelectors(params.stylesheets, params.mutations, completion); | 921 this._addSelectors(params.stylesheets, params.mutations, completion); |
821 }; | 922 }; |
822 document.addEventListener("DOMContentLoaded", handler); | 923 this.document.addEventListener("DOMContentLoaded", handler); |
823 } | 924 } |
824 else | 925 else |
825 { | 926 { |
826 this._filteringInProgress = true; | 927 this._filteringInProgress = true; |
827 this._addSelectors(stylesheets, mutations, completion); | 928 this._addSelectors(stylesheets, mutations, completion); |
828 } | 929 } |
829 }, | 930 }, |
830 | 931 |
831 onLoad(event) | 932 onLoad(event) |
832 { | 933 { |
833 let stylesheet = event.target.sheet; | 934 let stylesheet = event.target.sheet; |
834 if (stylesheet) | 935 if (stylesheet) |
835 this.queueFiltering([stylesheet]); | 936 this.queueFiltering([stylesheet]); |
836 }, | 937 }, |
837 | 938 |
838 observe(mutations) | 939 observe(mutations) |
839 { | 940 { |
941 if (testInfo) | |
942 { | |
943 // In test mode, filter out any mutations likely done by us | |
944 // (i.e. style="display: none !important"). This makes it easier to | |
945 // observe how the code responds to DOM mutations. | |
946 mutations = mutations.filter( | |
947 ({type, attributeName, target: {style: newValue}, oldValue}) => | |
948 !(type == "attributes" && attributeName == "style" && | |
949 newValue.display == "none" && oldValue.display != "none") | |
950 ); | |
951 | |
952 if (mutations.length == 0) | |
953 return; | |
954 } | |
hub
2018/05/24 19:19:09
... and this :-/
There should be a better way to
Manish Jethani
2018/05/25 07:21:25
OK, do you have any suggestions? This is only done
| |
955 | |
840 this.queueFiltering(null, mutations); | 956 this.queueFiltering(null, mutations); |
841 }, | 957 }, |
842 | 958 |
843 apply(patterns) | 959 apply(patterns) |
844 { | 960 { |
845 this.patterns = []; | 961 this.patterns = []; |
846 for (let pattern of patterns) | 962 for (let pattern of patterns) |
847 { | 963 { |
848 let selectors = this.parseSelector(pattern.selector); | 964 let selectors = this.parseSelector(pattern.selector); |
849 if (selectors != null && selectors.length > 0) | 965 if (selectors != null && selectors.length > 0) |
(...skipping 11 matching lines...) Expand all Loading... | |
861 characterData: shouldObserveCharacterData(this.patterns), | 977 characterData: shouldObserveCharacterData(this.patterns), |
862 subtree: true | 978 subtree: true |
863 } | 979 } |
864 ); | 980 ); |
865 this.document.addEventListener("load", this.onLoad.bind(this), true); | 981 this.document.addEventListener("load", this.onLoad.bind(this), true); |
866 } | 982 } |
867 } | 983 } |
868 }; | 984 }; |
869 | 985 |
870 exports.ElemHideEmulation = ElemHideEmulation; | 986 exports.ElemHideEmulation = ElemHideEmulation; |
LEFT | RIGHT |