summaryrefslogtreecommitdiffstats
path: root/toolkit/content/aboutUrlClassifier.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/aboutUrlClassifier.js')
-rw-r--r--toolkit/content/aboutUrlClassifier.js719
1 files changed, 719 insertions, 0 deletions
diff --git a/toolkit/content/aboutUrlClassifier.js b/toolkit/content/aboutUrlClassifier.js
new file mode 100644
index 0000000000..28a2381593
--- /dev/null
+++ b/toolkit/content/aboutUrlClassifier.js
@@ -0,0 +1,719 @@
+/* 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/. */
+
+const UPDATE_BEGIN = "safebrowsing-update-begin";
+const UPDATE_FINISH = "safebrowsing-update-finished";
+const JSLOG_PREF = "browser.safebrowsing.debug";
+
+window.onunload = function() {
+ Search.uninit();
+ Provider.uninit();
+ Cache.uninit();
+ Debug.uninit();
+};
+
+window.onload = function() {
+ Search.init();
+ Provider.init();
+ Cache.init();
+ Debug.init();
+};
+
+/*
+ * Search
+ */
+var Search = {
+ init() {
+ let classifier = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
+ Ci.nsIURIClassifier
+ );
+ let featureNames = classifier.getFeatureNames();
+
+ let fragment = document.createDocumentFragment();
+ featureNames.forEach(featureName => {
+ let container = document.createElement("label");
+ container.className = "toggle-container-with-text";
+ fragment.appendChild(container);
+
+ let checkbox = document.createElement("input");
+ checkbox.id = "feature_" + featureName;
+ checkbox.type = "checkbox";
+ checkbox.checked = true;
+ container.appendChild(checkbox);
+
+ let span = document.createElement("span");
+ container.appendChild(span);
+
+ let text = document.createTextNode(featureName);
+ span.appendChild(text);
+ });
+
+ let list = document.getElementById("search-features");
+ list.appendChild(fragment);
+
+ let btn = document.getElementById("search-button");
+ btn.addEventListener("click", this.search);
+
+ this.hideError();
+ this.hideResults();
+ },
+
+ uninit() {
+ let list = document.getElementById("search-features");
+ while (list.firstChild) {
+ list.firstChild.remove();
+ }
+
+ let btn = document.getElementById("search-button");
+ btn.removeEventListener("click", this.search);
+ },
+
+ search() {
+ Search.hideError();
+ Search.hideResults();
+
+ let input = document.getElementById("search-input").value;
+
+ let uri;
+ try {
+ uri = Services.io.newURI(input);
+ if (!uri) {
+ Search.reportError("url-classifier-search-error-invalid-url");
+ return;
+ }
+ } catch (ex) {
+ Search.reportError("url-classifier-search-error-invalid-url");
+ return;
+ }
+
+ let classifier = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
+ Ci.nsIURIClassifier
+ );
+
+ let featureNames = classifier.getFeatureNames();
+ let features = [];
+ featureNames.forEach(featureName => {
+ if (document.getElementById("feature_" + featureName).checked) {
+ let feature = classifier.getFeatureByName(featureName);
+ if (feature) {
+ features.push(feature);
+ }
+ }
+ });
+
+ if (!features.length) {
+ Search.reportError("url-classifier-search-error-no-features");
+ return;
+ }
+
+ let listType =
+ document.getElementById("search-listtype").value == 0
+ ? Ci.nsIUrlClassifierFeature.blocklist
+ : Ci.nsIUrlClassifierFeature.entitylist;
+ classifier.asyncClassifyLocalWithFeatures(uri, features, listType, list =>
+ Search.showResults(list)
+ );
+
+ Search.hideError();
+ },
+
+ hideError() {
+ let errorMessage = document.getElementById("search-error-message");
+ errorMessage.style.display = "none";
+ },
+
+ reportError(msg) {
+ let errorMessage = document.getElementById("search-error-message");
+ document.l10n.setAttributes(errorMessage, msg);
+ errorMessage.style.display = "";
+ },
+
+ hideResults() {
+ let resultTitle = document.getElementById("result-title");
+ resultTitle.style.display = "none";
+
+ let resultTable = document.getElementById("result-table");
+ resultTable.style.display = "none";
+ },
+
+ showResults(results) {
+ let fragment = document.createDocumentFragment();
+ results.forEach(result => {
+ let tr = document.createElement("tr");
+ fragment.appendChild(tr);
+
+ let th = document.createElement("th");
+ tr.appendChild(th);
+ th.appendChild(document.createTextNode(result.feature.name));
+
+ let td = document.createElement("td");
+ tr.appendChild(td);
+
+ let featureName = document.createElement("div");
+ document.l10n.setAttributes(
+ featureName,
+ "url-classifier-search-result-uri",
+ { uri: result.uri.spec }
+ );
+ td.appendChild(featureName);
+
+ let list = document.createElement("div");
+ document.l10n.setAttributes(list, "url-classifier-search-result-list", {
+ list: result.list,
+ });
+ td.appendChild(list);
+ });
+
+ let resultTable = document.getElementById("result-table");
+ while (resultTable.firstChild) {
+ resultTable.firstChild.remove();
+ }
+
+ resultTable.appendChild(fragment);
+ resultTable.style.display = "";
+
+ let resultTitle = document.getElementById("result-title");
+ resultTitle.style.display = "";
+ },
+};
+
+/*
+ * 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) {
+ let provider = child.split(".")[0];
+ if (this.isActiveProvider(provider)) {
+ this.providers.add(provider);
+ }
+ }
+
+ 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");
+ document.l10n.setAttributes(elem, "url-classifier-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")) {
+ document.l10n.setAttributes(elem, "url-classifier-success");
+ } else if (aData.startsWith("update error")) {
+ document.l10n.setAttributes(elem, "url-classifier-update-error", {
+ error: aData.split(": ")[1],
+ });
+ } else if (aData.startsWith("download error")) {
+ document.l10n.setAttributes(elem, "url-classifier-download-error", {
+ error: aData.split(": ")[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);
+ });
+
+ document.l10n.setAttributes(btn, "url-classifier-trigger-update");
+ td.appendChild(btn);
+ } else if (column.id === "col-lastupdateresult") {
+ document.l10n.setAttributes(td, "url-classifier-not-available");
+ } else {
+ td.appendChild(document.createTextNode(""));
+ }
+ 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) : null;
+
+ pref = "browser.safebrowsing.provider." + provider + ".nextupdatetime";
+ let nut = Services.prefs.getCharPref(pref, "");
+ values["col-nextupdatetime"] = nut ? new Date(nut * 1) : null;
+
+ 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) : null;
+
+ for (let key of Object.keys(values)) {
+ let elem = document.getElementById(provider + "-" + key);
+ if (values[key]) {
+ elem.removeAttribute("data-l10n-id");
+ elem.childNodes[0].nodeValue = values[key];
+ } else {
+ document.l10n.setAttributes(elem, "url-classifier-not-available");
+ }
+ }
+ }
+ },
+
+ // 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, "");
+
+ if (!listmanager.forceUpdates(tables)) {
+ // This may because of back-off algorithm.
+ let elem = document.getElementById(provider + "-col-lastupdateresult");
+ document.l10n.setAttributes(elem, "url-classifier-cannot-update");
+ }
+ },
+
+ // if we can find any table registered an updateURL in the listmanager,
+ // the provider is active. This is used to filter out google v2 provider
+ // without changing the preference.
+ isActiveProvider(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(",");
+
+ for (let i = 0; i < tables.length; i++) {
+ let updateUrl = listmanager.getUpdateUrl(tables[i]);
+ if (updateUrl) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+};
+
+/*
+ * 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);
+ }
+
+ if (typeof v === "object") {
+ if (v.l10n) {
+ document.l10n.setAttributes(td, v.l10n);
+ } else {
+ td.removeAttribute("data-l10n-id");
+ td.appendChild(v);
+ }
+ } else {
+ td.removeAttribute("data-l10n-id");
+ td.textContent = v;
+ }
+
+ 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([
+ { l10n: "url-classifier-not-available" },
+ { l10n: "url-classifier-not-available" },
+ ]);
+ }
+ 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",
+ "UrlClassifier",
+ "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("label");
+ 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 span = document.createElement("span");
+ span.appendChild(document.createTextNode(module));
+ container.appendChild(span, 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 logModules =
+ Services.env.get("MOZ_LOG") ||
+ Services.env.get("MOZ_LOG_MODULES") ||
+ Services.env.get("NSPR_LOG_MODULES");
+
+ if (logModules.length) {
+ 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 =
+ Services.env.get("MOZ_LOG_FILE") || Services.env.get("NSPR_LOG_FILE");
+ if (logFile.length) {
+ 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");
+ if (enabled) {
+ document.l10n.setAttributes(curJSLog, "url-classifier-enabled");
+ } else {
+ document.l10n.setAttributes(curJSLog, "url-classifier-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;
+ },
+};