Bug 1798651 Part 1: Make SynchronousTask accept a wait interval, and return result...
[gecko.git] / toolkit / content / aboutUrlClassifier.js
blob219f6d829fa9af810e58a3f02ce2175714eb8123
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const UPDATE_BEGIN = "safebrowsing-update-begin";
6 const UPDATE_FINISH = "safebrowsing-update-finished";
7 const JSLOG_PREF = "browser.safebrowsing.debug";
9 window.onunload = function() {
10   Search.uninit();
11   Provider.uninit();
12   Cache.uninit();
13   Debug.uninit();
16 window.onload = function() {
17   Search.init();
18   Provider.init();
19   Cache.init();
20   Debug.init();
24  * Search
25  */
26 var Search = {
27   init() {
28     let classifier = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
29       Ci.nsIURIClassifier
30     );
31     let featureNames = classifier.getFeatureNames();
33     let fragment = document.createDocumentFragment();
34     featureNames.forEach(featureName => {
35       let container = document.createElement("label");
36       container.className = "toggle-container-with-text";
37       fragment.appendChild(container);
39       let checkbox = document.createElement("input");
40       checkbox.id = "feature_" + featureName;
41       checkbox.type = "checkbox";
42       checkbox.checked = true;
43       container.appendChild(checkbox);
45       let span = document.createElement("span");
46       container.appendChild(span);
48       let text = document.createTextNode(featureName);
49       span.appendChild(text);
50     });
52     let list = document.getElementById("search-features");
53     list.appendChild(fragment);
55     let btn = document.getElementById("search-button");
56     btn.addEventListener("click", this.search);
58     this.hideError();
59     this.hideResults();
60   },
62   uninit() {
63     let list = document.getElementById("search-features");
64     while (list.firstChild) {
65       list.firstChild.remove();
66     }
68     let btn = document.getElementById("search-button");
69     btn.removeEventListener("click", this.search);
70   },
72   search() {
73     Search.hideError();
74     Search.hideResults();
76     let input = document.getElementById("search-input").value;
78     let uri;
79     try {
80       uri = Services.io.newURI(input);
81       if (!uri) {
82         Search.reportError("url-classifier-search-error-invalid-url");
83         return;
84       }
85     } catch (ex) {
86       Search.reportError("url-classifier-search-error-invalid-url");
87       return;
88     }
90     let classifier = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
91       Ci.nsIURIClassifier
92     );
94     let featureNames = classifier.getFeatureNames();
95     let features = [];
96     featureNames.forEach(featureName => {
97       if (document.getElementById("feature_" + featureName).checked) {
98         let feature = classifier.getFeatureByName(featureName);
99         if (feature) {
100           features.push(feature);
101         }
102       }
103     });
105     if (!features.length) {
106       Search.reportError("url-classifier-search-error-no-features");
107       return;
108     }
110     let listType =
111       document.getElementById("search-listtype").value == 0
112         ? Ci.nsIUrlClassifierFeature.blocklist
113         : Ci.nsIUrlClassifierFeature.entitylist;
114     classifier.asyncClassifyLocalWithFeatures(uri, features, listType, list =>
115       Search.showResults(list)
116     );
118     Search.hideError();
119   },
121   hideError() {
122     let errorMessage = document.getElementById("search-error-message");
123     errorMessage.style.display = "none";
124   },
126   reportError(msg) {
127     let errorMessage = document.getElementById("search-error-message");
128     document.l10n.setAttributes(errorMessage, msg);
129     errorMessage.style.display = "";
130   },
132   hideResults() {
133     let resultTitle = document.getElementById("result-title");
134     resultTitle.style.display = "none";
136     let resultTable = document.getElementById("result-table");
137     resultTable.style.display = "none";
138   },
140   showResults(results) {
141     let fragment = document.createDocumentFragment();
142     results.forEach(result => {
143       let tr = document.createElement("tr");
144       fragment.appendChild(tr);
146       let th = document.createElement("th");
147       tr.appendChild(th);
148       th.appendChild(document.createTextNode(result.feature.name));
150       let td = document.createElement("td");
151       tr.appendChild(td);
153       let featureName = document.createElement("div");
154       document.l10n.setAttributes(
155         featureName,
156         "url-classifier-search-result-uri",
157         { uri: result.uri.spec }
158       );
159       td.appendChild(featureName);
161       let list = document.createElement("div");
162       document.l10n.setAttributes(list, "url-classifier-search-result-list", {
163         list: result.list,
164       });
165       td.appendChild(list);
166     });
168     let resultTable = document.getElementById("result-table");
169     while (resultTable.firstChild) {
170       resultTable.firstChild.remove();
171     }
173     resultTable.appendChild(fragment);
174     resultTable.style.display = "";
176     let resultTitle = document.getElementById("result-title");
177     resultTitle.style.display = "";
178   },
182  * Provider
183  */
184 var Provider = {
185   providers: null,
187   updatingProvider: "",
189   init() {
190     this.providers = new Set();
191     let branch = Services.prefs.getBranch("browser.safebrowsing.provider.");
192     let children = branch.getChildList("");
193     for (let child of children) {
194       let provider = child.split(".")[0];
195       if (this.isActiveProvider(provider)) {
196         this.providers.add(provider);
197       }
198     }
200     this.register();
201     this.render();
202     this.refresh();
203   },
205   uninit() {
206     Services.obs.removeObserver(this.onBeginUpdate, UPDATE_BEGIN);
207     Services.obs.removeObserver(this.onFinishUpdate, UPDATE_FINISH);
208   },
210   onBeginUpdate(aSubject, aTopic, aData) {
211     this.updatingProvider = aData;
212     let p = this.updatingProvider;
214     // Disable update button for the provider while we are doing update.
215     document.getElementById("update-" + p).disabled = true;
217     let elem = document.getElementById(p + "-col-lastupdateresult");
218     document.l10n.setAttributes(elem, "url-classifier-updating");
219   },
221   onFinishUpdate(aSubject, aTopic, aData) {
222     let p = this.updatingProvider;
223     this.updatingProvider = "";
225     // It is possible that we get update-finished event only because
226     // about::url-classifier is opened after update-begin event is fired.
227     if (p === "") {
228       this.refresh();
229       return;
230     }
232     this.refresh([p]);
234     document.getElementById("update-" + p).disabled = false;
236     let elem = document.getElementById(p + "-col-lastupdateresult");
237     if (aData.startsWith("success")) {
238       document.l10n.setAttributes(elem, "url-classifier-success");
239     } else if (aData.startsWith("update error")) {
240       document.l10n.setAttributes(elem, "url-classifier-update-error", {
241         error: aData.split(": ")[1],
242       });
243     } else if (aData.startsWith("download error")) {
244       document.l10n.setAttributes(elem, "url-classifier-download-error", {
245         error: aData.split(": ")[1],
246       });
247     } else {
248       elem.childNodes[0].nodeValue = aData;
249     }
250   },
252   register() {
253     // Handle begin update
254     this.onBeginUpdate = this.onBeginUpdate.bind(this);
255     Services.obs.addObserver(this.onBeginUpdate, UPDATE_BEGIN);
257     // Handle finish update
258     this.onFinishUpdate = this.onFinishUpdate.bind(this);
259     Services.obs.addObserver(this.onFinishUpdate, UPDATE_FINISH);
260   },
262   // This should only be called once because we assume number of providers
263   // won't change.
264   render() {
265     let tbody = document.getElementById("provider-table-body");
267     for (let provider of this.providers) {
268       let tr = document.createElement("tr");
269       let cols = document.getElementById("provider-head-row").childNodes;
270       for (let column of cols) {
271         if (!column.id) {
272           continue;
273         }
274         let td = document.createElement("td");
275         td.id = provider + "-" + column.id;
277         if (column.id === "col-update") {
278           let btn = document.createElement("button");
279           btn.id = "update-" + provider;
280           btn.addEventListener("click", () => {
281             this.update(provider);
282           });
284           document.l10n.setAttributes(btn, "url-classifier-trigger-update");
285           td.appendChild(btn);
286         } else if (column.id === "col-lastupdateresult") {
287           document.l10n.setAttributes(td, "url-classifier-not-available");
288         } else {
289           td.appendChild(document.createTextNode(""));
290         }
291         tr.appendChild(td);
292       }
293       tbody.appendChild(tr);
294     }
295   },
297   refresh(listProviders = this.providers) {
298     for (let provider of listProviders) {
299       let values = {};
300       values["col-provider"] = provider;
302       let pref =
303         "browser.safebrowsing.provider." + provider + ".lastupdatetime";
304       let lut = Services.prefs.getCharPref(pref, "");
305       values["col-lastupdatetime"] = lut ? new Date(lut * 1) : null;
307       pref = "browser.safebrowsing.provider." + provider + ".nextupdatetime";
308       let nut = Services.prefs.getCharPref(pref, "");
309       values["col-nextupdatetime"] = nut ? new Date(nut * 1) : null;
311       let listmanager = Cc[
312         "@mozilla.org/url-classifier/listmanager;1"
313       ].getService(Ci.nsIUrlListManager);
314       let bot = listmanager.getBackOffTime(provider);
315       values["col-backofftime"] = bot ? new Date(bot * 1) : null;
317       for (let key of Object.keys(values)) {
318         let elem = document.getElementById(provider + "-" + key);
319         if (values[key]) {
320           elem.removeAttribute("data-l10n-id");
321           elem.childNodes[0].nodeValue = values[key];
322         } else {
323           document.l10n.setAttributes(elem, "url-classifier-not-available");
324         }
325       }
326     }
327   },
329   // Call update for the provider.
330   update(provider) {
331     let listmanager = Cc[
332       "@mozilla.org/url-classifier/listmanager;1"
333     ].getService(Ci.nsIUrlListManager);
335     let pref = "browser.safebrowsing.provider." + provider + ".lists";
336     let tables = Services.prefs.getCharPref(pref, "");
338     if (!listmanager.forceUpdates(tables)) {
339       // This may because of back-off algorithm.
340       let elem = document.getElementById(provider + "-col-lastupdateresult");
341       document.l10n.setAttributes(elem, "url-classifier-cannot-update");
342     }
343   },
345   // if we can find any table registered an updateURL in the listmanager,
346   // the provider is active. This is used to filter out google v2 provider
347   // without changing the preference.
348   isActiveProvider(provider) {
349     let listmanager = Cc[
350       "@mozilla.org/url-classifier/listmanager;1"
351     ].getService(Ci.nsIUrlListManager);
353     let pref = "browser.safebrowsing.provider." + provider + ".lists";
354     let tables = Services.prefs.getCharPref(pref, "").split(",");
356     for (let i = 0; i < tables.length; i++) {
357       let updateUrl = listmanager.getUpdateUrl(tables[i]);
358       if (updateUrl) {
359         return true;
360       }
361     }
363     return false;
364   },
368  * Cache
369  */
370 var Cache = {
371   // Tables that show cahe entries.
372   showCacheEnties: null,
374   init() {
375     this.showCacheEnties = new Set();
377     this.register();
378     this.render();
379   },
381   uninit() {
382     Services.obs.removeObserver(this.refresh, UPDATE_FINISH);
383   },
385   register() {
386     this.refresh = this.refresh.bind(this);
387     Services.obs.addObserver(this.refresh, UPDATE_FINISH);
388   },
390   render() {
391     this.createCacheEntries();
393     let refreshBtn = document.getElementById("refresh-cache-btn");
394     refreshBtn.addEventListener("click", () => {
395       this.refresh();
396     });
398     let clearBtn = document.getElementById("clear-cache-btn");
399     clearBtn.addEventListener("click", () => {
400       let dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
401         Ci.nsIUrlClassifierDBService
402       );
403       dbservice.clearCache();
404       // Since clearCache is async call, we just simply assume it will be
405       // updated in 100 milli-seconds.
406       setTimeout(() => {
407         this.refresh();
408       }, 100);
409     });
410   },
412   refresh() {
413     this.clearCacheEntries();
414     this.createCacheEntries();
415   },
417   clearCacheEntries() {
418     let ctbody = document.getElementById("cache-table-body");
419     while (ctbody.firstChild) {
420       ctbody.firstChild.remove();
421     }
423     let cetbody = document.getElementById("cache-entries-table-body");
424     while (cetbody.firstChild) {
425       cetbody.firstChild.remove();
426     }
427   },
429   createCacheEntries() {
430     function createRow(tds, body, cols) {
431       let tr = document.createElement("tr");
432       tds.forEach(function(v, i, a) {
433         let td = document.createElement("td");
434         if (i == 0 && tds.length != cols) {
435           td.setAttribute("colspan", cols - tds.length + 1);
436         }
438         if (typeof v === "object") {
439           if (v.l10n) {
440             document.l10n.setAttributes(td, v.l10n);
441           } else {
442             td.removeAttribute("data-l10n-id");
443             td.appendChild(v);
444           }
445         } else {
446           td.removeAttribute("data-l10n-id");
447           td.textContent = v;
448         }
450         tr.appendChild(td);
451       });
452       body.appendChild(tr);
453     }
455     let dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
456       Ci.nsIUrlClassifierInfo
457     );
459     for (let provider of Provider.providers) {
460       let pref = "browser.safebrowsing.provider." + provider + ".lists";
461       let tables = Services.prefs.getCharPref(pref, "").split(",");
463       for (let table of tables) {
464         dbservice.getCacheInfo(table, {
465           onGetCacheComplete: aCache => {
466             let entries = aCache.entries;
467             if (entries.length === 0) {
468               this.showCacheEnties.delete(table);
469               return;
470             }
472             let positiveCacheCount = 0;
473             for (let i = 0; i < entries.length; i++) {
474               let entry = entries.queryElementAt(
475                 i,
476                 Ci.nsIUrlClassifierCacheEntry
477               );
478               let matches = entry.matches;
479               positiveCacheCount += matches.length;
481               // If we don't have to show cache entries for this table then just
482               // skip the following code.
483               if (!this.showCacheEnties.has(table)) {
484                 continue;
485               }
487               let tds = [
488                 table,
489                 entry.prefix,
490                 new Date(entry.expiry * 1000).toString(),
491               ];
492               let j = 0;
493               do {
494                 if (matches.length >= 1) {
495                   let match = matches.queryElementAt(
496                     j,
497                     Ci.nsIUrlClassifierPositiveCacheEntry
498                   );
499                   let list = [
500                     match.fullhash,
501                     new Date(match.expiry * 1000).toString(),
502                   ];
503                   tds = tds.concat(list);
504                 } else {
505                   tds = tds.concat([
506                     { l10n: "url-classifier-not-available" },
507                     { l10n: "url-classifier-not-available" },
508                   ]);
509                 }
510                 createRow(
511                   tds,
512                   document.getElementById("cache-entries-table-body"),
513                   5
514                 );
515                 j++;
516                 tds = [""];
517               } while (j < matches.length);
518             }
520             // Create cache information entries.
521             let chk = document.createElement("input");
522             chk.type = "checkbox";
523             chk.checked = this.showCacheEnties.has(table);
524             chk.addEventListener("click", () => {
525               if (chk.checked) {
526                 this.showCacheEnties.add(table);
527               } else {
528                 this.showCacheEnties.delete(table);
529               }
530               this.refresh();
531             });
533             let tds = [table, entries.length, positiveCacheCount, chk];
534             createRow(
535               tds,
536               document.getElementById("cache-table-body"),
537               tds.length
538             );
539           },
540         });
541       }
542     }
544     let entries_div = document.getElementById("cache-entries");
545     entries_div.style.display =
546       this.showCacheEnties.size == 0 ? "none" : "block";
547   },
551  * Debug
552  */
553 var Debug = {
554   // url-classifier NSPR Log modules.
555   modules: [
556     "UrlClassifierDbService",
557     "nsChannelClassifier",
558     "UrlClassifier",
559     "UrlClassifierProtocolParser",
560     "UrlClassifierStreamUpdater",
561     "UrlClassifierPrefixSet",
562     "ApplicationReputation",
563   ],
565   init() {
566     this.register();
567     this.render();
568     this.refresh();
569   },
571   uninit() {
572     Services.prefs.removeObserver(JSLOG_PREF, this.refreshJSDebug);
573   },
575   register() {
576     this.refreshJSDebug = this.refreshJSDebug.bind(this);
577     Services.prefs.addObserver(JSLOG_PREF, this.refreshJSDebug);
578   },
580   render() {
581     // This function update the log module text field if we click
582     // safebrowsing log module check box.
583     function logModuleUpdate(module) {
584       let txt = document.getElementById("log-modules");
585       let chk = document.getElementById("chk-" + module);
587       let dst = chk.checked ? "," + module + ":5" : "";
588       let re = new RegExp(",?" + module + ":[0-9]");
590       let str = txt.value.replace(re, dst);
591       if (chk.checked) {
592         str = txt.value === str ? str + dst : str;
593       }
594       txt.value = str.replace(/^,/, "");
595     }
597     let setLog = document.getElementById("set-log-modules");
598     setLog.addEventListener("click", this.nsprlog);
600     let setLogFile = document.getElementById("set-log-file");
601     setLogFile.addEventListener("click", this.logfile);
603     let setJSLog = document.getElementById("js-log");
604     setJSLog.addEventListener("click", this.jslog);
606     let modules = document.getElementById("log-modules");
607     let sbModules = document.getElementById("sb-log-modules");
608     for (let module of this.modules) {
609       let container = document.createElement("label");
610       container.className = "toggle-container-with-text";
611       sbModules.appendChild(container);
613       let chk = document.createElement("input");
614       chk.id = "chk-" + module;
615       chk.type = "checkbox";
616       chk.checked = true;
617       chk.addEventListener("click", () => {
618         logModuleUpdate(module);
619       });
620       container.appendChild(chk, modules);
622       let span = document.createElement("span");
623       span.appendChild(document.createTextNode(module));
624       container.appendChild(span, modules);
625     }
627     this.modules.map(logModuleUpdate);
629     let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
630     file.append("safebrowsing.log");
632     let logFile = document.getElementById("log-file");
633     logFile.value = file.path;
635     let curLog = document.getElementById("cur-log-modules");
636     curLog.childNodes[0].nodeValue = "";
638     let curLogFile = document.getElementById("cur-log-file");
639     curLogFile.childNodes[0].nodeValue = "";
640   },
642   refresh() {
643     this.refreshJSDebug();
645     // Disable configure log modules if log modules are already set
646     // by environment variable.
647     let env = Cc["@mozilla.org/process/environment;1"].getService(
648       Ci.nsIEnvironment
649     );
651     let logModules =
652       env.get("MOZ_LOG") ||
653       env.get("MOZ_LOG_MODULES") ||
654       env.get("NSPR_LOG_MODULES");
656     if (logModules.length) {
657       document.getElementById("set-log-modules").disabled = true;
658       for (let module of this.modules) {
659         document.getElementById("chk-" + module).disabled = true;
660       }
662       let curLogModules = document.getElementById("cur-log-modules");
663       curLogModules.childNodes[0].nodeValue = logModules;
664     }
666     // Disable set log file if log file is already set
667     // by environment variable.
668     let logFile = env.get("MOZ_LOG_FILE") || env.get("NSPR_LOG_FILE");
669     if (logFile.length) {
670       document.getElementById("set-log-file").disabled = true;
671       document.getElementById("log-file").value = logFile;
672     }
673   },
675   refreshJSDebug() {
676     let enabled = Services.prefs.getBoolPref(JSLOG_PREF, false);
678     let jsChk = document.getElementById("js-log");
679     jsChk.checked = enabled;
681     let curJSLog = document.getElementById("cur-js-log");
682     if (enabled) {
683       document.l10n.setAttributes(curJSLog, "url-classifier-enabled");
684     } else {
685       document.l10n.setAttributes(curJSLog, "url-classifier-disabled");
686     }
687   },
689   jslog() {
690     let enabled = Services.prefs.getBoolPref(JSLOG_PREF, false);
691     Services.prefs.setBoolPref(JSLOG_PREF, !enabled);
692   },
694   nsprlog() {
695     // Turn off debugging for all the modules.
696     let children = Services.prefs.getBranch("logging.").getChildList("");
697     for (let pref of children) {
698       if (!pref.startsWith("config.")) {
699         Services.prefs.clearUserPref(`logging.${pref}`);
700       }
701     }
703     let value = document.getElementById("log-modules").value;
704     let logModules = value.split(",");
705     for (let module of logModules) {
706       let [key, value] = module.split(":");
707       Services.prefs.setIntPref(`logging.${key}`, parseInt(value, 10));
708     }
710     let curLogModules = document.getElementById("cur-log-modules");
711     curLogModules.childNodes[0].nodeValue = value;
712   },
714   logfile() {
715     let logFile = document.getElementById("log-file").value.trim();
716     Services.prefs.setCharPref("logging.config.LOG_FILE", logFile);
718     let curLogFile = document.getElementById("cur-log-file");
719     curLogFile.childNodes[0].nodeValue = logFile;
720   },