Left: | ||
Right: |
OLD | NEW |
---|---|
(Empty) | |
1 /* | |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | |
3 * Copyright (C) 2006-2015 Eyeo GmbH | |
4 * | |
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 | |
7 * published by the Free Software Foundation. | |
8 * | |
9 * Adblock Plus is distributed in the hope that it will be useful, | |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 * GNU General Public License for more details. | |
13 * | |
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/>. | |
16 */ | |
17 | |
18 "use strict"; | |
19 | |
20 (function() | |
21 { | |
22 var subscriptionsMap = Object.create(null); | |
23 var recommendationsMap = Object.create(null); | |
24 var filtersMap = Object.create(null); | |
25 var collections = Object.create(null); | |
26 | |
27 function Collection(details) | |
Felix Dahlke
2015/06/07 21:09:59
Does details have to be an array? From what I've s
saroyanm
2015/06/09 15:29:13
on line 139 I'm passing array from 2 elements.
"Ad
Felix Dahlke
2015/06/11 14:40:20
True, fair enough.
| |
28 { | |
29 this.details = details; | |
30 } | |
31 | |
32 Collection.prototype = Object.create(Array.prototype); | |
Felix Dahlke
2015/06/07 21:09:59
From what I've seen, the only member functions use
saroyanm
2015/06/09 15:29:13
I would like to understand why it's more preferabl
Felix Dahlke
2015/06/11 14:40:20
It's a pretty common principle in OOP (http://en.w
saroyanm
2015/06/12 10:51:18
Done.
| |
33 Collection.prototype.addItems = function() | |
34 { | |
35 var length = Array.prototype.push.apply(this, arguments); | |
36 if (length == 0) | |
37 return; | |
38 | |
39 this.sort(function(a, b) | |
40 { | |
41 var aValue = (a.title || a.url || a.text).toLowerCase(); | |
42 var bValue = (b.title || b.url || a.text).toLowerCase(); | |
43 if (aValue < bValue) | |
Felix Dahlke
2015/06/07 21:09:59
Nit: Would be shorter using localeCompare: `return
saroyanm
2015/06/09 15:29:13
I like this :) Done.
| |
44 return -1; | |
45 if (aValue > bValue) | |
46 return 1; | |
47 return 0; | |
48 }); | |
49 | |
50 for (var j = 0; j < this.details.length; j++) | |
51 { | |
52 var table = E(this.details[j].id); | |
53 var template = table.querySelector("template"); | |
54 for (var i = 0; i < arguments.length; i++) | |
55 { | |
56 var item = arguments[i]; | |
57 var text = item.title || item.url || item.text; | |
58 var listItem = document.createElement("li"); | |
59 listItem.appendChild(document.importNode(template.content, true)); | |
60 listItem.dataset.access = item.url || item.text; | |
61 listItem.querySelector(".display").textContent = text; | |
62 if (text) | |
63 listItem.dataset.search = text.toLowerCase(); | |
64 | |
65 var control = listItem.querySelector(".control"); | |
66 if (control) | |
67 { | |
68 control.addEventListener("click", this.details[j].onClick, false); | |
69 control.checked = item.disabled == false; | |
Felix Dahlke
2015/06/07 21:09:59
Nit: "Do not compare x == true or x == false. Use
Felix Dahlke
2015/06/07 21:15:24
Sorry, wrong link, should have been: https://devel
saroyanm
2015/06/09 15:29:13
In this case is bit complicated, while the design
Felix Dahlke
2015/06/11 14:40:20
Oh I didn't realise that. No then it's fine.
| |
70 } | |
71 | |
72 if (table.hasChildNodes) | |
Felix Dahlke
2015/06/07 21:09:59
Shouldn't this be `table.hasChildNodes()`?
saroyanm
2015/06/09 15:29:13
Done.
| |
73 table.insertBefore(listItem, table.childNodes[this.indexOf(item)]); | |
74 else | |
75 table.appendChild(listItem); | |
76 } | |
77 } | |
78 return length; | |
79 }; | |
80 | |
81 Collection.prototype.removeItem = function(item) | |
82 { | |
83 var index = this.indexOf(item); | |
84 if (index == -1) | |
85 return; | |
86 | |
87 this.splice(index, 1); | |
88 var access = (item.url || item.text).replace(/'/g, "\\'"); | |
89 for (var i = 0; i < this.details.length; i++) | |
90 { | |
91 var table = E(this.details[i].id); | |
92 var element = table.querySelector("[data-access='" + access + "']"); | |
93 element.parentElement.removeChild(element); | |
94 } | |
95 }; | |
96 | |
97 Collection.prototype.clearAll = function() | |
98 { | |
99 for (var i = 0; i < this.details.length; i++) | |
100 { | |
101 var table = E(this.details[i].id); | |
102 var template = table.querySelector("template"); | |
103 table.innerHTML = ""; | |
104 table.appendChild(template); | |
105 } | |
106 this.length = 0; | |
107 }; | |
108 | |
109 function toggleSubscription(e) | |
Felix Dahlke
2015/06/07 21:09:59
Nit: Wouldn't insist on it, but being an event han
saroyanm
2015/06/09 15:29:13
Done.
| |
110 { | |
111 e.preventDefault(); | |
112 var subscriptionUrl = e.target.parentNode.dataset.access; | |
113 if (!e.target.checked) | |
114 removeSubscription(subscriptionUrl); | |
115 else | |
116 addEnableSubscription(subscriptionUrl); | |
117 } | |
118 | |
119 function addLanguageSubscription(e) | |
120 { | |
121 e.preventDefault(); | |
122 var url = this.parentNode.dataset.access; | |
123 addEnableSubscription(url); | |
124 } | |
125 | |
126 function triggerRemoveFilter() | |
127 { | |
128 var filter = this.parentNode.dataset.access; | |
129 removeFilter(filter); | |
130 } | |
131 | |
132 collections.popular = new Collection( | |
133 [ | |
134 { | |
135 id: "recommend-list-table", | |
136 onClick: toggleSubscription | |
137 } | |
138 ]); | |
139 collections.langs = new Collection( | |
140 [ | |
141 { | |
142 id: "blocking-languages-table", | |
143 onClick: toggleSubscription | |
144 }, | |
145 { | |
146 id: "blocking-languages-modal-table" | |
147 } | |
148 ]); | |
149 collections.allLangs = new Collection( | |
150 [ | |
151 { | |
152 id: "all-lang-table", | |
153 onClick: addLanguageSubscription | |
154 } | |
155 ]); | |
156 collections.acceptableAds = new Collection( | |
157 [ | |
158 { | |
159 id: "acceptableads-table", | |
160 onClick: toggleSubscription | |
161 } | |
162 ]); | |
163 collections.custom = new Collection( | |
164 [ | |
165 { | |
166 id: "custom-list-table", | |
167 onClick: toggleSubscription | |
168 } | |
169 ]); | |
170 collections.whitelist = new Collection( | |
171 [ | |
172 { | |
173 id: "whitelisting-table", | |
174 onClick: triggerRemoveFilter | |
175 } | |
176 ]); | |
177 | |
178 function updateSubscription(subscription) | |
179 { | |
180 var subscriptionUrl = subscription.url; | |
181 var knownSubscription = subscriptionsMap[subscriptionUrl]; | |
182 if (knownSubscription) | |
183 knownSubscription.disabled = subscription.disabled; | |
184 else | |
185 { | |
186 getAcceptableAdsURL(function(acceptableAdsUrl) | |
187 { | |
188 function onObjectChanged() | |
189 { | |
190 var access = (subscriptionUrl || subscription.text).replace(/'/g, "\\' "); | |
191 var elements = document.querySelectorAll("[data-access='" + access + " ']"); | |
192 for (var i = 0; i < elements.length; i++) | |
193 { | |
194 var element = elements[i]; | |
195 var control = element.querySelector(".control"); | |
196 if (control.localName == "input") | |
197 control.checked = subscription.disabled == false; | |
198 if (subscriptionUrl in recommendationsMap) | |
199 { | |
200 var recommendation = recommendationsMap[subscriptionUrl]; | |
201 if (recommendation.isAdsType) | |
202 { | |
203 if (subscription.disabled == false) | |
204 { | |
205 collections.allLangs.removeItem(subscription); | |
206 collections.langs.addItems(subscription); | |
207 } | |
208 else | |
209 { | |
210 collections.allLangs.addItems(subscription); | |
211 collections.langs.removeItem(subscription); | |
212 } | |
213 } | |
214 } | |
215 } | |
216 } | |
217 | |
218 if (!Object.observe) | |
219 { | |
220 ["disabled"].forEach(function(property) | |
Felix Dahlke
2015/06/07 21:09:59
This could be simplified :D
saroyanm
2015/06/09 15:29:13
For future we are going to not only listen to "dis
Felix Dahlke
2015/06/11 14:40:20
If we're absolutely sure we'll need it, I'm OK wit
| |
221 { | |
222 subscription["$" + property] = subscription[property]; | |
223 Object.defineProperty(subscription, property, | |
224 { | |
225 get: function() | |
226 { | |
227 return this["$" + property]; | |
228 }, | |
229 set: function(value) | |
230 { | |
231 this["$" + property] = value; | |
232 onObjectChanged(); | |
233 } | |
234 }); | |
235 }); | |
236 } | |
237 else | |
238 { | |
239 Object.observe(subscription, function(changes) | |
Felix Dahlke
2015/06/07 21:09:59
Nit: Why not just `Object.observe(subscription, on
saroyanm
2015/06/09 15:29:13
Done.
| |
240 { | |
241 onObjectChanged(); | |
242 }); | |
243 } | |
244 | |
245 var collection = null; | |
246 if (subscriptionUrl in recommendationsMap) | |
247 { | |
248 var recommendation = recommendationsMap[subscriptionUrl]; | |
249 if (recommendation.isPopular) | |
250 collection = collections.popular; | |
251 else if (recommendation.isAdsType && subscription.disabled == false) | |
252 collection = collections.langs; | |
253 else | |
254 collection = collections.allLangs; | |
255 } | |
256 else if (subscriptionUrl == acceptableAdsUrl) | |
257 collection = collections.acceptableAds; | |
258 else | |
259 collection = collections.custom; | |
260 | |
261 collection.addItems(subscription); | |
262 subscriptionsMap[subscriptionUrl] = subscription; | |
263 }); | |
264 } | |
265 } | |
266 | |
267 function updateFilter(filter) | |
268 { | |
269 var match = filter.text.match(/^@@\|\|([^\/:]+)\^\$document$/); | |
270 if (match && !filtersMap[filter.text]) | |
271 { | |
272 filter.title = match[1]; | |
273 collections.whitelist.addItems(filter); | |
274 filtersMap[filter.text] = filter | |
275 } | |
276 else | |
277 { | |
278 // TODO: add `filters[i].text` to list of custom filters | |
279 } | |
280 } | |
281 | |
282 function loadRecommendations() | |
283 { | |
284 var request = new XMLHttpRequest(); | |
285 request.open("GET", "subscriptions.xml", false); | |
286 request.onload = function() | |
Felix Dahlke
2015/06/07 21:09:59
Nit: Use addEventListener()?
saroyanm
2015/06/09 15:29:13
Done.
| |
287 { | |
288 var list = document.getElementById("subscriptionSelector"); | |
289 var docElem = request.responseXML.documentElement; | |
290 var elements = docElem.getElementsByTagName("subscription"); | |
291 for (var i = 0; i < elements.length; i++) | |
292 { | |
293 var element = elements[i]; | |
294 var subscription = Object.create(null); | |
295 subscription.title = element.getAttribute("title"); | |
296 subscription.url = element.getAttribute("url"); | |
297 subscription.disabled = null; | |
298 subscription.downloadStatus = null; | |
299 subscription.homepage = null; | |
300 subscription.lastSuccess = null; | |
301 var recommendation = Object.create(null); | |
302 recommendation.isAdsType = false; | |
303 recommendation.isPopular = false; | |
304 var prefix = element.getAttribute("prefixes"); | |
305 if (prefix) | |
306 { | |
307 var prefix = element.getAttribute("prefixes").replace(/,/g, "_"); | |
308 subscription.title = ext.i18n.getMessage("options_language_" + prefix) ; | |
309 recommendation.isAdsType = true; | |
310 } | |
311 else | |
312 subscription.title = element.getAttribute("specialization"); | |
313 | |
314 if (element.getAttribute("popular")) | |
315 recommendation.isPopular = true; | |
316 | |
317 recommendationsMap[subscription.url] = recommendation; | |
318 updateSubscription(subscription); | |
319 } | |
320 }; | |
321 request.send(null); | |
322 } | |
323 | |
324 function onDOMLoaded() | |
325 { | |
326 var recommendationTemplate = document.querySelector("#recommend-list-table t emplate"); | |
327 var popularText = ext.i18n.getMessage("options_popular"); | |
328 recommendationTemplate.content.querySelector(".popular").textContent = popul arText; | |
329 var languagesTemplate = document.querySelector("#all-lang-table template"); | |
330 var buttonText = ext.i18n.getMessage("options_button_add"); | |
331 languagesTemplate.content.querySelector(".button-add span").textContent = bu ttonText; | |
332 | |
333 updateShareLink(); | |
334 populateLists(); | |
335 | |
336 var tabList = document.querySelectorAll("#main-navigation-tabs li"); | |
337 for (var i = 0; i < tabList.length; i++) | |
338 { | |
339 tabList[i].addEventListener("click", function(e) | |
340 { | |
341 document.body.dataset.tab = e.currentTarget.dataset.show; | |
342 }, false); | |
343 } | |
344 | |
345 function searchLanguage() | |
Felix Dahlke
2015/06/07 21:09:59
Nit: Similar to above, I'd find this easier to und
saroyanm
2015/06/09 15:29:13
Done.
| |
346 { | |
347 var searchStyle = E("search-style"); | |
348 if (!this.value) | |
349 searchStyle.innerHTML = ""; | |
350 else | |
351 searchStyle.innerHTML = "#all-lang-table li:not([data-search*=\"" + this .value.toLowerCase() + "\"]) { display: none; }"; | |
352 } | |
353 | |
354 // Update version number in navigation sidebar | |
355 ext.backgroundPage.sendMessage( | |
356 { | |
357 method: "app.get", | |
358 what: "addonVersion" | |
359 }, | |
360 function(addonVersion) | |
361 { | |
362 E("abp-version").textContent = addonVersion; | |
363 }); | |
364 | |
365 var placeholderValue = ext.i18n.getMessage("options_modal_language_find"); | |
366 E("find-language").setAttribute("placeholder", placeholderValue); | |
367 E("add-blocking-list").addEventListener("click", function() | |
368 { | |
369 openModal("customlist"); | |
370 }, false); | |
371 E("add-website-language").addEventListener("click", function() | |
372 { | |
373 openModal("language"); | |
374 }, false); | |
375 E("modal-close").addEventListener("click", function() | |
376 { | |
377 delete document.body.dataset.modal; | |
378 }, false); | |
379 E("edit-ownBlockingList-button").addEventListener("click", editCustomFilters , false); | |
380 E("find-language").addEventListener("keyup", searchLanguage, false); | |
381 E("whitelisting").addEventListener("click", function(e) | |
382 { | |
383 var id = e.target.id; | |
384 if (id == "whitelisting-add-icon" || id == "whitelisting-enter-icon") | |
385 addWhitelistedDomain(); | |
386 else if (id == "whitelisting-cancel-button") | |
387 E("whitelisting-textbox").value = ""; | |
388 }, false); | |
389 E("whitelisting-add-button").addEventListener("click", addWhitelistedDomain, false); | |
390 E("whitelisting-textbox").addEventListener("keypress", function(e) | |
391 { | |
392 // e.keyCode has been deprecated so we use e.key instead | |
Felix Dahlke
2015/06/07 21:09:59
Nit: "so we attempt to use e.key"? Since we're act
saroyanm
2015/06/09 15:29:13
Done.
| |
393 if (e.key && e.key == "Enter") | |
394 addWhitelistedDomain(); | |
395 else if (!e.key && e.keyCode == 13) //keyCode "13" corresponds to "Enter" | |
396 addWhitelistedDomain(); | |
Felix Dahlke
2015/06/07 21:09:59
Nit: Seeing how the else body is identical to the
saroyanm
2015/06/09 15:29:13
Done.
| |
397 }, false); | |
398 E("import-blockingList-button").addEventListener("click", function() | |
399 { | |
400 var url = E("blockingList-textbox").value; | |
401 addEnableSubscription(url); | |
402 delete document.body.dataset.modal; | |
403 }, false); | |
404 } | |
405 | |
406 function openModal(name) | |
407 { | |
408 document.body.dataset.modal = name; | |
409 } | |
410 | |
411 function populateLists() | |
412 { | |
413 subscriptionsMap = Object.create(null); | |
414 filtersMap = Object.create(null); | |
415 recommendationsMap = Object.create(null); | |
416 | |
417 // Empty collections and lists | |
418 for (var property in collections) | |
419 collections[property].clearAll(); | |
420 | |
421 ext.backgroundPage.sendMessage( | |
422 { | |
423 type: "subscriptions.get", | |
424 special: true | |
425 }, | |
426 function(subscriptions) | |
427 { | |
428 // Load filters | |
429 for (var i = 0; i < subscriptions.length; i++) | |
430 { | |
431 ext.backgroundPage.sendMessage( | |
432 { | |
433 type: "filters.get", | |
434 subscriptionUrl: subscriptions[i].url | |
435 }, | |
436 function(filters) | |
437 { | |
438 for (var i = 0; i < filters.length; i++) | |
439 updateFilter(filters[i]); | |
440 }); | |
441 } | |
442 }); | |
443 loadRecommendations(); | |
444 getAcceptableAdsURL(function(acceptableAdsUrl) | |
445 { | |
446 var subscription = Object.create(null); | |
447 subscription.url = acceptableAdsUrl; | |
448 subscription.disabled = true; | |
449 subscription.title = ext.i18n.getMessage("options_acceptableAds_descriptio n"); | |
450 updateSubscription(subscription); | |
451 | |
452 // Load user subscriptions | |
453 ext.backgroundPage.sendMessage( | |
454 { | |
455 type: "subscriptions.get", | |
456 downloadable: true | |
457 }, | |
458 function(subscriptions) | |
459 { | |
460 for (var i = 0; i < subscriptions.length; i++) | |
461 onSubscriptionMessage("added", subscriptions[i]); | |
462 }); | |
463 }); | |
464 } | |
465 | |
466 function addWhitelistedDomain() | |
467 { | |
468 var domain = E("whitelisting-textbox"); | |
469 if (domain.value) | |
470 { | |
471 ext.backgroundPage.sendMessage( | |
472 { | |
473 type: "filters.add", | |
474 text: "@@||" + domain.value.toLowerCase() + "^$document" | |
475 }); | |
476 } | |
477 | |
478 domain.value = ""; | |
479 } | |
480 | |
481 function editCustomFilters() | |
482 { | |
483 | |
Felix Dahlke
2015/06/07 21:09:59
Nit: Seeing how we have a bunch of TODO comments h
saroyanm
2015/06/09 15:29:13
Done.
| |
484 } | |
485 | |
486 function getAcceptableAdsURL(callback) | |
487 { | |
488 ext.backgroundPage.sendMessage( | |
489 { | |
490 type: "prefs.get", | |
491 key: "subscriptions_exceptionsurl" | |
492 }, | |
493 function(value) | |
494 { | |
495 getAcceptableAdsURL = function(callback) | |
496 { | |
497 callback(value); | |
498 } | |
499 getAcceptableAdsURL(callback); | |
500 }); | |
501 } | |
502 | |
503 function addEnableSubscription(url, title, homepage) | |
504 { | |
505 var messageType = null; | |
506 var knownSubscription = subscriptionsMap[url]; | |
507 if (knownSubscription && knownSubscription.disabled == true) | |
508 messageType = "subscriptions.toggle" | |
509 else | |
510 messageType = "subscriptions.add" | |
511 | |
512 var message = { | |
513 type: messageType, | |
514 url: url | |
515 }; | |
516 if (title) | |
517 message.title = title; | |
518 if (homepage) | |
519 message.homepage = homepage; | |
520 | |
521 ext.backgroundPage.sendMessage(message); | |
522 } | |
523 | |
524 function removeSubscription(url) | |
525 { | |
526 ext.backgroundPage.sendMessage( | |
527 { | |
528 type: "subscriptions.remove", | |
529 url: url | |
530 }); | |
531 } | |
532 | |
533 function removeFilter(filter) | |
534 { | |
535 ext.backgroundPage.sendMessage( | |
536 { | |
537 type: "filters.remove", | |
538 text: filter | |
539 }); | |
540 } | |
541 | |
542 function onFilterMessage(action, filter) | |
543 { | |
544 switch (action) | |
545 { | |
546 case "added": | |
547 updateFilter(filter); | |
548 updateShareLink(); | |
549 break; | |
550 case "loaded": | |
551 populateLists(); | |
552 break; | |
553 case "removed": | |
554 var knownFilter = filtersMap[filter.text]; | |
555 collections.whitelist.removeItem(knownFilter); | |
556 delete filtersMap[filter.text]; | |
557 updateShareLink(); | |
558 break; | |
559 } | |
560 } | |
561 | |
562 function onSubscriptionMessage(action, subscription) | |
563 { | |
564 switch (action) | |
565 { | |
566 case "added": | |
567 updateSubscription(subscription); | |
568 updateShareLink(); | |
569 break; | |
570 case "disabled": | |
Felix Dahlke
2015/06/07 21:09:59
Merge with case "added" above?
saroyanm
2015/06/09 15:29:13
Done.
| |
571 updateSubscription(subscription); | |
572 updateShareLink(); | |
573 break; | |
574 case "homepage": | |
575 // TODO: NYI | |
576 break; | |
577 case "removed": | |
578 getAcceptableAdsURL(function(acceptableAdsUrl) | |
579 { | |
580 if (subscription.url == acceptableAdsUrl) | |
581 { | |
582 subscription.disabled = true; | |
583 updateSubscription(subscription); | |
584 } | |
585 else | |
586 { | |
587 var knownSubscription = subscriptionsMap[subscription.url]; | |
588 if (subscription.url in recommendationsMap) | |
589 knownSubscription.disabled = true; | |
590 else | |
591 { | |
592 collections.custom.removeItem(knownSubscription); | |
593 delete subscriptionsMap[subscription.url]; | |
594 } | |
595 } | |
596 updateShareLink(); | |
597 }); | |
598 break; | |
599 case "title": | |
600 // TODO: NYI | |
601 break; | |
602 } | |
603 } | |
604 | |
605 function showAddSubscriptionDialog(subscription) | |
606 { | |
607 E("blockingList-textbox").value = subscription.url; | |
608 openModal("customlist"); | |
609 } | |
610 | |
611 function updateShareLink() | |
612 { | |
613 ext.backgroundPage.sendMessage( | |
614 { | |
615 type: "filters.blocked", | |
616 url: "https://platform.twitter.com/widgets/", | |
617 requestType: "SCRIPT", | |
618 docDomain: "adblockplus.org", | |
619 thirdParty: true | |
620 }, | |
621 function(blocked) | |
622 { | |
623 // TODO: modify "share" link accordingly | |
624 }); | |
625 } | |
626 | |
627 function E(id) | |
Felix Dahlke
2015/06/07 21:09:59
Not a fan of this function, made things harder to
Felix Dahlke
2015/06/08 19:44:51
Just realised that we have E() as well in the Fire
| |
628 { | |
629 return document.getElementById(id); | |
630 } | |
631 | |
632 ext.onMessage.addListener(function(message) | |
633 { | |
634 switch (message.type) | |
635 { | |
636 case "app.listen": | |
637 if (message.action == "addSubscription") | |
638 showAddSubscriptionDialog(message.args[0]); | |
639 break; | |
640 case "filters.listen": | |
641 onFilterMessage(message.action, message.args[0]); | |
642 break; | |
643 case "subscriptions.listen": | |
644 onSubscriptionMessage(message.action, message.args[0]); | |
645 break; | |
646 } | |
647 }); | |
648 | |
649 ext.backgroundPage.sendMessage( | |
650 { | |
651 type: "app.listen", | |
652 filter: ["addSubscription"] | |
653 }); | |
654 ext.backgroundPage.sendMessage( | |
655 { | |
656 type: "filters.listen", | |
657 filter: ["added", "loaded", "removed"] | |
658 }); | |
659 ext.backgroundPage.sendMessage( | |
660 { | |
661 type: "subscriptions.listen", | |
662 filter: ["added", "disabled", "homepage", "removed", "title"] | |
663 }); | |
664 | |
665 window.addEventListener("DOMContentLoaded", onDOMLoaded, false); | |
666 })(); | |
OLD | NEW |