tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

thread.js (74476B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 // protocol.js uses objects as exceptions in order to define
      8 // error packets.
      9 /* eslint-disable no-throw-literal */
     10 
     11 const { Actor } = require("resource://devtools/shared/protocol/Actor.js");
     12 const { Pool } = require("resource://devtools/shared/protocol/Pool.js");
     13 const { threadSpec } = require("resource://devtools/shared/specs/thread.js");
     14 
     15 const {
     16  createValueGrip,
     17 } = require("resource://devtools/server/actors/object/utils.js");
     18 const {
     19  ObjectActorPool,
     20 } = require("resource://devtools/server/actors/object/ObjectActorPool.js");
     21 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
     22 const Debugger = require("Debugger");
     23 const { assert, dumpn, reportException } = DevToolsUtils;
     24 const {
     25  getAvailableEventBreakpoints,
     26  eventBreakpointForNotification,
     27  eventsRequireNotifications,
     28  firstStatementBreakpointId,
     29  makeEventBreakpointMessage,
     30 } = require("resource://devtools/server/actors/utils/event-breakpoints.js");
     31 const {
     32  WatchpointMap,
     33 } = require("resource://devtools/server/actors/utils/watchpoint-map.js");
     34 
     35 const Targets = require("devtools/server/actors/targets/index");
     36 
     37 loader.lazyRequireGetter(
     38  this,
     39  "logEvent",
     40  "resource://devtools/server/actors/utils/logEvent.js",
     41  true
     42 );
     43 loader.lazyRequireGetter(
     44  this,
     45  "EnvironmentActor",
     46  "resource://devtools/server/actors/environment.js",
     47  true
     48 );
     49 loader.lazyRequireGetter(
     50  this,
     51  "BreakpointActorMap",
     52  "resource://devtools/server/actors/utils/breakpoint-actor-map.js",
     53  true
     54 );
     55 loader.lazyRequireGetter(
     56  this,
     57  "EventLoop",
     58  "resource://devtools/server/actors/utils/event-loop.js",
     59  true
     60 );
     61 loader.lazyRequireGetter(
     62  this,
     63  ["FrameActor", "getSavedFrameParent", "isValidSavedFrame"],
     64  "resource://devtools/server/actors/frame.js",
     65  true
     66 );
     67 loader.lazyRequireGetter(
     68  this,
     69  "HighlighterEnvironment",
     70  "resource://devtools/server/actors/highlighters.js",
     71  true
     72 );
     73 loader.lazyRequireGetter(
     74  this,
     75  "PausedDebuggerOverlay",
     76  "resource://devtools/server/actors/highlighters/paused-debugger.js",
     77  true
     78 );
     79 
     80 const PROMISE_REACTIONS = new WeakMap();
     81 function cacheReactionsForFrame(frame) {
     82  if (frame.asyncPromise) {
     83    const reactions = frame.asyncPromise.getPromiseReactions();
     84    const existingReactions = PROMISE_REACTIONS.get(frame.asyncPromise);
     85    if (
     86      reactions.length &&
     87      (!existingReactions || reactions.length > existingReactions.length)
     88    ) {
     89      PROMISE_REACTIONS.set(frame.asyncPromise, reactions);
     90    }
     91  }
     92 }
     93 
     94 function createStepForReactionTracking(onStep) {
     95  return function () {
     96    cacheReactionsForFrame(this);
     97    return onStep ? onStep.apply(this, arguments) : undefined;
     98  };
     99 }
    100 
    101 const getAsyncParentFrame = frame => {
    102  if (!frame.asyncPromise) {
    103    return null;
    104  }
    105 
    106  // We support returning Frame actors for frames that are suspended
    107  // at an 'await', and here we want to walk upward to look for the first
    108  // frame that will be resumed when the current frame's promise resolves.
    109  let reactions =
    110    PROMISE_REACTIONS.get(frame.asyncPromise) ||
    111    frame.asyncPromise.getPromiseReactions();
    112 
    113  // eslint-disable-next-line no-constant-condition
    114  while (true) {
    115    // We loop here because we may have code like:
    116    //
    117    //   async function inner(){ debugger; }
    118    //
    119    //   async function outer() {
    120    //     await Promise.resolve().then(() => inner());
    121    //   }
    122    //
    123    // where we can see that when `inner` resolves, we will resume from
    124    // `outer`, even though there is a layer of promises between, and
    125    // that layer could be any number of promises deep.
    126    if (!(reactions[0] instanceof Debugger.Object)) {
    127      break;
    128    }
    129 
    130    reactions = reactions[0].getPromiseReactions();
    131  }
    132 
    133  if (reactions[0] instanceof Debugger.Frame) {
    134    return reactions[0];
    135  }
    136  return null;
    137 };
    138 const RESTARTED_FRAMES = new WeakSet();
    139 
    140 // Thread actor possible states:
    141 const STATES = {
    142  //  Before ThreadActor.attach is called:
    143  DETACHED: "detached",
    144  //  After the actor is destroyed:
    145  EXITED: "exited",
    146 
    147  // States possible in between DETACHED AND EXITED:
    148  // Default state, when the thread isn't paused,
    149  RUNNING: "running",
    150  // When paused on any type of breakpoint, or, when the client requested an interrupt.
    151  PAUSED: "paused",
    152 };
    153 exports.STATES = STATES;
    154 
    155 // Possible values for the `why.type` attribute in "paused" event
    156 const PAUSE_REASONS = {
    157  ALREADY_PAUSED: "alreadyPaused",
    158  INTERRUPTED: "interrupted", // Associated with why.onNext attribute
    159  MUTATION_BREAKPOINT: "mutationBreakpoint", // Associated with why.mutationType and why.message attributes
    160  DEBUGGER_STATEMENT: "debuggerStatement",
    161  EXCEPTION: "exception",
    162  XHR: "XHR",
    163  EVENT_BREAKPOINT: "eventBreakpoint",
    164  RESUME_LIMIT: "resumeLimit",
    165 };
    166 exports.PAUSE_REASONS = PAUSE_REASONS;
    167 
    168 class ThreadActor extends Actor {
    169  /**
    170   * Creates a ThreadActor.
    171   *
    172   * ThreadActors manage execution/inspection of debuggees.
    173   *
    174   * @param {TargetActor} targetActor
    175   *        This `ThreadActor`'s parent actor. i.e. one of the many Target actors.
    176   */
    177  constructor(targetActor) {
    178    super(targetActor.conn, threadSpec);
    179 
    180    // This attribute is used by various other actors to find the target actor
    181    this.targetActor = targetActor;
    182 
    183    this._state = STATES.DETACHED;
    184    this._options = {
    185      skipBreakpoints: false,
    186    };
    187    this._gripDepth = 0;
    188    this._targetActorClosed = false;
    189    this._observingNetwork = false;
    190    this._shouldShowPauseOverlay = true;
    191    this._frameActors = [];
    192    this._xhrBreakpoints = [];
    193 
    194    this._dbg = null;
    195    this._threadLifetimePool = null;
    196    this._activeEventPause = null;
    197    this._pauseOverlay = null;
    198    this._priorPause = null;
    199 
    200    this._activeEventBreakpoints = new Set();
    201    this._frameActorMap = new WeakMap();
    202    this._debuggerSourcesSeen = new WeakSet();
    203 
    204    // A Set of URLs string to watch for when new sources are found by
    205    // the debugger instance.
    206    this._onLoadBreakpointURLs = new Set();
    207 
    208    // A WeakMap from Debugger.Frame to an exception value which will be ignored
    209    // when deciding to pause if the value is thrown by the frame. When we are
    210    // pausing on exceptions then we only want to pause when the youngest frame
    211    // throws a particular exception, instead of for all older frames as well.
    212    this._handledFrameExceptions = new WeakMap();
    213 
    214    this._watchpointsMap = new WatchpointMap(this);
    215 
    216    this.breakpointActorMap = new BreakpointActorMap(this);
    217 
    218    this._nestedEventLoop = new EventLoop({
    219      thread: this,
    220    });
    221 
    222    this.onNewSourceEvent = this.onNewSourceEvent.bind(this);
    223 
    224    this.createCompletionGrip = this.createCompletionGrip.bind(this);
    225    this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
    226    this.onNewScript = this.onNewScript.bind(this);
    227    this._onOpeningRequest = this._onOpeningRequest.bind(this);
    228    this._onNewDebuggee = this._onNewDebuggee.bind(this);
    229    this._onExceptionUnwind = this._onExceptionUnwind.bind(this);
    230    this._eventBreakpointListener = this._eventBreakpointListener.bind(this);
    231    this._onWindowReady = this._onWindowReady.bind(this);
    232    this._onWillNavigate = this._onWillNavigate.bind(this);
    233    this._onNavigate = this._onNavigate.bind(this);
    234 
    235    this.targetActor.on("window-ready", this._onWindowReady);
    236    this.targetActor.on("will-navigate", this._onWillNavigate);
    237    this.targetActor.on("navigate", this._onNavigate);
    238 
    239    this._firstStatementBreakpoint = null;
    240    this._debuggerNotificationObserver = new DebuggerNotificationObserver();
    241  }
    242 
    243  // Used by the ObjectActor to keep track of the depth of grip() calls.
    244  _gripDepth = null;
    245 
    246  get dbg() {
    247    if (!this._dbg) {
    248      this._dbg = this.targetActor.dbg;
    249      // Keep the debugger disabled until a client attaches.
    250      if (this._state === STATES.DETACHED) {
    251        this._dbg.disable();
    252      } else {
    253        this._dbg.enable();
    254      }
    255    }
    256    return this._dbg;
    257  }
    258 
    259  // Current state of the thread actor:
    260  //  - detached: state, before ThreadActor.attach is called,
    261  //  - exited: state, after the actor is destroyed,
    262  // States possible in between these two states:
    263  //  - running: default state, when the thread isn't paused,
    264  //  - paused: state, when paused on any type of breakpoint, or, when the client requested an interrupt.
    265  get state() {
    266    return this._state;
    267  }
    268 
    269  // XXX: soon to be equivalent to !isDestroyed once the thread actor is initialized on target creation.
    270  get attached() {
    271    return this.state == STATES.RUNNING || this.state == STATES.PAUSED;
    272  }
    273 
    274  get pauseLifetimePool() {
    275    return this._pausePool;
    276  }
    277 
    278  get threadLifetimePool() {
    279    if (!this._threadLifetimePool) {
    280      this._threadLifetimePool = new ObjectActorPool(this, "thread", true);
    281      this._threadLifetimePool.objectActors = new WeakMap();
    282    }
    283    return this._threadLifetimePool;
    284  }
    285 
    286  getThreadLifetimeObject(raw) {
    287    return this.threadLifetimePool.objectActors.get(raw);
    288  }
    289 
    290  promoteObjectToThreadLifetime(objectActor) {
    291    this.threadLifetimePool.manage(objectActor);
    292    this.threadLifetimePool.objectActors.set(objectActor.obj, objectActor);
    293  }
    294 
    295  get sourcesManager() {
    296    return this.targetActor.sourcesManager;
    297  }
    298 
    299  get breakpoints() {
    300    return this.targetActor.breakpoints;
    301  }
    302 
    303  get youngestFrame() {
    304    if (this.state != STATES.PAUSED) {
    305      return null;
    306    }
    307    return this.dbg.getNewestFrame();
    308  }
    309 
    310  get shouldSkipAnyBreakpoint() {
    311    return (
    312      // Disable all types of breakpoints if:
    313      // - the user explicitly requested it via the option
    314      this._options.skipBreakpoints ||
    315      // - or when we are evaluating some javascript via the console actor and disableBreaks
    316      //   has been set to true (which happens for most evaluating except the console input)
    317      this.insideClientEvaluation?.disableBreaks
    318    );
    319  }
    320 
    321  isPaused() {
    322    return this._state === STATES.PAUSED;
    323  }
    324 
    325  lastPausedPacket() {
    326    return this._priorPause;
    327  }
    328 
    329  /**
    330   * Remove all debuggees and clear out the thread's sources.
    331   */
    332  clearDebuggees() {
    333    if (this._dbg) {
    334      this.dbg.removeAllDebuggees();
    335    }
    336  }
    337 
    338  /**
    339   * Destroy the debugger and put the actor in the exited state.
    340   *
    341   * As part of destroy, we: clean up listeners, debuggees and
    342   * clear actor pools associated with the lifetime of this actor.
    343   */
    344  destroy() {
    345    dumpn("in ThreadActor.prototype.destroy");
    346    if (this._state == STATES.PAUSED) {
    347      this.doResume();
    348    }
    349 
    350    this.removeAllWatchpoints();
    351    this._xhrBreakpoints = [];
    352    this._updateNetworkObserver();
    353 
    354    this._activeEventBreakpoints = new Set();
    355    this._debuggerNotificationObserver.removeListener(
    356      this._eventBreakpointListener
    357    );
    358 
    359    for (const global of this.dbg.getDebuggees()) {
    360      try {
    361        this._debuggerNotificationObserver.disconnect(
    362          global.unsafeDereference()
    363        );
    364      } catch (e) {}
    365    }
    366 
    367    this.targetActor.off("window-ready", this._onWindowReady);
    368    this.targetActor.off("will-navigate", this._onWillNavigate);
    369    this.targetActor.off("navigate", this._onNavigate);
    370 
    371    this.sourcesManager.off("newSource", this.onNewSourceEvent);
    372    this.clearDebuggees();
    373    this._threadLifetimePool.destroy();
    374    this._threadLifetimePool = null;
    375    this._dbg = null;
    376    this._state = STATES.EXITED;
    377 
    378    super.destroy();
    379  }
    380 
    381  /**
    382   * Tells if the thread actor has been initialized/attached on target creation
    383   * by the server codebase. (And not late, from the frontend, by the TargetMixinFront class)
    384   */
    385  isAttached() {
    386    return !!this.alreadyAttached;
    387  }
    388 
    389  // Request handlers
    390  attach(options) {
    391    // Note that the client avoids trying to call attach if already attached.
    392    // But just in case, avoid any possible duplicate call to attach.
    393    if (this.alreadyAttached) {
    394      return;
    395    }
    396 
    397    if (this.state === STATES.EXITED) {
    398      throw {
    399        error: "exited",
    400        message: "threadActor has exited",
    401      };
    402    }
    403 
    404    if (this.state !== STATES.DETACHED) {
    405      throw {
    406        error: "wrongState",
    407        message: "Current state is " + this.state,
    408      };
    409    }
    410 
    411    this.dbg.onDebuggerStatement = this.onDebuggerStatement;
    412    this.dbg.onNewScript = this.onNewScript;
    413    this.dbg.onNewDebuggee = this._onNewDebuggee;
    414 
    415    this.sourcesManager.on("newSource", this.onNewSourceEvent);
    416 
    417    this.reconfigure(options);
    418 
    419    // Switch state from DETACHED to RUNNING
    420    this._state = STATES.RUNNING;
    421 
    422    this.alreadyAttached = true;
    423    this.dbg.enable();
    424 
    425    if (Services.obs) {
    426      // Set a wrappedJSObject property so |this| can be sent via the observer service
    427      // for the xpcshell harness.
    428      this.wrappedJSObject = this;
    429      Services.obs.notifyObservers(this, "devtools-thread-ready");
    430    }
    431  }
    432 
    433  toggleEventLogging(logEventBreakpoints) {
    434    this._options.logEventBreakpoints = logEventBreakpoints;
    435    return this._options.logEventBreakpoints;
    436  }
    437 
    438  get pauseOverlay() {
    439    if (this._pauseOverlay) {
    440      return this._pauseOverlay;
    441    }
    442 
    443    const env = new HighlighterEnvironment();
    444    env.initFromTargetActor(this.targetActor);
    445    const highlighter = new PausedDebuggerOverlay(env, {
    446      resume: () => this.resume(null),
    447      stepOver: () => this.resume({ type: "next" }),
    448    });
    449    this._pauseOverlay = highlighter;
    450    return highlighter;
    451  }
    452 
    453  _canShowOverlay() {
    454    // Only attempt to show on overlay on WindowGlobal targets, which displays a document.
    455    // Workers and content processes can't display any overlay.
    456    if (this.targetActor.targetType != Targets.TYPES.FRAME) {
    457      return false;
    458    }
    459 
    460    const { window } = this.targetActor;
    461 
    462    // The CanvasFrameAnonymousContentHelper class we're using to create the paused overlay
    463    // need to have access to a documentElement.
    464    // We might have access to a non-chrome window getter that is a Sandox (e.g. in the
    465    // case of ContentProcessTargetActor).
    466    if (!window?.document?.documentElement) {
    467      return false;
    468    }
    469 
    470    // Ignore privileged document (top level window, special about:* pages, …).
    471    if (window.isChromeWindow) {
    472      return false;
    473    }
    474 
    475    return true;
    476  }
    477 
    478  async showOverlay() {
    479    if (
    480      !this._shouldShowPauseOverlay ||
    481      !this.isPaused() ||
    482      !this._canShowOverlay()
    483    ) {
    484      return;
    485    }
    486 
    487    const reason = this._priorPause.why.type;
    488    await this.pauseOverlay.isReady;
    489 
    490    // we might not be paused anymore.
    491    if (!this.isPaused()) {
    492      return;
    493    }
    494 
    495    this.pauseOverlay.show(reason);
    496  }
    497 
    498  hideOverlay() {
    499    if (this._canShowOverlay() && this._pauseOverlay) {
    500      this.pauseOverlay.hide();
    501    }
    502  }
    503 
    504  /**
    505   * Tell the thread to automatically add a breakpoint on the first line of
    506   * a given file, when it is first loaded.
    507   *
    508   * This is currently only used by the xpcshell test harness, and unless
    509   * we decide to expand the scope of this feature, we should keep it that way.
    510   */
    511  setBreakpointOnLoad(urls) {
    512    this._onLoadBreakpointURLs = new Set(urls);
    513  }
    514 
    515  _findXHRBreakpointIndex(p, m) {
    516    return this._xhrBreakpoints.findIndex(
    517      ({ path, method }) => path === p && method === m
    518    );
    519  }
    520 
    521  // We clear the priorPause field when a breakpoint is added or removed
    522  // at the same location because we are no longer worried about pausing twice
    523  // at that location (e.g. debugger statement, stepping).
    524  _maybeClearPriorPause(location) {
    525    if (!this._priorPause) {
    526      return;
    527    }
    528 
    529    const { where } = this._priorPause.frame;
    530    if (where.line === location.line && where.column === location.column) {
    531      this._priorPause = null;
    532    }
    533  }
    534 
    535  async setBreakpoint(location, options) {
    536    // Automatically initialize the thread actor if it wasn't yet done.
    537    // Note that ideally, it should rather be done via reconfigure/thread configuration.
    538    if (this._state === STATES.DETACHED) {
    539      this.attach({});
    540      this.addAllSources();
    541    }
    542 
    543    let actor = this.breakpointActorMap.get(location);
    544    // Avoid resetting the exact same breakpoint twice
    545    if (actor && JSON.stringify(actor.options) == JSON.stringify(options)) {
    546      return;
    547    }
    548    if (!actor) {
    549      actor = this.breakpointActorMap.getOrCreateBreakpointActor(location);
    550    }
    551    actor.setOptions(options);
    552    this._maybeClearPriorPause(location);
    553 
    554    if (location.sourceUrl) {
    555      // There can be multiple source actors for a URL if there are multiple
    556      // inline sources on an HTML page.
    557      const sourceActors = this.sourcesManager.getSourceActorsByURL(
    558        location.sourceUrl
    559      );
    560      for (const sourceActor of sourceActors) {
    561        await sourceActor.applyBreakpoint(actor);
    562      }
    563    } else {
    564      const sourceActor = this.sourcesManager.getSourceActorById(
    565        location.sourceId
    566      );
    567      if (sourceActor) {
    568        await sourceActor.applyBreakpoint(actor);
    569      }
    570    }
    571  }
    572 
    573  removeBreakpoint(location) {
    574    const actor = this.breakpointActorMap.getOrCreateBreakpointActor(location);
    575    this._maybeClearPriorPause(location);
    576    actor.delete();
    577  }
    578 
    579  removeAllXHRBreakpoints() {
    580    this._xhrBreakpoints = [];
    581    return this._updateNetworkObserver();
    582  }
    583 
    584  removeXHRBreakpoint(path, method) {
    585    const index = this._findXHRBreakpointIndex(path, method);
    586 
    587    if (index >= 0) {
    588      this._xhrBreakpoints.splice(index, 1);
    589    }
    590    return this._updateNetworkObserver();
    591  }
    592 
    593  setXHRBreakpoint(path, method) {
    594    // request.path is a string,
    595    // If requested url contains the path, then we pause.
    596    const index = this._findXHRBreakpointIndex(path, method);
    597 
    598    if (index === -1) {
    599      this._xhrBreakpoints.push({ path, method });
    600    }
    601    return this._updateNetworkObserver();
    602  }
    603 
    604  getAvailableEventBreakpoints() {
    605    return getAvailableEventBreakpoints(this.targetActor.targetGlobal);
    606  }
    607  getActiveEventBreakpoints() {
    608    return Array.from(this._activeEventBreakpoints);
    609  }
    610 
    611  /**
    612   * Add event breakpoints to the list of active event breakpoints
    613   *
    614   * @param {Array<string>} ids: events to add (e.g. ["event.mouse.click","event.mouse.mousedown"])
    615   */
    616  addEventBreakpoints(ids) {
    617    this.setActiveEventBreakpoints(
    618      this.getActiveEventBreakpoints().concat(ids)
    619    );
    620  }
    621 
    622  /**
    623   * Remove event breakpoints from the list of active event breakpoints
    624   *
    625   * @param {Array<string>} ids: events to remove (e.g. ["event.mouse.click","event.mouse.mousedown"])
    626   */
    627  removeEventBreakpoints(ids) {
    628    this.setActiveEventBreakpoints(
    629      this.getActiveEventBreakpoints().filter(eventBp => !ids.includes(eventBp))
    630    );
    631  }
    632 
    633  /**
    634   * Set the the list of active event breakpoints
    635   *
    636   * @param {Array<string>} ids: events to add breakpoint for (e.g. ["event.mouse.click","event.mouse.mousedown"])
    637   */
    638  setActiveEventBreakpoints(ids) {
    639    this._activeEventBreakpoints = new Set(ids);
    640 
    641    if (eventsRequireNotifications(ids)) {
    642      this._debuggerNotificationObserver.addListener(
    643        this._eventBreakpointListener
    644      );
    645    } else {
    646      this._debuggerNotificationObserver.removeListener(
    647        this._eventBreakpointListener
    648      );
    649    }
    650 
    651    if (this._activeEventBreakpoints.has(firstStatementBreakpointId())) {
    652      this._ensureFirstStatementBreakpointInitialized();
    653 
    654      this._firstStatementBreakpoint.hit = frame =>
    655        this._pauseAndRespondEventBreakpoint(
    656          frame,
    657          firstStatementBreakpointId()
    658        );
    659    } else if (this._firstStatementBreakpoint) {
    660      // Disabling the breakpoint disables the feature as much as we need it
    661      // to. We do not bother removing breakpoints from the scripts themselves
    662      // here because the breakpoints will be a no-op if `hit` is `null`, and
    663      // if we wanted to remove them, we'd need a way to iterate through them
    664      // all, which would require us to hold strong references to them, which
    665      // just isn't needed. Plus, if the user disables and then re-enables the
    666      // feature again later, the breakpoints will still be there to work.
    667      this._firstStatementBreakpoint.hit = null;
    668    }
    669  }
    670 
    671  _ensureFirstStatementBreakpointInitialized() {
    672    if (this._firstStatementBreakpoint) {
    673      return;
    674    }
    675 
    676    this._firstStatementBreakpoint = { hit: null };
    677    for (const script of this.dbg.findScripts()) {
    678      this._maybeTrackFirstStatementBreakpoint(script);
    679    }
    680  }
    681 
    682  _maybeTrackFirstStatementBreakpointForNewGlobal(global) {
    683    if (this._firstStatementBreakpoint) {
    684      for (const script of this.dbg.findScripts({ global })) {
    685        this._maybeTrackFirstStatementBreakpoint(script);
    686      }
    687    }
    688  }
    689 
    690  _maybeTrackFirstStatementBreakpoint(script) {
    691    if (
    692      // If the feature is not enabled yet, there is nothing to do.
    693      !this._firstStatementBreakpoint ||
    694      // WASM files don't have a first statement.
    695      script.format !== "js" ||
    696      // All "top-level" scripts are non-functions, whether that's because
    697      // the script is a module, a global script, or an eval or what.
    698      script.isFunction
    699    ) {
    700      return;
    701    }
    702 
    703    const bps = script.getPossibleBreakpoints();
    704 
    705    // Scripts aren't guaranteed to have a step start if for instance the
    706    // file contains only function declarations, so in that case we try to
    707    // fall back to whatever we can find.
    708    let meta = bps.find(bp => bp.isStepStart) || bps[0];
    709    if (!meta) {
    710      // We've tried to avoid using `getAllColumnOffsets()` because the set of
    711      // locations included in this list is very under-defined, but for this
    712      // usecase it's not the end of the world. Maybe one day we could have an
    713      // "onEnterFrame" that was scoped to a specific script to avoid this.
    714      meta = script.getAllColumnOffsets()[0];
    715    }
    716 
    717    if (!meta) {
    718      // Not certain that this is actually possible, but including for sanity
    719      // so that we don't throw unexpectedly.
    720      return;
    721    }
    722    script.setBreakpoint(meta.offset, this._firstStatementBreakpoint);
    723  }
    724 
    725  _onNewDebuggee(global) {
    726    this._maybeTrackFirstStatementBreakpointForNewGlobal(global);
    727    try {
    728      this._debuggerNotificationObserver.connect(global.unsafeDereference());
    729    } catch (e) {}
    730  }
    731 
    732  _updateNetworkObserver() {
    733    // Workers don't have access to `Services` and even if they did, network
    734    // requests are all dispatched to the main thread, so there would be
    735    // nothing here to listen for. We'll need to revisit implementing
    736    // XHR breakpoints for workers.
    737    if (isWorker) {
    738      return false;
    739    }
    740 
    741    if (this._xhrBreakpoints.length && !this._observingNetwork) {
    742      this._observingNetwork = true;
    743      Services.obs.addObserver(
    744        this._onOpeningRequest,
    745        "http-on-opening-request"
    746      );
    747    } else if (this._xhrBreakpoints.length === 0 && this._observingNetwork) {
    748      this._observingNetwork = false;
    749      Services.obs.removeObserver(
    750        this._onOpeningRequest,
    751        "http-on-opening-request"
    752      );
    753    }
    754 
    755    return true;
    756  }
    757 
    758  _onOpeningRequest(subject) {
    759    if (this.shouldSkipAnyBreakpoint) {
    760      return;
    761    }
    762 
    763    const channel = subject.QueryInterface(Ci.nsIHttpChannel);
    764    const url = channel.URI.asciiSpec;
    765    const requestMethod = channel.requestMethod;
    766 
    767    let causeType = Ci.nsIContentPolicy.TYPE_OTHER;
    768    if (channel.loadInfo) {
    769      causeType = channel.loadInfo.externalContentPolicyType;
    770    }
    771 
    772    const isXHR =
    773      causeType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST ||
    774      causeType === Ci.nsIContentPolicy.TYPE_FETCH;
    775 
    776    if (!isXHR) {
    777      // We currently break only if the request is either fetch or xhr
    778      return;
    779    }
    780 
    781    let shouldPause = false;
    782    for (const { path, method } of this._xhrBreakpoints) {
    783      if (method !== "ANY" && method !== requestMethod) {
    784        continue;
    785      }
    786      if (url.includes(path)) {
    787        shouldPause = true;
    788        break;
    789      }
    790    }
    791 
    792    if (shouldPause) {
    793      const frame = this.dbg.getNewestFrame();
    794 
    795      // If there is no frame, this request was dispatched by logic that isn't
    796      // primarily JS, so pausing the event loop wouldn't make sense.
    797      // This covers background requests like loading the initial page document,
    798      // or loading favicons. This also includes requests dispatched indirectly
    799      // from workers. We'll need to handle them separately in the future.
    800      if (frame) {
    801        this._pauseAndRespond(frame, { type: PAUSE_REASONS.XHR });
    802      }
    803    }
    804  }
    805 
    806  reconfigure(options = {}) {
    807    if (this.state == STATES.EXITED) {
    808      throw {
    809        error: "wrongState",
    810      };
    811    }
    812    this._options = { ...this._options, ...options };
    813 
    814    if ("observeAsmJS" in options) {
    815      this.dbg.allowUnobservedAsmJS = !options.observeAsmJS;
    816    }
    817    if ("observeWasm" in options) {
    818      this.dbg.allowUnobservedWasm = !options.observeWasm;
    819    }
    820    if ("pauseOverlay" in options) {
    821      this._shouldShowPauseOverlay = !!options.pauseOverlay;
    822      if (this.isPaused()) {
    823        if (!this._shouldShowPauseOverlay) {
    824          this.hideOverlay();
    825        } else {
    826          this.showOverlay();
    827        }
    828      }
    829    }
    830 
    831    if (
    832      "pauseWorkersUntilAttach" in options &&
    833      this.targetActor.pauseWorkersUntilAttach
    834    ) {
    835      this.targetActor.pauseWorkersUntilAttach(options.pauseWorkersUntilAttach);
    836    }
    837 
    838    if (options.breakpoints) {
    839      for (const breakpoint of Object.values(options.breakpoints)) {
    840        this.setBreakpoint(breakpoint.location, breakpoint.options);
    841      }
    842    }
    843 
    844    if (options.eventBreakpoints) {
    845      this.setActiveEventBreakpoints(options.eventBreakpoints);
    846    }
    847 
    848    // Only consider this options if an explicit boolean value is passed.
    849    if (typeof this._options.shouldPauseOnDebuggerStatement == "boolean") {
    850      this.setPauseOnDebuggerStatement(
    851        this._options.shouldPauseOnDebuggerStatement
    852      );
    853    }
    854    this.setPauseOnExceptions(this._options.pauseOnExceptions);
    855  }
    856 
    857  _eventBreakpointListener(notification) {
    858    if (this._state === STATES.PAUSED || this._state === STATES.DETACHED) {
    859      return;
    860    }
    861 
    862    const eventBreakpoint = eventBreakpointForNotification(
    863      this.dbg,
    864      notification
    865    );
    866 
    867    if (!this._activeEventBreakpoints.has(eventBreakpoint)) {
    868      return;
    869    }
    870 
    871    if (notification.phase === "pre" && !this._activeEventPause) {
    872      this._activeEventPause = this._captureDebuggerHooks();
    873 
    874      this.dbg.onEnterFrame =
    875        this._makeEventBreakpointEnterFrame(eventBreakpoint);
    876    } else if (notification.phase === "post" && this._activeEventPause) {
    877      this._restoreDebuggerHooks(this._activeEventPause);
    878      this._activeEventPause = null;
    879    } else if (!notification.phase && !this._activeEventPause) {
    880      const frame = this.dbg.getNewestFrame();
    881      if (frame) {
    882        if (this.sourcesManager.isFrameBlackBoxed(frame)) {
    883          return;
    884        }
    885 
    886        this._pauseAndRespondEventBreakpoint(frame, eventBreakpoint);
    887      }
    888    }
    889  }
    890 
    891  _makeEventBreakpointEnterFrame(eventBreakpoint) {
    892    return frame => {
    893      if (this.sourcesManager.isFrameBlackBoxed(frame)) {
    894        return undefined;
    895      }
    896 
    897      this._restoreDebuggerHooks(this._activeEventPause);
    898      this._activeEventPause = null;
    899 
    900      return this._pauseAndRespondEventBreakpoint(frame, eventBreakpoint);
    901    };
    902  }
    903 
    904  _pauseAndRespondEventBreakpoint(frame, eventBreakpoint) {
    905    if (this.shouldSkipAnyBreakpoint) {
    906      return undefined;
    907    }
    908 
    909    if (this._options.logEventBreakpoints) {
    910      return logEvent({ threadActor: this, frame });
    911    }
    912 
    913    return this._pauseAndRespond(frame, {
    914      type: PAUSE_REASONS.EVENT_BREAKPOINT,
    915      breakpoint: eventBreakpoint,
    916      message: makeEventBreakpointMessage(eventBreakpoint),
    917    });
    918  }
    919 
    920  _captureDebuggerHooks() {
    921    return {
    922      onEnterFrame: this.dbg.onEnterFrame,
    923      onStep: this.dbg.onStep,
    924      onPop: this.dbg.onPop,
    925    };
    926  }
    927 
    928  _restoreDebuggerHooks(hooks) {
    929    this.dbg.onEnterFrame = hooks.onEnterFrame;
    930    this.dbg.onStep = hooks.onStep;
    931    this.dbg.onPop = hooks.onPop;
    932  }
    933 
    934  /**
    935   * Pause the debuggee, by entering a nested event loop, and return a 'paused'
    936   * packet to the client.
    937   *
    938   * @param Debugger.Frame frame
    939   *        The newest debuggee frame in the stack.
    940   * @param object reason
    941   *        An object with a 'type' property containing the reason for the pause.
    942   * @param function onPacket
    943   *        Hook to modify the packet before it is sent. Feel free to return a
    944   *        promise.
    945   */
    946  _pauseAndRespond(frame, reason, onPacket = k => k) {
    947    try {
    948      const packet = this._paused(frame);
    949      if (!packet) {
    950        return undefined;
    951      }
    952 
    953      const { sourceActor, line, column } =
    954        this.sourcesManager.getFrameLocation(frame);
    955 
    956      packet.why = reason;
    957 
    958      if (!sourceActor) {
    959        // If the frame location is in a source that not pass the 'isHiddenSource'
    960        // check and thus has no actor, we do not bother pausing.
    961        return undefined;
    962      }
    963 
    964      packet.frame.where = {
    965        actor: sourceActor.actorID,
    966        line,
    967        column,
    968      };
    969      const pkt = onPacket(packet);
    970 
    971      this._priorPause = pkt;
    972      this.emit("paused", pkt);
    973      this.showOverlay();
    974    } catch (error) {
    975      reportException("DBG-SERVER", error);
    976      this.conn.send({
    977        error: "unknownError",
    978        message: error.message + "\n" + error.stack,
    979      });
    980      return undefined;
    981    }
    982 
    983    try {
    984      this._nestedEventLoop.enter();
    985    } catch (e) {
    986      reportException("TA__pauseAndRespond", e);
    987    }
    988 
    989    if (this._requestedFrameRestart) {
    990      return null;
    991    }
    992 
    993    // If the parent actor has been closed, terminate the debuggee script
    994    // instead of continuing. Executing JS after the content window is gone is
    995    // a bad idea.
    996    return this._targetActorClosed ? null : undefined;
    997  }
    998 
    999  _makeOnEnterFrame() {
   1000    return frame => {
   1001      if (this._requestedFrameRestart) {
   1002        return null;
   1003      }
   1004 
   1005      // Continue forward until we get to a valid step target.
   1006      const { onStep, onPop } = this._makeSteppingHooks({
   1007        steppingType: "next",
   1008      });
   1009 
   1010      if (this.sourcesManager.isFrameBlackBoxed(frame)) {
   1011        return undefined;
   1012      }
   1013 
   1014      frame.onStep = onStep;
   1015      frame.onPop = onPop;
   1016      return undefined;
   1017    };
   1018  }
   1019 
   1020  _makeOnPop({ pauseAndRespond, steppingType }) {
   1021    const thread = this;
   1022    return function (completion) {
   1023      if (thread._requestedFrameRestart === this) {
   1024        return thread.restartFrame(this);
   1025      }
   1026 
   1027      // onPop is called when we temporarily leave an async/generator
   1028      if (steppingType != "finish" && (completion.await || completion.yield)) {
   1029        thread.suspendedFrame = this;
   1030        thread.dbg.onEnterFrame = undefined;
   1031        return undefined;
   1032      }
   1033 
   1034      // Note that we're popping this frame; we need to watch for
   1035      // subsequent step events on its caller.
   1036      this.reportedPop = true;
   1037 
   1038      // Cache the frame so that the onPop and onStep hooks are cleared
   1039      // on the next pause.
   1040      thread.suspendedFrame = this;
   1041 
   1042      if (
   1043        steppingType != "finish" &&
   1044        !thread.sourcesManager.isFrameBlackBoxed(this)
   1045      ) {
   1046        const pauseAndRespValue = pauseAndRespond(this, packet =>
   1047          thread.createCompletionGrip(packet, completion)
   1048        );
   1049 
   1050        // If the requested frame to restart differs from this frame, we don't
   1051        // need to restart it at this point.
   1052        if (thread._requestedFrameRestart === this) {
   1053          return thread.restartFrame(this);
   1054        }
   1055 
   1056        return pauseAndRespValue;
   1057      }
   1058 
   1059      thread._attachSteppingHooks(this, "next", completion);
   1060      return undefined;
   1061    };
   1062  }
   1063 
   1064  restartFrame(frame) {
   1065    this._requestedFrameRestart = null;
   1066    this._priorPause = null;
   1067 
   1068    if (
   1069      frame.type !== "call" ||
   1070      frame.script.isGeneratorFunction ||
   1071      frame.script.isAsyncFunction
   1072    ) {
   1073      return undefined;
   1074    }
   1075    RESTARTED_FRAMES.add(frame);
   1076 
   1077    const completion = frame.callee.apply(frame.this, frame.arguments);
   1078 
   1079    return completion;
   1080  }
   1081 
   1082  hasMoved(frame, newType) {
   1083    const newLocation = this.sourcesManager.getFrameLocation(frame);
   1084 
   1085    if (!this._priorPause) {
   1086      return true;
   1087    }
   1088 
   1089    // Recursion/Loops makes it okay to resume and land at
   1090    // the same breakpoint or debugger statement.
   1091    // It is not okay to transition from a breakpoint to debugger statement
   1092    // or a step to a debugger statement.
   1093    const { type } = this._priorPause.why;
   1094 
   1095    // Conditional breakpoint are doing something weird as they are using "breakpoint" type
   1096    // unless they throw in which case they will be "breakpointConditionThrown".
   1097    if (
   1098      type == newType ||
   1099      (type == "breakpointConditionThrown" && newType == "breakpoint")
   1100    ) {
   1101      return true;
   1102    }
   1103 
   1104    const { line, column } = this._priorPause.frame.where;
   1105    return line !== newLocation.line || column !== newLocation.column;
   1106  }
   1107 
   1108  _makeOnStep({ pauseAndRespond, startFrame, completion }) {
   1109    const thread = this;
   1110    return function () {
   1111      if (thread._validFrameStepOffset(this, startFrame, this.offset)) {
   1112        return pauseAndRespond(this, packet =>
   1113          thread.createCompletionGrip(packet, completion)
   1114        );
   1115      }
   1116 
   1117      return undefined;
   1118    };
   1119  }
   1120 
   1121  _validFrameStepOffset(frame, startFrame, offset) {
   1122    const meta = frame.script.getOffsetMetadata(offset);
   1123 
   1124    // Continue if:
   1125    // 1. the location is not a valid breakpoint position
   1126    // 2. the source is blackboxed
   1127    // 3. we have not moved since the last pause
   1128    if (
   1129      !meta.isBreakpoint ||
   1130      this.sourcesManager.isFrameBlackBoxed(frame) ||
   1131      !this.hasMoved(frame)
   1132    ) {
   1133      return false;
   1134    }
   1135 
   1136    // Pause if:
   1137    // 1. the frame has changed
   1138    // 2. the location is a step position.
   1139    return frame !== startFrame || meta.isStepStart;
   1140  }
   1141 
   1142  atBreakpointLocation(frame) {
   1143    const location = this.sourcesManager.getFrameLocation(frame);
   1144    return !!this.breakpointActorMap.get(location);
   1145  }
   1146 
   1147  createCompletionGrip(packet, completion) {
   1148    if (!completion) {
   1149      return packet;
   1150    }
   1151 
   1152    packet.why.frameFinished = {};
   1153 
   1154    if (completion.hasOwnProperty("return")) {
   1155      packet.why.frameFinished.return = this.createValueGrip(completion.return);
   1156    } else if (completion.hasOwnProperty("yield")) {
   1157      packet.why.frameFinished.return = this.createValueGrip(completion.yield);
   1158    } else if (completion.hasOwnProperty("throw")) {
   1159      packet.why.frameFinished.throw = this.createValueGrip(completion.throw);
   1160    }
   1161 
   1162    return packet;
   1163  }
   1164 
   1165  /**
   1166   * Define the JS hook functions for stepping.
   1167   */
   1168  _makeSteppingHooks({ steppingType, startFrame, completion }) {
   1169    // Bind these methods and state because some of the hooks are called
   1170    // with 'this' set to the current frame. Rather than repeating the
   1171    // binding in each _makeOnX method, just do it once here and pass it
   1172    // in to each function.
   1173    const steppingHookState = {
   1174      pauseAndRespond: (frame, onPacket = k => k) =>
   1175        this._pauseAndRespond(
   1176          frame,
   1177          { type: PAUSE_REASONS.RESUME_LIMIT },
   1178          onPacket
   1179        ),
   1180      startFrame: startFrame || this.youngestFrame,
   1181      steppingType,
   1182      completion,
   1183    };
   1184 
   1185    return {
   1186      onEnterFrame: this._makeOnEnterFrame(steppingHookState),
   1187      onPop: this._makeOnPop(steppingHookState),
   1188      onStep: this._makeOnStep(steppingHookState),
   1189    };
   1190  }
   1191 
   1192  /**
   1193   * Handle attaching the various stepping hooks we need to attach when we
   1194   * receive a resume request with a resumeLimit property.
   1195   *
   1196   * @param Object { resumeLimit }
   1197   *        The values received over the RDP.
   1198   * @returns A promise that resolves to true once the hooks are attached, or is
   1199   *          rejected with an error packet.
   1200   */
   1201  async _handleResumeLimit({ resumeLimit, frameActorID }) {
   1202    const steppingType = resumeLimit.type;
   1203    if (
   1204      !["break", "step", "next", "finish", "restart"].includes(steppingType)
   1205    ) {
   1206      return Promise.reject({
   1207        error: "badParameterType",
   1208        message: "Unknown resumeLimit type",
   1209      });
   1210    }
   1211 
   1212    let frame = this.youngestFrame;
   1213 
   1214    if (frameActorID) {
   1215      frame = this._framesPool.getActorByID(frameActorID).frame;
   1216      if (!frame) {
   1217        throw new Error("Frame should exist in the frames pool.");
   1218      }
   1219    }
   1220 
   1221    if (steppingType === "restart") {
   1222      if (
   1223        frame.type !== "call" ||
   1224        frame.script.isGeneratorFunction ||
   1225        frame.script.isAsyncFunction
   1226      ) {
   1227        return undefined;
   1228      }
   1229      this._requestedFrameRestart = frame;
   1230    }
   1231 
   1232    return this._attachSteppingHooks(frame, steppingType, undefined);
   1233  }
   1234 
   1235  _attachSteppingHooks(frame, steppingType, completion) {
   1236    // If we are stepping out of the onPop handler, we want to use "next" mode
   1237    // so that the parent frame's handlers behave consistently.
   1238    if (steppingType === "finish" && frame.reportedPop) {
   1239      steppingType = "next";
   1240    }
   1241 
   1242    // If there are no more frames on the stack, use "step" mode so that we will
   1243    // pause on the next script to execute.
   1244    const stepFrame = this._getNextStepFrame(frame);
   1245    if (!stepFrame) {
   1246      steppingType = "step";
   1247    }
   1248 
   1249    const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks({
   1250      steppingType,
   1251      completion,
   1252      startFrame: frame,
   1253    });
   1254 
   1255    if (steppingType === "step" || steppingType === "restart") {
   1256      this.dbg.onEnterFrame = onEnterFrame;
   1257    }
   1258 
   1259    if (stepFrame) {
   1260      switch (steppingType) {
   1261        case "step":
   1262        case "break":
   1263        case "next":
   1264          if (stepFrame.script) {
   1265            if (!this.sourcesManager.isFrameBlackBoxed(stepFrame)) {
   1266              stepFrame.onStep = onStep;
   1267            }
   1268          }
   1269        // eslint-disable-next-line no-fallthrough
   1270        case "finish":
   1271          stepFrame.onStep = createStepForReactionTracking(stepFrame.onStep);
   1272        // eslint-disable-next-line no-fallthrough
   1273        case "restart":
   1274          stepFrame.onPop = onPop;
   1275          break;
   1276      }
   1277    }
   1278 
   1279    return true;
   1280  }
   1281 
   1282  /**
   1283   * Clear the onStep and onPop hooks for all frames on the stack.
   1284   */
   1285  _clearSteppingHooks() {
   1286    if (this.suspendedFrame) {
   1287      this.suspendedFrame.onStep = undefined;
   1288      this.suspendedFrame.onPop = undefined;
   1289      this.suspendedFrame = undefined;
   1290    }
   1291 
   1292    let frame = this.youngestFrame;
   1293    if (frame?.onStack) {
   1294      while (frame) {
   1295        frame.onStep = undefined;
   1296        frame.onPop = undefined;
   1297        frame = frame.older;
   1298      }
   1299    }
   1300  }
   1301 
   1302  /**
   1303   * Handle a protocol request to resume execution of the debuggee.
   1304   */
   1305  async resume(resumeLimit, frameActorID) {
   1306    if (this._state !== STATES.PAUSED) {
   1307      return {
   1308        error: "wrongState",
   1309        message:
   1310          "Can't resume when debuggee isn't paused. Current state is '" +
   1311          this._state +
   1312          "'",
   1313        state: this._state,
   1314      };
   1315    }
   1316 
   1317    // In case of multiple nested event loops (due to multiple debuggers open in
   1318    // different tabs or multiple devtools clients connected to the same tab)
   1319    // only allow resumption in a LIFO order.
   1320    if (!this._nestedEventLoop.isTheLastPausedThreadActor()) {
   1321      return {
   1322        error: "wrongOrder",
   1323        message: "trying to resume in the wrong order.",
   1324      };
   1325    }
   1326 
   1327    try {
   1328      if (resumeLimit) {
   1329        await this._handleResumeLimit({ resumeLimit, frameActorID });
   1330      } else {
   1331        this._clearSteppingHooks();
   1332      }
   1333 
   1334      this.doResume({ resumeLimit });
   1335      return {};
   1336    } catch (error) {
   1337      return error instanceof Error
   1338        ? {
   1339            error: "unknownError",
   1340            message: DevToolsUtils.safeErrorString(error),
   1341          }
   1342        : // It is a known error, and the promise was rejected with an error
   1343          // packet.
   1344          error;
   1345    }
   1346  }
   1347 
   1348  /**
   1349   * Only resume and notify necessary observers. This should be used in cases
   1350   * when we do not want to notify the front end of a resume, for example when
   1351   * we are shutting down.
   1352   */
   1353  doResume() {
   1354    this._state = STATES.RUNNING;
   1355 
   1356    // Drop the actors in the pause actor pool.
   1357    this._pausePool.destroy();
   1358    this._pausePool = null;
   1359 
   1360    this._pauseActor = null;
   1361    this._nestedEventLoop.exit();
   1362 
   1363    // Tell anyone who cares of the resume (as of now, that's the xpcshell harness and
   1364    // devtools-startup.js when handling the --wait-for-jsdebugger flag)
   1365    this.emit("resumed");
   1366    this.hideOverlay();
   1367  }
   1368 
   1369  /**
   1370   * Set the debugging hook to pause on exceptions if configured to do so.
   1371   *
   1372   * Note that this is also called when evaluating conditional breakpoints.
   1373   *
   1374   * @param {boolean} doPause
   1375   *        Should watch for pause or not. `_onExceptionUnwind` function will
   1376   *        then be notified about new caught or uncaught exception being fired.
   1377   */
   1378  setPauseOnExceptions(doPause) {
   1379    if (doPause) {
   1380      this.dbg.onExceptionUnwind = this._onExceptionUnwind;
   1381    } else {
   1382      this.dbg.onExceptionUnwind = undefined;
   1383    }
   1384  }
   1385 
   1386  /**
   1387   * Set the debugging hook to pause on debugger statement if configured to do so.
   1388   *
   1389   * Note that the thread actor will pause on exception by default.
   1390   * This method has to be called with a falsy value to disable it.
   1391   *
   1392   * @param {boolean} doPause
   1393   *        Controls whether we should or should not pause on debugger statement.
   1394   */
   1395  setPauseOnDebuggerStatement(doPause) {
   1396    this.dbg.onDebuggerStatement = doPause
   1397      ? this.onDebuggerStatement
   1398      : undefined;
   1399  }
   1400 
   1401  isPauseOnExceptionsEnabled() {
   1402    return this.dbg.onExceptionUnwind == this._onExceptionUnwind;
   1403  }
   1404 
   1405  /**
   1406   * Helper method that returns the next frame when stepping.
   1407   */
   1408  _getNextStepFrame(frame) {
   1409    const endOfFrame = frame.reportedPop;
   1410    const stepFrame = endOfFrame
   1411      ? frame.older || getAsyncParentFrame(frame)
   1412      : frame;
   1413    if (!stepFrame || !stepFrame.script) {
   1414      return null;
   1415    }
   1416 
   1417    // Skips a frame that has been restarted.
   1418    if (RESTARTED_FRAMES.has(stepFrame)) {
   1419      return this._getNextStepFrame(stepFrame.older);
   1420    }
   1421 
   1422    return stepFrame;
   1423  }
   1424 
   1425  frames(start, count) {
   1426    if (this.state !== STATES.PAUSED) {
   1427      return {
   1428        error: "wrongState",
   1429        message:
   1430          "Stack frames are only available while the debuggee is paused.",
   1431      };
   1432    }
   1433 
   1434    // Find the starting frame...
   1435    let frame = this.youngestFrame;
   1436 
   1437    const walkToParentFrame = () => {
   1438      if (!frame) {
   1439        return;
   1440      }
   1441 
   1442      const currentFrame = frame;
   1443      frame = null;
   1444 
   1445      if (!(currentFrame instanceof Debugger.Frame)) {
   1446        frame = getSavedFrameParent(this, currentFrame);
   1447      } else if (currentFrame.older) {
   1448        frame = currentFrame.older;
   1449      } else if (
   1450        this._options.shouldIncludeSavedFrames &&
   1451        currentFrame.olderSavedFrame
   1452      ) {
   1453        frame = currentFrame.olderSavedFrame;
   1454        if (frame && !isValidSavedFrame(this, frame)) {
   1455          frame = null;
   1456        }
   1457      } else if (
   1458        this._options.shouldIncludeAsyncLiveFrames &&
   1459        currentFrame.asyncPromise
   1460      ) {
   1461        const asyncFrame = getAsyncParentFrame(currentFrame);
   1462        if (asyncFrame) {
   1463          frame = asyncFrame;
   1464        }
   1465      }
   1466    };
   1467 
   1468    let i = 0;
   1469    while (frame && i < start) {
   1470      walkToParentFrame();
   1471      i++;
   1472    }
   1473 
   1474    // Return count frames, or all remaining frames if count is not defined.
   1475    const frames = [];
   1476    for (; frame && (!count || i < start + count); i++, walkToParentFrame()) {
   1477      // SavedFrame instances don't have direct Debugger.Source object. If
   1478      // there is an active Debugger.Source that represents the SaveFrame's
   1479      // source, it will have already been created in the server.
   1480      if (frame instanceof Debugger.Frame) {
   1481        this.sourcesManager.createSourceActor(frame.script.source);
   1482      }
   1483 
   1484      if (RESTARTED_FRAMES.has(frame)) {
   1485        continue;
   1486      }
   1487 
   1488      const frameActor = this._createFrameActor(frame, i);
   1489      frames.push(frameActor);
   1490    }
   1491 
   1492    return { frames };
   1493  }
   1494 
   1495  addAllSources() {
   1496    // This method aims at instantiating Source Actors for all already existing
   1497    // sources (via `_addSource()`).
   1498    // This is called on each new target instantiation:
   1499    //   * when a new document or debugging context is instantiated. This
   1500    //     method should be a no-op as there should be no pre-existing sources.
   1501    //   * when devtools open. This time there might be pre-existing sources.
   1502    //
   1503    // We are using Debugger API `findSources()` for instantating source actors
   1504    // of all still-active sources. But we want to also "resurrect" sources
   1505    // which ran before DevTools were opened and were garbaged collected.
   1506    // `findSources()` won't return them.
   1507    // Debugger API `findSourceURLs()` will return the source URLs of all the
   1508    // sources, GC-ed and still active ones.
   1509    //
   1510    // We are using `urlMap` to identify the GC-ed sources.
   1511    //
   1512    // We have two special edgecases:
   1513    //
   1514    // # HTML sources and inline <script> tags
   1515    //
   1516    // HTML sources will be specific to a given URL, but may relate to multiple
   1517    // inline <script> tag. Each script will be related to a given Debugger API
   1518    // source and a given DevTools Source Actor.
   1519    // We collect all active sources in `urlMap`'s `sources` array so that we
   1520    // only resurrect the GC-ed inline <script> and not the one which are still
   1521    // active.
   1522    //
   1523    // # asm.js / wasm
   1524    //
   1525    // DevTools toggles Debugger API `allowUnobservedAsmJS` and
   1526    // `allowUnobservedWasm` to false on opening. This changes how asm.js and
   1527    // Wasm sources are compiled. But only to sources created after DevTools
   1528    // are opened. This typically requires to reload the page.
   1529    //
   1530    // Before DevTools are opened, the asm.js functions are compiled into wasm
   1531    // instances, and they are visible as "wasm" sources in `findSources()`.
   1532    // The wasm instance doesn't keep the top-level normal JS script and the
   1533    // corresponding JS source alive. If only the "wasm" source is found for
   1534    // certain URL, the source needs to be re-compiled.
   1535    //
   1536    // Here, we should be careful to re-compile these sources the way they were
   1537    // compiled before DevTools opening. Otherwise the re-compilation will
   1538    // create Debugger.Script instances backed by normal JS functions for those
   1539    // asm.js functions, which results in an inconsistency between what's
   1540    // running in the debuggee and what's shown in DevTools.
   1541    //
   1542    // We are using `urlMap`'s `hasWasm` to flag them and instruct
   1543    // `resurrectSource()` to re-compile the sources as if DevTools was off and
   1544    // without debugging ability.
   1545    const urlMap = {};
   1546    for (const url of this.dbg.findSourceURLs()) {
   1547      if (url !== "self-hosted") {
   1548        if (!urlMap[url]) {
   1549          urlMap[url] = { count: 0, sources: [], hasWasm: false };
   1550        }
   1551        urlMap[url].count++;
   1552      }
   1553    }
   1554 
   1555    const sources = this.dbg.findSources();
   1556 
   1557    for (const source of sources) {
   1558      this._addSource(source);
   1559 
   1560      if (source.introductionType === "wasm") {
   1561        const origURL = source.url.replace(/^wasm:/, "");
   1562        if (urlMap[origURL]) {
   1563          urlMap[origURL].hasWasm = true;
   1564        }
   1565      }
   1566 
   1567      // The following check should match the filtering done by `findSourceURLs`:
   1568      // https://searchfox.org/mozilla-central/rev/ac7a567f036e1954542763f4722fbfce041fb752/js/src/debugger/Debugger.cpp#2406-2409
   1569      // Otherwise we may populate `urlMap` incorrectly and resurrect sources that weren't GCed,
   1570      // and spawn duplicated SourceActors/Debugger.Source for the same actual source.
   1571      // `findSourceURLs` uses !introductionScript check as that allows to identify <script>'s
   1572      // loaded from the HTML page. This boolean will be defined only when the <script> tag
   1573      // is added by Javascript code at runtime.
   1574      // https://searchfox.org/mozilla-central/rev/3d03a3ca09f03f06ef46a511446537563f62a0c6/devtools/docs/user/debugger-api/debugger.source/index.rst#113
   1575      if (!source.introductionScript && urlMap[source.url]) {
   1576        urlMap[source.url].count--;
   1577        urlMap[source.url].sources.push(source);
   1578      }
   1579    }
   1580 
   1581    // Resurrect any URLs for which not all sources are accounted for.
   1582    for (const [url, data] of Object.entries(urlMap)) {
   1583      if (data.count > 0) {
   1584        this._resurrectSource(url, data.sources, data.hasWasm);
   1585      }
   1586    }
   1587  }
   1588 
   1589  sources() {
   1590    this.addAllSources();
   1591 
   1592    // No need to flush the new source packets here, as we are sending the
   1593    // list of sources out immediately and we don't need to invoke the
   1594    // overhead of an RDP packet for every source right now. Let the default
   1595    // timeout flush the buffered packets.
   1596 
   1597    const forms = [];
   1598    for (const source of this.sourcesManager.iter()) {
   1599      forms.push(source.form());
   1600    }
   1601    return forms;
   1602  }
   1603 
   1604  /**
   1605   * Disassociate all breakpoint actors from their scripts and clear the
   1606   * breakpoint handlers. This method can be used when the thread actor intends
   1607   * to keep the breakpoint store, but needs to clear any actual breakpoints,
   1608   * e.g. due to a page navigation. This way the breakpoint actors' script
   1609   * caches won't hold on to the Debugger.Script objects leaking memory.
   1610   */
   1611  disableAllBreakpoints() {
   1612    for (const bpActor of this.breakpointActorMap.findActors()) {
   1613      bpActor.removeScripts();
   1614    }
   1615  }
   1616 
   1617  removeAllBreakpoints() {
   1618    this.breakpointActorMap.removeAllBreakpoints();
   1619  }
   1620 
   1621  removeAllWatchpoints() {
   1622    for (const actor of this.threadLifetimePool.poolChildren()) {
   1623      if (actor.typeName == "obj") {
   1624        actor.removeWatchpoints();
   1625      }
   1626    }
   1627  }
   1628 
   1629  addWatchpoint(objActor, data) {
   1630    this._watchpointsMap.add(objActor, data);
   1631  }
   1632 
   1633  removeWatchpoint(objActor, property) {
   1634    this._watchpointsMap.remove(objActor, property);
   1635  }
   1636 
   1637  getWatchpoint(obj, property) {
   1638    return this._watchpointsMap.get(obj, property);
   1639  }
   1640 
   1641  /**
   1642   * Handle a protocol request to pause the debuggee.
   1643   */
   1644  interrupt(when) {
   1645    if (this.state == STATES.EXITED) {
   1646      return { type: "exited" };
   1647    } else if (this.state == STATES.PAUSED) {
   1648      // TODO: return the actual reason for the existing pause.
   1649      this.emit("paused", {
   1650        why: { type: PAUSE_REASONS.ALREADY_PAUSED },
   1651      });
   1652      return {};
   1653    } else if (this.state != STATES.RUNNING) {
   1654      return {
   1655        error: "wrongState",
   1656        message: "Received interrupt request in " + this.state + " state.",
   1657      };
   1658    }
   1659    try {
   1660      // If execution should pause just before the next JavaScript bytecode is
   1661      // executed, just set an onEnterFrame handler.
   1662      if (when == "onNext") {
   1663        const onEnterFrame = frame => {
   1664          this._pauseAndRespond(frame, {
   1665            type: PAUSE_REASONS.INTERRUPTED,
   1666            onNext: true,
   1667          });
   1668        };
   1669        this.dbg.onEnterFrame = onEnterFrame;
   1670        return {};
   1671      }
   1672 
   1673      // If execution should pause immediately, just put ourselves in the paused
   1674      // state.
   1675      const packet = this._paused();
   1676      if (!packet) {
   1677        return { error: "notInterrupted" };
   1678      }
   1679      packet.why = { type: PAUSE_REASONS.INTERRUPTED, onNext: false };
   1680 
   1681      // Send the response to the interrupt request now (rather than
   1682      // returning it), because we're going to start a nested event loop
   1683      // here.
   1684      this.conn.send({ from: this.actorID, type: "interrupt" });
   1685      this.emit("paused", packet);
   1686 
   1687      // Start a nested event loop.
   1688      this._nestedEventLoop.enter();
   1689 
   1690      // We already sent a response to this request, don't send one
   1691      // now.
   1692      return null;
   1693    } catch (e) {
   1694      reportException("DBG-SERVER", e);
   1695      return { error: "notInterrupted", message: e.toString() };
   1696    }
   1697  }
   1698 
   1699  _paused(frame) {
   1700    // We don't handle nested pauses correctly.  Don't try - if we're
   1701    // paused, just continue running whatever code triggered the pause.
   1702    // We don't want to actually have nested pauses (although we
   1703    // have nested event loops).  If code runs in the debuggee during
   1704    // a pause, it should cause the actor to resume (dropping
   1705    // pause-lifetime actors etc) and then repause when complete.
   1706 
   1707    if (this.state === STATES.PAUSED) {
   1708      return undefined;
   1709    }
   1710 
   1711    this._state = STATES.PAUSED;
   1712 
   1713    // Clear stepping hooks.
   1714    this.dbg.onEnterFrame = undefined;
   1715    this._requestedFrameRestart = null;
   1716    this._clearSteppingHooks();
   1717 
   1718    // Create the actor pool that will hold the pause actor and its
   1719    // children.
   1720    assert(!this._pausePool, "No pause pool should exist yet");
   1721    this._pausePool = new ObjectActorPool(this, "pause", true);
   1722 
   1723    // Give children of the pause pool a quick link back to the
   1724    // thread...
   1725    this._pausePool.threadActor = this;
   1726 
   1727    // Create the pause actor itself...
   1728    assert(!this._pauseActor, "No pause actor should exist yet");
   1729    this._pauseActor = new PauseActor(this._pausePool);
   1730    this._pausePool.manage(this._pauseActor);
   1731 
   1732    // Update the list of frames.
   1733    this._updateFrames();
   1734 
   1735    // Send off the paused packet and spin an event loop.
   1736    const packet = {
   1737      actor: this._pauseActor.actorID,
   1738    };
   1739 
   1740    if (frame) {
   1741      packet.frame = this._createFrameActor(frame);
   1742    }
   1743 
   1744    return packet;
   1745  }
   1746 
   1747  /**
   1748   * Expire frame actors for frames that are no longer on the current stack.
   1749   */
   1750  _updateFrames() {
   1751    // Create the actor pool that will hold the still-living frames.
   1752    const framesPool = new Pool(this.conn, "frames");
   1753    const frameList = [];
   1754 
   1755    for (const frameActor of this._frameActors) {
   1756      if (frameActor.frame.onStack) {
   1757        framesPool.manage(frameActor);
   1758        frameList.push(frameActor);
   1759      }
   1760    }
   1761 
   1762    // Remove the old frame actor pool, this will expire
   1763    // any actors that weren't added to the new pool.
   1764    if (this._framesPool) {
   1765      this._framesPool.destroy();
   1766    }
   1767 
   1768    this._frameActors = frameList;
   1769    this._framesPool = framesPool;
   1770  }
   1771 
   1772  _createFrameActor(frame, depth) {
   1773    let actor = this._frameActorMap.get(frame);
   1774    if (!actor || actor.isDestroyed()) {
   1775      actor = new FrameActor(frame, this, depth);
   1776      this._frameActors.push(actor);
   1777      this._framesPool.manage(actor);
   1778 
   1779      this._frameActorMap.set(frame, actor);
   1780    }
   1781    return actor;
   1782  }
   1783 
   1784  /**
   1785   * Create and return an environment actor that corresponds to the provided
   1786   * Debugger.Environment.
   1787   *
   1788   * @param Debugger.Environment environment
   1789   *        The lexical environment we want to extract.
   1790   * @param object pool
   1791   *        The pool where the newly-created actor will be placed.
   1792   * @return The EnvironmentActor for environment or undefined for host
   1793   *         functions or functions scoped to a non-debuggee global.
   1794   */
   1795  createEnvironmentActor(environment, pool) {
   1796    if (!environment) {
   1797      return undefined;
   1798    }
   1799 
   1800    if (environment.actor) {
   1801      return environment.actor;
   1802    }
   1803 
   1804    const actor = new EnvironmentActor(environment, this);
   1805    pool.manage(actor);
   1806    environment.actor = actor;
   1807 
   1808    return actor;
   1809  }
   1810 
   1811  /**
   1812   * Create a grip for the given debuggee value.
   1813   * Depdending on if the thread is paused, the object actor may have a different lifetime:
   1814   *  - when thread is paused, the object actor will be kept alive until the thread is resumed
   1815   *    (which also happens when we step)
   1816   *  - when thread is not paused, the object actor will be kept alive until the related target
   1817   *    is destroyed (thread stops or devtools closes)
   1818   *
   1819   * @param value Debugger.Object|any
   1820   *        A Debugger.Object for all JS objects, or any primitive JS type.
   1821   * @return The value's grip
   1822   *        Primitive JS type, Object actor Form JSON object, or a JSON object to describe the value.
   1823   */
   1824  createValueGrip(value) {
   1825    // When the thread is paused, all objects are stored in a transient pool
   1826    // which will be cleared on resume (which also happens when we step).
   1827    const pool = this._pausePool || this.threadLifetimePool;
   1828 
   1829    return createValueGrip(this, value, pool);
   1830  }
   1831 
   1832  _onWindowReady({ isTopLevel, isBFCache }) {
   1833    // Note that this code relates to the disabling of Debugger API from will-navigate listener.
   1834    // And should only be triggered when the target actor doesn't follow WindowGlobal lifecycle.
   1835    // i.e. when the Thread Actor manages more than one top level WindowGlobal.
   1836    if (isTopLevel && this.state != STATES.DETACHED) {
   1837      this.sourcesManager.reset();
   1838      this.clearDebuggees();
   1839      this.dbg.enable();
   1840    }
   1841 
   1842    // Refresh the debuggee list when a new window object appears (top window or
   1843    // iframe).
   1844    if (this.attached) {
   1845      this.dbg.addDebuggees();
   1846    }
   1847 
   1848    // BFCache navigations reuse old sources, so send existing sources to the
   1849    // client instead of waiting for onNewScript debugger notifications.
   1850    if (isBFCache) {
   1851      this.addAllSources();
   1852    }
   1853  }
   1854 
   1855  _onWillNavigate({ isTopLevel }) {
   1856    if (!isTopLevel) {
   1857      return;
   1858    }
   1859 
   1860    // Proceed normally only if the debuggee is not paused.
   1861    if (this.state == STATES.PAUSED) {
   1862      // If we were paused while navigating to a new page,
   1863      // we resume previous page execution, so that the document can be sucessfully unloaded.
   1864      // And we disable the Debugger API, so that we do not hit any breakpoint or trigger any
   1865      // thread actor feature. We will re-enable it just before the next page starts loading,
   1866      // from window-ready listener. That's for when the target doesn't follow WindowGlobal
   1867      // lifecycle.
   1868      // When the target follows the WindowGlobal lifecycle, we will stiff resume and disable
   1869      // this thread actor. It will soon be destroyed. And a new target will pick up
   1870      // the next WindowGlobal and spawn a new Debugger API, via ThreadActor.attach().
   1871      this.doResume();
   1872      this.dbg.disable();
   1873    }
   1874 
   1875    this.removeAllWatchpoints();
   1876    this.disableAllBreakpoints();
   1877    this.dbg.onEnterFrame = undefined;
   1878  }
   1879 
   1880  _onNavigate() {
   1881    if (this.state == STATES.RUNNING) {
   1882      this.dbg.enable();
   1883    }
   1884  }
   1885 
   1886  // JS Debugger API hooks.
   1887  pauseForMutationBreakpoint(
   1888    mutationType,
   1889    targetNode,
   1890    ancestorNode,
   1891    action = "" // "add" or "remove"
   1892  ) {
   1893    if (
   1894      !["subtreeModified", "nodeRemoved", "attributeModified"].includes(
   1895        mutationType
   1896      )
   1897    ) {
   1898      throw new Error("Unexpected mutation breakpoint type");
   1899    }
   1900 
   1901    if (this.shouldSkipAnyBreakpoint) {
   1902      return undefined;
   1903    }
   1904 
   1905    const frame = this.dbg.getNewestFrame();
   1906    if (!frame) {
   1907      return undefined;
   1908    }
   1909 
   1910    if (this.sourcesManager.isFrameBlackBoxed(frame)) {
   1911      return undefined;
   1912    }
   1913 
   1914    const global = (targetNode.ownerDocument || targetNode).defaultView;
   1915    assert(global && this.dbg.hasDebuggee(global));
   1916 
   1917    const targetObj = this.dbg
   1918      .makeGlobalObjectReference(global)
   1919      .makeDebuggeeValue(targetNode);
   1920 
   1921    let ancestorObj = null;
   1922    if (ancestorNode) {
   1923      ancestorObj = this.dbg
   1924        .makeGlobalObjectReference(global)
   1925        .makeDebuggeeValue(ancestorNode);
   1926    }
   1927 
   1928    return this._pauseAndRespond(
   1929      frame,
   1930      {
   1931        type: PAUSE_REASONS.MUTATION_BREAKPOINT,
   1932        mutationType,
   1933        message: `DOM Mutation: '${mutationType}'`,
   1934      },
   1935      pkt => {
   1936        // We have to create the object actors late, from here because `_pausePool` is `null` beforehand,
   1937        // and the actors created by createValueGrip would otherwise be registered in the thread lifetime pool
   1938        pkt.why.nodeGrip = this.createValueGrip(targetObj);
   1939        pkt.why.ancestorGrip = ancestorObj
   1940          ? this.createValueGrip(ancestorObj)
   1941          : null;
   1942        pkt.why.action = action;
   1943        return pkt;
   1944      }
   1945    );
   1946  }
   1947 
   1948  /**
   1949   * A function that the engine calls when a debugger statement has been
   1950   * executed in the specified frame.
   1951   *
   1952   * @param frame Debugger.Frame
   1953   *        The stack frame that contained the debugger statement.
   1954   */
   1955  onDebuggerStatement(frame) {
   1956    // Don't pause if:
   1957    // 1. breakpoints are disabled
   1958    // 2. we have not moved since the last pause
   1959    // 3. the source is blackboxed
   1960    // 4. there is a breakpoint at the same location
   1961    if (
   1962      this.shouldSkipAnyBreakpoint ||
   1963      !this.hasMoved(frame, "debuggerStatement") ||
   1964      this.sourcesManager.isFrameBlackBoxed(frame) ||
   1965      this.atBreakpointLocation(frame)
   1966    ) {
   1967      return undefined;
   1968    }
   1969 
   1970    return this._pauseAndRespond(frame, {
   1971      type: PAUSE_REASONS.DEBUGGER_STATEMENT,
   1972    });
   1973  }
   1974 
   1975  skipBreakpoints(skip) {
   1976    this._options.skipBreakpoints = skip;
   1977    return { skip };
   1978  }
   1979 
   1980  // Bug 1686485 is meant to remove usages of this request
   1981  // in favor direct call to `reconfigure`
   1982  pauseOnExceptions(pauseOnExceptions, ignoreCaughtExceptions) {
   1983    this.reconfigure({
   1984      pauseOnExceptions,
   1985      ignoreCaughtExceptions,
   1986    });
   1987    return {};
   1988  }
   1989 
   1990  /**
   1991   * A function that the engine calls when an exception has been thrown and has
   1992   * propagated to the specified frame.
   1993   *
   1994   * @param youngestFrame Debugger.Frame
   1995   *        The youngest remaining stack frame.
   1996   * @param value object
   1997   *        The exception that was thrown.
   1998   */
   1999  _onExceptionUnwind(youngestFrame, value) {
   2000    // Ignore any reported exception if we are already paused
   2001    if (this.isPaused()) {
   2002      return undefined;
   2003    }
   2004 
   2005    // Ignore shouldSkipAnyBreakpoint if we are explicitly requested to do so.
   2006    // Typically, when we are evaluating conditional breakpoints, we want to report any exception.
   2007    if (
   2008      this.shouldSkipAnyBreakpoint &&
   2009      !this.insideClientEvaluation?.reportExceptionsWhenBreaksAreDisabled
   2010    ) {
   2011      return undefined;
   2012    }
   2013 
   2014    let willBeCaught = false;
   2015    for (let frame = youngestFrame; frame != null; frame = frame.older) {
   2016      if (frame.script.isInCatchScope(frame.offset)) {
   2017        willBeCaught = true;
   2018        break;
   2019      }
   2020    }
   2021 
   2022    if (willBeCaught && this._options.ignoreCaughtExceptions) {
   2023      return undefined;
   2024    }
   2025 
   2026    if (
   2027      this._handledFrameExceptions.has(youngestFrame) &&
   2028      this._handledFrameExceptions.get(youngestFrame) === value
   2029    ) {
   2030      return undefined;
   2031    }
   2032 
   2033    // NS_ERROR_NO_INTERFACE exceptions are a special case in browser code,
   2034    // since they're almost always thrown by QueryInterface functions, and
   2035    // handled cleanly by native code.
   2036    if (!isWorker && value == Cr.NS_ERROR_NO_INTERFACE) {
   2037      return undefined;
   2038    }
   2039 
   2040    // Don't pause on exceptions thrown while inside an evaluation being done on
   2041    // behalf of the client.
   2042    if (this.insideClientEvaluation) {
   2043      return undefined;
   2044    }
   2045 
   2046    if (this.sourcesManager.isFrameBlackBoxed(youngestFrame)) {
   2047      return undefined;
   2048    }
   2049 
   2050    // Now that we've decided to pause, ignore this exception if it's thrown by
   2051    // any older frames.
   2052    for (let frame = youngestFrame.older; frame != null; frame = frame.older) {
   2053      this._handledFrameExceptions.set(frame, value);
   2054    }
   2055 
   2056    try {
   2057      const packet = this._paused(youngestFrame);
   2058      if (!packet) {
   2059        return undefined;
   2060      }
   2061 
   2062      packet.why = {
   2063        type: PAUSE_REASONS.EXCEPTION,
   2064        exception: this.createValueGrip(value),
   2065      };
   2066      this.emit("paused", packet);
   2067 
   2068      this._nestedEventLoop.enter();
   2069    } catch (e) {
   2070      reportException("TA_onExceptionUnwind", e);
   2071    }
   2072 
   2073    return undefined;
   2074  }
   2075 
   2076  /**
   2077   * A function that the engine calls when a new script has been loaded.
   2078   *
   2079   * @param script Debugger.Script
   2080   *        The source script that has been loaded into a debuggee compartment.
   2081   */
   2082  onNewScript(script) {
   2083    this._addSource(script.source);
   2084 
   2085    this._maybeTrackFirstStatementBreakpoint(script);
   2086  }
   2087 
   2088  /**
   2089   * A function called when there's a new source from a thread actor's sources.
   2090   * Emits `newSource` on the thread actor.
   2091   *
   2092   * @param {SourceActor} source
   2093   */
   2094  onNewSourceEvent(source) {
   2095    // When this target is supported by the Watcher Actor,
   2096    // and we listen to SOURCE, we avoid emitting the newSource RDP event
   2097    // as it would be duplicated with the Resource/watchResources API.
   2098    // Could probably be removed once bug 1680280 is fixed.
   2099    if (!this._shouldEmitNewSource) {
   2100      return;
   2101    }
   2102 
   2103    // Bug 1516197: New sources are likely detected due to either user
   2104    // interaction on the page, or devtools requests sent to the server.
   2105    // We use executeSoon because we don't want to block those operations
   2106    // by sending packets in the middle of them.
   2107    DevToolsUtils.executeSoon(() => {
   2108      if (this.isDestroyed()) {
   2109        return;
   2110      }
   2111      this.emit("newSource", {
   2112        source: source.form(),
   2113      });
   2114    });
   2115  }
   2116 
   2117  // API used by the Watcher Actor to disable the newSource events
   2118  // Could probably be removed once bug 1680280 is fixed.
   2119  _shouldEmitNewSource = true;
   2120  disableNewSourceEvents() {
   2121    this._shouldEmitNewSource = false;
   2122  }
   2123 
   2124  /**
   2125   * Filtering function to filter out sources for which we don't want to notify/create
   2126   * source actors
   2127   *
   2128   * @param {Debugger.Source} source
   2129   *        The source to accept or ignore
   2130   * @param Boolean
   2131   *        True, if we want to create a source actor.
   2132   */
   2133  _acceptSource(source) {
   2134    // We have some spurious source created by ExtensionContent.sys.mjs when debugging tabs.
   2135    // These sources are internal stuff injected by WebExt codebase to implement content
   2136    // scripts. We can't easily ignore them from Debugger API, so ignore them
   2137    // when debugging a tab (i.e. browser-element). As we still want to debug them
   2138    // from the browser toolbox.
   2139    if (
   2140      this.targetActor.sessionContext.type == "browser-element" &&
   2141      source.url.endsWith("ExtensionContent.sys.mjs")
   2142    ) {
   2143      return false;
   2144    }
   2145 
   2146    return true;
   2147  }
   2148 
   2149  /**
   2150   * Add the provided source to the server cache.
   2151   *
   2152   * @param aSource Debugger.Source
   2153   *        The source that will be stored.
   2154   */
   2155  _addSource(source) {
   2156    if (!this._acceptSource(source)) {
   2157      return;
   2158    }
   2159 
   2160    // Preloaded WebExtension content scripts may be cached internally by
   2161    // ExtensionContent.jsm and ThreadActor would ignore them on a page reload
   2162    // because it finds them in the _debuggerSourcesSeen WeakSet,
   2163    // and so we also need to be sure that there is still a source actor for the source.
   2164    let sourceActor;
   2165    if (
   2166      this._debuggerSourcesSeen.has(source) &&
   2167      this.sourcesManager.hasSourceActor(source)
   2168    ) {
   2169      sourceActor = this.sourcesManager.getSourceActor(source);
   2170      sourceActor.resetDebuggeeScripts();
   2171    } else {
   2172      sourceActor = this.sourcesManager.createSourceActor(source);
   2173    }
   2174 
   2175    const sourceUrl = sourceActor.url;
   2176    if (this._onLoadBreakpointURLs.has(sourceUrl)) {
   2177      // Immediately set a breakpoint on first line
   2178      // (note that this is only used by `./mach xpcshell-test --jsdebugger`)
   2179      this.setBreakpoint({ sourceUrl, line: 1 }, {});
   2180      // But also query asynchronously the first really breakable line
   2181      // as the first may not be valid and won't break.
   2182      (async () => {
   2183        const [firstLine] = await sourceActor.getBreakableLines();
   2184        if (firstLine != 1) {
   2185          this.setBreakpoint({ sourceUrl, line: firstLine }, {});
   2186        }
   2187      })();
   2188    }
   2189 
   2190    const bpActors = this.breakpointActorMap
   2191      .findActors()
   2192      .filter(
   2193        actor =>
   2194          actor.location.sourceUrl && actor.location.sourceUrl == sourceUrl
   2195      );
   2196 
   2197    for (const actor of bpActors) {
   2198      sourceActor.applyBreakpoint(actor);
   2199    }
   2200 
   2201    this._debuggerSourcesSeen.add(source);
   2202  }
   2203 
   2204  /**
   2205   * Create a new source by refetching the specified URL and instantiating all
   2206   * sources that were found in the result.
   2207   *
   2208   * @param url The URL string to fetch.
   2209   * @param existingInlineSources The inline sources for the URL the debugger knows about
   2210   *                              already, and that we shouldn't re-create (only used when
   2211   *                              url content type is text/html).
   2212   * @param forceEnableAsmJS A boolean to force enable the asm.js feature.
   2213   *                         See the comment inside addAllSources for more
   2214   *                         details.
   2215   */
   2216  async _resurrectSource(url, existingInlineSources, forceEnableAsmJS) {
   2217    let { content, contentType, sourceMapURL } =
   2218      await this.sourcesManager.urlContents(
   2219        url,
   2220        /* partial */ false,
   2221        /* canUseCache */ true
   2222      );
   2223 
   2224    // Newlines in all sources should be normalized. Do this with HTML content
   2225    // to simplify the comparisons below.
   2226    content = content.replace(/\r\n?|\u2028|\u2029/g, "\n");
   2227 
   2228    if (contentType == "text/html") {
   2229      // HTML files can contain any number of inline sources. We have to find
   2230      // all the inline sources and their start line without running any of the
   2231      // scripts on the page. The approach used here is approximate.
   2232      if (!this.targetActor.window) {
   2233        return;
   2234      }
   2235 
   2236      // Find the offsets in the HTML at which inline scripts might start.
   2237      const scriptTagMatches = content.matchAll(/<script[^>]*>/gi);
   2238      const scriptStartOffsets = [...scriptTagMatches].map(
   2239        rv => rv.index + rv[0].length
   2240      );
   2241 
   2242      // Find the script tags in this HTML page by parsing a new document from
   2243      // the contentand looking for its script elements.
   2244      const document = new DOMParser().parseFromString(content, "text/html");
   2245 
   2246      // For each inline source found, see if there is a start offset for what
   2247      // appears to be a script tag, whose contents match the inline source.
   2248      [...document.scripts].forEach(script => {
   2249        const text = script.innerText;
   2250 
   2251        // We only want to handle inline scripts
   2252        if (script.src) {
   2253          return;
   2254        }
   2255 
   2256        // Don't create source for empty script tag
   2257        if (!text.trim()) {
   2258          return;
   2259        }
   2260 
   2261        const scriptStartOffsetIndex = scriptStartOffsets.findIndex(
   2262          offset => content.substring(offset, offset + text.length) == text
   2263        );
   2264        // Bail if we couldn't find the start offset for the script
   2265        if (scriptStartOffsetIndex == -1) {
   2266          return;
   2267        }
   2268 
   2269        const scriptStartOffset = scriptStartOffsets[scriptStartOffsetIndex];
   2270        // Remove the offset from the array to mitigate any issue we might with scripts
   2271        // sharing the same text content.
   2272        scriptStartOffsets.splice(scriptStartOffsetIndex, 1);
   2273 
   2274        const allLineBreaks = [
   2275          ...content.substring(0, scriptStartOffset).matchAll("\n"),
   2276        ];
   2277        const startLine = 1 + allLineBreaks.length;
   2278        // NOTE: Debugger.Source.prototype.startColumn is 1-based.
   2279        //       Create 1-based column here for the following comparison,
   2280        //       and also the createSource call below.
   2281        const startColumn =
   2282          1 +
   2283          scriptStartOffset -
   2284          (allLineBreaks.length ? allLineBreaks.at(-1).index - 1 : 0);
   2285 
   2286        // Don't create a source if we already found one for this script
   2287        if (
   2288          existingInlineSources.find(
   2289            source =>
   2290              source.startLine == startLine && source.startColumn == startColumn
   2291          )
   2292        ) {
   2293          return;
   2294        }
   2295 
   2296        try {
   2297          const global = this.dbg.getDebuggees()[0];
   2298          // NOTE: Debugger.Object.prototype.createSource takes 1-based column.
   2299          this._addSource(
   2300            global.createSource({
   2301              text,
   2302              url,
   2303              startLine,
   2304              startColumn,
   2305              isScriptElement: true,
   2306              forceEnableAsmJS,
   2307            })
   2308          );
   2309        } catch (e) {
   2310          //  Ignore parse errors.
   2311        }
   2312      });
   2313 
   2314      // If no scripts were found, we might have an inaccurate content type and
   2315      // the file is actually JavaScript. Fall through and add the entire file
   2316      // as the source.
   2317      if (document.scripts.length) {
   2318        return;
   2319      }
   2320    }
   2321 
   2322    // Other files should only contain javascript, so add the file contents as
   2323    // the source itself.
   2324    try {
   2325      const global = this.dbg.getDebuggees()[0];
   2326      this._addSource(
   2327        global.createSource({
   2328          text: content,
   2329          url,
   2330          startLine: 1,
   2331          sourceMapURL,
   2332          forceEnableAsmJS,
   2333        })
   2334      );
   2335    } catch (e) {
   2336      // Ignore parse errors.
   2337    }
   2338  }
   2339 
   2340  dumpThread() {
   2341    return {
   2342      pauseOnExceptions: this._options.pauseOnExceptions,
   2343      ignoreCaughtExceptions: this._options.ignoreCaughtExceptions,
   2344      logEventBreakpoints: this._options.logEventBreakpoints,
   2345      skipBreakpoints: this.shouldSkipAnyBreakpoint,
   2346      breakpoints: this.breakpointActorMap.listKeys(),
   2347    };
   2348  }
   2349 
   2350  // NOTE: dumpPools is defined in the Thread actor to avoid
   2351  // adding it to multiple target specs and actors.
   2352  dumpPools() {
   2353    return this.conn.dumpPools();
   2354  }
   2355 
   2356  logLocation(prefix, frame) {
   2357    const loc = this.sourcesManager.getFrameLocation(frame);
   2358    dump(`${prefix} (${loc.line}, ${loc.column})\n`);
   2359  }
   2360 }
   2361 
   2362 exports.ThreadActor = ThreadActor;
   2363 
   2364 /**
   2365 * Creates a PauseActor.
   2366 *
   2367 * PauseActors exist for the lifetime of a given debuggee pause.  Used to
   2368 * scope pause-lifetime grips.
   2369 */
   2370 class PauseActor {
   2371  /**
   2372   * @param {Pool} pool: The actor pool created for this pause.
   2373   */
   2374  constructor(pool) {
   2375    this.pool = pool;
   2376  }
   2377  typeName = "pause";
   2378 }
   2379 
   2380 // Utility functions.
   2381 
   2382 /**
   2383 * Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has
   2384 * become a dead object, return |undefined|.
   2385 *
   2386 * @param Debugger.Object wrappedGlobal
   2387 *        The |Debugger.Object| which wraps a global.
   2388 *
   2389 * @returns {object | undefined}
   2390 *          Returns the unwrapped global object or |undefined| if unwrapping
   2391 *          failed.
   2392 */
   2393 exports.unwrapDebuggerObjectGlobal = wrappedGlobal => {
   2394  try {
   2395    // Because of bug 991399 we sometimes get nuked window references here. We
   2396    // just bail out in that case.
   2397    //
   2398    // Note that addon sandboxes have a DOMWindow as their prototype. So make
   2399    // sure that we can touch the prototype too (whatever it is), in case _it_
   2400    // is it a nuked window reference. We force stringification to make sure
   2401    // that any dead object proxies make themselves known.
   2402    const global = wrappedGlobal.unsafeDereference();
   2403    Object.getPrototypeOf(global) + "";
   2404    return global;
   2405  } catch (e) {
   2406    return undefined;
   2407  }
   2408 };