forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			780 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			780 lines
		
	
	
	
		
			24 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/. */
 | |
| 
 | |
| "use strict";
 | |
| const { XPCOMUtils } = ChromeUtils.importESModule(
 | |
|   "resource://gre/modules/XPCOMUtils.sys.mjs"
 | |
| );
 | |
| const gDashboard = Cc["@mozilla.org/network/dashboard;1"].getService(
 | |
|   Ci.nsIDashboard
 | |
| );
 | |
| const gDirServ = Cc["@mozilla.org/file/directory_service;1"].getService(
 | |
|   Ci.nsIDirectoryServiceProvider
 | |
| );
 | |
| 
 | |
| const { ProfilerMenuButton } = ChromeUtils.importESModule(
 | |
|   "resource://devtools/client/performance-new/popup/menu-button.sys.mjs"
 | |
| );
 | |
| const { CustomizableUI } = ChromeUtils.importESModule(
 | |
|   "resource:///modules/CustomizableUI.sys.mjs"
 | |
| );
 | |
| 
 | |
| ChromeUtils.defineLazyGetter(this, "ProfilerPopupBackground", function () {
 | |
|   return ChromeUtils.importESModule(
 | |
|     "resource://devtools/client/performance-new/shared/background.sys.mjs"
 | |
|   );
 | |
| });
 | |
| 
 | |
| const $ = document.querySelector.bind(document);
 | |
| const $$ = document.querySelectorAll.bind(document);
 | |
| 
 | |
| function fileEnvVarPresent() {
 | |
|   return Services.env.get("MOZ_LOG_FILE") || Services.env.get("NSPR_LOG_FILE");
 | |
| }
 | |
| 
 | |
