summaryrefslogtreecommitdiffstats
path: root/toolkit/content/aboutNetworking.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/content/aboutNetworking.js593
1 files changed, 593 insertions, 0 deletions
diff --git a/toolkit/content/aboutNetworking.js b/toolkit/content/aboutNetworking.js
new file mode 100644
index 0000000000..25ee33507a
--- /dev/null
+++ b/toolkit/content/aboutNetworking.js
@@ -0,0 +1,593 @@
+/* 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/. */
+
+"use strict";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const FileUtils = ChromeUtils.import("resource://gre/modules/FileUtils.jsm")
+ .FileUtils;
+const gEnv = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+);
+const gDashboard = Cc["@mozilla.org/network/dashboard;1"].getService(
+ Ci.nsIDashboard
+);
+const gDirServ = Cc["@mozilla.org/file/directory_service;1"].getService(
+ Ci.nsIDirectoryServiceProvider
+);
+const gNetLinkSvc =
+ Cc["@mozilla.org/network/network-link-service;1"] &&
+ Cc["@mozilla.org/network/network-link-service;1"].getService(
+ Ci.nsINetworkLinkService
+ );
+const gDNSService = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+
+const gRequestNetworkingData = {
+ http: gDashboard.requestHttpConnections,
+ sockets: gDashboard.requestSockets,
+ dns: gDashboard.requestDNSInfo,
+ websockets: gDashboard.requestWebsocketConnections,
+ dnslookuptool: () => {},
+ logging: () => {},
+ rcwn: gDashboard.requestRcwnStats,
+ networkid: displayNetworkID,
+};
+const gDashboardCallbacks = {
+ http: displayHttp,
+ sockets: displaySockets,
+ dns: displayDns,
+ websockets: displayWebsockets,
+ rcwn: displayRcwnStats,
+};
+
+const REFRESH_INTERVAL_MS = 3000;
+
+function col(element) {
+ let col = document.createElement("td");
+ let content = document.createTextNode(element);
+ col.appendChild(content);
+ return col;
+}
+
+function displayHttp(data) {
+ let cont = document.getElementById("http_content");
+ let parent = cont.parentNode;
+ let new_cont = document.createElement("tbody");
+ new_cont.setAttribute("id", "http_content");
+
+ for (let i = 0; i < data.connections.length; i++) {
+ let row = document.createElement("tr");
+ row.appendChild(col(data.connections[i].host));
+ row.appendChild(col(data.connections[i].port));
+ row.appendChild(col(data.connections[i].httpVersion));
+ row.appendChild(col(data.connections[i].ssl));
+ row.appendChild(col(data.connections[i].active.length));
+ row.appendChild(col(data.connections[i].idle.length));
+ new_cont.appendChild(row);
+ }
+
+ parent.replaceChild(new_cont, cont);
+}
+
+function displaySockets(data) {
+ let cont = document.getElementById("sockets_content");
+ let parent = cont.parentNode;
+ let new_cont = document.createElement("tbody");
+ new_cont.setAttribute("id", "sockets_content");
+
+ for (let i = 0; i < data.sockets.length; i++) {
+ let row = document.createElement("tr");
+ row.appendChild(col(data.sockets[i].host));
+ row.appendChild(col(data.sockets[i].port));
+ row.appendChild(col(data.sockets[i].tcp));
+ row.appendChild(col(data.sockets[i].active));
+ row.appendChild(col(data.sockets[i].sent));
+ row.appendChild(col(data.sockets[i].received));
+ new_cont.appendChild(row);
+ }
+
+ parent.replaceChild(new_cont, cont);
+}
+
+function displayDns(data) {
+ let suffixContent = document.getElementById("dns_suffix_content");
+ let suffixParent = suffixContent.parentNode;
+ let suffixes = [];
+ try {
+ suffixes = gNetLinkSvc.dnsSuffixList; // May throw
+ } catch (e) {}
+ let suffix_tbody = document.createElement("tbody");
+ suffix_tbody.id = "dns_suffix_content";
+ for (let suffix of suffixes) {
+ let row = document.createElement("tr");
+ row.appendChild(col(suffix));
+ suffix_tbody.appendChild(row);
+ }
+ suffixParent.replaceChild(suffix_tbody, suffixContent);
+
+ let trr_url_tbody = document.createElement("tbody");
+ trr_url_tbody.id = "dns_trr_url";
+ let trr_url = document.createElement("tr");
+ trr_url.appendChild(col(gDNSService.currentTrrURI));
+ trr_url_tbody.appendChild(trr_url);
+ let prevURL = document.getElementById("dns_trr_url");
+ prevURL.parentNode.replaceChild(trr_url_tbody, prevURL);
+
+ let cont = document.getElementById("dns_content");
+ let parent = cont.parentNode;
+ let new_cont = document.createElement("tbody");
+ new_cont.setAttribute("id", "dns_content");
+
+ for (let i = 0; i < data.entries.length; i++) {
+ let row = document.createElement("tr");
+ row.appendChild(col(data.entries[i].hostname));
+ row.appendChild(col(data.entries[i].family));
+ row.appendChild(col(data.entries[i].trr));
+ let column = document.createElement("td");
+
+ for (let j = 0; j < data.entries[i].hostaddr.length; j++) {
+ column.appendChild(document.createTextNode(data.entries[i].hostaddr[j]));
+ column.appendChild(document.createElement("br"));
+ }
+
+ row.appendChild(column);
+ row.appendChild(col(data.entries[i].expiration));
+ row.appendChild(col(data.entries[i].originAttributesSuffix));
+ new_cont.appendChild(row);
+ }
+
+ parent.replaceChild(new_cont, cont);
+}
+
+function displayWebsockets(data) {
+ let cont = document.getElementById("websockets_content");
+ let parent = cont.parentNode;
+ let new_cont = document.createElement("tbody");
+ new_cont.setAttribute("id", "websockets_content");
+
+ for (let i = 0; i < data.websockets.length; i++) {
+ let row = document.createElement("tr");
+ row.appendChild(col(data.websockets[i].hostport));
+ row.appendChild(col(data.websockets[i].encrypted));
+ row.appendChild(col(data.websockets[i].msgsent));
+ row.appendChild(col(data.websockets[i].msgreceived));
+ row.appendChild(col(data.websockets[i].sentsize));
+ row.appendChild(col(data.websockets[i].receivedsize));
+ new_cont.appendChild(row);
+ }
+
+ parent.replaceChild(new_cont, cont);
+}
+
+function displayRcwnStats(data) {
+ let status = Services.prefs.getBoolPref("network.http.rcwn.enabled");
+ let linkType = Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN;
+ try {
+ linkType = gNetLinkSvc.linkType;
+ } catch (e) {}
+ if (
+ !(
+ linkType == Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN ||
+ linkType == Ci.nsINetworkLinkService.LINK_TYPE_ETHERNET ||
+ linkType == Ci.nsINetworkLinkService.LINK_TYPE_USB ||
+ linkType == Ci.nsINetworkLinkService.LINK_TYPE_WIFI
+ )
+ ) {
+ status = false;
+ }
+
+ let cacheWon = data.rcwnCacheWonCount;
+ let netWon = data.rcwnNetWonCount;
+ let total = data.totalNetworkRequests;
+ let cacheSlow = data.cacheSlowCount;
+ let cacheNotSlow = data.cacheNotSlowCount;
+
+ document.getElementById("rcwn_status").innerText = status;
+ document.getElementById("total_req_count").innerText = total;
+ document.getElementById("rcwn_cache_won_count").innerText = cacheWon;
+ document.getElementById("rcwn_cache_net_count").innerText = netWon;
+ document.getElementById("rcwn_cache_slow").innerText = cacheSlow;
+ document.getElementById("rcwn_cache_not_slow").innerText = cacheNotSlow;
+
+ // Keep in sync with CachePerfStats::EDataType in CacheFileUtils.h
+ const perfStatTypes = ["open", "read", "write", "entryopen"];
+
+ const perfStatFieldNames = ["avgShort", "avgLong", "stddevLong"];
+
+ for (let typeIndex in perfStatTypes) {
+ for (let statFieldIndex in perfStatFieldNames) {
+ document.getElementById(
+ "rcwn_perfstats_" +
+ perfStatTypes[typeIndex] +
+ "_" +
+ perfStatFieldNames[statFieldIndex]
+ ).innerText =
+ data.perfStats[typeIndex][perfStatFieldNames[statFieldIndex]];
+ }
+ }
+}
+
+function displayNetworkID() {
+ try {
+ let linkIsUp = gNetLinkSvc.isLinkUp;
+ let linkStatusKnown = gNetLinkSvc.linkStatusKnown;
+ let networkID = gNetLinkSvc.networkID;
+
+ document.getElementById("networkid_isUp").innerText = linkIsUp;
+ document.getElementById(
+ "networkid_statusKnown"
+ ).innerText = linkStatusKnown;
+ document.getElementById("networkid_id").innerText = networkID;
+ } catch (e) {
+ document.getElementById("networkid_isUp").innerText = "<unknown>";
+ document.getElementById("networkid_statusKnown").innerText = "<unknown>";
+ document.getElementById("networkid_id").innerText = "<unknown>";
+ }
+}
+
+function requestAllNetworkingData() {
+ for (let id in gRequestNetworkingData) {
+ requestNetworkingDataForTab(id);
+ }
+}
+
+function requestNetworkingDataForTab(id) {
+ gRequestNetworkingData[id](gDashboardCallbacks[id]);
+}
+
+let gInited = false;
+function init() {
+ if (gInited) {
+ return;
+ }
+ gInited = true;
+ gDashboard.enableLogging = true;
+
+ requestAllNetworkingData();
+
+ let autoRefresh = document.getElementById("autorefcheck");
+ if (autoRefresh.checked) {
+ setAutoRefreshInterval(autoRefresh);
+ }
+
+ autoRefresh.addEventListener("click", function() {
+ let refrButton = document.getElementById("refreshButton");
+ if (this.checked) {
+ setAutoRefreshInterval(this);
+ refrButton.disabled = "disabled";
+ } else {
+ clearInterval(this.interval);
+ refrButton.disabled = null;
+ }
+ });
+
+ let refr = document.getElementById("refreshButton");
+ refr.addEventListener("click", requestAllNetworkingData);
+ if (document.getElementById("autorefcheck").checked) {
+ refr.disabled = "disabled";
+ }
+
+ // Event delegation on #categories element
+ let menu = document.getElementById("categories");
+ menu.addEventListener("click", function click(e) {
+ if (e.target && e.target.parentNode == menu) {
+ show(e.target);
+ }
+ });
+
+ let dnsLookupButton = document.getElementById("dnsLookupButton");
+ dnsLookupButton.addEventListener("click", function() {
+ doLookup();
+ });
+
+ let clearDNSCache = document.getElementById("clearDNSCache");
+ clearDNSCache.addEventListener("click", function() {
+ gDNSService.clearCache(true);
+ });
+
+ let setLogButton = document.getElementById("set-log-file-button");
+ setLogButton.addEventListener("click", setLogFile);
+
+ let setModulesButton = document.getElementById("set-log-modules-button");
+ setModulesButton.addEventListener("click", setLogModules);
+
+ let startLoggingButton = document.getElementById("start-logging-button");
+ startLoggingButton.addEventListener("click", startLogging);
+
+ let stopLoggingButton = document.getElementById("stop-logging-button");
+ stopLoggingButton.addEventListener("click", stopLogging);
+
+ try {
+ let file = gDirServ.getFile("TmpD", {});
+ file.append("log.txt");
+ document.getElementById("log-file").value = file.path;
+ } catch (e) {
+ console.error(e);
+ }
+
+ // Update the value of the log file.
+ updateLogFile();
+
+ // Update the active log modules
+ updateLogModules();
+
+ // If we can't set the file and the modules at runtime,
+ // the start and stop buttons wouldn't really do anything.
+ if (setLogButton.disabled && setModulesButton.disabled) {
+ startLoggingButton.disabled = true;
+ stopLoggingButton.disabled = true;
+ }
+
+ if (location.hash) {
+ let sectionButton = document.getElementById(
+ "category-" + location.hash.substring(1)
+ );
+ if (sectionButton) {
+ sectionButton.click();
+ }
+ }
+}
+
+function updateLogFile() {
+ let logPath = "";
+
+ // Try to get the environment variable for the log file
+ logPath = gEnv.get("MOZ_LOG_FILE") || gEnv.get("NSPR_LOG_FILE");
+ let currentLogFile = document.getElementById("current-log-file");
+ let setLogFileButton = document.getElementById("set-log-file-button");
+
+ // If the log file was set from an env var, we disable the ability to set it
+ // at runtime.
+ if (logPath.length) {
+ currentLogFile.innerText = logPath;
+ setLogFileButton.disabled = true;
+ } else {
+ // There may be a value set by a pref.
+ currentLogFile.innerText = gDashboard.getLogPath();
+ }
+}
+
+function updateLogModules() {
+ // Try to get the environment variable for the log file
+ let logModules =
+ gEnv.get("MOZ_LOG") ||
+ gEnv.get("MOZ_LOG_MODULES") ||
+ gEnv.get("NSPR_LOG_MODULES");
+ let currentLogModules = document.getElementById("current-log-modules");
+ let setLogModulesButton = document.getElementById("set-log-modules-button");
+ if (logModules.length) {
+ currentLogModules.innerText = logModules;
+ // If the log modules are set by an environment variable at startup, do not
+ // allow changing them throught a pref. It would be difficult to figure out
+ // which ones are enabled and which ones are not. The user probably knows
+ // what he they are doing.
+ setLogModulesButton.disabled = true;
+ } else {
+ let activeLogModules = [];
+ try {
+ if (Services.prefs.getBoolPref("logging.config.add_timestamp")) {
+ activeLogModules.push("timestamp");
+ }
+ } catch (e) {}
+ try {
+ if (Services.prefs.getBoolPref("logging.config.sync")) {
+ activeLogModules.push("sync");
+ }
+ } catch (e) {}
+
+ let children = Services.prefs.getBranch("logging.").getChildList("");
+
+ for (let pref of children) {
+ if (pref.startsWith("config.")) {
+ continue;
+ }
+
+ try {
+ let value = Services.prefs.getIntPref(`logging.${pref}`);
+ activeLogModules.push(`${pref}:${value}`);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ currentLogModules.innerText = activeLogModules.join(",");
+ }
+}
+
+function setLogFile() {
+ let setLogButton = document.getElementById("set-log-file-button");
+ if (setLogButton.disabled) {
+ // There's no point trying since it wouldn't work anyway.
+ return;
+ }
+ let logFile = document.getElementById("log-file").value.trim();
+ Services.prefs.setCharPref("logging.config.LOG_FILE", logFile);
+ updateLogFile();
+}
+
+function clearLogModules() {
+ // Turn off all the modules.
+ let children = Services.prefs.getBranch("logging.").getChildList("");
+ for (let pref of children) {
+ if (!pref.startsWith("config.")) {
+ Services.prefs.clearUserPref(`logging.${pref}`);
+ }
+ }
+ Services.prefs.clearUserPref("logging.config.add_timestamp");
+ Services.prefs.clearUserPref("logging.config.sync");
+ updateLogModules();
+}
+
+function setLogModules() {
+ let setLogModulesButton = document.getElementById("set-log-modules-button");
+ if (setLogModulesButton.disabled) {
+ // The modules were set via env var, so we shouldn't try to change them.
+ return;
+ }
+
+ let modules = document.getElementById("log-modules").value.trim();
+
+ // Clear previously set log modules.
+ clearLogModules();
+
+ let logModules = modules.split(",");
+ for (let module of logModules) {
+ if (module == "timestamp") {
+ Services.prefs.setBoolPref("logging.config.add_timestamp", true);
+ } else if (module == "rotate") {
+ // XXX: rotate is not yet supported.
+ } else if (module == "append") {
+ // XXX: append is not yet supported.
+ } else if (module == "sync") {
+ Services.prefs.setBoolPref("logging.config.sync", true);
+ } else {
+ let lastColon = module.lastIndexOf(":");
+ let key = module.slice(0, lastColon);
+ let value = parseInt(module.slice(lastColon + 1), 10);
+ Services.prefs.setIntPref(`logging.${key}`, value);
+ }
+ }
+
+ updateLogModules();
+}
+
+function startLogging() {
+ setLogFile();
+ setLogModules();
+}
+
+function stopLogging() {
+ clearLogModules();
+ // clear the log file as well
+ Services.prefs.clearUserPref("logging.config.LOG_FILE");
+ updateLogFile();
+}
+
+function show(button) {
+ let current_tab = document.querySelector(".active");
+ let category = button.getAttribute("id").substring("category-".length);
+ let content = document.getElementById(category);
+ if (current_tab == content) {
+ return;
+ }
+ current_tab.classList.remove("active");
+ current_tab.hidden = true;
+ content.classList.add("active");
+ content.hidden = false;
+
+ let current_button = document.querySelector("[selected=true]");
+ current_button.removeAttribute("selected");
+ button.setAttribute("selected", "true");
+
+ let autoRefresh = document.getElementById("autorefcheck");
+ if (autoRefresh.checked) {
+ clearInterval(autoRefresh.interval);
+ setAutoRefreshInterval(autoRefresh);
+ }
+
+ let title = document.getElementById("sectionTitle");
+ title.textContent = button.children[0].textContent;
+ location.hash = category;
+}
+
+function setAutoRefreshInterval(checkBox) {
+ let active_tab = document.querySelector(".active");
+ checkBox.interval = setInterval(function() {
+ requestNetworkingDataForTab(active_tab.id);
+ }, REFRESH_INTERVAL_MS);
+}
+
+// We use the pageshow event instead of onload. This is needed because sometimes
+// the page is loaded via session-restore/bfcache. In such cases we need to call
+// init() to keep the page behaviour consistent with the ticked checkboxes.
+// Mostly the issue is with the autorefresh checkbox.
+window.addEventListener("pageshow", function() {
+ init();
+});
+
+function doLookup() {
+ let host = document.getElementById("host").value;
+ if (host) {
+ try {
+ gDashboard.requestDNSLookup(host, displayDNSLookup);
+ } catch (e) {}
+ try {
+ gDashboard.requestDNSHTTPSRRLookup(host, displayHTTPSRRLookup);
+ } catch (e) {}
+ }
+}
+
+function displayDNSLookup(data) {
+ let cont = document.getElementById("dnslookuptool_content");
+ let parent = cont.parentNode;
+ let new_cont = document.createElement("tbody");
+ new_cont.setAttribute("id", "dnslookuptool_content");
+
+ if (data.answer) {
+ for (let address of data.address) {
+ let row = document.createElement("tr");
+ row.appendChild(col(address));
+ new_cont.appendChild(row);
+ }
+ } else {
+ new_cont.appendChild(col(data.error));
+ }
+
+ parent.replaceChild(new_cont, cont);
+}
+
+function displayHTTPSRRLookup(data) {
+ let cont = document.getElementById("https_rr_content");
+ let parent = cont.parentNode;
+ let new_cont = document.createElement("tbody");
+ new_cont.setAttribute("id", "https_rr_content");
+
+ if (data.answer) {
+ for (let record of data.records) {
+ let row = document.createElement("tr");
+ let alpn = record.alpn ? `alpn="${record.alpn.alpn}" ` : "";
+ let noDefaultAlpn = record.noDefaultAlpn ? "noDefaultAlpn " : "";
+ let port = record.port ? `port="${record.port.port}" ` : "";
+ let echConfig = record.echConfig
+ ? `echConfig="${record.echConfig.echConfig}" `
+ : "";
+ let ODoHConfig = record.ODoHConfig
+ ? `ODoHConfig="${record.ODoHConfig.ODoHConfig}" `
+ : "";
+ let ipv4hint = "";
+ let ipv6hint = "";
+ if (record.ipv4Hint) {
+ let ipv4Str = "";
+ for (let addr of record.ipv4Hint.address) {
+ ipv4Str += `${addr}, `;
+ }
+ // Remove ", " at the end.
+ ipv4Str = ipv4Str.slice(0, -2);
+ ipv4hint = `ipv4hint="${ipv4Str}" `;
+ }
+ if (record.ipv6Hint) {
+ let ipv6Str = "";
+ for (let addr of record.ipv6Hint.address) {
+ ipv6Str += `${addr}, `;
+ }
+ // Remove ", " at the end.
+ ipv6Str = ipv6Str.slice(0, -2);
+ ipv6hint = `ipv6hint="${ipv6Str}" `;
+ }
+
+ let str = `${record.priority} ${record.targetName} `;
+ str += `(${alpn}${noDefaultAlpn}${port}`;
+ str += `${ipv4hint}${echConfig}${ipv6hint}`;
+ str += `${ODoHConfig})`;
+ row.appendChild(col(str));
+ new_cont.appendChild(row);
+ }
+ } else {
+ new_cont.appendChild(col(data.error));
+ }
+
+ parent.replaceChild(new_cont, cont);
+}