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/. */
7 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
8 const FileUtils = ChromeUtils.import("resource://gre/modules/FileUtils.jsm")
10 const gEnv = Cc["@mozilla.org/process/environment;1"].getService(
13 const gDashboard = Cc["@mozilla.org/network/dashboard;1"].getService(
16 const gDirServ = Cc["@mozilla.org/file/directory_service;1"].getService(
17 Ci.nsIDirectoryServiceProvider
20 Cc["@mozilla.org/network/network-link-service;1"] &&
21 Cc["@mozilla.org/network/network-link-service;1"].getService(
22 Ci.nsINetworkLinkService
24 const gDNSService = Cc["@mozilla.org/network/dns-service;1"].getService(
28 const gRequestNetworkingData = {
29 http: gDashboard.requestHttpConnections,
30 sockets: gDashboard.requestSockets,
31 dns: gDashboard.requestDNSInfo,
32 websockets: gDashboard.requestWebsocketConnections,
33 dnslookuptool: () => {},
35 rcwn: gDashboard.requestRcwnStats,
36 networkid: displayNetworkID,
38 const gDashboardCallbacks = {
40 sockets: displaySockets,
42 websockets: displayWebsockets,
43 rcwn: displayRcwnStats,
46 const REFRESH_INTERVAL_MS = 3000;
48 function col(element) {
49 let col = document.createElement("td");
50 let content = document.createTextNode(element);
51 col.appendChild(content);
55 function displayHttp(data) {
56 let cont = document.getElementById("http_content");
57 let parent = cont.parentNode;
58 let new_cont = document.createElement("tbody");
59 new_cont.setAttribute("id", "http_content");
61 for (let i = 0; i < data.connections.length; i++) {
62 let row = document.createElement("tr");
63 row.appendChild(col(data.connections[i].host));
64 row.appendChild(col(data.connections[i].port));
65 row.appendChild(col(data.connections[i].httpVersion));
66 row.appendChild(col(data.connections[i].ssl));
67 row.appendChild(col(data.connections[i].active.length));
68 row.appendChild(col(data.connections[i].idle.length));
69 new_cont.appendChild(row);
72 parent.replaceChild(new_cont, cont);
75 function displaySockets(data) {
76 let cont = document.getElementById("sockets_content");
77 let parent = cont.parentNode;
78 let new_cont = document.createElement("tbody");
79 new_cont.setAttribute("id", "sockets_content");
81 for (let i = 0; i < data.sockets.length; i++) {
82 let row = document.createElement("tr");
83 row.appendChild(col(data.sockets[i].host));
84 row.appendChild(col(data.sockets[i].port));
85 row.appendChild(col(data.sockets[i].type));
86 row.appendChild(col(data.sockets[i].active));
87 row.appendChild(col(data.sockets[i].sent));
88 row.appendChild(col(data.sockets[i].received));
89 new_cont.appendChild(row);
92 parent.replaceChild(new_cont, cont);
95 function displayDns(data) {
96 let suffixContent = document.getElementById("dns_suffix_content");
97 let suffixParent = suffixContent.parentNode;
100 suffixes = gNetLinkSvc.dnsSuffixList; // May throw
102 let suffix_tbody = document.createElement("tbody");
103 suffix_tbody.id = "dns_suffix_content";
104 for (let suffix of suffixes) {
105 let row = document.createElement("tr");
106 row.appendChild(col(suffix));
107 suffix_tbody.appendChild(row);
109 suffixParent.replaceChild(suffix_tbody, suffixContent);
111 let trr_url_tbody = document.createElement("tbody");
112 trr_url_tbody.id = "dns_trr_url";
113 let trr_url = document.createElement("tr");
114 trr_url.appendChild(col(gDNSService.currentTrrURI));
115 trr_url_tbody.appendChild(trr_url);
116 let prevURL = document.getElementById("dns_trr_url");
117 prevURL.parentNode.replaceChild(trr_url_tbody, prevURL);
119 let cont = document.getElementById("dns_content");
120 let parent = cont.parentNode;
121 let new_cont = document.createElement("tbody");
122 new_cont.setAttribute("id", "dns_content");
124 for (let i = 0; i < data.entries.length; i++) {
125 let row = document.createElement("tr");
126 row.appendChild(col(data.entries[i].hostname));
127 row.appendChild(col(data.entries[i].family));
128 row.appendChild(col(data.entries[i].trr));
129 let column = document.createElement("td");
131 for (let j = 0; j < data.entries[i].hostaddr.length; j++) {
132 column.appendChild(document.createTextNode(data.entries[i].hostaddr[j]));
133 column.appendChild(document.createElement("br"));
136 row.appendChild(column);
137 row.appendChild(col(data.entries[i].expiration));
138 row.appendChild(col(data.entries[i].originAttributesSuffix));
139 new_cont.appendChild(row);
142 parent.replaceChild(new_cont, cont);
145 function displayWebsockets(data) {
146 let cont = document.getElementById("websockets_content");
147 let parent = cont.parentNode;
148 let new_cont = document.createElement("tbody");
149 new_cont.setAttribute("id", "websockets_content");
151 for (let i = 0; i < data.websockets.length; i++) {
152 let row = document.createElement("tr");
153 row.appendChild(col(data.websockets[i].hostport));
154 row.appendChild(col(data.websockets[i].encrypted));
155 row.appendChild(col(data.websockets[i].msgsent));
156 row.appendChild(col(data.websockets[i].msgreceived));
157 row.appendChild(col(data.websockets[i].sentsize));
158 row.appendChild(col(data.websockets[i].receivedsize));
159 new_cont.appendChild(row);
162 parent.replaceChild(new_cont, cont);
165 function displayRcwnStats(data) {
166 let status = Services.prefs.getBoolPref("network.http.rcwn.enabled");
167 let linkType = Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN;
169 linkType = gNetLinkSvc.linkType;
173 linkType == Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN ||
174 linkType == Ci.nsINetworkLinkService.LINK_TYPE_ETHERNET ||
175 linkType == Ci.nsINetworkLinkService.LINK_TYPE_USB ||
176 linkType == Ci.nsINetworkLinkService.LINK_TYPE_WIFI
182 let cacheWon = data.rcwnCacheWonCount;
183 let netWon = data.rcwnNetWonCount;
184 let total = data.totalNetworkRequests;
185 let cacheSlow = data.cacheSlowCount;
186 let cacheNotSlow = data.cacheNotSlowCount;
188 document.getElementById("rcwn_status").innerText = status;
189 document.getElementById("total_req_count").innerText = total;
190 document.getElementById("rcwn_cache_won_count").innerText = cacheWon;
191 document.getElementById("rcwn_cache_net_count").innerText = netWon;
192 document.getElementById("rcwn_cache_slow").innerText = cacheSlow;
193 document.getElementById("rcwn_cache_not_slow").innerText = cacheNotSlow;
195 // Keep in sync with CachePerfStats::EDataType in CacheFileUtils.h
196 const perfStatTypes = ["open", "read", "write", "entryopen"];
198 const perfStatFieldNames = ["avgShort", "avgLong", "stddevLong"];
200 for (let typeIndex in perfStatTypes) {
201 for (let statFieldIndex in perfStatFieldNames) {
202 document.getElementById(
204 perfStatTypes[typeIndex] +
206 perfStatFieldNames[statFieldIndex]
208 data.perfStats[typeIndex][perfStatFieldNames[statFieldIndex]];
213 function displayNetworkID() {
215 let linkIsUp = gNetLinkSvc.isLinkUp;
216 let linkStatusKnown = gNetLinkSvc.linkStatusKnown;
217 let networkID = gNetLinkSvc.networkID;
219 document.getElementById("networkid_isUp").innerText = linkIsUp;
220 document.getElementById(
221 "networkid_statusKnown"
222 ).innerText = linkStatusKnown;
223 document.getElementById("networkid_id").innerText = networkID;
225 document.getElementById("networkid_isUp").innerText = "<unknown>";
226 document.getElementById("networkid_statusKnown").innerText = "<unknown>";
227 document.getElementById("networkid_id").innerText = "<unknown>";
231 function requestAllNetworkingData() {
232 for (let id in gRequestNetworkingData) {
233 requestNetworkingDataForTab(id);
237 function requestNetworkingDataForTab(id) {
238 gRequestNetworkingData[id](gDashboardCallbacks[id]);
247 gDashboard.enableLogging = true;
249 requestAllNetworkingData();
251 let autoRefresh = document.getElementById("autorefcheck");
252 if (autoRefresh.checked) {
253 setAutoRefreshInterval(autoRefresh);
256 autoRefresh.addEventListener("click", function() {
257 let refrButton = document.getElementById("refreshButton");
259 setAutoRefreshInterval(this);
260 refrButton.disabled = "disabled";
262 clearInterval(this.interval);
263 refrButton.disabled = null;
267 let refr = document.getElementById("refreshButton");
268 refr.addEventListener("click", requestAllNetworkingData);
269 if (document.getElementById("autorefcheck").checked) {
270 refr.disabled = "disabled";
273 // Event delegation on #categories element
274 let menu = document.getElementById("categories");
275 menu.addEventListener("click", function click(e) {
276 if (e.target && e.target.parentNode == menu) {
281 let dnsLookupButton = document.getElementById("dnsLookupButton");
282 dnsLookupButton.addEventListener("click", function() {
286 let clearDNSCache = document.getElementById("clearDNSCache");
287 clearDNSCache.addEventListener("click", function() {
288 gDNSService.clearCache(true);
291 let setLogButton = document.getElementById("set-log-file-button");
292 setLogButton.addEventListener("click", setLogFile);
294 let setModulesButton = document.getElementById("set-log-modules-button");
295 setModulesButton.addEventListener("click", setLogModules);
297 let startLoggingButton = document.getElementById("start-logging-button");
298 startLoggingButton.addEventListener("click", startLogging);
300 let stopLoggingButton = document.getElementById("stop-logging-button");
301 stopLoggingButton.addEventListener("click", stopLogging);
304 let file = gDirServ.getFile("TmpD", {});
305 file.append("log.txt");
306 document.getElementById("log-file").value = file.path;
311 // Update the value of the log file.
314 // Update the active log modules
317 // If we can't set the file and the modules at runtime,
318 // the start and stop buttons wouldn't really do anything.
319 if (setLogButton.disabled && setModulesButton.disabled) {
320 startLoggingButton.disabled = true;
321 stopLoggingButton.disabled = true;
325 let sectionButton = document.getElementById(
326 "category-" + location.hash.substring(1)
329 sectionButton.click();
334 function updateLogFile() {
337 // Try to get the environment variable for the log file
338 logPath = gEnv.get("MOZ_LOG_FILE") || gEnv.get("NSPR_LOG_FILE");
339 let currentLogFile = document.getElementById("current-log-file");
340 let setLogFileButton = document.getElementById("set-log-file-button");
342 // If the log file was set from an env var, we disable the ability to set it
344 if (logPath.length) {
345 currentLogFile.innerText = logPath;
346 setLogFileButton.disabled = true;
348 // There may be a value set by a pref.
349 currentLogFile.innerText = gDashboard.getLogPath();
353 function updateLogModules() {
354 // Try to get the environment variable for the log file
356 gEnv.get("MOZ_LOG") ||
357 gEnv.get("MOZ_LOG_MODULES") ||
358 gEnv.get("NSPR_LOG_MODULES");
359 let currentLogModules = document.getElementById("current-log-modules");
360 let setLogModulesButton = document.getElementById("set-log-modules-button");
361 if (logModules.length) {
362 currentLogModules.innerText = logModules;
363 // If the log modules are set by an environment variable at startup, do not
364 // allow changing them throught a pref. It would be difficult to figure out
365 // which ones are enabled and which ones are not. The user probably knows
366 // what he they are doing.
367 setLogModulesButton.disabled = true;
369 let activeLogModules = [];
371 if (Services.prefs.getBoolPref("logging.config.add_timestamp")) {
372 activeLogModules.push("timestamp");
376 if (Services.prefs.getBoolPref("logging.config.sync")) {
377 activeLogModules.push("sync");
381 let children = Services.prefs.getBranch("logging.").getChildList("");
383 for (let pref of children) {
384 if (pref.startsWith("config.")) {
389 let value = Services.prefs.getIntPref(`logging.${pref}`);
390 activeLogModules.push(`${pref}:${value}`);
396 currentLogModules.innerText = activeLogModules.join(",");
400 function setLogFile() {
401 let setLogButton = document.getElementById("set-log-file-button");
402 if (setLogButton.disabled) {
403 // There's no point trying since it wouldn't work anyway.
406 let logFile = document.getElementById("log-file").value.trim();
407 Services.prefs.setCharPref("logging.config.LOG_FILE", logFile);
411 function clearLogModules() {
412 // Turn off all the modules.
413 let children = Services.prefs.getBranch("logging.").getChildList("");
414 for (let pref of children) {
415 if (!pref.startsWith("config.")) {
416 Services.prefs.clearUserPref(`logging.${pref}`);
419 Services.prefs.clearUserPref("logging.config.add_timestamp");
420 Services.prefs.clearUserPref("logging.config.sync");
424 function setLogModules() {
425 let setLogModulesButton = document.getElementById("set-log-modules-button");
426 if (setLogModulesButton.disabled) {
427 // The modules were set via env var, so we shouldn't try to change them.
431 let modules = document.getElementById("log-modules").value.trim();
433 // Clear previously set log modules.
436 let logModules = modules.split(",");
437 for (let module of logModules) {
438 if (module == "timestamp") {
439 Services.prefs.setBoolPref("logging.config.add_timestamp", true);
440 } else if (module == "rotate") {
441 // XXX: rotate is not yet supported.
442 } else if (module == "append") {
443 // XXX: append is not yet supported.
444 } else if (module == "sync") {
445 Services.prefs.setBoolPref("logging.config.sync", true);
447 let lastColon = module.lastIndexOf(":");
448 let key = module.slice(0, lastColon);
449 let value = parseInt(module.slice(lastColon + 1), 10);
450 Services.prefs.setIntPref(`logging.${key}`, value);
457 function startLogging() {
462 function stopLogging() {
464 // clear the log file as well
465 Services.prefs.clearUserPref("logging.config.LOG_FILE");
469 function show(button) {
470 let current_tab = document.querySelector(".active");
471 let category = button.getAttribute("id").substring("category-".length);
472 let content = document.getElementById(category);
473 if (current_tab == content) {
476 current_tab.classList.remove("active");
477 current_tab.hidden = true;
478 content.classList.add("active");
479 content.hidden = false;
481 let current_button = document.querySelector("[selected=true]");
482 current_button.removeAttribute("selected");
483 button.setAttribute("selected", "true");
485 let autoRefresh = document.getElementById("autorefcheck");
486 if (autoRefresh.checked) {
487 clearInterval(autoRefresh.interval);
488 setAutoRefreshInterval(autoRefresh);
491 let title = document.getElementById("sectionTitle");
492 title.textContent = button.children[0].textContent;
493 location.hash = category;
496 function setAutoRefreshInterval(checkBox) {
497 let active_tab = document.querySelector(".active");
498 checkBox.interval = setInterval(function() {
499 requestNetworkingDataForTab(active_tab.id);
500 }, REFRESH_INTERVAL_MS);
503 // We use the pageshow event instead of onload. This is needed because sometimes
504 // the page is loaded via session-restore/bfcache. In such cases we need to call
505 // init() to keep the page behaviour consistent with the ticked checkboxes.
506 // Mostly the issue is with the autorefresh checkbox.
507 window.addEventListener("pageshow", function() {
511 function doLookup() {
512 let host = document.getElementById("host").value;
515 gDashboard.requestDNSLookup(host, displayDNSLookup);
518 gDashboard.requestDNSHTTPSRRLookup(host, displayHTTPSRRLookup);
523 function displayDNSLookup(data) {
524 let cont = document.getElementById("dnslookuptool_content");
525 let parent = cont.parentNode;
526 let new_cont = document.createElement("tbody");
527 new_cont.setAttribute("id", "dnslookuptool_content");
530 for (let address of data.address) {
531 let row = document.createElement("tr");
532 row.appendChild(col(address));
533 new_cont.appendChild(row);
536 new_cont.appendChild(col(data.error));
539 parent.replaceChild(new_cont, cont);
542 function displayHTTPSRRLookup(data) {
543 let cont = document.getElementById("https_rr_content");
544 let parent = cont.parentNode;
545 let new_cont = document.createElement("tbody");
546 new_cont.setAttribute("id", "https_rr_content");
549 for (let record of data.records) {
550 let row = document.createElement("tr");
551 let alpn = record.alpn ? `alpn="${record.alpn.alpn}" ` : "";
552 let noDefaultAlpn = record.noDefaultAlpn ? "noDefaultAlpn " : "";
553 let port = record.port ? `port="${record.port.port}" ` : "";
554 let echConfig = record.echConfig
555 ? `echConfig="${record.echConfig.echConfig}" `
557 let ODoHConfig = record.ODoHConfig
558 ? `odoh="${record.ODoHConfig.ODoHConfig}" `
562 if (record.ipv4Hint) {
564 for (let addr of record.ipv4Hint.address) {
565 ipv4Str += `${addr}, `;
567 // Remove ", " at the end.
568 ipv4Str = ipv4Str.slice(0, -2);
569 ipv4hint = `ipv4hint="${ipv4Str}" `;
571 if (record.ipv6Hint) {
573 for (let addr of record.ipv6Hint.address) {
574 ipv6Str += `${addr}, `;
576 // Remove ", " at the end.
577 ipv6Str = ipv6Str.slice(0, -2);
578 ipv6hint = `ipv6hint="${ipv6Str}" `;
581 let str = `${record.priority} ${record.targetName} `;
582 str += `(${alpn}${noDefaultAlpn}${port}`;
583 str += `${ipv4hint}${echConfig}${ipv6hint}`;
584 str += `${ODoHConfig})`;
585 row.appendChild(col(str));
586 new_cont.appendChild(row);
589 new_cont.appendChild(col(data.error));
592 parent.replaceChild(new_cont, cont);