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

Side by Side Diff: test/browser/elemHideEmulation.js

Issue 29383960: Issue 3143 - Filter elements with :-abp-has() (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore
Patch Set: Reworked the pseudo class to generate selectors. Created May 3, 2017, 1:42 a.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 | « lib/filterClasses.js ('k') | test/filterClasses.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-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 "use strict"; 18 "use strict";
19 19
20 /* globals ElemHideEmulation */ 20 /* globals ElemHideEmulation, pseudoClassHasSelectorRegExp, splitSelector,
21 parseSelector,
22 PlainSelector, HasSelector, PropsSelector */
21 23
22 let myUrl = document.currentScript.src; 24 let myUrl = document.currentScript.src;
23 25
24 exports.tearDown = function(callback) 26 exports.tearDown = function(callback)
25 { 27 {
26 let styleElements = document.head.getElementsByTagName("style"); 28 let styleElements = document.head.getElementsByTagName("style");
27 while (styleElements.length) 29 while (styleElements.length)
28 styleElements[0].parentNode.removeChild(styleElements[0]); 30 styleElements[0].parentNode.removeChild(styleElements[0]);
31
32 let child;
33 while (child = document.body.firstChild)
34 document.body.removeChild(child);
35
29 callback(); 36 callback();
30 }; 37 };
31 38
32 function unexpectedError(error) 39 function unexpectedError(error)
33 { 40 {
34 console.error(error); 41 console.error(error);
35 this.ok(false, "Unexpected error: " + error); 42 this.ok(false, "Unexpected error: " + error);
36 } 43 }
37 44
38 function expectHidden(test, element) 45 function expectHidden(test, element)
(...skipping 23 matching lines...) Expand all
62 if (styleElements.length) 69 if (styleElements.length)
63 styleElement = styleElements[0]; 70 styleElement = styleElements[0];
64 else 71 else
65 { 72 {
66 styleElement = document.createElement("style"); 73 styleElement = document.createElement("style");
67 document.head.appendChild(styleElement); 74 document.head.appendChild(styleElement);
68 } 75 }
69 styleElement.sheet.insertRule(rule, styleElement.sheet.cssRules.length); 76 styleElement.sheet.insertRule(rule, styleElement.sheet.cssRules.length);
70 } 77 }
71 78
72 function createElementWithStyle(styleBlock) 79 // insert a <div> with a unique id and and empty CSS rule
80 // for the the selector matching the id.
81 function createElementWithStyle(styleBlock, parent)
73 { 82 {
74 let element = document.createElement("div"); 83 let element = document.createElement("div");
75 element.id = findUniqueId(); 84 element.id = findUniqueId();
76 document.body.appendChild(element); 85 if (!parent)
86 document.body.appendChild(element);
87 else
88 parent.appendChild(element);
77 insertStyleRule("#" + element.id + " " + styleBlock); 89 insertStyleRule("#" + element.id + " " + styleBlock);
78 return element; 90 return element;
79 } 91 }
80 92
81 function applyElemHideEmulation(selectors) 93 // Will ensure the class ElemHideEmulation is loaded
94 // and then will call the callback.
95 // NOTE: if it never loads, this will probably hang in an infinite
96 // loop
97 function loadElemHideEmulation()
82 { 98 {
83 if (typeof ElemHideEmulation == "undefined") 99 if (typeof ElemHideEmulation == "undefined")
84 { 100 {
85 return loadScript(myUrl + "/../../../lib/common.js").then(() => 101 return loadScript(myUrl + "/../../../lib/common.js").then(() =>
86 { 102 {
87 return loadScript(myUrl + "/../../../chrome/content/elemHideEmulation.js") ; 103 return loadScript(myUrl + "/../../../chrome/content/elemHideEmulation.js") ;
88 }).then(() => 104 }).then(() =>
89 { 105 {
90 return applyElemHideEmulation(selectors); 106 return loadElemHideEmulation();
91 }); 107 });
92 } 108 }
93 109
94 let elemHideEmulation = new ElemHideEmulation( 110 return Promise.resolve();
95 window, 111 }
96 callback => 112
97 { 113 // instantiate a ElemHideEmulation with @selectors.
98 let patterns = []; 114 function applyElemHideEmulation(selectors)
99 selectors.forEach(selector => 115 {
116 return loadElemHideEmulation().then(() =>
117 {
118 let elemHideEmulation = new ElemHideEmulation(
119 window,
120 callback =>
100 { 121 {
101 patterns.push({selector}); 122 let patterns = [];
102 }); 123 selectors.forEach(selector =>
103 callback(patterns); 124 {
104 }, newSelectors => 125 patterns.push({selector});
105 { 126 });
106 if (!newSelectors.length) 127 callback(patterns);
107 return; 128 },
108 let selector = newSelectors.join(", "); 129 newSelectors =>
109 insertStyleRule(selector + "{display: none !important;}"); 130 {
110 } 131 if (!newSelectors.length)
111 ); 132 return;
112 133 let selector = newSelectors.join(", ");
113 elemHideEmulation.apply(); 134 insertStyleRule(selector + "{display: none !important;}");
114 return Promise.resolve(); 135 },
115 } 136 elements =>
137 {
138 if (!elements.length)
139 return;
140 for (let element of elements)
141 element.style.display = "none";
142 }
143 );
144
145 elemHideEmulation.apply();
146 return Promise.resolve();
147 });
148 }
149
150 exports.testParseSelector = function(test)
151 {
152 loadElemHideEmulation().then(() =>
153 {
154 let selectors = parseSelector("");
155 test.equal(selectors.length, 0);
156
157 let selector = "div > :-abp-has(> div.inside) > div";
158 selectors = parseSelector(selector);
159
160 test.equal(selectors.length, 3);
161 test.ok(selectors[0] instanceof PlainSelector);
162 test.ok(selectors[1] instanceof HasSelector);
163 test.ok(selectors[2] instanceof PlainSelector);
164
165 selector = "div > div:-abp-has(> div.inside) > div";
166 selectors = parseSelector(selector);
167
168 test.equal(selectors.length, 3);
169 test.ok(selectors[0] instanceof PlainSelector);
170 test.ok(selectors[1] instanceof HasSelector);
171 test.ok(selectors[2] instanceof PlainSelector);
172
173 /*
174 selector = "div > :-abp-has(> div.inside) > :-abp-properties('background-col or: rgb(0, 0, 0)')";
175 selectors = parseSelector(selector);
176
177 test.equal(selectors.length, 3);
178 test.ok(selectors[0] instanceof PlainSelector);
179 test.ok(selectors[1] instanceof HasSelector);
180 test.ok(selectors[2] instanceof PropsSelector);
181 */
182 }).catch(unexpectedError.bind(test)).then(() => test.done());
183 };
184
185 function buildDom(doc)
186 {
187 doc.body.innerHTML = `<div id="parent">
188 <div id="middle">
189 <div id="middle1"><div id="inside" class="inside"></div></div>
190 </div>
191 <div id="sibling">
192 <div id="tohide">to hide</div>
193 </div>
194 <div id="sibling2">
195 <div id="sibling21"><div id="sibling211" class="inside"></div></div>
196 </div>
197 </div>`;
198 let parent = document.getElementById("parent");
199 let middle = document.getElementById("middle");
200 let inside = document.getElementById("inside");
201 let sibling = document.getElementById("sibling");
202 let sibling2 = document.getElementById("sibling2");
203 let toHide = document.getElementById("tohide");
204 return {parent, middle, inside, sibling, sibling2, toHide};
205 }
206
207 exports.testPlainSelector = function(test)
208 {
209 let nodes = buildDom(document);
210
211 loadElemHideEmulation().then(() =>
212 {
213 let selector = new PlainSelector("div > div");
214
215 let iter = selector.getSelectors("foo > ");
216 let value = iter.next();
217 test.equal(value.value[0], "foo > div > div");
218 test.ok(iter.next().done);
219
220 iter = selector.getElements("", document, [document.sheet]);
221 value = iter.next();
222 test.ok(!value.done);
223 test.equal(value.value, nodes.middle);
224 value = iter.next();
225 test.ok(!value.done);
226 test.equal(value.value.id, "middle1");
227 value = iter.next();
228 test.ok(!value.done);
229 test.equal(value.value, nodes.inside);
230 }).catch(unexpectedError.bind(test)).then(() => test.done());
231 };
232
233 exports.testHasSelector = function(test)
234 {
235 let nodes = buildDom(document);
236
237 loadElemHideEmulation().then(() =>
238 {
239 let selector = new HasSelector("> div.inside");
240
241 let iter = selector.getSelectors("", document, document.sheet);
242 let value = iter.next();
243 test.ok(!value.done);
244 test.equal(value.value[0], "#middle1");
245
246 iter = selector.getElements("", document, document.sheet);
247 value = iter.next();
248 test.ok(!value.done);
249 test.equal(value.value.id, "middle1");
250 value = iter.next();
251 test.ok(!value.done);
252 test.equal(value.value.id, "sibling21");
253 value = iter.next();
254 test.ok(value.done);
255 }).catch(unexpectedError.bind(test)).then(() => test.done());
256 };
257
258 exports.testPseudoHasRule = function(test)
259 {
260 loadElemHideEmulation().then(() =>
261 {
262 let selector = "div:-abp-has(span)";
263 // testing the regexp
264 let match = pseudoClassHasSelectorRegExp.exec(selector);
265 test.ok(match);
266 test.equal(match[1], "span");
267
268 selector = ":-abp-has(div.inside)";
269 match = pseudoClassHasSelectorRegExp.exec(selector);
270 test.ok(match);
271 test.equal(match[1], "div.inside");
272 }).catch(unexpectedError.bind(test)).then(() => test.done());
273 };
274
275 exports.testSplitStyleRule = function(test)
276 {
277 loadElemHideEmulation().then(() =>
278 {
279 let selectors = splitSelector("div:-abp-has(div) > [-abp-properties='backgro und-color: rgb(0, 0, 0)'] > span");
280 test.ok(selectors);
281 test.equal(selectors.length, 1, "There is only one selector");
282
283 selectors = splitSelector("div:-abp-has(div), [-abp-properties='background-c olor: rgb(0, 0, 0)']");
284 test.ok(selectors);
285 test.equal(selectors.length, 2, "There are two selectors");
286 }).catch(unexpectedError.bind(test)).then(() => test.done());
287 };
116 288
117 exports.testVerbatimPropertySelector = function(test) 289 exports.testVerbatimPropertySelector = function(test)
118 { 290 {
119 let toHide = createElementWithStyle("{background-color: #000}"); 291 let toHide = createElementWithStyle("{background-color: #000}");
120 applyElemHideEmulation( 292 applyElemHideEmulation(
121 ["[-abp-properties='background-color: rgb(0, 0, 0)']"] 293 [":-abp-properties('background-color: rgb(0, 0, 0)')"]
122 ).then(() => 294 ).then(() =>
123 { 295 {
296 expectHidden(test, toHide);
297 }).catch(unexpectedError.bind(test)).then(() => test.done());
298 };
299
300 exports.testVerbatimPropertySelectorWithPrefix = function(test)
301 {
302 let parent = createElementWithStyle("{background-color: #000}");
303 let toHide = createElementWithStyle("{background-color: #000}", parent);
304 applyElemHideEmulation(
305 ["div > :-abp-properties('background-color: rgb(0, 0, 0)')"]
306 ).then(() =>
307 {
308 expectVisible(test, parent);
309 expectHidden(test, toHide);
310 }).catch(unexpectedError.bind(test)).then(() => test.done());
311 };
312
313 exports.testVerbatimPropertySelectorWithPrefixNoMatch = function(test)
314 {
315 let parent = createElementWithStyle("{background-color: #000}");
316 let toHide = createElementWithStyle("{background-color: #fff}", parent);
317 applyElemHideEmulation(
318 ["div > :-abp-properties('background-color: rgb(0, 0, 0)')"]
319 ).then(() =>
320 {
321 expectVisible(test, parent);
322 expectVisible(test, toHide);
323 }).catch(unexpectedError.bind(test)).then(() => test.done());
324 };
325
326 exports.testVerbatimPropertySelectorWithSuffix = function(test)
327 {
328 let parent = createElementWithStyle("{background-color: #000}");
329 let toHide = createElementWithStyle("{background-color: #000}", parent);
330 applyElemHideEmulation(
331 [":-abp-properties('background-color: rgb(0, 0, 0)') > div"]
332 ).then(() =>
333 {
334 expectVisible(test, parent);
335 expectHidden(test, toHide);
336 }).catch(unexpectedError.bind(test)).then(() => test.done());
337 };
338
339 exports.testVerbatimPropertyPseudoSelectorWithPrefixAndSuffix = function(test)
340 {
341 let parent = createElementWithStyle("{background-color: #000}");
342 let middle = createElementWithStyle("{background-color: #000}", parent);
343 let toHide = createElementWithStyle("{background-color: #000}", middle);
344 applyElemHideEmulation(
345 ["div > :-abp-properties('background-color: rgb(0, 0, 0)') > div"]
346 ).then(() =>
347 {
348 expectVisible(test, parent);
349 expectVisible(test, middle);
124 expectHidden(test, toHide); 350 expectHidden(test, toHide);
125 }).catch(unexpectedError.bind(test)).then(() => test.done()); 351 }).catch(unexpectedError.bind(test)).then(() => test.done());
126 }; 352 };
127 353
128 exports.testPropertySelectorWithWildcard = function(test) 354 exports.testPropertySelectorWithWildcard = function(test)
129 { 355 {
130 let toHide = createElementWithStyle("{background-color: #000}"); 356 let toHide = createElementWithStyle("{background-color: #000}");
131 applyElemHideEmulation( 357 applyElemHideEmulation(
132 ["[-abp-properties='*color: rgb(0, 0, 0)']"] 358 [":-abp-properties('*color: rgb(0, 0, 0)')"]
133 ).then(() => 359 ).then(() =>
134 { 360 {
135 expectHidden(test, toHide); 361 expectHidden(test, toHide);
136 }).catch(unexpectedError.bind(test)).then(() => test.done()); 362 }).catch(unexpectedError.bind(test)).then(() => test.done());
137 }; 363 };
138 364
139 exports.testPropertySelectorWithRegularExpression = function(test) 365 exports.testPropertySelectorWithRegularExpression = function(test)
140 { 366 {
141 let toHide = createElementWithStyle("{background-color: #000}"); 367 let toHide = createElementWithStyle("{background-color: #000}");
142 applyElemHideEmulation( 368 applyElemHideEmulation(
143 ["[-abp-properties='/.*color: rgb\\(0, 0, 0\\)/']"] 369 [":-abp-properties('/.*color: rgb\\(0, 0, 0\\)/')"]
144 ).then(() => 370 ).then(() =>
145 { 371 {
146 expectHidden(test, toHide); 372 expectHidden(test, toHide);
147 }).catch(unexpectedError.bind(test)).then(() => test.done()); 373 }).catch(unexpectedError.bind(test)).then(() => test.done());
148 }; 374 };
149 375
150 exports.testPropertySelectorWithEscapedBrace = function(test) 376 exports.testPropertySelectorWithEscapedBrace = function(test)
151 { 377 {
152 let toHide = createElementWithStyle("{background-color: #000}"); 378 let toHide = createElementWithStyle("{background-color: #000}");
153 applyElemHideEmulation( 379 applyElemHideEmulation(
154 ["[-abp-properties='/background.\\x7B 0,6\\x7D : rgb\\(0, 0, 0\\)/']"] 380 [":-abp-properties('/background.\\x7B 0,6\\x7D : rgb\\(0, 0, 0\\)/')"]
155 ).then(() => 381 ).then(() =>
156 { 382 {
157 expectHidden(test, toHide); 383 expectHidden(test, toHide);
158 }).catch(unexpectedError.bind(test)).then(() => test.done()); 384 }).catch(unexpectedError.bind(test)).then(() => test.done());
159 }; 385 };
160 386
161 exports.testPropertySelectorWithImproperlyEscapedBrace = function(test) 387 exports.testPropertySelectorWithImproperlyEscapedBrace = function(test)
162 { 388 {
163 let toHide = createElementWithStyle("{background-color: #000}"); 389 let toHide = createElementWithStyle("{background-color: #000}");
164 applyElemHideEmulation( 390 applyElemHideEmulation(
165 ["[-abp-properties='/background.\\x7B0,6\\x7D: rgb\\(0, 0, 0\\)/']"] 391 [":-abp-properties('/background.\\x7B0,6\\x7D: rgb\\(0, 0, 0\\)/')"]
166 ).then(() => 392 ).then(() =>
167 { 393 {
168 expectVisible(test, toHide); 394 expectVisible(test, toHide);
169 }).catch(unexpectedError.bind(test)).then(() => test.done()); 395 }).catch(unexpectedError.bind(test)).then(() => test.done());
170 }; 396 };
171 397
172 exports.testDynamicallyChangedProperty = function(test) 398 exports.testDynamicallyChangedProperty = function(test)
173 { 399 {
174 let toHide = createElementWithStyle("{}"); 400 let toHide = createElementWithStyle("{}");
175 applyElemHideEmulation( 401 applyElemHideEmulation(
176 ["[-abp-properties='background-color: rgb(0, 0, 0)']"] 402 [":-abp-properties('background-color: rgb(0, 0, 0)')"]
177 ).then(() => 403 ).then(() =>
178 { 404 {
179 expectVisible(test, toHide); 405 expectVisible(test, toHide);
180 insertStyleRule("#" + toHide.id + " {background-color: #000}"); 406 insertStyleRule("#" + toHide.id + " {background-color: #000}");
181 return new Promise((resolve, reject) => 407 return new Promise((resolve, reject) =>
182 { 408 {
183 window.setTimeout(() => 409 window.setTimeout(() =>
184 { 410 {
185 expectHidden(test, toHide); 411 expectHidden(test, toHide);
186 resolve(); 412 resolve();
187 }, 0); 413 }, 0);
188 }); 414 });
189 }).catch(unexpectedError.bind(test)).then(() => test.done()); 415 }).catch(unexpectedError.bind(test)).then(() => test.done());
190 }; 416 };
417
418 exports.testPseudoClassHasSelector = function(test)
419 {
420 let toHide = createElementWithStyle("{}");
421 applyElemHideEmulation(
422 ["div:-abp-has(div)"]
423 ).then(() =>
424 {
425 expectVisible(test, toHide);
426 }).catch(unexpectedError.bind(test)).then(() => test.done());
427 };
428
429 exports.testPseudoClassHasSelectorWithPrefix = function(test)
430 {
431 let parent = createElementWithStyle("{}");
432 let child = createElementWithStyle("{}", parent);
433 applyElemHideEmulation(
434 ["div:-abp-has(div)"]
435 ).then(() =>
436 {
437 expectHidden(test, parent);
438 expectVisible(test, child);
439 }).catch(unexpectedError.bind(test)).then(() => test.done());
440 };
441
442 exports.testPseudoClassHasSelectorWithSuffix = function(test)
443 {
444 let parent = createElementWithStyle("{}");
445 let middle = createElementWithStyle("{}", parent);
446 let child = createElementWithStyle("{}", middle);
447 applyElemHideEmulation(
448 ["div:-abp-has(div) > div"]
449 ).then(() =>
450 {
451 expectVisible(test, parent);
452 expectHidden(test, middle);
453 expectHidden(test, child);
454 }).catch(unexpectedError.bind(test)).then(() => test.done());
455 };
456
457 exports.testPseudoClassHasSelectorWithSuffixSibling = function(test)
458 {
459 let parent = createElementWithStyle("{}");
460 let middle = createElementWithStyle("{}", parent);
461 let toHide = createElementWithStyle("{}");
462 applyElemHideEmulation(
463 ["div:-abp-has(div) + div"]
464 ).then(() =>
465 {
466 expectVisible(test, parent);
467 expectVisible(test, middle);
468 expectHidden(test, toHide);
469 }).catch(unexpectedError.bind(test)).then(() => test.done());
470 };
471
472 exports.testPseudoClassHasSelectorWithSuffixSiblingChild = function(test)
473 {
474 // <div>
475 // <div></div>
476 // <div>
477 // <div>to hide</div>
478 // </div>
479 // </div>
480 let parent = createElementWithStyle("{}");
481 let middle = createElementWithStyle("{}", parent);
482 let sibling = createElementWithStyle("{}");
483 let toHide = createElementWithStyle("{}", sibling);
484 applyElemHideEmulation(
485 ["div:-abp-has(div) + div > div"]
486 ).then(() =>
487 {
488 expectVisible(test, parent);
489 expectVisible(test, middle);
490 expectVisible(test, sibling);
491 expectHidden(test, toHide);
492 }).catch(unexpectedError.bind(test)).then(() => test.done());
493 };
494
495 function runTestPseudoClassHasSelectorWithHasAndWithSuffixSibling(test, selector )
496 {
497 document.body.innerHTML = `<div id="parent">
498 <div id="middle">
499 <div id="middle1"><div id="inside" class="inside"></div></div>
500 </div>
501 <div id="sibling">
502 <div id="tohide">to hide</div>
503 </div>
504 <div id="sibling2">
505 <div id="sibling21"><div id="sibling211" class="inside"></div></div>
506 </div>
507 </div>`;
508 let parent = document.getElementById("parent");
509 let middle = document.getElementById("middle");
510 let inside = document.getElementById("inside");
511 let sibling = document.getElementById("sibling");
512 let sibling2 = document.getElementById("sibling2");
513 let toHide = document.getElementById("tohide");
514
515 insertStyleRule(".inside {}");
516
517 applyElemHideEmulation(
518 [selector]
519 ).then(() =>
520 {
521 expectVisible(test, parent);
522 expectVisible(test, middle);
523 expectVisible(test, inside);
524 expectVisible(test, sibling);
525 expectVisible(test, sibling2);
526 expectHidden(test, toHide);
527 }).catch(unexpectedError.bind(test)).then(() => test.done());
528 }
529
530 exports.testPseudoClassHasSelectorWithHasAndWithSuffixSibling = function(test)
531 {
532 runTestPseudoClassHasSelectorWithHasAndWithSuffixSibling(test, "div:-abp-has(: -abp-has(div.inside)) + div > div");
533 };
534
535 exports.testPseudoClassHasSelectorWithHasAndWithSuffixSibling2 = function(test)
536 {
537 runTestPseudoClassHasSelectorWithHasAndWithSuffixSibling(test, "div:-abp-has(: -abp-has(> div.inside)) + div > div");
538 };
539
540 exports.testPseudoClassHasSelectorWithPropSelector = function(test)
541 {
542 let parent = createElementWithStyle("{}");
543 let child = createElementWithStyle("{background-color: #000}", parent);
544 applyElemHideEmulation(
545 ["div:-abp-has(:-abp-properties(\"background-color: rgb(0, 0, 0)\"))"]
546 ).then(() =>
547 {
548 expectVisible(test, child);
549 expectHidden(test, parent);
550 }).catch(unexpectedError.bind(test)).then(() => test.done());
551 };
OLDNEW
« no previous file with comments | « lib/filterClasses.js ('k') | test/filterClasses.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld