Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/devtools/client/framework/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 155 kB image not shown  

Quelle  toolbox.js   Sprache: JAVA

 
/* 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 MAX_ORDINAL = 99;
const SPLITCONSOLE_OPEN_PREF = "devtools.toolbox.splitconsole.open";
const SPLITCONSOLE_ENABLED_PREF = "devtools.toolbox.splitconsole.enabled";
const SPLITCONSOLE_HEIGHT_PREF = "devtools.toolbox.splitconsoleHeight";
const DEVTOOLS_ALWAYS_ON_TOP = "devtools.toolbox.alwaysOnTop";
const DISABLE_AUTOHIDE_PREF = "ui.popup.disable_autohide";
const PSEUDO_LOCALE_PREF = "intl.l10n.pseudo";
const HOST_HISTOGRAM = "DEVTOOLS_TOOLBOX_HOST";
const HTML_NS = "http://www.w3.org/1999/xhtml";
const REGEX_4XX_5XX = /^[4,5]\d\d$/;

const BROWSERTOOLBOX_SCOPE_PREF = "devtools.browsertoolbox.scope";
const BROWSERTOOLBOX_SCOPE_EVERYTHING = "everything";
const BROWSERTOOLBOX_SCOPE_PARENTPROCESS = "parent-process";

const { debounce } = require("resource://devtools/shared/debounce.js");
const { throttle } = require("resource://devtools/shared/throttle.js");
const {
  safeAsyncMethod,
} = require("resource://devtools/shared/async-utils.js");
var { gDevTools } = require("resource://devtools/client/framework/devtools.js");
var EventEmitter = require("resource://devtools/shared/event-emitter.js");
const Selection = require("resource://devtools/client/framework/selection.js");
var Telemetry = require("resource://devtools/client/shared/telemetry.js");
const {
  getUnicodeUrl,
} = require("resource://devtools/client/shared/unicode-url.js");
var { DOMHelpers } = require("resource://devtools/shared/dom-helpers.js");
const { KeyCodes } = require("resource://devtools/client/shared/keycodes.js");
const {
  FluentL10n,
} = require("resource://devtools/client/shared/fluent-l10n/fluent-l10n.js");

var Startup = Cc["@mozilla.org/devtools/startup-clh;1"].getService(
  Ci.nsISupports
).wrappedJSObject;

const { BrowserLoader } = ChromeUtils.importESModule(
  "resource://devtools/shared/loader/browser-loader.sys.mjs"
);

const {
  MultiLocalizationHelper,
} = require("resource://devtools/shared/l10n.js");
const L10N = new MultiLocalizationHelper(
  "devtools/client/locales/toolbox.properties",
  "chrome://branding/locale/brand.properties",
  "devtools/client/locales/menus.properties"
);

loader.lazyRequireGetter(
  this,
  "registerStoreObserver",
  "resource://devtools/client/shared/redux/subscriber.js",
  true
);
loader.lazyRequireGetter(
  this,
  "createToolboxStore",
  "resource://devtools/client/framework/store.js",
  true
);
loader.lazyRequireGetter(
  this,
  ["registerWalkerListeners""removeTarget"],
  "resource://devtools/client/framework/actions/index.js",
  true
);
loader.lazyRequireGetter(
  this,
  ["selectTarget"],
  "resource://devtools/shared/commands/target/actions/targets.js",
  true
);
loader.lazyRequireGetter(
  this,
  "TRACER_LOG_METHODS",
  "resource://devtools/shared/specs/tracer.js",
  true
);

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
  TYPES: "resource://devtools/shared/highlighters.mjs",
});
loader.lazyRequireGetter(this"flags""resource://devtools/shared/flags.js");
loader.lazyRequireGetter(
  this,
  "KeyShortcuts",
  "resource://devtools/client/shared/key-shortcuts.js"
);
loader.lazyRequireGetter(
  this,
  "ZoomKeys",
  "resource://devtools/client/shared/zoom-keys.js"
);
loader.lazyRequireGetter(
  this,
  "ToolboxButtons",
  "resource://devtools/client/definitions.js",
  true
);
loader.lazyRequireGetter(
  this,
  "SourceMapURLService",
  "resource://devtools/client/framework/source-map-url-service.js",
  true
);
loader.lazyRequireGetter(
  this,
  "BrowserConsoleManager",
  "resource://devtools/client/webconsole/browser-console-manager.js",
  true
);
loader.lazyRequireGetter(
  this,
  "viewSource",
  "resource://devtools/client/shared/view-source.js"
);
loader.lazyRequireGetter(
  this,
  "buildHarLog",
  "resource://devtools/client/netmonitor/src/har/har-builder-utils.js",
  true
);
loader.lazyRequireGetter(
  this,
  "NetMonitorAPI",
  "resource://devtools/client/netmonitor/src/api.js",
  true
);
loader.lazyRequireGetter(
  this,
  "sortPanelDefinitions",
  "resource://devtools/client/framework/toolbox-tabs-order-manager.js",
  true
);
loader.lazyRequireGetter(
  this,
  "createEditContextMenu",
  "resource://devtools/client/framework/toolbox-context-menu.js",
  true
);
loader.lazyRequireGetter(
  this,
  "getSelectedTarget",
  "resource://devtools/shared/commands/target/selectors/targets.js",
  true
);
loader.lazyRequireGetter(
  this,
  "remoteClientManager",
  "resource://devtools/client/shared/remote-debugging/remote-client-manager.js",
  true
);
loader.lazyRequireGetter(
  this,
  "ResponsiveUIManager",
  "resource://devtools/client/responsive/manager.js"
);
loader.lazyRequireGetter(
  this,
  "DevToolsUtils",
  "resource://devtools/shared/DevToolsUtils.js"
);
loader.lazyRequireGetter(
  this,
  "NodePicker",
  "resource://devtools/client/inspector/node-picker.js"
);

loader.lazyGetter(this"domNodeConstants", () => {
  return require("resource://devtools/shared/dom-node-constants.js");
});

loader.lazyRequireGetter(
  this,
  "NodeFront",
  "resource://devtools/client/fronts/node.js",
  true
);

loader.lazyRequireGetter(
  this,
  "PICKER_TYPES",
  "resource://devtools/shared/picker-constants.js"
);

loader.lazyRequireGetter(
  this,
  "HarAutomation",
  "resource://devtools/client/netmonitor/src/har/har-automation.js",
  true
);

loader.lazyRequireGetter(
  this,
  "getThreadOptions",
  "resource://devtools/client/shared/thread-utils.js",
  true
);
loader.lazyRequireGetter(
  this,
  "SourceMapLoader",
  "resource://devtools/client/shared/source-map-loader/index.js",
  true
);
loader.lazyRequireGetter(
  this,
  "openProfilerTab",
  "resource://devtools/client/performance-new/shared/browser.js",
  true
);
loader.lazyGetter(this"ProfilerBackground", () => {
  return ChromeUtils.importESModule(
    "resource://devtools/client/performance-new/shared/background.sys.mjs"
  );
});

const BOOLEAN_CONFIGURATION_PREFS = {
  "devtools.cache.disabled": {
    name: "cacheDisabled",
  },
  "devtools.custom-formatters.enabled": {
    name: "customFormatters",
  },
  "devtools.serviceWorkers.testing.enabled": {
    name: "serviceWorkersTestingEnabled",
  },
  "devtools.inspector.simple-highlighters-reduced-motion": {
    name: "useSimpleHighlightersForReducedMotion",
  },
  "devtools.debugger.features.overlay": {
    name: "pauseOverlay",
    threadtrue,
  },
  "devtools.debugger.features.javascript-tracing": {
    name: "isTracerFeatureEnabled",
  },
};

/**
 * A "Toolbox" is the component that holds all the tools for one specific
 * target. Visually, it's a document that includes the tools tabs and all
 * the iframes where the tool panels will be living in.
 *
 * @param {object} commands
 *        The context to inspect identified by this commands.
 * @param {string} selectedTool
 *        Tool to select initially
 * @param {Toolbox.HostType} hostType
 *        Type of host that will host the toolbox (e.g. sidebar, window)
 * @param {DOMWindow} contentWindow
 *        The window object of the toolbox document
 * @param {string} frameId
 *        A unique identifier to differentiate toolbox documents from the
 *        chrome codebase when passing DOM messages
 */

function Toolbox(commands, selectedTool, hostType, contentWindow, frameId) {
  this._win = contentWindow;
  this.frameId = frameId;
  this.selection = new Selection();
  this.telemetry = new Telemetry({ useSessionId: true });
  // This attribute helps identify one particular toolbox instance.
  this.sessionId = this.telemetry.sessionId;

  // This attribute is meant to be a public attribute on the Toolbox object
  // It exposes commands modules listed in devtools/shared/commands/index.js
  // which are an abstraction on top of RDP methods.
  // See devtools/shared/commands/README.md
  this.commands = commands;
  this._descriptorFront = commands.descriptorFront;

  // Map of the available DevTools WebExtensions:
  //   Map<extensionUUID, extensionName>
  this._webExtensions = new Map();

  this._toolPanels = new Map();
  this._inspectorExtensionSidebars = new Map();

  this._netMonitorAPI = null;

  // Map of frames (id => frame-info) and currently selected frame id.
  this.frameMap = new Map();
  this.selectedFrameId = null;

  // Number of targets currently paused
  this._pausedTargets = new Set();

  /**
   * KeyShortcuts instance specific to WINDOW host type.
   * This is the key shortcuts that are only register when the toolbox
   * is loaded in its own window. Otherwise, these shortcuts are typically
   * registered by devtools-startup.js module.
   */

  this._windowHostShortcuts = null;

  this._toolRegistered = this._toolRegistered.bind(this);
  this._toolUnregistered = this._toolUnregistered.bind(this);
  this._refreshHostTitle = this._refreshHostTitle.bind(this);
  this.toggleNoAutohide = this.toggleNoAutohide.bind(this);
  this.toggleAlwaysOnTop = this.toggleAlwaysOnTop.bind(this);
  this.disablePseudoLocale = () => this.changePseudoLocale("none");
  this.enableAccentedPseudoLocale = () => this.changePseudoLocale("accented");
  this.enableBidiPseudoLocale = () => this.changePseudoLocale("bidi");
  this._updateFrames = this._updateFrames.bind(this);
  this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this);
  this.closeToolbox = this.closeToolbox.bind(this);
  this.destroy = this.destroy.bind(this);
  this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
  this._onFocus = this._onFocus.bind(this);
  this._onBlur = this._onBlur.bind(this);
  this._onBrowserMessage = this._onBrowserMessage.bind(this);
  this._onTabsOrderUpdated = this._onTabsOrderUpdated.bind(this);
  this._onToolbarFocus = this._onToolbarFocus.bind(this);
  this._onToolbarArrowKeypress = this._onToolbarArrowKeypress.bind(this);
  this._onPickerClick = this._onPickerClick.bind(this);
  this._onPickerKeypress = this._onPickerKeypress.bind(this);
  this._onPickerStarting = this._onPickerStarting.bind(this);
  this._onPickerStarted = this._onPickerStarted.bind(this);
  this._onPickerStopped = this._onPickerStopped.bind(this);
  this._onPickerCanceled = this._onPickerCanceled.bind(this);
  this._onPickerPicked = this._onPickerPicked.bind(this);
  this._onPickerPreviewed = this._onPickerPreviewed.bind(this);
  this._onInspectObject = this._onInspectObject.bind(this);
  this._onNewSelectedNodeFront = this._onNewSelectedNodeFront.bind(this);
  this._onToolSelected = this._onToolSelected.bind(this);
  this._onContextMenu = this._onContextMenu.bind(this);
  this._onMouseDown = this._onMouseDown.bind(this);
  this.updateToolboxButtonsVisibility =
    this.updateToolboxButtonsVisibility.bind(this);
  this.updateToolboxButtons = this.updateToolboxButtons.bind(this);
  this.selectTool = this.selectTool.bind(this);
  this._pingTelemetrySelectTool = this._pingTelemetrySelectTool.bind(this);
  this.toggleSplitConsole = this.toggleSplitConsole.bind(this);
  this.toggleOptions = this.toggleOptions.bind(this);
  this._onTargetAvailable = this._onTargetAvailable.bind(this);
  this._onTargetDestroyed = this._onTargetDestroyed.bind(this);
  this._onTargetSelected = this._onTargetSelected.bind(this);
  this._onResourceAvailable = this._onResourceAvailable.bind(this);
  this._onResourceUpdated = this._onResourceUpdated.bind(this);
  this._onToolSelectedStopPicker = this._onToolSelectedStopPicker.bind(this);

  // `component` might be null if the toolbox was destroying during the throttling
  this._throttledSetToolboxButtons = throttle(
    () => this.component?.setToolboxButtons(this.toolbarButtons),
    500,
    this
  );

  this._debounceUpdateFocusedState = debounce(
    () => {
      this.component?.setFocusedState(this._isToolboxFocused);
    },
    500,
    this
  );

  if (!selectedTool) {
    selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
  }
  this._defaultToolId = selectedTool;

  this._hostType = hostType;

  this.isOpen = new Promise(
    function (resolve) {
      this._resolveIsOpen = resolve;
    }.bind(this)
  );

  EventEmitter.decorate(this);

  this.on("host-changed"this._refreshHostTitle);
  this.on("select"this._onToolSelected);

  this.selection.on("new-node-front"this._onNewSelectedNodeFront);

  gDevTools.on("tool-registered"this._toolRegistered);
  gDevTools.on("tool-unregistered"this._toolUnregistered);

  /**
   * Get text direction for the current locale direction.
   *
   * `getComputedStyle` forces a synchronous reflow, so use a lazy getter in order to
   * call it only once.
   */

  loader.lazyGetter(this"direction", () => {
    const { documentElement } = this.doc;
    const isRtl =
      this.win.getComputedStyle(documentElement).direction === "rtl";
    return isRtl ? "rtl" : "ltr";
  });
}
exports.Toolbox = Toolbox;

/**
 * The toolbox can be 'hosted' either embedded in a browser window
 * or in a separate window.
 */

Toolbox.HostType = {
  BOTTOM: "bottom",
  RIGHT: "right",
  LEFT: "left",
  WINDOW: "window",
  BROWSERTOOLBOX: "browsertoolbox",
  // This is typically used by `about:debugging`, when opening toolbox in a new tab,
  // via `about:devtools-toolbox` URLs.
  PAGE: "page",
};

Toolbox.prototype = {
  _URL: "about:devtools-toolbox",

  _prefs: {
    LAST_TOOL: "devtools.toolbox.selectedTool",
  },

  get nodePicker() {
    if (!this._nodePicker) {
      this._nodePicker = new NodePicker(this.commands, this.selection);
      this._nodePicker.on("picker-starting"this._onPickerStarting);
      this._nodePicker.on("picker-started"this._onPickerStarted);
      this._nodePicker.on("picker-stopped"this._onPickerStopped);
      this._nodePicker.on("picker-node-canceled"this._onPickerCanceled);
      this._nodePicker.on("picker-node-picked"this._onPickerPicked);
      this._nodePicker.on("picker-node-previewed"this._onPickerPreviewed);
    }

    return this._nodePicker;
  },

  get store() {
    if (!this._store) {
      this._store = createToolboxStore();
    }
    return this._store;
  },

  get currentToolId() {
    return this._currentToolId;
  },

  set currentToolId(id) {
    this._currentToolId = id;
    this.component.setCurrentToolId(id);
  },

  get defaultToolId() {
    return this._defaultToolId;
  },

  get panelDefinitions() {
    return this._panelDefinitions;
  },

  set panelDefinitions(definitions) {
    this._panelDefinitions = definitions;
    this._combineAndSortPanelDefinitions();
  },

  get visibleAdditionalTools() {
    if (!this._visibleAdditionalTools) {
      this._visibleAdditionalTools = [];
    }

    return this._visibleAdditionalTools;
  },

  set visibleAdditionalTools(tools) {
    this._visibleAdditionalTools = tools;
    if (this.isReady) {
      this._combineAndSortPanelDefinitions();
    }
  },

  /**
   * Combines the built-in panel definitions and the additional tool definitions that
   * can be set by add-ons.
   */

  _combineAndSortPanelDefinitions() {
    let definitions = [
      ...this._panelDefinitions,
      ...this.getVisibleAdditionalTools(),
    ];
    definitions = sortPanelDefinitions(definitions);
    this.component.setPanelDefinitions(definitions);
  },

  lastUsedToolId: null,

  /**
   * Returns a *copy* of the _toolPanels collection.
   *
   * @return {Map} panels
   *         All the running panels in the toolbox
   */

  getToolPanels() {
    return new Map(this._toolPanels);
  },

  /**
   * Access the panel for a given tool
   */

  getPanel(id) {
    return this._toolPanels.get(id);
  },

  /**
   * Get the panel instance for a given tool once it is ready.
   * If the tool is already opened, the promise will resolve immediately,
   * otherwise it will wait until the tool has been opened before resolving.
   *
   * Note that this does not open the tool, use selectTool if you'd
   * like to select the tool right away.
   *
   * @param  {String} id
   *         The id of the panel, for example "jsdebugger".
   * @returns Promise
   *          A promise that resolves once the panel is ready.
   */

  getPanelWhenReady(id) {
    const panel = this.getPanel(id);
    return new Promise(resolve => {
      if (panel) {
        resolve(panel);
      } else {
        this.on(id + "-ready", initializedPanel => {
          resolve(initializedPanel);
        });
      }
    });
  },

  /**
   * This is a shortcut for getPanel(currentToolId) because it is much more
   * likely that we're going to want to get the panel that we've just made
   * visible
   */

  getCurrentPanel() {
    return this._toolPanels.get(this.currentToolId);
  },

  /**
   * Get the current top level target the toolbox is debugging.
   *
   * This will only be defined *after* calling Toolbox.open(),
   * after it has called `targetCommands.startListening`.
   */

  get target() {
    return this.commands.targetCommand.targetFront;
  },

  get threadFront() {
    return this.commands.targetCommand.targetFront.threadFront;
  },

  /**
   * Get/alter the host of a Toolbox, i.e. is it in browser or in a separate
   * tab. See HostType for more details.
   */

  get hostType() {
    return this._hostType;
  },

  /**
   * Shortcut to the window containing the toolbox UI
   */

  get win() {
    return this._win;
  },

  /**
   * When the toolbox is loaded in a frame with type="content", win.parent will not return
   * the parent Chrome window. This getter should return the parent Chrome window
   * regardless of the frame type. See Bug 1539979.
   */

  get topWindow() {
    return DevToolsUtils.getTopWindow(this.win);
  },

  get topDoc() {
    return this.topWindow.document;
  },

  /**
   * Shortcut to the document containing the toolbox UI
   */

  get doc() {
    return this.win.document;
  },

  /**
   * Get the toggled state of the split console
   */

  get splitConsole() {
    return this._splitConsole;
  },

  /**
   * Get the focused state of the split console
   */

  isSplitConsoleFocused() {
    if (!this._splitConsole) {
      return false;
    }
    const focusedWin = Services.focus.focusedWindow;
    return (
      focusedWin &&
      focusedWin ===
        this.doc.querySelector("#toolbox-panel-iframe-webconsole").contentWindow
    );
  },

  /**
   * Get the enabled split console setting, and if it's not set, set it with updateIsSplitConsoleEnabled
   * @returns {boolean} devtools.toolbox.splitconsole.enabled option
   */

  isSplitConsoleEnabled() {
    if (typeof this._splitConsoleEnabled !== "boolean") {
      this.updateIsSplitConsoleEnabled();
    }

    return this._splitConsoleEnabled;
  },

  get isBrowserToolbox() {
    return this.hostType === Toolbox.HostType.BROWSERTOOLBOX;
  },

  get isMultiProcessBrowserToolbox() {
    return this.isBrowserToolbox;
  },

  /**
   * Set a given target as selected (which may impact the console evaluation context selector).
   *
   * @param {String} targetActorID: The actorID of the target we want to select.
   */

  selectTarget(targetActorID) {
    if (this.getSelectedTargetFront()?.actorID !== targetActorID) {
      // The selected target is managed by the TargetCommand's store.
      // So dispatch this action against that other store.
      this.commands.targetCommand.store.dispatch(selectTarget(targetActorID));
    }
  },

  /**
   * @returns {ThreadFront|null} The selected thread front, or null if there is none.
   */

  getSelectedTargetFront() {
    // The selected target is managed by the TargetCommand's store.
    // So pull the state from that other store.
    const selectedTarget = getSelectedTarget(
      this.commands.targetCommand.store.getState()
    );
    if (!selectedTarget) {
      return null;
    }

    return this.commands.client.getFrontByID(selectedTarget.actorID);
  },

  /**
   * For now, the debugger isn't hooked to TargetCommand's store
   * to display its thread list. So manually forward target selection change
   * to the debugger via a dedicated action
   */

  _onTargetCommandStateChange(state, oldState) {
    if (getSelectedTarget(state) !== getSelectedTarget(oldState)) {
      const dbg = this.getPanel("jsdebugger");
      if (!dbg) {
        return;
      }

      const threadActorID = getSelectedTarget(state)?.threadFront?.actorID;
      if (!threadActorID) {
        return;
      }

      dbg.selectThread(threadActorID);
    }
  },

  /**
   * Called on each new THREAD_STATE resource
   *
   * @param {Object} resource The THREAD_STATE resource
   */

  _onThreadStateChanged(resource) {
    if (resource.state == "paused") {
      this._onTargetPaused(resource.targetFront, resource.why.type);
    } else if (resource.state == "resumed") {
      this._onTargetResumed(resource.targetFront);
    }
  },

  /**
   * This listener is called by TracerCommand, sooner than the JSTRACER_STATE resource.
   * This is called when the frontend toggles the tracer, before the server started interpreting the request.
   * This allows to open the console before we start receiving traces.
   */

  async onTracerToggled() {
    const { tracerCommand } = this.commands;
    if (!tracerCommand.isTracingEnabled) {
      return;
    }
    const { logMethod } = this.commands.tracerCommand.getTracingOptions();
    if (
      logMethod == TRACER_LOG_METHODS.CONSOLE &&
      this.currentToolId !== "webconsole"
    ) {
      await this.openSplitConsole({ focusConsoleInput: false });
    } else if (logMethod == TRACER_LOG_METHODS.DEBUGGER_SIDEBAR) {
      const panel = await this.selectTool("jsdebugger");
      panel.showTracerSidebar();
    }
  },

  /**
   * Called on each new JSTRACER_STATE resource
   *
   * @param {Object} resource The JSTRACER_STATE resource
   */

  async _onTracingStateChanged(resource) {
    const { profile } = resource;
    if (!profile) {
      return;
    }
    const browser = await openProfilerTab({ defaultPanel: "stack-chart" });

    const profileCaptureResult = {
      type: "SUCCESS",
      profile,
    };
    ProfilerBackground.registerProfileCaptureForBrowser(
      browser,
      profileCaptureResult,
      null
    );
  },

  /**
   * Called whenever a given target got its execution paused.
   *
   * Be careful, this method is synchronous, but highlightTool, raise, selectTool
   * are all async.
   *
   * @param {TargetFront} targetFront
   * @param {string} reason
   *        Reason why the execution paused
   */

  _onTargetPaused(targetFront, reason) {
    // Suppress interrupted events by default because the thread is
    // paused/resumed a lot for various actions.
    if (reason === "interrupted") {
      return;
    }

    this.highlightTool("jsdebugger");

    if (
      reason === "debuggerStatement" ||
      reason === "mutationBreakpoint" ||
      reason === "eventBreakpoint" ||
      reason === "breakpoint" ||
      reason === "exception" ||
      reason === "resumeLimit" ||
      reason === "XHR" ||
      reason === "breakpointConditionThrown"
    ) {
      this.raise();
      this.selectTool("jsdebugger", reason);
      // Each Target/Thread can be paused only once at a time,
      // so, for each pause, we should have a related resumed event.
      // But we may have multiple targets paused at the same time
      this._pausedTargets.add(targetFront);
      this.emit("toolbox-paused");
    }
  },

  /**
   * Called whenever a given target got its execution resumed.
   *
   * @param {TargetFront} targetFront
   */

  _onTargetResumed(targetFront) {
    if (this.isHighlighted("jsdebugger")) {
      this._pausedTargets.delete(targetFront);
      if (this._pausedTargets.size == 0) {
        this.emit("toolbox-resumed");
        this.unhighlightTool("jsdebugger");
      }
    }
  },

  /**
   * This method will be called for the top-level target, as well as any potential
   * additional targets we may care about.
   */

  async _onTargetAvailable({ targetFront, isTargetSwitching }) {
    if (targetFront.isTopLevel) {
      // Attach to a new top-level target.
      // For now, register these event listeners only on the top level target
      if (!targetFront.targetForm.ignoreSubFrames) {
        targetFront.on("frame-update"this._updateFrames);
      }
      const consoleFront = await targetFront.getFront("console");
      consoleFront.on("inspectObject"this._onInspectObject);
    }

    // Walker listeners allow to monitor DOM Mutation breakpoint updates.
    // All targets should be monitored.
    targetFront.watchFronts("inspector", async inspectorFront => {
      registerWalkerListeners(this.store, inspectorFront.walker);
    });

    if (targetFront.isTopLevel && isTargetSwitching) {
      // These methods expect the target to be attached, which is guaranteed by the time
      // _onTargetAvailable is called by the targetCommand.
      await this._listFrames();
      // The target may have been destroyed while calling _listFrames if we navigate quickly
      if (targetFront.isDestroyed()) {
        return;
      }
    }

    if (targetFront.targetForm.ignoreSubFrames) {
      this._updateFrames({
        frames: [
          {
            id: targetFront.actorID,
            targetFront,
            url: targetFront.url,
            title: targetFront.title,
            isTopLevel: targetFront.isTopLevel,
          },
        ],
      });
    }

    // If a new popup is debugged, automagically switch the toolbox to become
    // an independant window so that we can easily keep debugging the new tab.
    // Only do that if that's not the current top level, otherwise it means
    // we opened a toolbox dedicated to the popup.
    if (
      targetFront.targetForm.isPopup &&
      !targetFront.isTopLevel &&
      this._descriptorFront.isLocalTab
    ) {
      await this.switchHostToTab(targetFront.targetForm.browsingContextID);
    }
  },

  async _onTargetSelected({ targetFront }) {
    this._updateFrames({ selected: targetFront.actorID });
    this.selectTarget(targetFront.actorID);
    this._refreshHostTitle();
  },

  _onTargetDestroyed({ targetFront }) {
    removeTarget(this.store, targetFront);

    if (targetFront.isTopLevel) {
      const consoleFront = targetFront.getCachedFront("console");
      // If the target has already been destroyed, its console front will
      // also already be destroyed and so we won't be able to retrieve it.
      // Nor is it important to clear its listener as fronts automatically clears
      // all their listeners on destroy.
      if (consoleFront) {
        consoleFront.off("inspectObject"this._onInspectObject);
      }
      targetFront.off("frame-update"this._updateFrames);
    } else if (this.selection) {
      this.selection.onTargetDestroyed(targetFront);
    }

    // When navigating the old (top level) target can get destroyed before the thread state changed
    // event for the target is received, so it gets lost. This currently happens with bf-cache
    // navigations when paused, so lets make sure we resumed if not.
    //
    // We should also resume if a paused non-top-level target is destroyed
    if (targetFront.isTopLevel || this._pausedTargets.has(targetFront)) {
      this._onTargetResumed(targetFront);
    }

    if (targetFront.targetForm.ignoreSubFrames) {
      this._updateFrames({
        frames: [
          {
            // The Target Front may already be destroyed and `actorID` be null.
            id: targetFront.persistedActorID,
            destroy: true,
          },
        ],
      });
    }
  },

  _onTargetThreadFrontResumeWrongOrder() {
    const box = this.getNotificationBox();
    box.appendNotification(
      L10N.getStr("toolbox.resumeOrderWarning"),
      "wrong-resume-order",
      "",
      box.PRIORITY_WARNING_HIGH
    );
  },

  /**
   * Open the toolbox
   */

  async open() {
    try {
      // Kick off async loading the Fluent bundles.
      const fluentL10n = new FluentL10n();
      const fluentInitPromise = fluentL10n.init([
        "devtools/client/toolbox.ftl",
      ]);

      const isToolboxURL = this.win.location.href.startsWith(this._URL);
      if (isToolboxURL) {
        // Update the URL so that onceDOMReady watch for the right url.
        this._URL = this.win.location.href;
      }

      // To avoid any possible artifact, wait for the document to be fully loaded
      // before creating the Browser Loader based on toolbox window object.
      await new Promise(resolve => {
        DOMHelpers.onceDOMReady(
          this.win,
          () => {
            resolve();
          },
          this._URL
        );
      });

      // Setup the Toolbox Browser Loader, used to load React component modules
      // which expect to be loaded with toolbox.xhtml document as global scope.
      this.browserRequire = BrowserLoader({
        window: this.win,
        useOnlyShared: true,
      }).require;

      // Wait for fluent initialization before mounting React component,
      // which depends on it.
      await fluentInitPromise;

      // Mount toolbox React components and update all its state that can be updated synchronously.
      // Do that early as it will be used to render any exception happening next.
      this._mountReactComponent(fluentL10n.getBundles());

      // Bug 1709063: Use commands.resourceCommand instead of toolbox.resourceCommand
      this.resourceCommand = this.commands.resourceCommand;

      this.commands.targetCommand.on(
        "target-thread-wrong-order-on-resume",
        this._onTargetThreadFrontResumeWrongOrder.bind(this)
      );
      registerStoreObserver(
        this.commands.targetCommand.store,
        this._onTargetCommandStateChange.bind(this)
      );

      // Optimization: fire up a few other things before waiting on
      // the iframe being ready (makes startup faster)
      await this.commands.targetCommand.startListening();

      // Transfer settings early, before watching resources as it may impact them.
      // (this is the case for custom formatter pref and console messages)
      await this._listenAndApplyConfigurationPref();

      // The targetCommand is created right before this code.
      // It means that this call to watchTargets is the first,
      // and we are registering the first target listener, which means
      // Toolbox._onTargetAvailable will be called first, before any other
      // onTargetAvailable listener that might be registered on targetCommand.
      await this.commands.targetCommand.watchTargets({
        types: this.commands.targetCommand.ALL_TYPES,
        onAvailable: this._onTargetAvailable,
        onSelected: this._onTargetSelected,
        onDestroyed: this._onTargetDestroyed,
      });

      const watchedResources = [
        // Watch for console API messages, errors and network events in order to populate
        // the error count icon in the toolbox.
        this.resourceCommand.TYPES.CONSOLE_MESSAGE,
        this.resourceCommand.TYPES.ERROR_MESSAGE,
        this.resourceCommand.TYPES.DOCUMENT_EVENT,
        this.resourceCommand.TYPES.THREAD_STATE,
      ];

      let tracerInitialization;
      if (
        Services.prefs.getBoolPref(
          "devtools.debugger.features.javascript-tracing",
          false
        )
      ) {
        watchedResources.push(this.resourceCommand.TYPES.JSTRACER_STATE);
        tracerInitialization = this.commands.tracerCommand.initialize();
        this.onTracerToggled = this.onTracerToggled.bind(this);
        this.commands.tracerCommand.on("toggle"this.onTracerToggled);
      }

      if (!this.isBrowserToolbox) {
        // Independently of watching network event resources for the error count icon,
        // we need to start tracking network activity on toolbox open for targets such
        // as tabs, in order to ensure there is always at least one listener existing
        // for network events across the lifetime of the various panels, so stopping
        // the resource command from clearing out its cache of network event resources.
        watchedResources.push(this.resourceCommand.TYPES.NETWORK_EVENT);
      }

      const onResourcesWatched = this.resourceCommand.watchResources(
        watchedResources,
        {
          onAvailable: this._onResourceAvailable,
          onUpdated: this._onResourceUpdated,
        }
      );

      this.isReady = true;

      const framesPromise = this._listFrames();

      Services.prefs.addObserver(
        BROWSERTOOLBOX_SCOPE_PREF,
        this._refreshHostTitle
      );

      this._buildDockOptions();
      this._buildInitialPanelDefinitions();
      this._setDebugTargetData();

      this._addWindowListeners();
      this._addChromeEventHandlerEvents();

      // Get the tab bar of the ToolboxController to attach the "keypress" event listener to.
      this._tabBar = this.doc.querySelector(".devtools-tabbar");
      this._tabBar.addEventListener("keypress"this._onToolbarArrowKeypress);

      this._componentMount.setAttribute(
        "aria-label",
        L10N.getStr("toolbox.label")
      );

      this.webconsolePanel = this.doc.querySelector(
        "#toolbox-panel-webconsole"
      );
      this.webconsolePanel.style.height =
        Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF) + "px";
      this.webconsolePanel.addEventListener(
        "resize",
        this._saveSplitConsoleHeight
      );

      this._buildButtons();

      this._pingTelemetry();

      // The isToolSupported check needs to happen after the target is
      // remoted, otherwise we could have done it in the toolbox constructor
      // (bug 1072764).
      const toolDef = gDevTools.getToolDefinition(this._defaultToolId);
      if (!toolDef || !toolDef.isToolSupported(this)) {
        this._defaultToolId = "webconsole";
      }

      // Update all ToolboxController state that can only be done asynchronously
      await this._setInitialMeatballState();

      // Start rendering the toolbox toolbar before selecting the tool, as the tools
      // can take a few hundred milliseconds seconds to start up.
      //
      // Delay React rendering as Toolbox.open is synchronous.
      // Even if this involve promises, it is synchronous. Toolbox.open already loads
      // react modules and freeze the event loop for a significant time.
      // requestIdleCallback allows releasing it to allow user events to be processed.
      // Use 16ms maximum delay to allow one frame to be rendered at 60FPS
      // (1000ms/60FPS=16ms)
      this.win.requestIdleCallback(
        () => {
          this.component.setCanRender();
        },
        { timeout: 16 }
      );

      await this.selectTool(this._defaultToolId, "initial_panel");

      // Wait until the original tool is selected so that the split
      // console input will receive focus.
      let splitConsolePromise = Promise.resolve();
      if (Services.prefs.getBoolPref(SPLITCONSOLE_OPEN_PREF)) {
        splitConsolePromise = this.openSplitConsole();
        this.telemetry.addEventProperty(
          this.topWindow,
          "open",
          "tools",
          null,
          "splitconsole",
          true
        );
      } else {
        this.telemetry.addEventProperty(
          this.topWindow,
          "open",
          "tools",
          null,
          "splitconsole",
          false
        );
      }

      await Promise.all([
        splitConsolePromise,
        framesPromise,
        onResourcesWatched,
        tracerInitialization,
      ]);

      // We do not expect the focus to be restored when using about:debugging toolboxes
      // Otherwise, when reloading the toolbox, the debugged tab will be focused.
      if (this.hostType !== Toolbox.HostType.PAGE) {
        // Request the actor to restore the focus to the content page once the
        // target is detached. This typically happens when the console closes.
        // We restore the focus as it may have been stolen by the console input.
        await this.commands.targetConfigurationCommand.updateConfiguration({
          restoreFocus: true,
        });
      }

      await this.initHarAutomation();

      this.emit("ready");
      this._resolveIsOpen();
    } catch (error) {
      console.error(
        "Exception while opening the toolbox",
        String(error),
        error
      );
      // While the exception stack is correctly printed in the Browser console when
      // passing `e` to console.error, it is not on the stdout, so print it via dump.
      dump(error.stack + "\n");
      if (error.serverStack) {
        dump("Server stack:" + error.serverStack + "\n");
      }

      // If the exception happens *after* the React component were initialized,
      // try to display the exception to the user via AppErrorBoundary component
      if (this._appBoundary) {
        this._appBoundary.setState({
          errorMsg: error.toString(),
          errorStack: error.stack,
          errorInfo: {
            serverStack: error.serverStack,
          },
          toolbox: this,
        });
      }
    }
  },

  /**
   * Retrieve the ChromeEventHandler associated to the toolbox frame.
   * When DevTools are loaded in a content frame, this will return the containing chrome
   * frame. Events from nested frames will bubble up to this chrome frame, which allows to
   * listen to events from nested frames.
   */

  getChromeEventHandler() {
    if (!this.win || !this.win.docShell) {
      return null;
    }
    return this.win.docShell.chromeEventHandler;
  },

  /**
   * Attach events on the chromeEventHandler for the current window. When loaded in a
   * frame with type set to "content", events will not bubble across frames. The
   * chromeEventHandler does not have this limitation and will catch all events triggered
   * on any of the frames under the devtools document.
   *
   * Events relying on the chromeEventHandler need to be added and removed at specific
   * moments in the lifecycle of the toolbox, so all the events relying on it should be
   * grouped here.
   */

  _addChromeEventHandlerEvents() {
    // win.docShell.chromeEventHandler might not be accessible anymore when removing the
    // events, so we can't rely on a dynamic getter here.
    // Keep a reference on the chromeEventHandler used to addEventListener to be sure we
    // can remove the listeners afterwards.
    this._chromeEventHandler = this.getChromeEventHandler();
    if (!this._chromeEventHandler) {
      return;
    }

    // Add shortcuts and window-host-shortcuts that use the ChromeEventHandler as target.
    this._addShortcuts();
    this._addWindowHostShortcuts();

    this._chromeEventHandler.addEventListener(
      "keypress",
      this._splitConsoleOnKeypress
    );
    this._chromeEventHandler.addEventListener("focus"this._onFocus, true);
    this._chromeEventHandler.addEventListener("blur"this._onBlur, true);
    this._chromeEventHandler.addEventListener(
      "contextmenu",
      this._onContextMenu
    );
    this._chromeEventHandler.addEventListener("mousedown"this._onMouseDown);
  },

  _removeChromeEventHandlerEvents() {
    if (!this._chromeEventHandler) {
      return;
    }

    // Remove shortcuts and window-host-shortcuts that use the ChromeEventHandler as
    // target.
    this._removeShortcuts();
    this._removeWindowHostShortcuts();

    this._chromeEventHandler.removeEventListener(
      "keypress",
      this._splitConsoleOnKeypress
    );
    this._chromeEventHandler.removeEventListener("focus"this._onFocus, true);
    this._chromeEventHandler.removeEventListener("focus"this._onBlur, true);
    this._chromeEventHandler.removeEventListener(
      "contextmenu",
      this._onContextMenu
    );
    this._chromeEventHandler.removeEventListener(
      "mousedown",
      this._onMouseDown
    );

    this._chromeEventHandler = null;
  },

  _addShortcuts() {
    // Create shortcuts instance for the toolbox
    if (!this.shortcuts) {
      this.shortcuts = new KeyShortcuts({
        window: this.doc.defaultView,
        // The toolbox key shortcuts should be triggered from any frame in DevTools.
        // Use the chromeEventHandler as the target to catch events from all frames.
        target: this.getChromeEventHandler(),
      });
    }

    // Listen for the shortcut key to show the frame list
    this.shortcuts.on(L10N.getStr("toolbox.showFrames.key"), event => {
      if (event.target.id === "command-button-frames") {
        event.target.click();
      }
    });

    // Listen for tool navigation shortcuts.
    this.shortcuts.on(L10N.getStr("toolbox.nextTool.key"), event => {
      this.selectNextTool();
      event.preventDefault();
    });
    this.shortcuts.on(L10N.getStr("toolbox.previousTool.key"), event => {
      this.selectPreviousTool();
      event.preventDefault();
    });
    this.shortcuts.on(L10N.getStr("toolbox.toggleHost.key"), event => {
      this.switchToPreviousHost();
      event.preventDefault();
    });

    // List for Help/Settings key.
    this.shortcuts.on(L10N.getStr("toolbox.help.key"), this.toggleOptions);

    if (!this.isBrowserToolbox) {
      // Listen for Reload shortcuts
      [
        ["reload"false],
        ["reload2"false],
        ["forceReload"true],
        ["forceReload2"true],
      ].forEach(([id, bypassCache]) => {
        const key = L10N.getStr("toolbox." + id + ".key");
        this.shortcuts.on(key, event => {
          this.reload(bypassCache);

          // Prevent Firefox shortcuts from reloading the page
          event.preventDefault();
        });
      });
    }

    // Add zoom-related shortcuts.
    if (this.hostType != Toolbox.HostType.PAGE) {
      // When the toolbox is rendered in a tab (ie host type is PAGE), the
      // zoom should be handled by the default browser shortcuts.
      ZoomKeys.register(this.win, this.shortcuts);
    }
  },

  /**
   * Reload the debugged context.
   *
   * @param {Boolean} bypassCache
   *        If true, bypass any cache when reloading.
   */

  async reload(bypassCache) {
    const box = this.getNotificationBox();
    const notification = box.getNotificationWithValue("reload-error");
    if (notification) {
      notification.close();
    }

    // When reloading a Web Extension, the top level target isn't destroyed.
    // Which prevents some panels (like console and netmonitor) from being correctly cleared.
    const consolePanel = this.getPanel("webconsole");
    if (consolePanel) {
      // Navigation to a null URL will be translated into a reload message
      // when persist log is enabled.
      consolePanel.hud.ui.handleWillNavigate({
        timeStamp: new Date(),
        url: null,
      });
    }
    const netPanel = this.getPanel("netmonitor");
    if (netPanel) {
      // Fake a navigation, which will clear the netmonitor, if persists is disabled.
      netPanel.panelWin.connector.willNavigate();
    }

    try {
      await this.commands.targetCommand.reloadTopLevelTarget(bypassCache);
    } catch (e) {
      let { message } = e;

      // Remove Protocol.JS exception header to focus on the likely manifest error
      message = message.replace("Protocol error (SyntaxError):""");

      box.appendNotification(
        L10N.getFormatStr("toolbox.errorOnReload", message),
        "reload-error",
        "",
        box.PRIORITY_CRITICAL_HIGH
      );
    }
  },

  _removeShortcuts() {
    if (this.shortcuts) {
      this.shortcuts.destroy();
      this.shortcuts = null;
    }
  },

  /**
   * Adds the keys and commands to the Toolbox Window in window mode.
   */

  _addWindowHostShortcuts() {
    if (this.hostType != Toolbox.HostType.WINDOW) {
      // Those shortcuts are only valid for host type WINDOW.
      return;
    }

    if (!this._windowHostShortcuts) {
      this._windowHostShortcuts = new KeyShortcuts({
        window: this.win,
        // The window host key shortcuts should be triggered from any frame in DevTools.
        // Use the chromeEventHandler as the target to catch events from all frames.
        target: this.getChromeEventHandler(),
      });
    }

    const shortcuts = this._windowHostShortcuts;

    for (const item of Startup.KeyShortcuts) {
      const { id, toolId, shortcut, modifiers } = item;
      const electronKey = KeyShortcuts.parseXulKey(modifiers, shortcut);

      if (id == "browserConsole") {
        // Add key for toggling the browser console from the detached window
        shortcuts.on(electronKey, () => {
          BrowserConsoleManager.toggleBrowserConsole();
        });
      } else if (toolId) {
        // KeyShortcuts contain tool-specific and global key shortcuts,
        // here we only need to copy shortcut specific to each tool.
        shortcuts.on(electronKey, () => {
          this.selectTool(toolId, "key_shortcut").then(() =>
            this.fireCustomKey(toolId)
          );
        });
      }
    }

    // CmdOrCtrl+W is registered only when the toolbox is running in
    // detached window. In the other case the entire browser tab
    // is closed when the user uses this shortcut.
    shortcuts.on(L10N.getStr("toolbox.closeToolbox.key"), this.closeToolbox);

    // The others are only registered in window host type as for other hosts,
    // these keys are already registered by devtools-startup.js
    shortcuts.on(
      L10N.getStr("toolbox.toggleToolboxF12.key"),
      this.closeToolbox
    );
    if (lazy.AppConstants.platform == "macosx") {
      shortcuts.on(
        L10N.getStr("toolbox.toggleToolboxOSX.key"),
        this.closeToolbox
      );
    } else {
      shortcuts.on(L10N.getStr("toolbox.toggleToolbox.key"), this.closeToolbox);
    }
  },

  _removeWindowHostShortcuts() {
    if (this._windowHostShortcuts) {
      this._windowHostShortcuts.destroy();
      this._windowHostShortcuts = null;
    }
  },

  _onContextMenu(e) {
    // Handle context menu events in standard input elements: <input> and <textarea>.
    // Also support for custom input elements using .devtools-input class
    // (e.g. CodeMirror instances).
    const isInInput =
      e.originalTarget.closest("input[type=text]") ||
      e.originalTarget.closest("input[type=search]") ||
      e.originalTarget.closest("input:not([type])") ||
      e.originalTarget.closest(".devtools-input") ||
      e.originalTarget.closest("textarea");

    const doc = e.originalTarget.ownerDocument;
    const isHTMLPanel = doc.documentElement.namespaceURI === HTML_NS;

    if (
      // Context-menu events on input elements will use a custom context menu.
      isInInput ||
      // Context-menu events from HTML panels should not trigger the default
      // browser context menu for HTML documents.
      isHTMLPanel
    ) {
      e.stopPropagation();
      e.preventDefault();
    }

    if (isInInput) {
      this.openTextBoxContextMenu(e.screenX, e.screenY);
    }
  },

  _onMouseDown(e) {
    const isMiddleClick = e.button === 1;
    if (isMiddleClick) {
      // Middle clicks will trigger the scroll lock feature to turn on.
      // When the DevTools toolbox was running in an <iframe>, this behavior was
      // disabled by default. When running in a <browser> element, we now need
      // to catch and preventDefault() on those events.
      e.preventDefault();
    }
  },

  _getDebugTargetData() {
    const url = new URL(this.win.location);
    const remoteId = url.searchParams.get("remoteId");
    const runtimeInfo = remoteClientManager.getRuntimeInfoByRemoteId(remoteId);
    const connectionType =
      remoteClientManager.getConnectionTypeByRemoteId(remoteId);

    return {
      connectionType,
      runtimeInfo,
      descriptorType: this._descriptorFront.descriptorType,
      descriptorName: this._descriptorFront.name,
    };
  },

  isDebugTargetFenix() {
    return this._getDebugTargetData()?.runtimeInfo?.isFenix;
  },

  /**
   * loading React modules when needed (to avoid performance penalties
   * during Firefox start up time).
   */

  get React() {
    return this.browserRequire("devtools/client/shared/vendor/react");
  },

  get ReactDOM() {
    return this.browserRequire("devtools/client/shared/vendor/react-dom");
  },

  get ReactRedux() {
    return this.browserRequire("devtools/client/shared/vendor/react-redux");
  },

  get ToolboxController() {
    return this.browserRequire(
      "devtools/client/framework/components/ToolboxController"
    );
  },

  get AppErrorBoundary() {
    return this.browserRequire(
      "resource://devtools/client/shared/components/AppErrorBoundary.js"
    );
  },

  /**
   * A common access point for the client-side mapping service for source maps that
   * any panel can use.  This is a "low-level" API that connects to
   * the source map worker.
   */

  get sourceMapLoader() {
    if (this._sourceMapLoader) {
      return this._sourceMapLoader;
    }
    this._sourceMapLoader = new SourceMapLoader(this.commands.targetCommand);
    return this._sourceMapLoader;
  },

  /**
   * Expose the "Parser" debugger worker to both webconsole and debugger.
   *
   * Note that the Browser Console will also self-instantiate it as it doesn't involve a toolbox.
   */

  get parserWorker() {
    if (this._parserWorker) {
      return this._parserWorker;
    }

    const {
      ParserDispatcher,
    } = require("resource://devtools/client/debugger/src/workers/parser/index.js");

    this._parserWorker = new ParserDispatcher();
    return this._parserWorker;
  },

  /**
   * Clients wishing to use source maps but that want the toolbox to
   * track the source and style sheet actor mapping can use this
   * source map service.  This is a higher-level service than the one
   * returned by |sourceMapLoader|, in that it automatically tracks
   * source and style sheet actor IDs.
   */

  get sourceMapURLService() {
    if (this._sourceMapURLService) {
      return this._sourceMapURLService;
    }
    this._sourceMapURLService = new SourceMapURLService(
      this.commands,
      this.sourceMapLoader
    );
    return this._sourceMapURLService;
  },

  // Return HostType id for telemetry
  _getTelemetryHostId() {
    switch (this.hostType) {
      case Toolbox.HostType.BOTTOM:
        return 0;
      case Toolbox.HostType.RIGHT:
        return 1;
      case Toolbox.HostType.WINDOW:
        return 2;
      case Toolbox.HostType.BROWSERTOOLBOX:
        return 3;
      case Toolbox.HostType.LEFT:
        return 4;
      case Toolbox.HostType.PAGE:
        return 5;
      default:
        return 9;
    }
  },

  // Return HostType string for telemetry
  _getTelemetryHostString() {
    switch (this.hostType) {
      case Toolbox.HostType.BOTTOM:
        return "bottom";
      case Toolbox.HostType.LEFT:
        return "left";
      case Toolbox.HostType.RIGHT:
        return "right";
      case Toolbox.HostType.WINDOW:
        return "window";
      case Toolbox.HostType.PAGE:
        return "page";
      case Toolbox.HostType.BROWSERTOOLBOX:
        return "other";
      default:
        return "bottom";
    }
  },

  _pingTelemetry() {
    Services.prefs.setBoolPref("devtools.everOpened"true);
    this.telemetry.toolOpened("toolbox"this);

    this.telemetry
      .getHistogramById(HOST_HISTOGRAM)
      .add(this._getTelemetryHostId());

    // Log current theme. The question we want to answer is:
    // "What proportion of users use which themes?"
    const currentTheme = Services.prefs.getCharPref("devtools.theme");
    Glean.devtools.currentTheme[currentTheme].add(1);

    const browserWin = this.topWindow;
    this.telemetry.preparePendingEvent(browserWin, "open""tools"null, [
      "entrypoint",
      "first_panel",
      "host",
      "shortcut",
      "splitconsole",
      "width",
    ]);
    this.telemetry.addEventProperty(
      browserWin,
      "open",
      "tools",
      null,
      "host",
      this._getTelemetryHostString()
    );
  },

  /**
   * Create a simple object to store the state of a toolbox button. The checked state of
   * a button can be updated arbitrarily outside of the scope of the toolbar and its
   * controllers. In order to simplify this interaction this object emits an
   * "updatechecked" event any time the isChecked value is updated, allowing any consuming
   * components to listen and respond to updates.
   *
   * @param {Object} options:
   *
   * @property {String} id - The id of the button or command.
   * @property {String} className - An optional additional className for the button.
   * @property {String} description - The value that will display as a tooltip and in
   *                    the options panel for enabling/disabling.
   * @property {Boolean} disabled - An optional disabled state for the button.
   * @property {Function} onClick - The function to run when the button is activated by
   *                      click or keyboard shortcut. First argument will be the 'click'
   *                      event, and second argument is the toolbox instance.
   * @property {Boolean} isInStartContainer - Buttons can either be placed at the start
   *                     of the toolbar, or at the end.
   * @property {Function} setup - Function run immediately to listen for events changing
   *                      whenever the button is checked or unchecked. The toolbox object
   *                      is passed as first argument and a callback is passed as second
   *                       argument, to be called whenever the checked state changes.
   * @property {Function} teardown - Function run on toolbox close to let a chance to
   *                      unregister listeners set when `setup` was called and avoid
   *                      memory leaks. The same arguments than `setup` function are
   *                      passed to `teardown`.
   * @property {Function} isToolSupported - Function to automatically enable/disable
   *                      the button based on the toolbox. If the toolbox don't support
   *                      the button feature, this method should return false.
   * @property {Function} isCurrentlyVisible - Function to automatically
   *                      hide/show the button based on current state.
   * @property {Function} isChecked - Optional function called to known if the button
   *                      is toggled or not. The function should return true when
   *                      the button should be displayed as toggled on.
   */

  _createButtonState(options) {
    let isCheckedValue = false;
    const {
      id,
      className,
      description,
      disabled,
      onClick,
      isInStartContainer,
      setup,
      teardown,
      isToolSupported,
      isCurrentlyVisible,
      isChecked,
      isToggle,
      onKeyDown,
      experimentalURL,
    } = options;
    const toolbox = this;
    const button = {
      id,
      className,
      description,
      disabled,
      async onClick(event) {
        if (typeof onClick == "function") {
          await onClick(event, toolbox);
          button.emit("updatechecked");
        }
      },
      onKeyDown(event) {
        if (typeof onKeyDown == "function") {
          onKeyDown(event, toolbox);
        }
      },
      isToolSupported,
      isCurrentlyVisible,
      get isChecked() {
        if (typeof isChecked == "function") {
          return isChecked(toolbox);
        }
        return isCheckedValue;
      },
      set isChecked(value) {
        // Note that if options.isChecked is given, this is ignored
        isCheckedValue = value;
        this.emit("updatechecked");
      },
      isToggle,
      // The preference for having this button visible.
      visibilityswitch: `devtools.${id}.enabled`,
      // The toolbar has a container at the start and end of the toolbar for
      // holding buttons. By default the buttons are placed in the end container.
      isInStartContainer: !!isInStartContainer,
      experimentalURL,
      getContextMenu() {
        if (options.getContextMenu) {
          return options.getContextMenu(toolbox);
        }
        return null;
      },
    };
    if (typeof setup == "function") {
      // Use async function as tracer's definition requires an async function to be passed
      // for "toggle" event listener.
      const onChange = async () => {
        button.emit("updatechecked");
      };
      setup(this, onChange);
      // Save a reference to the cleanup method that will unregister the onChange
      // callback. Immediately bind the function argument so that we don't have to
      // also save a reference to them.
      button.teardown = teardown.bind(options, this, onChange);
    }
    button.isVisible = this._commandIsVisible(button);

    EventEmitter.decorate(button);

    return button;
  },

  _splitConsoleOnKeypress(e) {
    if (e.keyCode !== KeyCodes.DOM_VK_ESCAPE || !this.isSplitConsoleEnabled()) {
      return;
    }

    const currentPanel = this.getCurrentPanel();
    if (
      typeof currentPanel.onToolboxChromeEventHandlerEscapeKeyDown ===
      "function"
    ) {
      const ac = new this.win.AbortController();
      currentPanel.onToolboxChromeEventHandlerEscapeKeyDown(ac);
      if (ac.signal.aborted) {
        return;
      }
    }

    this.toggleSplitConsole();
    // If the debugger is paused, don't let the ESC key stop any pending navigation.
    // If the host is page, don't let the ESC stop the load of the webconsole frame.
    if (
      this.threadFront.state == "paused" ||
      this.hostType === Toolbox.HostType.PAGE
    ) {
      e.preventDefault();
    }
  },

  /**
   * Add a shortcut key that should work when a split console
   * has focus to the toolbox.
   *
   * @param {String} key
   *        The electron key shortcut.
   * @param {Function} handler
   *        The callback that should be called when the provided key shortcut is pressed.
   * @param {String} whichTool
   *        The tool the key belongs to. The corresponding handler will only be triggered
   *        if this tool is active.
   */

  useKeyWithSplitConsole(key, handler, whichTool) {
    this.shortcuts.on(key, event => {
      if (this.currentToolId === whichTool && this.isSplitConsoleFocused()) {
        handler();
        event.preventDefault();
      }
    });
  },

  _addWindowListeners() {
    this.win.addEventListener("unload"this.destroy);
    this.win.addEventListener("message"this._onBrowserMessage, true);
  },

  _removeWindowListeners() {
    // The host iframe's contentDocument may already be gone.
    if (this.win) {
      this.win.removeEventListener("unload"this.destroy);
      this.win.removeEventListener("message"this._onBrowserMessage, true);
    }
  },

  // Called whenever the chrome send a message
  _onBrowserMessage(event) {
    if (event.data?.name === "switched-host") {
      this._onSwitchedHost(event.data);
    }
    if (event.data?.name === "switched-host-to-tab") {
      this._onSwitchedHostToTab(event.data.browsingContextID);
    }
    if (event.data?.name === "host-raised") {
      this.emit("host-raised");
    }
  },

  _saveSplitConsoleHeight() {
    const height = parseInt(this.webconsolePanel.style.height, 10);
    if (!isNaN(height)) {
      Services.prefs.setIntPref(SPLITCONSOLE_HEIGHT_PREF, height);
    }
  },

  /**
   * Make sure that the console is showing up properly based on all the
   * possible conditions.
   *   1) If the console tab is selected, then regardless of split state
   *      it should take up the full height of the deck, and we should
   *      hide the deck and splitter.
   *   2) If the console tab is not selected and it is split, then we should
   *      show the splitter, deck, and console.
   *   3) If the console tab is not selected and it is *not* split,
   *      then we should hide the console and splitter, and show the deck
   *      at full height.
   */

  _refreshConsoleDisplay() {
    const deck = this.doc.getElementById("toolbox-deck");
    const webconsolePanel = this.webconsolePanel;
    const splitter = this.doc.getElementById("toolbox-console-splitter");
    const openedConsolePanel = this.currentToolId === "webconsole";

    if (openedConsolePanel) {
      deck.collapsed = true;
      deck.removeAttribute("expanded");
      splitter.hidden = true;
      webconsolePanel.collapsed = false;
      webconsolePanel.setAttribute("expanded""");
    } else {
      deck.collapsed = false;
      deck.toggleAttribute("expanded", !this.splitConsole);
      splitter.hidden = !this.splitConsole;
      webconsolePanel.collapsed = !this.splitConsole;
      webconsolePanel.removeAttribute("expanded");
    }
  },

  /**
   * Handle any custom key events.  Returns true if there was a custom key
   * binding run.
   * @param {string} toolId Which tool to run the command on (skip if not
   * current)
   */

  fireCustomKey(toolId) {
    const toolDefinition = gDevTools.getToolDefinition(toolId);

    if (
      toolDefinition.onkey &&
      (this.currentToolId === toolId ||
        (toolId == "webconsole" && this.splitConsole))
    ) {
      toolDefinition.onkey(this.getCurrentPanel(), this);
    }
  },

  /**
   * Build the notification box as soon as needed.
   */

  get notificationBox() {
    if (!this._notificationBox) {
      let { NotificationBox, PriorityLevels } = this.browserRequire(
        "devtools/client/shared/components/NotificationBox"
      );

      NotificationBox = this.React.createFactory(NotificationBox);

      // Render NotificationBox and assign priority levels to it.
      const box = this.doc.getElementById("toolbox-notificationbox");
      this._notificationBox = Object.assign(
        this.ReactDOM.render(NotificationBox({ wrapping: true }), box),
        PriorityLevels
      );
    }
    return this._notificationBox;
  },

  /**
   * Build the options for changing hosts. Called every time
   * the host changes.
   */

  _buildDockOptions() {
    if (!this._descriptorFront.isLocalTab) {
      this.component.setDockOptionsEnabled(false);
      this.component.setCanCloseToolbox(false);
      return;
    }

    this.component.setDockOptionsEnabled(true);
    this.component.setCanCloseToolbox(
      this.hostType !== Toolbox.HostType.WINDOW
    );

    const hostTypes = [];
    for (const type in Toolbox.HostType) {
      const position = Toolbox.HostType[type];
      if (
        position == Toolbox.HostType.BROWSERTOOLBOX ||
        position == Toolbox.HostType.PAGE
      ) {
        continue;
      }

      hostTypes.push({
        position,
        switchHost: this.switchHost.bind(this, position),
      });
    }

    this.component.setCurrentHostType(this.hostType);
    this.component.setHostTypes(hostTypes);
  },

  postMessage(msg) {
    // We sometime try to send messages in middle of destroy(), where the
    // toolbox iframe may already be detached.
    if (!this._destroyer) {
      // Toolbox document is still chrome and disallow identifying message
      // origin via event.source as it is null. So use a custom id.
      msg.frameId = this.frameId;
      this.topWindow.postMessage(msg, "*");
    }
  },

  /**
   * This will fetch the panel definitions from the constants in definitions module
   * and populate the state within the ToolboxController component.
   */

  async _buildInitialPanelDefinitions() {
    // Get the initial list of tab definitions. This list can be amended at a later time
    // by tools registering themselves.
    const definitions = gDevTools.getToolDefinitionArray();
    definitions.forEach(definition => this._buildPanelForTool(definition));

    // Get the definitions that will only affect the main tab area.
    this.panelDefinitions = definitions.filter(
      definition =>
        definition.isToolSupported(this) && definition.id !== "options"
    );
  },

  async _setInitialMeatballState() {
    let disableAutohide, pseudoLocale;
    // Popup auto-hide disabling is only available in browser toolbox and webextension toolboxes.
    if (
      this.isBrowserToolbox ||
      this._descriptorFront.isWebExtensionDescriptor
    ) {
      disableAutohide = await this._isDisableAutohideEnabled();
    }
    // Pseudo locale items are only displayed in the browser toolbox
    if (this.isBrowserToolbox) {
      pseudoLocale = await this.getPseudoLocale();
    }
    // Parallelize the asynchronous calls, so that the DOM is only updated once when
    // updating the React components.
    if (typeof disableAutohide == "boolean") {
      this.component.setDisableAutohide(disableAutohide);
    }
    if (typeof pseudoLocale == "string") {
      this.component.setPseudoLocale(pseudoLocale);
    }
    if (
      this._descriptorFront.isWebExtensionDescriptor &&
      this.hostType === Toolbox.HostType.WINDOW
    ) {
      const alwaysOnTop = Services.prefs.getBoolPref(
        DEVTOOLS_ALWAYS_ON_TOP,
        false
      );
      this.component.setAlwaysOnTop(alwaysOnTop);
    }
  },

  /**
   * Initiate toolbox React components and all it's properties. Do the initial render.
   *
   * @param {Object} fluentBundles
   *        A FluentBundle instance used to display any localized text in the React component.
   */

  _mountReactComponent(fluentBundles) {
    // Ensure the toolbar doesn't try to render until the tool is ready.
    const element = this.React.createElement(
      this.AppErrorBoundary,
      {
        componentName: "General",
        panel: L10N.getStr("webDeveloperToolsMenu.label"),
      },
      this.React.createElement(this.ToolboxController, {
        ref: r => {
          this.component = r;
        },
        L10N,
--> --------------------

--> maximum size reached

--> --------------------

100%


¤ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.