| function moduleEnvVarPresent() {
 | |
|   return Services.env.get("MOZ_LOG") || Services.env.get("NSPR_LOG");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * All the information associated with a logging presets:
 | |
|  * - `modules` is the list of log modules and option, the same that would have
 | |
|  *   been set as a MOZ_LOG environment variable
 | |
|  * - l10nIds.label and l10nIds.description are the Ids of the strings that
 | |
|  *   appear in the dropdown selector, and a one-liner describing the purpose of
 | |
|  *   a particular logging preset
 | |
|  * - profilerPreset is the name of a Firefox Profiler preset [1]. In general,
 | |
|  *   the profiler preset will have the correct set of threads for a particular
 | |
|  *   logging preset, so that all logging statements are recorded in the profile
 | |
|  *   as markers.
 | |
|  *
 | |
|  * [1]: The keys of the `presets` object defined in
 | |
|  * https://searchfox.org/mozilla-central/source/devtools/client/performance-new/shared/background.sys.mjs
 | |
|  */
 | |
| 
 | |
| const gOsSpecificLoggingPresets = (() => {
 | |
|   // Microsoft Windows
 | |
|   if (navigator.platform.startsWith("Win")) {
 | |
|     return {
 | |
|       windows: {
 | |
|         modules:
 | |
|           "timestamp,sync,Widget:5,BaseWidget:5,WindowsEvent:4,TaskbarConcealer:5,FileDialog:5",
 | |
|         l10nIds: {
 | |
|           label: "about-logging-preset-windows-label",
 | |
|           description: "about-logging-preset-windows-description",
 | |
|         },
 | |
|       },
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   return {};
 | |
| })();
 | |
| 
 | |
| const gLoggingPresets = {
 | |
|   networking: {
 | |
|     modules:
 | |
|       "timestamp,sync,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5,EarlyHint:5",
 | |
|     l10nIds: {
 | |
|       label: "about-logging-preset-networking-label",
 | |
|       description: "about-logging-preset-networking-description",
 | |
|     },
 | |
|     profilerPreset: "networking",
 | |
|   },
 | |
|   cookie: {
 | |
|     modules: "timestamp,sync,nsHttp:5,cache2:5,cookie:5",
 | |
|     l10nIds: {
 | |
|       label: "about-logging-preset-networking-cookie-label",
 | |
|       description: "about-logging-preset-networking-cookie-description",
 | |
|     },
 | |
|   },
 | |
|   websocket: {
 | |
|     modules:
 | |
|       "timestamp,sync,nsHttp:5,nsWebSocket:5,nsSocketTransport:5,nsHostResolver:5",
 | |
|     l10nIds: {
 | |
|       label: "about-logging-preset-networking-websocket-label",
 | |
|       description: "about-logging-preset-networking-websocket-description",
 | |
|     },
 | |
|   },
 | |
|   http3: {
 | |
|     modules:
 | |
|       "timestamp,sync,nsHttp:5,nsSocketTransport:5,nsHostResolver:5,neqo_http3::*:5,neqo_transport::*:5",
 | |
|     l10nIds: {
 | |
|       label: "about-logging-preset-networking-http3-label",
 | |
|       description: "about-logging-preset-networking-http3-description",
 | |
|     },
 | |
|   },
 | |
|   "http3-upload-speed": {
 | |
|     modules: "timestamp,neqo_transport::*:3",
 | |
|     l10nIds: {
 | |
|       label: "about-logging-preset-networking-http3-upload-speed-label",
 | |
|       description:
 | |
|         "about-logging-preset-networking-http3-upload-speed-description",
 | |
|     },
 | |
|   },
 | |
|   "media-playback": {
 | |
|     modules:
 | |
|       "HTMLMediaElement:4,HTMLMediaElementEvents:4,cubeb:5,PlatformDecoderModule:5,AudioSink:5,AudioSinkWrapper:5,MediaDecoderStateMachine:4,MediaDecoder:4,MediaFormatReader:5,GMP:5,EME:5,MediaSource:4",
 | |
|     l10nIds: {
 | |
|       label: "about-logging-preset-media-playback-label",
 | |
|       description: "about-logging-preset-media-playback-description",
 | |
|     },
 | |
|     profilerPreset: "media",
 | |
|   },
 | |
|   webrtc: {
 | |
|     modules:
 | |
|       "jsep:5,sdp:5,signaling:5,mtransport:5,RTCRtpReceiver:5,RTCRtpSender:5,RTCDMTFSender:5,VideoFrameConverter:5,WebrtcTCPSocket:5,CamerasChild:5,CamerasParent:5,VideoEngine:5,ShmemPool:5,TabShare:5,MediaChild:5,MediaParent:5,MediaManager:5,MediaTrackGraph:5,cubeb:5,MediaStream:5,MediaStreamTrack:5,DriftCompensator:5,ForwardInputTrack:5,MediaRecorder:5,MediaEncoder:5,TrackEncoder:5,VP8TrackEncoder:5,Muxer:5,GetUserMedia:5,MediaPipeline:5,PeerConnectionImpl:5,WebAudioAPI:5,webrtc_trace:5,RTCRtpTransceiver:5,ForwardedInputTrack:5,HTMLMediaElement:5,HTMLMediaElementEvents:5",
 | |
|     l10nIds: {
 | |
|       label: "about-logging-preset-webrtc-label",
 | |
|       description: "about-logging-preset-webrtc-description",
 | |
|     },
 | |
|     profilerPreset: "media",
 | |
|   },
 | |
|   webgpu: {
 | |
|     modules:
 | |
|       "wgpu_core::*:5,wgpu_hal::*:5,wgpu_types::*:5,naga::*:5,wgpu_bindings::*:5,WebGPU:5",
 | |
|     l10nIds: {
 | |
|       label: "about-logging-preset-webgpu-label",
 | |
|       description: "about-logging-preset-webgpu-description",
 | |
|     },
 | |
|   },
 | |
|   gfx: {
 | |
|     modules:
 | |
|       "webrender::*:5,webrender_bindings::*:5,webrender_types::*:5,gfx2d:5,WebRenderBridgeParent:5,DcompSurface:5,apz.displayport:5,layout:5,dl.content:5,dl.parent:5,nsRefreshDriver:5,fontlist:5,fontinit:5,textrun:5,textrunui:5,textperf:5",
 | |
|     l10nIds: {
 | |
|       label: "about-logging-preset-gfx-label",
 | |
|       description: "about-logging-preset-gfx-description",
 | |
|     },
 | |
|     // The graphics profiler preset enables the threads we want but loses the screenshots.
 | |
|     // We could add an extra preset for that if we miss it.
 | |
|     profilerPreset: "graphics",
 | |
|   },
 | |
|   ...gOsSpecificLoggingPresets,
 | |
|   custom: {
 | |
|     modules: "",
 | |
|     l10nIds: {
 | |
|       label: "about-logging-preset-custom-label",
 | |
|       description: "about-logging-preset-custom-description",
 | |
|     },
 | |
|   },
 | |
| };
 | |
| 
 | |
| const gLoggingSettings = {
 | |
|   // Possible values: "profiler" and "file".
 | |
|   loggingOutputType: "profiler",
 | |
|   running: false,
 | |
|   // If non-null, the profiler preset to use. If null, the preset selected in
 | |
|   // the dropdown is going to be used. It is also possible to use a "custom"
 | |
|   // preset and an explicit list of modules.
 | |
|   loggingPreset: null,
 | |
|   // If non-null, the profiler preset to use. If a logging preset is being used,
 | |
|   // and this is null, the profiler preset associated to the logging preset is
 | |
|   // going to be used. Otherwise, a generic profiler preset is going to be used
 | |
|   // ("firefox-platform").
 | |
|   profilerPreset: null,
 | |
|   // If non-null, the threads that will be recorded by the Firefox Profiler. If
 | |
|   // null, the threads from the profiler presets are going to be used.
 | |
|   profilerThreads: null,
 | |
|   // If non-null, stack traces will be recorded for MOZ_LOG profiler markers.
 | |
|   // This is set only when coming from the URL, not when the user changes the UI.
 | |
|   profilerStacks: null,
 | |
| };
 | |
| 
 | |
| // When the profiler has been started, this holds the promise the
 | |
| // Services.profiler.StartProfiler returns, to ensure the profiler has
 | |
| // effectively started.
 | |
| let gProfilerPromise = null;
 | |
| 
 | |
| // Used in tests
 | |
| function presets() {
 | |
|   return gLoggingPresets;
 | |
| }
 | |
| 
 | |
| // Used in tests
 | |
| function settings() {
 | |
|   return gLoggingSettings;
 | |
| }
 | |
| 
 | |
| // Used in tests
 | |
| function profilerPromise() {
 | |
|   return gProfilerPromise;
 | |
| }
 | |
| 
 | |
| function populatePresets() {
 | |
|   let dropdown = $("#logging-preset-dropdown");
 | |
|   for (let presetName in gLoggingPresets) {
 | |
|     let preset = gLoggingPresets[presetName];
 | |
|     let option = document.createElement("option");
 | |
|     document.l10n.setAttributes(option, preset.l10nIds.label);
 | |
|     option.value = presetName;
 | |
|     dropdown.appendChild(option);
 | |
|     if (option.value === gLoggingSettings.loggingPreset) {
 | |
|       option.setAttribute("selected", true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function setPresetAndDescription(preset) {
 | |
|     document.l10n.setAttributes(
 | |
|       $("#logging-preset-description"),
 | |
|       gLoggingPresets[preset].l10nIds.description
 | |
|     );
 | |
|     gLoggingSettings.loggingPreset = preset;
 | |
|   }
 | |
| 
 | |
|   dropdown.onchange = function () {
 | |
|     // When switching to custom, leave the existing module list, to allow
 | |
|     // editing.
 | |
|     if (dropdown.value != "custom") {
 | |
|       $("#log-modules").value = gLoggingPresets[dropdown.value].modules;
 | |
|     }
 | |
|     setPresetAndDescription(dropdown.value);
 | |
|     Services.prefs.setCharPref("logging.config.preset", dropdown.value);
 | |
|   };
 | |
| 
 | |
|   $("#log-modules").value = gLoggingPresets[dropdown.value].modules;
 | |
|   setPresetAndDescription(dropdown.value);
 | |
|   // When changing the list switch to custom.
 | |
|   $("#log-modules").oninput = () => {
 | |
|     dropdown.value = "custom";
 | |
|   };
 | |
| }
 | |
| 
 | |
| function updateLoggingOutputType(profilerOutputType) {
 | |
|   gLoggingSettings.loggingOutputType = profilerOutputType;
 | |
|   Services.prefs.setCharPref("logging.config.output_type", profilerOutputType);
 | |
|   $(`input[type=radio][value=${profilerOutputType}]`).checked = true;
 | |
| 
 | |
|   switch (profilerOutputType) {
 | |
|     case "profiler":
 | |
|       if (!gLoggingSettings.profilerStacks) {
 | |
|         // If this value is set from the URL, do not allow to change it.
 | |
|         $("#with-profiler-stacks-checkbox").disabled = false;
 | |
|       }
 | |
|       // hide options related to file output for clarity
 | |
|       $("#log-file-configuration").hidden = true;
 | |
|       break;
 | |
|     case "file":
 | |
|       $("#with-profiler-stacks-checkbox").disabled = true;
 | |
|       $("#log-file-configuration").hidden = false;
 | |
|       $("#no-log-file").hidden = !!$("#current-log-file").innerText.length;
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function displayErrorMessage(error) {
 | |
|   var err = $("#error");
 | |
|   err.hidden = false;
 | |
| 
 | |
|   var errorDescription = $("#error-description");
 | |
|   document.l10n.setAttributes(errorDescription, error.l10nId, {
 | |
|     k: error.key,
 | |
|     v: error.value,
 | |
|   });
 | |
| }
 | |
| 
 | |
| class ParseError extends Error {
 | |
|   constructor(l10nId, key, value) {
 | |
|     super(name);
 | |
|     this.l10nId = l10nId;
 | |
|     this.key = key;
 | |
|     this.value = value;
 | |
|   }
 | |
|   name = "ParseError";
 | |
|   l10nId;
 | |
|   key;
 | |
|   value;
 | |
| }
 | |
| 
 | |
| function parseURL() {
 | |
|   let options = new URL(document.location.href).searchParams;
 | |
| 
 | |
|   if (!options) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   let modulesOverriden = null,
 | |
|     outputTypeOverriden = null,
 | |
|     loggingPresetOverriden = null,
 | |
|     threadsOverriden = null,
 | |
|     profilerPresetOverriden = null,
 | |
|     profilerStacksOverriden = null;
 | |
|   try {
 | |
|     for (let [k, v] of options) {
 | |
|       switch (k) {
 | |
|         case "modules":
 | |
|         case "module":
 | |
|           modulesOverriden = v;
 | |
|           break;
 | |
|         case "output":
 | |
|         case "output-type":
 | |
|           if (v !== "profiler" && v !== "file") {
 | |
|             throw new ParseError("about-logging-invalid-output", k, v);
 | |
|           }
 | |
|           outputTypeOverriden = v;
 | |
|           break;
 | |
|         case "preset":
 | |
|         case "logging-preset":
 | |
|           if (!Object.keys(gLoggingPresets).includes(v)) {
 | |
|             throw new ParseError("about-logging-unknown-logging-preset", k, v);
 | |
|           }
 | |
|           loggingPresetOverriden = v;
 | |
|           break;
 | |
|         case "threads":
 | |
|         case "thread":
 | |
|           threadsOverriden = v;
 | |
|           break;
 | |
|         case "profiler-preset":
 | |
|           if (!Object.keys(ProfilerPopupBackground.presets).includes(v)) {
 | |
|             throw new Error(["about-logging-unknown-profiler-preset", k, v]);
 | |
|           }
 | |
|           profilerPresetOverriden = v;
 | |
|           break;
 | |
|         case "profilerstacks":
 | |
|           profilerStacksOverriden = true;
 | |
|           break;
 | |
|         default:
 | |
|           throw new ParseError("about-logging-unknown-option", k, v);
 | |
|       }
 | |
|     }
 | |
|   } catch (e) {
 | |
|     displayErrorMessage(e);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Detect combinations that don't make sense
 | |
|   if (
 | |
|     (profilerPresetOverriden || threadsOverriden) &&
 | |
|     outputTypeOverriden == "file"
 | |
|   ) {
 | |
|     displayErrorMessage(
 | |
|       new ParseError("about-logging-file-and-profiler-override")
 | |
|     );
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Configuration is deemed at least somewhat valid, override each setting in
 | |
|   // turn
 | |
|   let someElementsDisabled = false;
 | |
| 
 | |
|   if (modulesOverriden || loggingPresetOverriden) {
 | |
|     // Don't allow changing those if set by the URL
 | |
|     let logModules = $("#log-modules");
 | |
|     var dropdown = $("#logging-preset-dropdown");
 | |
|     if (loggingPresetOverriden) {
 | |
|       dropdown.value = loggingPresetOverriden;
 | |
|       dropdown.onchange();
 | |
|     }
 | |
|     if (modulesOverriden) {
 | |
|       logModules.value = modulesOverriden;
 | |
|       dropdown.value = "custom";
 | |
|       dropdown.onchange();
 | |
|       dropdown.disabled = true;
 | |
|       someElementsDisabled = true;
 | |
|     }
 | |
|     logModules.disabled = true;
 | |
|     $("#set-log-modules-button").disabled = true;
 | |
|     $("#logging-preset-dropdown").disabled = true;
 | |
|     someElementsDisabled = true;
 | |
|     updateLogModules();
 | |
|   }
 | |
|   if (outputTypeOverriden) {
 | |
|     $$("input[type=radio]").forEach(e => (e.disabled = true));
 | |
|     someElementsDisabled = true;
 | |
|     updateLoggingOutputType(outputTypeOverriden);
 | |
|   }
 | |
|   if (profilerStacksOverriden) {
 | |
|     const checkbox = $("#with-profiler-stacks-checkbox");
 | |
|     checkbox.disabled = true;
 | |
|     someElementsDisabled = true;
 | |
|     Services.prefs.setBoolPref("logging.config.profilerstacks", true);
 | |
|     gLoggingSettings.profilerStacks = true;
 | |
|   }
 | |
| 
 | |
|   if (loggingPresetOverriden) {
 | |
|     gLoggingSettings.loggingPreset = loggingPresetOverriden;
 | |
|   }
 | |
|   if (profilerPresetOverriden) {
 | |
|     gLoggingSettings.profilerPreset = profilerPresetOverriden;
 | |
|   }
 | |
|   if (threadsOverriden) {
 | |
|     gLoggingSettings.profilerThreads = threadsOverriden;
 | |
|   }
 | |
| 
 | |
|   $("#some-elements-unavailable").hidden = !someElementsDisabled;
 | |
| }
 | |
| 
 | |
| let gInited = false;
 | |
| function init() {
 | |
|   if (gInited) {
 | |
|     return;
 | |
|   }
 | |
|   gInited = true;
 | |
|   gDashboard.enableLogging = true;
 | |
| 
 | |
|   populatePresets();
 | |
|   parseURL();
 | |
| 
 | |
|   $("#log-file-configuration").addEventListener("submit", e => {
 | |
|     e.preventDefault();
 | |
|     setLogFile();
 | |
|   });
 | |
| 
 | |
|   $("#log-modules-form").addEventListener("submit", e => {
 | |
|     e.preventDefault();
 | |
|     setLogModules();
 | |
|   });
 | |
| 
 | |
|   let toggleLoggingButton = $("#toggle-logging-button");
 | |
|   toggleLoggingButton.addEventListener("click", startStopLogging);
 | |
| 
 | |
|   $$("input[type=radio]").forEach(radio => {
 | |
|     radio.onchange = e => {
 | |
|       updateLoggingOutputType(e.target.value);
 | |
|     };
 | |
|   });
 | |
| 
 | |
|   $("#with-profiler-stacks-checkbox").addEventListener("change", e => {
 | |
|     Services.prefs.setBoolPref(
 | |
|       "logging.config.profilerstacks",
 | |
|       e.target.checked
 | |
|     );
 | |
|     updateLogModules();
 | |
|   });
 | |
| 
 | |
|   let loggingOutputType = Services.prefs.getCharPref(
 | |
|     "logging.config.output_type",
 | |
|     "profiler"
 | |
|   );
 | |
|   if (loggingOutputType.length) {
 | |
|     updateLoggingOutputType(loggingOutputType);
 | |
|   }
 | |
| 
 | |
|   $("#with-profiler-stacks-checkbox").checked = Services.prefs.getBoolPref(
 | |
|     "logging.config.profilerstacks",
 | |
|     false
 | |
|   );
 | |
| 
 | |
|   try {
 | |
|     let loggingPreset = Services.prefs.getCharPref("logging.config.preset");
 | |
|     gLoggingSettings.loggingPreset = loggingPreset;
 | |
|   } catch {}
 | |
| 
 | |
|   try {
 | |
|     let running = Services.prefs.getBoolPref("logging.config.running");
 | |
|     gLoggingSettings.running = running;
 | |
|     document.l10n.setAttributes(
 | |
|       $("#toggle-logging-button"),
 | |
|       `about-logging-${gLoggingSettings.running ? "stop" : "start"}-logging`
 | |
|     );
 | |
|   } catch {}
 | |
| 
 | |
|   try {
 | |
|     let file = gDirServ.getFile("TmpD", {});
 | |
|     file.append("log.txt");
 | |
|     $("#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 (
 | |
|     ($("#set-log-file-button").disabled ||
 | |
|       $("#set-log-modules-button").disabled) &&
 | |
|     moduleEnvVarPresent()
 | |
|   ) {
 | |
|     $("#buttons-disabled").hidden = false;
 | |
|     toggleLoggingButton.disabled = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function updateLogFile(file) {
 | |
|   let logPath = "";
 | |
| 
 | |
|   // Try to get the environment variable for the log file
 | |
|   logPath =
 | |
|     Services.env.get("MOZ_LOG_FILE") || Services.env.get("NSPR_LOG_FILE");
 | |
|   let currentLogFile = $("#current-log-file");
 | |
|   let setLogFileButton = $("#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 if (gDashboard.getLogPath() != ".moz_log") {
 | |
|     // There may be a value set by a pref.
 | |
|     currentLogFile.innerText = gDashboard.getLogPath();
 | |
|   } else if (file !== undefined) {
 | |
|     currentLogFile.innerText = file;
 | |
|   } else {
 | |
|     try {
 | |
|       let file = gDirServ.getFile("TmpD", {});
 | |
|       file.append("log.txt");
 | |
|       $("#log-file").value = file.path;
 | |
|     } catch (e) {
 | |
|       console.error(e);
 | |
|     }
 | |
|     // Fall back to the temp dir
 | |
|     currentLogFile.innerText = $("#log-file").value;
 | |
|   }
 | |
| 
 | |
|   let openLogFileButton = $("#open-log-file-button");
 | |
|   openLogFileButton.disabled = true;
 | |
| 
 | |
|   if (currentLogFile.innerText.length) {
 | |
|     let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
 | |
|     file.initWithPath(currentLogFile.innerText);
 | |
| 
 | |
|     if (file.exists()) {
 | |
|       openLogFileButton.disabled = false;
 | |
|       openLogFileButton.onclick = function () {
 | |
|         file.reveal();
 | |
|       };
 | |
|     }
 | |
|   }
 | |
|   $("#no-log-file").hidden = !!currentLogFile.innerText.length;
 | |
|   $("#current-log-file").hidden = !currentLogFile.innerText.length;
 | |
| }
 | |
| 
 | |
| function updateLogModules() {
 | |
|   // Try to get the environment variable for the log file
 | |
|   let logModules =
 | |
|     Services.env.get("MOZ_LOG") ||
 | |
|     Services.env.get("MOZ_LOG_MODULES") ||
 | |
|     Services.env.get("NSPR_LOG_MODULES");
 | |
|   let currentLogModules = $("#current-log-modules");
 | |
|   let setLogModulesButton = $("#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 = [];
 | |
|     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);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (activeLogModules.length) {
 | |
|       // Add some options only if some modules are present.
 | |
|       if (Services.prefs.getBoolPref("logging.config.add_timestamp", false)) {
 | |
|         activeLogModules.push("timestamp");
 | |
|       }
 | |
|       if (Services.prefs.getBoolPref("logging.config.sync", false)) {
 | |
|         activeLogModules.push("sync");
 | |
|       }
 | |
|       if (Services.prefs.getBoolPref("logging.config.profilerstacks", false)) {
 | |
|         activeLogModules.push("profilerstacks");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (activeLogModules.length !== 0) {
 | |
|       currentLogModules.innerText = activeLogModules.join(",");
 | |
|       currentLogModules.hidden = false;
 | |
|       $("#no-log-modules").hidden = true;
 | |
|     } else {
 | |
|       currentLogModules.innerText = "";
 | |
|       currentLogModules.hidden = true;
 | |
|       $("#no-log-modules").hidden = false;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| function setLogFile() {
 | |
|   let setLogButton = $("#set-log-file-button");
 | |
|   if (setLogButton.disabled) {
 | |
|     // There's no point trying since it wouldn't work anyway.
 | |
|     return;
 | |
|   }
 | |
|   let logFile = $("#log-file").value.trim();
 | |
|   Services.prefs.setCharPref("logging.config.LOG_FILE", logFile);
 | |
|   updateLogFile(logFile);
 | |
| }
 | |
| 
 | |
| 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() {
 | |
|   if (moduleEnvVarPresent()) {
 | |
|     // The modules were set via env var, so we shouldn't try to change them.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   let modules = $("#log-modules").value.trim();
 | |
| 
 | |
|   // Clear previously set log modules.
 | |
|   clearLogModules();
 | |
| 
 | |
|   if (modules.length !== 0) {
 | |
|     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 if (module == "profilerstacks") {
 | |
|         Services.prefs.setBoolPref("logging.config.profilerstacks", 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 isLogging() {
 | |
|   try {
 | |
|     return Services.prefs.getBoolPref("logging.config.running");
 | |
|   } catch {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function startStopLogging() {
 | |
|   if (isLogging()) {
 | |
|     document.l10n.setAttributes(
 | |
|       $("#toggle-logging-button"),
 | |
|       "about-logging-start-logging"
 | |
|     );
 | |
|     stopLogging();
 | |
|   } else {
 | |
|     document.l10n.setAttributes(
 | |
|       $("#toggle-logging-button"),
 | |
|       "about-logging-stop-logging"
 | |
|     );
 | |
|     startLogging();
 | |
|   }
 | |
| }
 | |
| 
 | |
| function startLogging() {
 | |
|   setLogModules();
 | |
|   if (gLoggingSettings.loggingOutputType === "profiler") {
 | |
|     const pageContext = "aboutlogging";
 | |
|     const supportedFeatures = Services.profiler.GetFeatures();
 | |
|     if (
 | |
|       gLoggingSettings.loggingPreset != "custom" ||
 | |
|       gLoggingSettings.profilerPreset
 | |
|     ) {
 | |
|       // Change the preset before starting the profiler, so that the
 | |
|       // underlying profiler code picks up the right configuration.
 | |
|       // If a profiler preset has been explicitely provided (via URL parameters),
 | |
|       // pick it. Otherwise, pick the preset for this particular logging preset.
 | |
|       const profilerPreset =
 | |
|         gLoggingSettings.profilerPreset ??
 | |
|         gLoggingPresets[gLoggingSettings.loggingPreset].profilerPreset;
 | |
|       ProfilerPopupBackground.changePreset(
 | |
|         "aboutlogging",
 | |
|         profilerPreset,
 | |
|         supportedFeatures
 | |
|       );
 | |
|     } else {
 | |
|       // a baseline set of threads, and possibly others, overriden by the URL
 | |
|       ProfilerPopupBackground.changePreset(
 | |
|         "aboutlogging",
 | |
|         "firefox-platform",
 | |
|         supportedFeatures
 | |
|       );
 | |
|     }
 | |
|     let { entries, interval, features, threads, duration } =
 | |
|       ProfilerPopupBackground.getRecordingSettings(
 | |
|         pageContext,
 | |
|         Services.profiler.GetFeatures()
 | |
|       );
 | |
| 
 | |
|     if (gLoggingSettings.profilerThreads) {
 | |
|       threads.push(...gLoggingSettings.profilerThreads.split(","));
 | |
|       // Small hack: if cubeb is being logged, it's almost always necessary (and
 | |
|       // never harmful) to enable audio callback tracing, otherwise, no log
 | |
|       // statements will be recorded from real-time threads.
 | |
|       if (gLoggingSettings.profilerThreads.includes("cubeb")) {
 | |
|         features.push("audiocallbacktracing");
 | |
|       }
 | |
|     }
 | |
|     const win = Services.wm.getMostRecentWindow("navigator:browser");
 | |
|     const windowid = win?.gBrowser?.selectedBrowser?.browsingContext?.browserId;
 | |
| 
 | |
|     // Force displaying the profiler button in the navbar if not preset, so
 | |
|     // that there is a visual indication profiling is in progress.
 | |
|     if (!ProfilerMenuButton.isInNavbar()) {
 | |
|       // Ensure the widget is enabled.
 | |
|       Services.prefs.setBoolPref(
 | |
|         "devtools.performance.popup.feature-flag",
 | |
|         true
 | |
|       );
 | |
|       // Enable the profiler menu button.
 | |
|       ProfilerMenuButton.addToNavbar();
 | |
|       // Dispatch the change event manually, so that the shortcuts will also be
 | |
|       // added.
 | |
|       CustomizableUI.dispatchToolboxEvent("customizationchange");
 | |
|     }
 | |
| 
 | |
|     gProfilerPromise = Services.profiler.StartProfiler(
 | |
|       entries,
 | |
|       interval,
 | |
|       features,
 | |
|       threads,
 | |
|       windowid,
 | |
|       duration
 | |
|     );
 | |
|   } else {
 | |
|     setLogFile();
 | |
|   }
 | |
|   Services.prefs.setBoolPref("logging.config.running", true);
 | |
| }
 | |
| 
 | |
| async function stopLogging() {
 | |
|   if (gLoggingSettings.loggingOutputType === "profiler") {
 | |
|     await ProfilerPopupBackground.captureProfile("aboutlogging");
 | |
|   } else {
 | |
|     Services.prefs.clearUserPref("logging.config.LOG_FILE");
 | |
|     updateLogFile();
 | |
|   }
 | |
|   Services.prefs.setBoolPref("logging.config.running", false);
 | |
|   clearLogModules();
 | |
| }
 | |
| 
 | |
| // 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();
 | |
| });
 | 
