forked from mirrors/gecko-dev
		
	 e18e651af1
			
		
	
	
		e18e651af1
		
	
	
	
	
		
			
			When about:url-classifier ask cache information by using |GetCacheInfo| API, this API will send a SYNC request to off-main thread. If NSS module is not initialized then off-main thread will send init request to main thread, and this causes a deadlock. This patch change |GetCacheInfo| API to asynchronous so main thread will not be blocked. MozReview-Commit-ID: 3y2hOjCoRUj --HG-- extra : rebase_source : 4d1ec1b7fd960cdf90bca577879b41fbbc2a4e17
		
			
				
	
	
		
			495 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			495 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| var Ci = Components.interfaces;
 | |
| var Cc = Components.classes;
 | |
| var Cu = Components.utils;
 | |
| 
 | |
| Cu.import("resource://gre/modules/Services.jsm");
 | |
| 
 | |
| const bundle = Services.strings.createBundle(
 | |
|   "chrome://global/locale/aboutUrlClassifier.properties");
 | |
| 
 | |
| const UPDATE_BEGIN = "safebrowsing-update-begin";
 | |
| const UPDATE_FINISH = "safebrowsing-update-finished";
 | |
| const JSLOG_PREF = "browser.safebrowsing.debug";
 | |
| 
 | |
| const STR_NA = bundle.GetStringFromName("NotAvailable");
 | |
| 
 | |
| function unLoad() {
 | |
|   window.removeEventListener("unload", unLoad);
 | |
| 
 | |
|   Provider.uninit();
 | |
|   Cache.uninit();
 | |
|   Debug.uninit();
 | |
| }
 | |
| 
 | |
