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

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

Issue 29448560: Issue 5249 - Implement :-abp-contains() (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Left Patch Set: Updated and rebased Created June 8, 2017, 12:19 a.m.
Right Patch Set: Use includes() and improve test. Created June 28, 2017, 4:11 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 abpSelectorRegexp = /:-abp-([\w-]+)\(/i; 22 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i;
23 23
24 let reportError = () => {};
25
24 function splitSelector(selector) 26 function splitSelector(selector)
25 { 27 {
26 if (selector.indexOf(",") == -1) 28 if (selector.indexOf(",") == -1)
27 return [selector]; 29 return [selector];
28 30
29 let selectors = []; 31 let selectors = [];
30 let start = 0; 32 let start = 0;
31 let level = 0; 33 let level = 0;
32 let sep = ""; 34 let sep = "";
33 35
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
121 if (parens == 0) 123 if (parens == 0)
122 break; 124 break;
123 } 125 }
124 } 126 }
125 127
126 if (parens > 0) 128 if (parens > 0)
127 return null; 129 return null;
128 return {text: content.substring(startIndex, i), end: i}; 130 return {text: content.substring(startIndex, i), end: i};
129 } 131 }
130 132
131 /** Parse the selector
132 * @param {string} selector the selector to parse
133 * @return {Object} selectors is an array of objects,
134 * or null in case of errors. hide is true if we'll hide
135 * elements instead of styles..
136 */
137 function parseSelector(selector)
138 {
139 if (selector.length == 0)
140 return [];
141
142 let match = abpSelectorRegexp.exec(selector);
143 if (!match)
144 return [new PlainSelector(selector)];
145
146 let selectors = [];
147 if (match.index > 0)
148 selectors.push(new PlainSelector(selector.substr(0, match.index)));
149
150 let startIndex = match.index + match[0].length;
151 let content = parseSelectorContent(selector, startIndex);
152 if (content == null)
153 {
154 console.error(new SyntaxError("Failed parsing AdBlock Plus " +
155 `selector ${selector}, didn't ` +
156 "find closing parenthesis."));
157 return null;
158 }
159 if (match[1] == "properties")
160 selectors.push(new PropsSelector(content.text));
161 else if (match[1] == "has")
162 {
163 let hasSelector = new HasSelector(content.text);
164 if (!hasSelector.valid())
165 return null;
166 selectors.push(hasSelector);
167 }
168 else if (match[1] == "contains")
169 selectors.push(new ContainsSelector(content.text));
170 else
171 {
172 // this is an error, can't parse selector.
173 console.error(new SyntaxError("Failed parsing AdBlock Plus " +
174 `selector ${selector}, invalid ` +
175 `pseudo-class -abp-${match[1]}().`));
176 return null;
177 }
178
179 let suffix = parseSelector(selector.substr(content.end + 1));
180 if (suffix == null)
181 return null;
182
183 selectors.push(...suffix);
184
185 return selectors;
186 }
187
188 /** Stringified style objects 133 /** Stringified style objects
189 * @typedef {Object} StringifiedStyle 134 * @typedef {Object} StringifiedStyle
190 * @property {string} style CSS style represented a string. 135 * @property {string} style CSS style represented by a string.
191 * @property {string[]} subSelectors selectors for the rule. 136 * @property {string[]} subSelectors selectors the CSS properties apply to.
192 */ 137 */
193 138
194 /** 139 /**
195 * Turn a CSSStyleRule into a StringifiedStyle 140 * Produce a string representation of the stylesheet entry.
196 * @param {CSSStyleRule} rule the CSS style rule. 141 * @param {CSSStyleRule} rule the CSS style rule.
197 * @return {StringifiedStyle} the stringified style. 142 * @return {StringifiedStyle} the stringified style.
198 */ 143 */
199 function stringifyStyle(rule) 144 function stringifyStyle(rule)
200 { 145 {
201 let styles = []; 146 let styles = [];
202 for (let i = 0; i < rule.style.length; i++) 147 for (let i = 0; i < rule.style.length; i++)
203 { 148 {
204 let property = rule.style.item(i); 149 let property = rule.style.item(i);
205 let value = rule.style.getPropertyValue(property); 150 let value = rule.style.getPropertyValue(property);
206 let priority = rule.style.getPropertyPriority(property); 151 let priority = rule.style.getPropertyPriority(property);
207 styles.push(property + ": " + value + (priority ? " !" + priority : "") + 152 styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`);
208 ";");
209 } 153 }
210 styles.sort(); 154 styles.sort();
211 return { 155 return {
212 style: styles.join(" "), 156 style: styles.join(" "),
213 subSelectors: splitSelector(rule.selectorText) 157 subSelectors: splitSelector(rule.selectorText)
214 }; 158 };
215 } 159 }
216 160
217 function* evaluate(chain, index, prefix, subtree, styles) 161 function* evaluate(chain, index, prefix, subtree, styles)
218 { 162 {
(...skipping 21 matching lines...) Expand all
240 * @param {StringifiedStyle[]} styles the stringified style objects. 184 * @param {StringifiedStyle[]} styles the stringified style objects.
241 */ 185 */
242 *getSelectors(prefix, subtree, styles) 186 *getSelectors(prefix, subtree, styles)
243 { 187 {
244 yield [prefix + this._selector, subtree]; 188 yield [prefix + this._selector, subtree];
245 } 189 }
246 }; 190 };
247 191
248 const incompletePrefixRegexp = /[\s>+~]$/; 192 const incompletePrefixRegexp = /[\s>+~]$/;
249 193
250 function HasSelector(selector) 194 function HasSelector(selectors)
251 { 195 {
252 this._innerSelectors = parseSelector(selector); 196 this._innerSelectors = selectors;
253 } 197 }
254 198
255 HasSelector.prototype = { 199 HasSelector.prototype = {
256 requiresHiding: true, 200 requiresHiding: true,
257
258 valid()
259 {
260 return this._innerSelectors != null;
261 },
262 201
263 *getSelectors(prefix, subtree, styles) 202 *getSelectors(prefix, subtree, styles)
264 { 203 {
265 for (let element of this.getElements(prefix, subtree, styles)) 204 for (let element of this.getElements(prefix, subtree, styles))
266 yield [makeSelector(element, ""), element]; 205 yield [makeSelector(element, ""), element];
267 }, 206 },
268 207
269 /** 208 /**
270 * Generator function returning selected elements. 209 * Generator function returning selected elements.
271 * @param {string} prefix the prefix for the selector. 210 * @param {string} prefix the prefix for the selector.
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
304 for (let element of this.getElements(prefix, subtree, stylesheet)) 243 for (let element of this.getElements(prefix, subtree, stylesheet))
305 yield [makeSelector(element, ""), subtree]; 244 yield [makeSelector(element, ""), subtree];
306 }, 245 },
307 246
308 *getElements(prefix, subtree, stylesheet) 247 *getElements(prefix, subtree, stylesheet)
309 { 248 {
310 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? 249 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
311 prefix + "*" : prefix; 250 prefix + "*" : prefix;
312 let elements = subtree.querySelectorAll(actualPrefix); 251 let elements = subtree.querySelectorAll(actualPrefix);
313 for (let element of elements) 252 for (let element of elements)
314 if (element.textContent == this._text) 253 if (element.textContent.includes(this._text))
315 yield element; 254 yield element;
316 } 255 }
317 }; 256 };
318 257
319 function PropsSelector(propertyExpression) 258 function PropsSelector(propertyExpression)
320 { 259 {
321 let regexpString; 260 let regexpString;
322 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && 261 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" &&
323 propertyExpression[propertyExpression.length - 1] == "/") 262 propertyExpression[propertyExpression.length - 1] == "/")
324 { 263 {
325 regexpString = propertyExpression.slice(1, -1) 264 regexpString = propertyExpression.slice(1, -1)
326 .replace("\\x7B ", "{").replace("\\x7D ", "}"); 265 .replace("\\x7B ", "{").replace("\\x7D ", "}");
327 } 266 }
328 else 267 else
329 regexpString = filterToRegExp(propertyExpression); 268 regexpString = filterToRegExp(propertyExpression);
330 269
331 this._regexp = new RegExp(regexpString, "i"); 270 this._regexp = new RegExp(regexpString, "i");
332 } 271 }
333 272
334 PropsSelector.prototype = { 273 PropsSelector.prototype = {
274 preferHideWithSelector: true,
275
335 *findPropsSelectors(styles, prefix, regexp) 276 *findPropsSelectors(styles, prefix, regexp)
336 { 277 {
337 for (let style of styles) 278 for (let style of styles)
338 if (regexp.test(style.style)) 279 if (regexp.test(style.style))
339 for (let subSelector of style.subSelectors) 280 for (let subSelector of style.subSelectors)
340 yield prefix + subSelector; 281 yield prefix + subSelector;
341 }, 282 },
342 283
343 *getSelectors(prefix, subtree, styles) 284 *getSelectors(prefix, subtree, styles)
344 { 285 {
(...skipping 18 matching lines...) Expand all
363 { 304 {
364 return new URL(stylesheet.href).origin == this.window.location.origin; 305 return new URL(stylesheet.href).origin == this.window.location.origin;
365 } 306 }
366 catch (e) 307 catch (e)
367 { 308 {
368 // Invalid URL, assume that it is first-party. 309 // Invalid URL, assume that it is first-party.
369 return true; 310 return true;
370 } 311 }
371 }, 312 },
372 313
314 /** Parse the selector
315 * @param {string} selector the selector to parse
316 * @return {Array} selectors is an array of objects,
317 * or null in case of errors.
318 */
319 parseSelector(selector)
320 {
321 if (selector.length == 0)
322 return [];
323
324 let match = abpSelectorRegexp.exec(selector);
325 if (!match)
326 return [new PlainSelector(selector)];
327
328 let selectors = [];
329 if (match.index > 0)
330 selectors.push(new PlainSelector(selector.substr(0, match.index)));
331
332 let startIndex = match.index + match[0].length;
333 let content = parseSelectorContent(selector, startIndex);
334 if (!content)
335 {
336 this.window.console.error(
337 new SyntaxError("Failed to parse Adblock Plus " +
338 `selector ${selector} ` +
339 "due to unmatched parentheses."));
340 return null;
341 }
342 if (match[1] == "properties")
343 selectors.push(new PropsSelector(content.text));
344 else if (match[1] == "has")
345 {
346 let hasSelectors = this.parseSelector(content.text);
347 if (hasSelectors == null)
348 return null;
349 selectors.push(new HasSelector(hasSelectors));
350 }
351 else if (match[1] == "contains")
352 selectors.push(new ContainsSelector(content.text));
353 else
354 {
355 // this is an error, can't parse selector.
356 this.window.console.error(
357 new SyntaxError("Failed to parse Adblock Plus " +
358 `selector ${selector}, invalid ` +
359 `pseudo-class :-abp-${match[1]}().`));
360 return null;
361 }
362
363 let suffix = this.parseSelector(selector.substr(content.end + 1));
364 if (suffix == null)
365 return null;
366
367 selectors.push(...suffix);
368
369 if (selectors.length == 1 && selectors[0] instanceof ContainsSelector)
370 {
371 this.window.console.error(
372 new SyntaxError("Failed to parse Adblock Plus " +
373 `selector ${selector}, can't ` +
374 "have a lonely :-abp-contains()."));
375 return null;
376 }
377 return selectors;
378 },
379
373 addSelectors(stylesheets) 380 addSelectors(stylesheets)
374 { 381 {
375 let selectors = []; 382 let selectors = [];
376 let selectorFilters = []; 383 let selectorFilters = [];
377 384
378 let elements = []; 385 let elements = [];
379 let elementFilters = []; 386 let elementFilters = [];
380 387
381 let cssStyles = []; 388 let cssStyles = [];
382 389
(...skipping 10 matching lines...) Expand all
393 400
394 for (let rule of rules) 401 for (let rule of rules)
395 { 402 {
396 if (rule.type != rule.STYLE_RULE) 403 if (rule.type != rule.STYLE_RULE)
397 continue; 404 continue;
398 405
399 cssStyles.push(stringifyStyle(rule)); 406 cssStyles.push(stringifyStyle(rule));
400 } 407 }
401 } 408 }
402 409
410 let {document} = this.window;
403 for (let pattern of this.patterns) 411 for (let pattern of this.patterns)
404 { 412 {
405 for (let selector of evaluate(pattern.selectors, 413 for (let selector of evaluate(pattern.selectors,
406 0, "", document, cssStyles)) 414 0, "", document, cssStyles))
407 { 415 {
408 if (!pattern.selectors.some(s => s.requiresHiding)) 416 if (pattern.selectors.some(s => s.preferHideWithSelector) &&
417 !pattern.selectors.some(s => s.requiresHiding))
409 { 418 {
410 selectors.push(selector); 419 selectors.push(selector);
411 selectorFilters.push(pattern.text); 420 selectorFilters.push(pattern.text);
412 } 421 }
413 else 422 else
414 { 423 {
415 for (let element of document.querySelectorAll(selector)) 424 for (let element of document.querySelectorAll(selector))
416 { 425 {
417 elements.push(element); 426 elements.push(element);
418 elementFilters.push(pattern.text); 427 elementFilters.push(pattern.text);
(...skipping 13 matching lines...) Expand all
432 this.addSelectors([stylesheet]); 441 this.addSelectors([stylesheet]);
433 }, 442 },
434 443
435 apply() 444 apply()
436 { 445 {
437 this.getFiltersFunc(patterns => 446 this.getFiltersFunc(patterns =>
438 { 447 {
439 this.patterns = []; 448 this.patterns = [];
440 for (let pattern of patterns) 449 for (let pattern of patterns)
441 { 450 {
442 let selectors = parseSelector(pattern.selector); 451 let selectors = this.parseSelector(pattern.selector);
443 if (selectors != null && selectors.length > 0) 452 if (selectors != null && selectors.length > 0)
444 this.patterns.push({selectors, text: pattern.text}); 453 this.patterns.push({selectors, text: pattern.text});
445 } 454 }
446 455
447 if (this.patterns.length > 0) 456 if (this.patterns.length > 0)
448 { 457 {
449 let {document} = this.window; 458 let {document} = this.window;
450 this.addSelectors(document.styleSheets); 459 this.addSelectors(document.styleSheets);
451 document.addEventListener("load", this.onLoad.bind(this), true); 460 document.addEventListener("load", this.onLoad.bind(this), true);
452 } 461 }
453 }); 462 });
454 } 463 }
455 }; 464 };
LEFTRIGHT

Powered by Google App Engine
This is Rietveld