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

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

Issue 29493648: Issue 5436 - Allow relative selectors in :-abp-has() (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Left Patch Set: Forgot a small cleanup change Created July 20, 2017, 7:21 p.m.
Right Patch Set: Updated with review feedback Created Aug. 16, 2017, 3:30 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-2017 eyeo GmbH 3 * Copyright (C) 2006-2017 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 /* globals filterToRegExp */ 18 /* globals filterToRegExp */
19 19
20 "use strict"; 20 "use strict";
21 21
22 const MIN_INVOCATION_INTERVAL = 3000; 22 const MIN_INVOCATION_INTERVAL = 3000;
23 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; 23 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i;
24 24
25 let reportError = () => {};
26
27 function splitSelector(selector) 25 function splitSelector(selector)
28 { 26 {
29 if (selector.indexOf(",") == -1) 27 if (selector.indexOf(",") == -1)
30 return [selector]; 28 return [selector];
31 29
32 let selectors = []; 30 let selectors = [];
33 let start = 0; 31 let start = 0;
34 let level = 0; 32 let level = 0;
35 let sep = ""; 33 let sep = "";
36 34
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after
184 * @param {Node} subtree the subtree we work on. 182 * @param {Node} subtree the subtree we work on.
185 * @param {StringifiedStyle[]} styles the stringified style objects. 183 * @param {StringifiedStyle[]} styles the stringified style objects.
186 */ 184 */
187 *getSelectors(prefix, subtree, styles) 185 *getSelectors(prefix, subtree, styles)
188 { 186 {
189 yield [prefix + this._selector, subtree]; 187 yield [prefix + this._selector, subtree];
190 } 188 }
191 }; 189 };
192 190
193 const incompletePrefixRegexp = /[\s>+~]$/; 191 const incompletePrefixRegexp = /[\s>+~]$/;
194 const relativeSelectorRegexp = /^[\s>+~]/; 192 const relativeSelectorRegexp = /^[>+~]/;
Wladimir Palant 2017/08/10 13:42:17 \s doesn't belong in here.
hub 2017/08/14 14:25:32 Done.
195 193
196 function HasSelector(selectors) 194 function HasSelector(selectors)
197 { 195 {
198 this._innerSelectors = selectors; 196 this._innerSelectors = selectors;
199 } 197 }
200 198
201 HasSelector.prototype = { 199 HasSelector.prototype = {
202 requiresHiding: true, 200 requiresHiding: true,
203 201
204 get dependsOnStyles() 202 get dependsOnStyles()
(...skipping 17 matching lines...) Expand all
222 { 220 {
223 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? 221 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
224 prefix + "*" : prefix; 222 prefix + "*" : prefix;
225 let elements = subtree.querySelectorAll(actualPrefix); 223 let elements = subtree.querySelectorAll(actualPrefix);
226 for (let element of elements) 224 for (let element of elements)
227 { 225 {
228 let iter = evaluate(this._innerSelectors, 0, "", element, styles); 226 let iter = evaluate(this._innerSelectors, 0, "", element, styles);
229 for (let selector of iter) 227 for (let selector of iter)
230 { 228 {
231 if (relativeSelectorRegexp.test(selector)) 229 if (relativeSelectorRegexp.test(selector))
232 selector = ":scope" + selector; 230 selector = ":scope" + selector;
Wladimir Palant 2017/08/10 13:36:08 Nice one, I didn't know about :scope yet. Browser
hub 2017/08/14 14:25:32 Good point. Will catch the exception. I'll see if
233 if (element.querySelector(selector)) 231 try
234 yield element; 232 {
235 } 233 if (element.querySelector(selector))
236 } 234 yield element;
235 }
236 catch (e)
237 {
238 // :scope isn't supported on Edge, ignore error caused by it.
239 }
240 }
241 }
242 }
243 };
244
245 function ContainsSelector(textContent)
246 {
247 this._text = textContent;
248 }
249
250 ContainsSelector.prototype = {
251 requiresHiding: true,
252
253 *getSelectors(prefix, subtree, stylesheet)
254 {
255 for (let element of this.getElements(prefix, subtree, stylesheet))
256 yield [makeSelector(element, ""), subtree];
257 },
258
259 *getElements(prefix, subtree, stylesheet)
260 {
261 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
262 prefix + "*" : prefix;
263 let elements = subtree.querySelectorAll(actualPrefix);
264 for (let element of elements)
265 if (element.textContent.includes(this._text))
266 yield element;
237 } 267 }
238 }; 268 };
239 269
240 function PropsSelector(propertyExpression) 270 function PropsSelector(propertyExpression)
241 { 271 {
242 let regexpString; 272 let regexpString;
243 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && 273 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" &&
244 propertyExpression[propertyExpression.length - 1] == "/") 274 propertyExpression[propertyExpression.length - 1] == "/")
245 { 275 {
246 regexpString = propertyExpression.slice(1, -1) 276 regexpString = propertyExpression.slice(1, -1)
247 .replace("\\x7B ", "{").replace("\\x7D ", "}"); 277 .replace("\\x7B ", "{").replace("\\x7D ", "}");
248 } 278 }
249 else 279 else
250 regexpString = filterToRegExp(propertyExpression); 280 regexpString = filterToRegExp(propertyExpression);
251 281
252 this._regexp = new RegExp(regexpString, "i"); 282 this._regexp = new RegExp(regexpString, "i");
253 } 283 }
254 284
255 PropsSelector.prototype = { 285 PropsSelector.prototype = {
256 preferHideWithSelector: true, 286 preferHideWithSelector: true,
257 dependsOnStyles: true, 287 dependsOnStyles: true,
258 288
259 *findPropsSelectors(styles, prefix, regexp) 289 *findPropsSelectors(styles, prefix, regexp)
260 { 290 {
261 for (let style of styles) 291 for (let style of styles)
262 if (regexp.test(style.style)) 292 if (regexp.test(style.style))
263 for (let subSelector of style.subSelectors) 293 for (let subSelector of style.subSelectors)
294 {
295 let idx = subSelector.lastIndexOf("::");
296 if (idx != -1)
297 subSelector = subSelector.substr(0, idx);
264 yield prefix + subSelector; 298 yield prefix + subSelector;
299 }
265 }, 300 },
266 301
267 *getSelectors(prefix, subtree, styles) 302 *getSelectors(prefix, subtree, styles)
268 { 303 {
269 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) 304 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp))
270 yield [selector, subtree]; 305 yield [selector, subtree];
271 } 306 }
272 }; 307 };
273 308
274 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, 309 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc,
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
324 } 359 }
325 if (match[1] == "properties") 360 if (match[1] == "properties")
326 selectors.push(new PropsSelector(content.text)); 361 selectors.push(new PropsSelector(content.text));
327 else if (match[1] == "has") 362 else if (match[1] == "has")
328 { 363 {
329 let hasSelectors = this.parseSelector(content.text); 364 let hasSelectors = this.parseSelector(content.text);
330 if (hasSelectors == null) 365 if (hasSelectors == null)
331 return null; 366 return null;
332 selectors.push(new HasSelector(hasSelectors)); 367 selectors.push(new HasSelector(hasSelectors));
333 } 368 }
369 else if (match[1] == "contains")
370 selectors.push(new ContainsSelector(content.text));
334 else 371 else
335 { 372 {
336 // this is an error, can't parse selector. 373 // this is an error, can't parse selector.
337 this.window.console.error( 374 this.window.console.error(
338 new SyntaxError("Failed to parse Adblock Plus " + 375 new SyntaxError("Failed to parse Adblock Plus " +
339 `selector ${selector}, invalid ` + 376 `selector ${selector}, invalid ` +
340 `pseudo-class :-abp-${match[1]}().`)); 377 `pseudo-class :-abp-${match[1]}().`));
341 return null; 378 return null;
342 } 379 }
343 380
344 let suffix = this.parseSelector(selector.substr(content.end + 1)); 381 let suffix = this.parseSelector(selector.substr(content.end + 1));
345 if (suffix == null) 382 if (suffix == null)
346 return null; 383 return null;
347 384
348 selectors.push(...suffix); 385 selectors.push(...suffix);
349 386
387 if (selectors.length == 1 && selectors[0] instanceof ContainsSelector)
388 {
389 this.window.console.error(
390 new SyntaxError("Failed to parse Adblock Plus " +
391 `selector ${selector}, can't ` +
392 "have a lonely :-abp-contains()."));
393 return null;
394 }
350 return selectors; 395 return selectors;
351 }, 396 },
352 397
353 _lastInvocation: 0, 398 _lastInvocation: 0,
354 399
355 /** 400 /**
356 * Processes the current document and applies all rules to it. 401 * Processes the current document and applies all rules to it.
357 * @param {CSSStyleSheet[]} [stylesheets] 402 * @param {CSSStyleSheet[]} [stylesheets]
358 * The list of new stylesheets that have been added to the document and 403 * The list of new stylesheets that have been added to the document and
359 * made reprocessing necessary. This parameter shouldn't be passed in for 404 * made reprocessing necessary. This parameter shouldn't be passed in for
360 * the initial processing, all of document's stylesheets will be considered 405 * the initial processing, all of document's stylesheets will be considered
361 * then and all rules, including the ones not dependent on styles. 406 * then and all rules, including the ones not dependent on styles.
362 */ 407 */
363 addSelectors(stylesheets) 408 addSelectors(stylesheets)
364 { 409 {
365 this._lastInvocation = Date.now(); 410 this._lastInvocation = Date.now();
366 411
367 let selectors = []; 412 let selectors = [];
368 let selectorFilters = []; 413 let selectorFilters = [];
369 414
370 let elements = []; 415 let elements = [];
371 let elementFilters = []; 416 let elementFilters = [];
372 417
373 let cssStyles = []; 418 let cssStyles = [];
374 419
375 let stylesheetOnlyChange = !!stylesheets; 420 let stylesheetOnlyChange = !!stylesheets;
376 if (!stylesheets) 421 if (!stylesheets)
377 stylesheets = this.window.document.styleSheets; 422 stylesheets = this.window.document.styleSheets;
378 423
379 for (let stylesheet of stylesheets) 424 // Chrome < 51 doesn't have an iterable StyleSheetList
380 { 425 // https://issues.adblockplus.org/ticket/5381
426 for (let i = 0; i < stylesheets.length; i++)
427 {
428 let stylesheet = stylesheets[i];
381 // Explicitly ignore third-party stylesheets to ensure consistent behavior 429 // Explicitly ignore third-party stylesheets to ensure consistent behavior
382 // between Firefox and Chrome. 430 // between Firefox and Chrome.
383 if (!this.isSameOrigin(stylesheet)) 431 if (!this.isSameOrigin(stylesheet))
384 continue; 432 continue;
385 433
386 let rules = stylesheet.cssRules; 434 let rules = stylesheet.cssRules;
387 if (!rules) 435 if (!rules)
388 continue; 436 continue;
389 437
390 for (let rule of rules) 438 for (let rule of rules)
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
469 517
470 if (this.patterns.length > 0) 518 if (this.patterns.length > 0)
471 { 519 {
472 let {document} = this.window; 520 let {document} = this.window;
473 this.addSelectors(); 521 this.addSelectors();
474 document.addEventListener("load", this.onLoad.bind(this), true); 522 document.addEventListener("load", this.onLoad.bind(this), true);
475 } 523 }
476 }); 524 });
477 } 525 }
478 }; 526 };
LEFTRIGHT

Powered by Google App Engine
This is Rietveld