| function onLoad() {
 | |
|   window.removeEventListener("load", onLoad);
 | |
|   window.addEventListener("unload", unLoad);
 | |
| 
 | |
|   Provider.init();
 | |
|   Cache.init();
 | |
|   Debug.init();
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Provider
 | |
|  */
 | |
| var Provider = {
 | |
|   providers: null,
 | |
| 
 | |
|   updatingProvider: "",
 | |
| 
 | |
|   init() {
 | |
|     this.providers = new Set();
 | |
|     let branch = Services.prefs.getBranch("browser.safebrowsing.provider.");
 | |
|     let children = branch.getChildList("", {});
 | |
|     for (let child of children) {
 | |
|       this.providers.add(child.split(".")[0]);
 | |
|     }
 | |
| 
 | |
|     this.register();
 | |
|     this.render();
 | |
|     this.refresh();
 | |
|   },
 | |
| 
 | |
|   uninit() {
 | |
|     Services.obs.removeObserver(this.onBeginUpdate, UPDATE_BEGIN);
 | |
|     Services.obs.removeObserver(this.onFinishUpdate, UPDATE_FINISH);
 | |
|   },
 | |
| 
 | |
|   onBeginUpdate(aSubject, aTopic, aData) {
 | |
|     this.updatingProvider = aData;
 | |
|     let p = this.updatingProvider;
 | |
| 
 | |
|     // Disable update button for the provider while we are doing update.
 | |
|     document.getElementById("update-" + p).disabled = true;
 | |
| 
 | |
|     let elem = document.getElementById(p + "-col-lastupdateresult");
 | |
|     elem.childNodes[0].nodeValue = bundle.GetStringFromName("Updating");
 | |
|   },
 | |
| 
 | |
|   onFinishUpdate(aSubject, aTopic, aData) {
 | |
|     let p = this.updatingProvider;
 | |
|     this.updatingProvider = "";
 | |
| 
 | |
|     // It is possible that we get update-finished event only because
 | |
|     // about::url-classifier is opened after update-begin event is fired.
 | |
|     if (p === "") {
 | |
|       this.refresh();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this.refresh([p]);
 | |
| 
 | |
|     document.getElementById("update-" + p).disabled = false;
 | |
| 
 | |
|     let elem = document.getElementById(p + "-col-lastupdateresult");
 | |
|     if (aData.startsWith("success")) {
 | |
|       elem.childNodes[0].nodeValue = bundle.GetStringFromName("success");
 | |
|     } else if (aData.startsWith("update error")) {
 | |
|       elem.childNodes[0].nodeValue =
 | |
|         bundle.formatStringFromName("updateError", [aData.split(": ")[1]], 1);
 | |
|     } else if (aData.startsWith("download error")) {
 | |
|       elem.childNodes[0].nodeValue =
 | |
|         bundle.formatStringFromName("downloadError", [aData.split(": ")[1]], 1);
 | |
|     } else {
 | |
|       elem.childNodes[0].nodeValue = aData;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   register() {
 | |
|     // Handle begin update
 | |
|     this.onBeginUpdate = this.onBeginUpdate.bind(this);
 | |
|     Services.obs.addObserver(this.onBeginUpdate, UPDATE_BEGIN);
 | |
| 
 | |
|     // Handle finish update
 | |
|     this.onFinishUpdate = this.onFinishUpdate.bind(this);
 | |
|     Services.obs.addObserver(this.onFinishUpdate, UPDATE_FINISH);
 | |
|   },
 | |
| 
 | |
|   // This should only be called once because we assume number of providers
 | |
|   // won't change.
 | |
|   render() {
 | |
|     let tbody = document.getElementById("provider-table-body");
 | |
| 
 | |
|     for (let provider of this.providers) {
 | |
|       let tr = document.createElement("tr");
 | |
|       let cols = document.getElementById("provider-head-row").childNodes;
 | |
|       for (let column of cols) {
 | |
|         if (!column.id) {
 | |
|           continue;
 | |
|         }
 | |
|         let td = document.createElement("td");
 | |
|         td.id = provider + "-" + column.id;
 | |
| 
 | |
|         if (column.id === "col-update") {
 | |
|           let btn = document.createElement("button");
 | |
|           btn.id = "update-" + provider;
 | |
|           btn.addEventListener("click", () => { this.update(provider); });
 | |
| 
 | |
|           let str = bundle.GetStringFromName("TriggerUpdate");
 | |
|           btn.appendChild(document.createTextNode(str));
 | |
|           td.appendChild(btn);
 | |
|         } else {
 | |
|           let str = column.id === "col-lastupdateresult" ? STR_NA : "";
 | |
|           td.appendChild(document.createTextNode(str));
 | |
|         }
 | |
|         tr.appendChild(td);
 | |
|       }
 | |
|       tbody.appendChild(tr);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   refresh(listProviders = this.providers) {
 | |
|     for (let provider of listProviders) {
 | |
|       let values = {};
 | |
|       values["col-provider"] = provider;
 | |
| 
 | |
|       let pref = "browser.safebrowsing.provider." + provider + ".lastupdatetime";
 | |
|       let lut = Services.prefs.getCharPref(pref, "");
 | |
|       values["col-lastupdatetime"] = lut ? new Date(lut * 1) : STR_NA;
 | |
| 
 | |
|       pref = "browser.safebrowsing.provider." + provider + ".nextupdatetime";
 | |
|       let nut = Services.prefs.getCharPref(pref, "");
 | |
|       values["col-nextupdatetime"] = nut ? new Date(nut * 1) : STR_NA;
 | |
| 
 | |
|       let listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"]
 | |
|                         .getService(Ci.nsIUrlListManager);
 | |
|       let bot = listmanager.getBackOffTime(provider);
 | |
|       values["col-backofftime"] = bot ? new Date(bot * 1) : STR_NA;
 | |
| 
 | |
|       for (let key of Object.keys(values)) {
 | |
|         let elem = document.getElementById(provider + "-" + key);
 | |
|         elem.childNodes[0].nodeValue = values[key];
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // Call update for the provider.
 | |
|   update(provider) {
 | |
|     let listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"]
 | |
|                       .getService(Ci.nsIUrlListManager);
 | |
| 
 | |
|     let pref = "browser.safebrowsing.provider." + provider + ".lists";
 | |
|     let tables = Services.prefs.getCharPref(pref, "").split(",");
 | |
|     let table = tables.find(t => listmanager.getUpdateUrl(t) != "");
 | |
| 
 | |
|     let updateUrl = listmanager.getUpdateUrl(table);
 | |
|     if (!listmanager.checkForUpdates(updateUrl)) {
 | |
|       // This may because of back-off algorithm.
 | |
|       let elem = document.getElementById(provider + "-col-lastupdateresult");
 | |
|       elem.childNodes[0].nodeValue = bundle.GetStringFromName("CannotUpdate");
 | |
|     }
 | |
|   },
 | |
| 
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Cache
 | |
|  */
 | |
| var Cache = {
 | |
|   // Tables that show cahe entries.
 | |
|   showCacheEnties: null,
 | |
| 
 | |
|   init() {
 | |
|     this.showCacheEnties = new Set();
 | |
| 
 | |
|     this.register();
 | |
|     this.render();
 | |
|   },
 | |
| 
 | |
|   uninit() {
 | |
|     Services.obs.removeObserver(this.refresh, UPDATE_FINISH);
 | |
|   },
 | |
| 
 | |
|   register() {
 | |
|     this.refresh = this.refresh.bind(this);
 | |
|     Services.obs.addObserver(this.refresh, UPDATE_FINISH);
 | |
|   },
 | |
| 
 | |
|   render() {
 | |
|     this.createCacheEntries();
 | |
| 
 | |
|     let refreshBtn = document.getElementById("refresh-cache-btn");
 | |
|     refreshBtn.addEventListener("click", () => { this.refresh(); });
 | |
| 
 | |
|     let clearBtn = document.getElementById("clear-cache-btn");
 | |
|     clearBtn.addEventListener("click", () => {
 | |
|       let dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"]
 | |
|                       .getService(Ci.nsIUrlClassifierDBService);
 | |
|       dbservice.clearCache();
 | |
|       // Since clearCache is async call, we just simply assume it will be
 | |
|       // updated in 100 milli-seconds.
 | |
|       setTimeout(() => { this.refresh(); }, 100);
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   refresh() {
 | |
|     this.clearCacheEntries();
 | |
|     this.createCacheEntries();
 | |
|   },
 | |
| 
 | |
|   clearCacheEntries() {
 | |
|     let ctbody = document.getElementById("cache-table-body");
 | |
|     while (ctbody.firstChild) {
 | |
|       ctbody.firstChild.remove();
 | |
|     }
 | |
| 
 | |
|     let cetbody = document.getElementById("cache-entries-table-body");
 | |
|     while (cetbody.firstChild) {
 | |
|       cetbody.firstChild.remove();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   createCacheEntries() {
 | |
|     function createRow(tds, body, cols) {
 | |
|       let tr = document.createElement("tr");
 | |
|       tds.forEach(function(v, i, a) {
 | |
|         let td = document.createElement("td");
 | |
|         if (i == 0 && tds.length != cols) {
 | |
|           td.setAttribute("colspan", cols - tds.length + 1);
 | |
|         }
 | |
|         let elem = typeof v === "object" ? v : document.createTextNode(v);
 | |
|         td.appendChild(elem);
 | |
|         tr.appendChild(td);
 | |
|       });
 | |
|       body.appendChild(tr);
 | |
|     }
 | |
| 
 | |
|     let dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"]
 | |
|                     .getService(Ci.nsIUrlClassifierInfo);
 | |
| 
 | |
|     for (let provider of Provider.providers) {
 | |
|       let pref = "browser.safebrowsing.provider." + provider + ".lists";
 | |
|       let tables = Services.prefs.getCharPref(pref, "").split(",");
 | |
| 
 | |
|       for (let table of tables) {
 | |
|         dbservice.getCacheInfo(table, {
 | |
|           onGetCacheComplete: (aCache) => {
 | |
|             let entries = aCache.entries;
 | |
|             if (entries.length === 0) {
 | |
|               this.showCacheEnties.delete(table);
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             let positiveCacheCount = 0;
 | |
|             for (let i = 0; i < entries.length ; i++) {
 | |
|               let entry = entries.queryElementAt(i, Ci.nsIUrlClassifierCacheEntry);
 | |
|               let matches = entry.matches;
 | |
|               positiveCacheCount += matches.length;
 | |
| 
 | |
|               // If we don't have to show cache entries for this table then just
 | |
|               // skip the following code.
 | |
|               if (!this.showCacheEnties.has(table)) {
 | |
|                 continue;
 | |
|               }
 | |
| 
 | |
|               let tds = [table, entry.prefix, new Date(entry.expiry * 1000).toString()];
 | |
|               let j = 0;
 | |
|               do {
 | |
|                 if (matches.length >= 1) {
 | |
|                   let match =
 | |
|                     matches.queryElementAt(j, Ci.nsIUrlClassifierPositiveCacheEntry);
 | |
|                   let list = [match.fullhash, new Date(match.expiry * 1000).toString()];
 | |
|                   tds = tds.concat(list);
 | |
|                 } else {
 | |
|                   tds = tds.concat([STR_NA, STR_NA]);
 | |
|                 }
 | |
|                 createRow(tds, document.getElementById("cache-entries-table-body"), 5);
 | |
|                 j++;
 | |
|                 tds = [""];
 | |
|               } while (j < matches.length);
 | |
|             }
 | |
| 
 | |
|             // Create cache information entries.
 | |
|             let chk = document.createElement("input");
 | |
|             chk.type = "checkbox";
 | |
|             chk.checked = this.showCacheEnties.has(table);
 | |
|             chk.addEventListener("click", () => {
 | |
|               if (chk.checked) {
 | |
|                 this.showCacheEnties.add(table);
 | |
|               } else {
 | |
|                 this.showCacheEnties.delete(table);
 | |
|               }
 | |
|               this.refresh();
 | |
|             });
 | |
| 
 | |
|             let tds = [table, entries.length, positiveCacheCount, chk];
 | |
|             createRow(tds, document.getElementById("cache-table-body"), tds.length);
 | |
|           }
 | |
|         });
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let entries_div = document.getElementById("cache-entries");
 | |
|     entries_div.style.display = this.showCacheEnties.size == 0 ? "none" : "block";
 | |
|   },
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Debug
 | |
|  */
 | |
| var Debug = {
 | |
|   // url-classifier NSPR Log modules.
 | |
|   modules: ["UrlClassifierDbService",
 | |
|             "nsChannelClassifier",
 | |
|             "UrlClassifierProtocolParser",
 | |
|             "UrlClassifierStreamUpdater",
 | |
|             "UrlClassifierPrefixSet",
 | |
|             "ApplicationReputation"],
 | |
| 
 | |
|   init() {
 | |
|     this.register();
 | |
|     this.render();
 | |
|     this.refresh();
 | |
|   },
 | |
| 
 | |
|   uninit() {
 | |
|     Services.prefs.removeObserver(JSLOG_PREF, this.refreshJSDebug);
 | |
|   },
 | |
| 
 | |
|   register() {
 | |
|     this.refreshJSDebug = this.refreshJSDebug.bind(this);
 | |
|     Services.prefs.addObserver(JSLOG_PREF, this.refreshJSDebug);
 | |
|   },
 | |
| 
 | |
|   render() {
 | |
|     // This function update the log module text field if we click
 | |
|     // safebrowsing log module check box.
 | |
|     function logModuleUpdate(module) {
 | |
|       let txt = document.getElementById("log-modules");
 | |
|       let chk = document.getElementById("chk-" + module);
 | |
| 
 | |
|       let dst = chk.checked ? "," + module + ":5" : "";
 | |
|       let re = new RegExp(",?" + module + ":[0-9]");
 | |
| 
 | |
|       let str = txt.value.replace(re, dst);
 | |
|       if (chk.checked) {
 | |
|         str = txt.value === str ? str + dst : str;
 | |
|       }
 | |
|       txt.value = str.replace(/^,/, "");
 | |
|     }
 | |
| 
 | |
|     let setLog = document.getElementById("set-log-modules");
 | |
|     setLog.addEventListener("click", this.nsprlog);
 | |
| 
 | |
|     let setLogFile = document.getElementById("set-log-file");
 | |
|     setLogFile.addEventListener("click", this.logfile);
 | |
| 
 | |
|     let setJSLog = document.getElementById("js-log");
 | |
|     setJSLog.addEventListener("click", this.jslog);
 | |
| 
 | |
|     let modules = document.getElementById("log-modules");
 | |
|     let sbModules = document.getElementById("sb-log-modules");
 | |
|     for (let module of this.modules) {
 | |
|       let container = document.createElement("div");
 | |
|       container.className = "toggle-container-with-text";
 | |
|       sbModules.appendChild(container);
 | |
| 
 | |
|       let chk = document.createElement("input");
 | |
|       chk.id = "chk-" + module;
 | |
|       chk.type = "checkbox";
 | |
|       chk.checked = true;
 | |
|       chk.addEventListener("click", () => { logModuleUpdate(module); });
 | |
|       container.appendChild(chk, modules);
 | |
| 
 | |
|       let label = document.createElement("label");
 | |
|       label.for = chk.id;
 | |
|       label.appendChild(document.createTextNode(module));
 | |
|       container.appendChild(label, modules);
 | |
|     }
 | |
| 
 | |
|     this.modules.map(logModuleUpdate);
 | |
| 
 | |
|     let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
 | |
|     file.append("safebrowsing.log");
 | |
| 
 | |
|     let logFile = document.getElementById("log-file");
 | |
|     logFile.value = file.path;
 | |
| 
 | |
|     let curLog = document.getElementById("cur-log-modules");
 | |
|     curLog.childNodes[0].nodeValue = "";
 | |
| 
 | |
|     let curLogFile = document.getElementById("cur-log-file");
 | |
|     curLogFile.childNodes[0].nodeValue = "";
 | |
|   },
 | |
| 
 | |
|   refresh() {
 | |
|     this.refreshJSDebug();
 | |
| 
 | |
|     // Disable configure log modules if log modules are already set
 | |
|     // by environment variable.
 | |
|     let env = Cc["@mozilla.org/process/environment;1"]
 | |
|               .getService(Ci.nsIEnvironment);
 | |
| 
 | |
|     let logModules = env.get("MOZ_LOG") ||
 | |
|                      env.get("MOZ_LOG_MODULES") ||
 | |
|                      env.get("NSPR_LOG_MODULES");
 | |
| 
 | |
|     if (logModules.length > 0) {
 | |
|       document.getElementById("set-log-modules").disabled = true;
 | |
|       for (let module of this.modules) {
 | |
|         document.getElementById("chk-" + module).disabled = true;
 | |
|       }
 | |
| 
 | |
|       let curLogModules = document.getElementById("cur-log-modules");
 | |
|       curLogModules.childNodes[0].nodeValue = logModules;
 | |
|     }
 | |
| 
 | |
|     // Disable set log file if log file is already set
 | |
|     // by environment variable.
 | |
|     let logFile = env.get("MOZ_LOG_FILE") || env.get("NSPR_LOG_FILE");
 | |
|     if (logFile.length > 0) {
 | |
|       document.getElementById("set-log-file").disabled = true;
 | |
|       document.getElementById("log-file").value = logFile;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   refreshJSDebug() {
 | |
|     let enabled = Services.prefs.getBoolPref(JSLOG_PREF, false);
 | |
| 
 | |
|     let jsChk = document.getElementById("js-log");
 | |
|     jsChk.checked = enabled;
 | |
| 
 | |
|     let curJSLog = document.getElementById("cur-js-log");
 | |
|     curJSLog.childNodes[0].nodeValue = enabled ?
 | |
|       bundle.GetStringFromName("Enabled") :
 | |
|       bundle.GetStringFromName("Disabled");
 | |
|   },
 | |
| 
 | |
|   jslog() {
 | |
|     let enabled = Services.prefs.getBoolPref(JSLOG_PREF, false);
 | |
|     Services.prefs.setBoolPref(JSLOG_PREF, !enabled);
 | |
|   },
 | |
| 
 | |
|   nsprlog() {
 | |
|     // Turn off debugging for all the modules.
 | |
|     let children = Services.prefs.getBranch("logging.").getChildList("", {});
 | |
|     for (let pref of children) {
 | |
|       if (!pref.startsWith("config.")) {
 | |
|         Services.prefs.clearUserPref(`logging.${pref}`);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let value = document.getElementById("log-modules").value;
 | |
|     let logModules = value.split(",");
 | |
|     for (let module of logModules) {
 | |
|       let [key, value] = module.split(":");
 | |
|       Services.prefs.setIntPref(`logging.${key}`, parseInt(value, 10));
 | |
|     }
 | |
| 
 | |
|     let curLogModules = document.getElementById("cur-log-modules");
 | |
|     curLogModules.childNodes[0].nodeValue = value;
 | |
|   },
 | |
| 
 | |
|   logfile() {
 | |
|     let logFile = document.getElementById("log-file").value.trim();
 | |
|     Services.prefs.setCharPref("logging.config.LOG_FILE", logFile);
 | |
| 
 | |
|     let curLogFile = document.getElementById("cur-log-file");
 | |
|     curLogFile.childNodes[0].nodeValue = logFile;
 | |
|   }
 | |
| };
 |