tor-browser

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

eval-with-debugger.js (25041B)


      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 const Debugger = require("Debugger");
      8 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
      9 
     10 const lazy = {};
     11 if (!isWorker) {
     12  ChromeUtils.defineESModuleGetters(
     13    lazy,
     14    {
     15      Reflect: "resource://gre/modules/reflect.sys.mjs",
     16    },
     17    { global: "contextual" }
     18  );
     19 }
     20 loader.lazyRequireGetter(
     21  this,
     22  ["isCommand"],
     23  "resource://devtools/server/actors/webconsole/commands/parser.js",
     24  true
     25 );
     26 loader.lazyRequireGetter(
     27  this,
     28  "WebConsoleCommandsManager",
     29  "resource://devtools/server/actors/webconsole/commands/manager.js",
     30  true
     31 );
     32 
     33 loader.lazyRequireGetter(
     34  this,
     35  "LongStringActor",
     36  "resource://devtools/server/actors/string.js",
     37  true
     38 );
     39 loader.lazyRequireGetter(
     40  this,
     41  "eagerEcmaAllowlist",
     42  "resource://devtools/server/actors/webconsole/eager-ecma-allowlist.js"
     43 );
     44 loader.lazyRequireGetter(
     45  this,
     46  "eagerFunctionAllowlist",
     47  "resource://devtools/server/actors/webconsole/eager-function-allowlist.js"
     48 );
     49 
     50 function isObject(value) {
     51  return Object(value) === value;
     52 }
     53 
     54 /**
     55 * Evaluates a string using the debugger API.
     56 *
     57 * To allow the variables view to update properties from the Web Console we
     58 * provide the "selectedObjectActor" mechanism: the Web Console tells the
     59 * ObjectActor ID for which it desires to evaluate an expression. The
     60 * Debugger.Object pointed at by the actor ID is bound such that it is
     61 * available during expression evaluation (executeInGlobalWithBindings()).
     62 *
     63 * Example:
     64 *   _self['foobar'] = 'test'
     65 * where |_self| refers to the desired object.
     66 *
     67 * The |frameActor| property allows the Web Console client to provide the
     68 * frame actor ID, such that the expression can be evaluated in the
     69 * user-selected stack frame.
     70 *
     71 * For the above to work we need the debugger and the Web Console to share
     72 * a connection, otherwise the Web Console actor will not find the frame
     73 * actor.
     74 *
     75 * The Debugger.Frame comes from the jsdebugger's Debugger instance, which
     76 * is different from the Web Console's Debugger instance. This means that
     77 * for evaluation to work, we need to create a new instance for the Web
     78 * Console Commands helpers - they need to be Debugger.Objects coming from the
     79 * jsdebugger's Debugger instance.
     80 *
     81 * When |selectedObjectActor| is used objects can come from different iframes,
     82 * from different domains. To avoid permission-related errors when objects
     83 * come from a different window, we also determine the object's own global,
     84 * such that evaluation happens in the context of that global. This means that
     85 * evaluation will happen in the object's iframe, rather than the top level
     86 * window.
     87 *
     88 * @param string string
     89 *        String to evaluate.
     90 * @param object [options]
     91 *        Options for evaluation:
     92 *        - selectedObjectActor: the ObjectActor ID to use for evaluation.
     93 *          |evalWithBindings()| will be called with one additional binding:
     94 *          |_self| which will point to the Debugger.Object of the given
     95 *          ObjectActor. Executes with the top level window as the global.
     96 *        - frameActor: the FrameActor ID to use for evaluation. The given
     97 *        debugger frame is used for evaluation, instead of the global window.
     98 *        - selectedNodeActor: the NodeActor ID of the currently selected node
     99 *        in the Inspector (or null, if there is no selection). This is used
    100 *        for helper functions that make reference to the currently selected
    101 *        node, like $0.
    102 *        - innerWindowID: An optional window id to use instead of webConsole.evalWindow.
    103 *        This is used by function that need to evaluate in a different window for which
    104 *        we don't have a dedicated target (for example a non-remote iframe).
    105 *        - eager: Set to true if you want the evaluation to bail if it may have side effects.
    106 *        - url: the url to evaluate the script as. Defaults to "debugger eval code",
    107 *        or "debugger eager eval code" if eager is true.
    108 *        - preferConsoleCommandsOverLocalSymbols: Set to true if console commands
    109 *        should override local symbols.
    110 * @param object webConsole
    111 *
    112 * @return object
    113 *         An object that holds the following properties:
    114 *         - dbg: the debugger where the string was evaluated.
    115 *         - frame: (optional) the frame where the string was evaluated.
    116 *         - global: the Debugger.Object for the global where the string was evaluated in.
    117 *         - result: the result of the evaluation.
    118 */
    119 function evalWithDebugger(string, options = {}, webConsole) {
    120  const trimmedString = string.trim();
    121  // The help function needs to be easy to guess, so accept "?" as a shortcut
    122  if (trimmedString === "?") {
    123    return evalWithDebugger(":help", options, webConsole);
    124  }
    125 
    126  const isCmd = isCommand(trimmedString);
    127 
    128  if (isCmd && options.eager) {
    129    return {
    130      result: null,
    131    };
    132  }
    133 
    134  const { frame, dbg } = getFrameDbg(options, webConsole);
    135 
    136  const { dbgGlobal, bindSelf } = getDbgGlobal(options, dbg, webConsole);
    137 
    138  // If the strings starts with a `:`, do not try to evaluate the strings
    139  // and instead only call the related command function directly from
    140  // the privileged codebase.
    141  if (isCmd) {
    142    try {
    143      return WebConsoleCommandsManager.executeCommand(
    144        webConsole,
    145        dbgGlobal,
    146        options.selectedNodeActor,
    147        string
    148      );
    149    } catch (e) {
    150      // Catch any exception and return a result similar to the output
    151      // of executeCommand to notify the client about this unexpected error.
    152      return {
    153        helperResult: {
    154          type: "exception",
    155          message: e.message,
    156        },
    157      };
    158    }
    159  }
    160 
    161  const helpers = WebConsoleCommandsManager.getWebConsoleCommands(
    162    webConsole,
    163    dbgGlobal,
    164    frame,
    165    string,
    166    options.selectedNodeActor,
    167    options.preferConsoleCommandsOverLocalSymbols
    168  );
    169  let { bindings } = helpers;
    170 
    171  // Ease calling the help command by not requiring the "()".
    172  // But wait for the bindings computation in order to know if "help" variable
    173  // was overloaded by the page. If it is missing from bindings, it is overloaded and we should
    174  // display its value by doing a regular evaluation.
    175  if (trimmedString === "help" && bindings.help) {
    176    return evalWithDebugger(":help", options, webConsole);
    177  }
    178 
    179  // '_self' refers to the JS object references via options.selectedObjectActor.
    180  // This isn't exposed on typical console evaluation, but only when "Store As Global"
    181  // runs an invisible script storing `_self` into `temp${i}`.
    182  if (bindSelf) {
    183    bindings._self = bindSelf;
    184  }
    185 
    186  // Log points calls this method from the server side and pass additional variables
    187  // to be exposed to the evaluated JS string
    188  if (options.bindings) {
    189    bindings = { ...bindings, ...options.bindings };
    190  }
    191 
    192  const evalOptions = {};
    193 
    194  const urlOption =
    195    options.url || (options.eager ? "debugger eager eval code" : null);
    196  if (typeof urlOption === "string") {
    197    evalOptions.url = urlOption;
    198  }
    199 
    200  if (typeof options.lineNumber === "number") {
    201    evalOptions.lineNumber = options.lineNumber;
    202  }
    203 
    204  if (options.disableBreaks || options.eager) {
    205    // When we are disabling breakpoints for a given evaluation, or when we are doing an eager evaluation,
    206    // also prevent spawning related Debugger.Source object to avoid showing it
    207    // in the debugger UI
    208    evalOptions.hideFromDebugger = true;
    209  }
    210 
    211  if (options.preferConsoleCommandsOverLocalSymbols) {
    212    evalOptions.useInnerBindings = true;
    213  }
    214 
    215  updateConsoleInputEvaluation(dbg, webConsole);
    216 
    217  const evalString = getEvalInput(string, bindings);
    218  const result = getEvalResult(
    219    dbg,
    220    evalString,
    221    evalOptions,
    222    bindings,
    223    frame,
    224    dbgGlobal,
    225    options.eager
    226  );
    227 
    228  // Attempt to initialize any declarations found in the evaluated string
    229  // since they may now be stuck in an "initializing" state due to the
    230  // error. Already-initialized bindings will be ignored.
    231  if (!frame && result && "throw" in result) {
    232    forceLexicalInitForVariableDeclarationsInThrowingExpression(
    233      dbgGlobal,
    234      string
    235    );
    236  }
    237 
    238  return {
    239    result,
    240    // Retrieve the result of commands, if any ran
    241    helperResult: helpers.getHelperResult(),
    242    dbg,
    243    frame,
    244    dbgGlobal,
    245  };
    246 }
    247 exports.evalWithDebugger = evalWithDebugger;
    248 
    249 /**
    250 * Sub-function to reduce the complexity of evalWithDebugger.
    251 * This focuses on calling Debugger.Frame or Debugger.Object eval methods.
    252 *
    253 * @param {Debugger} dbg
    254 * @param {string} string
    255 *        The string to evaluate.
    256 * @param {object} evalOptions
    257 *        Spidermonkey options to pass to eval methods.
    258 * @param {object} bindings
    259 *        Dictionary object with symbols to override in the evaluation.
    260 * @param {Debugger.Frame} frame
    261 *        If paused, the paused frame.
    262 * @param {Debugger.Object} dbgGlobal
    263 *        The target's global.
    264 * @param {boolean} eager
    265 *        Is this an eager evaluation?
    266 * @return {object}
    267 *        The evaluation result object.
    268 *        See `Debugger.Ojbect.executeInGlobalWithBindings` definition.
    269 */
    270 function getEvalResult(
    271  dbg,
    272  string,
    273  evalOptions,
    274  bindings,
    275  frame,
    276  dbgGlobal,
    277  eager
    278 ) {
    279  // When we are doing an eager evaluation, we aren't using the target's Debugger object
    280  // but a special one, dedicated to each evaluation.
    281  let noSideEffectDebugger = null;
    282  if (eager) {
    283    noSideEffectDebugger = makeSideeffectFreeDebugger(dbg);
    284 
    285    // When a sideeffect-free debugger has been created, we need to eval
    286    // in the context of that debugger in order for the side-effect tracking
    287    // to apply.
    288    if (frame) {
    289      frame = noSideEffectDebugger.adoptFrame(frame);
    290    } else {
    291      dbgGlobal = noSideEffectDebugger.adoptDebuggeeValue(dbgGlobal);
    292    }
    293    if (bindings) {
    294      bindings = Object.keys(bindings).reduce((acc, key) => {
    295        acc[key] = noSideEffectDebugger.adoptDebuggeeValue(bindings[key]);
    296        return acc;
    297      }, {});
    298    }
    299  }
    300 
    301  try {
    302    let result;
    303    if (frame) {
    304      result = frame.evalWithBindings(string, bindings, evalOptions);
    305    } else {
    306      result = dbgGlobal.executeInGlobalWithBindings(
    307        string,
    308        bindings,
    309        evalOptions
    310      );
    311    }
    312    if (noSideEffectDebugger && result) {
    313      if ("return" in result) {
    314        result.return = dbg.adoptDebuggeeValue(result.return);
    315      }
    316      if ("throw" in result) {
    317        result.throw = dbg.adoptDebuggeeValue(result.throw);
    318      }
    319    }
    320    return result;
    321  } finally {
    322    // We need to be absolutely sure that the sideeffect-free debugger's
    323    // debuggees are removed because otherwise we risk them terminating
    324    // execution of later code in the case of unexpected exceptions.
    325    if (noSideEffectDebugger) {
    326      noSideEffectDebugger.onNativeCall = undefined;
    327      noSideEffectDebugger.shouldAvoidSideEffects = false;
    328      // Ensure removing the debuggee only as the very last step as various
    329      // cleanups within the Debugger API are done per still-registered debuggee.
    330      noSideEffectDebugger.removeAllDebuggees();
    331    }
    332  }
    333 }
    334 
    335 /**
    336 * Force lexical initialization for let/const variables declared in a throwing expression.
    337 * By spec, a lexical declaration is added to the *page-visible* global lexical environment
    338 * for those variables, meaning they can't be redeclared (See Bug 1246215).
    339 *
    340 * This function gets the AST of the throwing expression to collect all the let/const
    341 * declarations and call `forceLexicalInitializationByName`, which will initialize them
    342 * to undefined, making it possible for them to be redeclared.
    343 *
    344 * @param {DebuggerObject} dbgGlobal
    345 * @param {string} string: The expression that was evaluated and threw
    346 * @returns
    347 */
    348 function forceLexicalInitForVariableDeclarationsInThrowingExpression(
    349  dbgGlobal,
    350  string
    351 ) {
    352  // Reflect is not usable in workers, so return early to avoid logging an error
    353  // to the console when loading it.
    354  if (isWorker) {
    355    return;
    356  }
    357 
    358  let ast;
    359  // Parse errors will raise an exception. We can/should ignore the error
    360  // since it's already being handled elsewhere and we are only interested
    361  // in initializing bindings.
    362  try {
    363    ast = lazy.Reflect.parse(string);
    364  } catch (e) {
    365    return;
    366  }
    367 
    368  try {
    369    for (const line of ast.body) {
    370      // Only let and const declarations put bindings into an
    371      // "initializing" state.
    372      if (!(line.kind == "let" || line.kind == "const")) {
    373        continue;
    374      }
    375 
    376      const identifiers = [];
    377      for (const decl of line.declarations) {
    378        switch (decl.id.type) {
    379          case "Identifier":
    380            // let foo = bar;
    381            identifiers.push(decl.id.name);
    382            break;
    383          case "ArrayPattern":
    384            // let [foo, bar]    = [1, 2];
    385            // let [foo=99, bar] = [1, 2];
    386            for (const e of decl.id.elements) {
    387              if (e.type == "Identifier") {
    388                identifiers.push(e.name);
    389              } else if (e.type == "AssignmentExpression") {
    390                identifiers.push(e.left.name);
    391              }
    392            }
    393            break;
    394          case "ObjectPattern":
    395            // let {bilbo, my}    = {bilbo: "baggins", my: "precious"};
    396            // let {blah: foo}    = {blah: yabba()}
    397            // let {blah: foo=99} = {blah: yabba()}
    398            for (const prop of decl.id.properties) {
    399              // key
    400              if (prop.key?.type == "Identifier") {
    401                identifiers.push(prop.key.name);
    402              }
    403              // value
    404              if (prop.value?.type == "Identifier") {
    405                identifiers.push(prop.value.name);
    406              } else if (prop.value?.type == "AssignmentExpression") {
    407                identifiers.push(prop.value.left.name);
    408              } else if (prop.type === "SpreadExpression") {
    409                identifiers.push(prop.expression.name);
    410              }
    411            }
    412            break;
    413        }
    414      }
    415 
    416      for (const name of identifiers) {
    417        dbgGlobal.forceLexicalInitializationByName(name);
    418      }
    419    }
    420  } catch (ex) {
    421    console.error(
    422      "Error in forceLexicalInitForVariableDeclarationsInThrowingExpression:",
    423      ex
    424    );
    425  }
    426 }
    427 
    428 /**
    429 * Creates a side-effect-free Debugger instance.
    430 *
    431 * @param {Debugger} targetActorDbg
    432 *        The target actor's dbg object, crafted by make-debugger.js module.
    433 * @return {Debugger}
    434 *         Side-effect-free Debugger instance.
    435 */
    436 function makeSideeffectFreeDebugger(targetActorDbg) {
    437  // Populate the cached Map once before the evaluation
    438  ensureSideEffectFreeNatives();
    439 
    440  // Note: It is critical for debuggee performance that we implement all of
    441  // this debuggee tracking logic with a separate Debugger instance.
    442  // Bug 1617666 arises otherwise if we set an onEnterFrame hook on the
    443  // existing debugger object and then later clear it.
    444  //
    445  // Also note that we aren't registering any global to this debugger.
    446  // We will only adopt values into it: the paused frame (if any) or the
    447  // target's global (when not paused).
    448  const dbg = new Debugger();
    449 
    450  // Special flag in order to ensure that any evaluation or call being
    451  // made via this debugger will be ignored by all debuggers except this one.
    452  dbg.exclusiveDebuggerOnEval = true;
    453 
    454  // We need to register all JS globals that the evaluation may use.
    455  // By default WindowGlobalTarget (with "EFT" mode enabled by default) is specific to
    456  // only one JS global, related to the WindowGlobal it relates to.
    457  // But there is two edgecases:
    458  //  - the browser toolbox WindowGlobalTarget still have EFT turned off and each target
    459  //    may involve many WindowGlobal and so many JS globals.
    460  //  - if the current target's document has some iframes (or is an iframe) running in the
    461  //    same process, the evaluation may use `globalThis.contentWindow` or
    462  //   `iframeElement.(top|parent)` and so involve any of these same-process JS globals.
    463  //
    464  // While we don't have to do this for regular evaluations, it is important for
    465  // side-effect-free one as onNativeCall is only called for these registered globals.
    466  // If we miss one, we would prevent detecting side-effect calls in the missed global.
    467  const globals = targetActorDbg.findDebuggees(true);
    468  for (const global of globals) {
    469    try {
    470      dbg.addDebuggee(global);
    471    } catch (e) {
    472      // Ignore exceptions from the following cases:
    473      //   * A global from the same compartment (happens with parent process)
    474      //   * A dead wrapper (happens when the reference gets nuked after
    475      //     findAllGlobals call)
    476      if (
    477        !e.message.includes(
    478          "debugger and debuggee must be in different compartments"
    479        ) &&
    480        !e.message.includes("can't access dead object")
    481      ) {
    482        throw e;
    483      }
    484    }
    485  }
    486 
    487  const timeoutDuration = 100;
    488  const endTime = Date.now() + timeoutDuration;
    489  let count = 0;
    490  function shouldCancel() {
    491    // To keep the evaled code as quick as possible, we avoid querying the
    492    // current time on ever single step and instead check every 100 steps
    493    // as an arbitrary count that seemed to be "often enough".
    494    return ++count % 100 === 0 && Date.now() > endTime;
    495  }
    496 
    497  const executedScripts = new Set();
    498  const handler = {
    499    hit: () => null,
    500  };
    501  dbg.onEnterFrame = frame => {
    502    if (shouldCancel()) {
    503      return null;
    504    }
    505    frame.onStep = () => {
    506      if (shouldCancel()) {
    507        return null;
    508      }
    509      return undefined;
    510    };
    511 
    512    const script = frame.script;
    513 
    514    if (executedScripts.has(script)) {
    515      return undefined;
    516    }
    517    executedScripts.add(script);
    518 
    519    const offsets = script.getEffectfulOffsets();
    520    for (const offset of offsets) {
    521      script.setBreakpoint(offset, handler);
    522    }
    523 
    524    return undefined;
    525  };
    526 
    527  // The debugger only calls onNativeCall handlers on the debugger that is
    528  // explicitly calling either eval, DebuggerObject.apply or DebuggerObject.call,
    529  // so we need to add this hook on "dbg" even though the rest of our hooks work via "newDbg".
    530  const { SIDE_EFFECT_FREE } = WebConsoleCommandsManager;
    531  dbg.onNativeCall = (callee, reason) => {
    532    try {
    533      // Setters are always effectful. Natives called normally or called via
    534      // getters are handled with an allowlist.
    535      if (
    536        (reason == "get" || reason == "call") &&
    537        nativeIsEagerlyEvaluateable(callee)
    538      ) {
    539        // Returning undefined causes execution to continue normally.
    540        return undefined;
    541      }
    542    } catch (err) {
    543      DevToolsUtils.reportException(
    544        "evalWithDebugger onNativeCall",
    545        new Error("Unable to validate native function against allowlist")
    546      );
    547    }
    548 
    549    // The WebConsole Commands manager will use Cu.exportFunction which will force
    550    // to call a native method which is hard to identify.
    551    // getEvalResult will flag those getter methods with a magic attribute.
    552    if (
    553      reason == "call" &&
    554      callee.unsafeDereference().isSideEffectFree === SIDE_EFFECT_FREE
    555    ) {
    556      // Returning undefined causes execution to continue normally.
    557      return undefined;
    558    }
    559 
    560    // Returning null terminates the current evaluation.
    561    return null;
    562  };
    563  dbg.shouldAvoidSideEffects = true;
    564 
    565  return dbg;
    566 }
    567 
    568 // Native functions which are considered to be side effect free.
    569 let gSideEffectFreeNatives; // string => Array(Function)
    570 
    571 /**
    572 * Generate gSideEffectFreeNatives map.
    573 */
    574 function ensureSideEffectFreeNatives() {
    575  if (gSideEffectFreeNatives) {
    576    return;
    577  }
    578 
    579  const { natives: domNatives } = eagerFunctionAllowlist;
    580 
    581  const natives = [
    582    ...eagerEcmaAllowlist.functions,
    583    ...eagerEcmaAllowlist.getters,
    584 
    585    // Pull in all of the non-ECMAScript native functions that we want to
    586    // allow as well.
    587    ...domNatives,
    588  ];
    589 
    590  const map = new Map();
    591  for (const n of natives) {
    592    if (!map.has(n.name)) {
    593      map.set(n.name, []);
    594    }
    595    map.get(n.name).push(n);
    596  }
    597 
    598  gSideEffectFreeNatives = map;
    599 }
    600 
    601 function nativeIsEagerlyEvaluateable(fn) {
    602  if (fn.isBoundFunction) {
    603    fn = fn.boundTargetFunction;
    604  }
    605 
    606  // We assume all DOM getters have no major side effect, and they are
    607  // eagerly-evaluateable.
    608  //
    609  // JitInfo is used only by methods/accessors in WebIDL, and being
    610  // "a getter with JitInfo" can be used as a condition to check if given
    611  // function is DOM getter.
    612  //
    613  // This includes privileged interfaces in addition to standard web APIs.
    614  if (fn.isNativeGetterWithJitInfo()) {
    615    return true;
    616  }
    617 
    618  // Natives with certain names are always considered side effect free.
    619  switch (fn.name) {
    620    case "toString":
    621    case "toLocaleString":
    622    case "valueOf":
    623      return true;
    624  }
    625 
    626  // This needs to use isSameNativeWithJitInfo instead of isSameNative, given
    627  // DOM methods share single native function with different JSJitInto,
    628  // and isSameNative cannot distinguish between side-effect-free methods
    629  // and others.
    630  const natives = gSideEffectFreeNatives.get(fn.name);
    631  return natives && natives.some(n => fn.isSameNativeWithJitInfo(n));
    632 }
    633 
    634 function updateConsoleInputEvaluation(dbg, webConsole) {
    635  // Adopt webConsole._lastConsoleInputEvaluation value in the new debugger,
    636  // to prevent "Debugger.Object belongs to a different Debugger" exceptions
    637  // related to the $_ bindings if the debugger object is changed from the
    638  // last evaluation.
    639  if (webConsole._lastConsoleInputEvaluation) {
    640    webConsole._lastConsoleInputEvaluation = dbg.adoptDebuggeeValue(
    641      webConsole._lastConsoleInputEvaluation
    642    );
    643  }
    644 }
    645 
    646 function getEvalInput(string) {
    647  const trimmedString = string.trim();
    648  // Add easter egg for console.mihai().
    649  if (
    650    trimmedString == "console.mihai()" ||
    651    trimmedString == "console.mihai();"
    652  ) {
    653    return '"http://incompleteness.me/blog/2015/02/09/console-dot-mihai/"';
    654  }
    655  return string;
    656 }
    657 
    658 function getFrameDbg(options, webConsole) {
    659  if (!options.frameActor) {
    660    return { frame: null, dbg: webConsole.dbg };
    661  }
    662  // Find the Debugger.Frame of the given FrameActor.
    663  const frameActor = webConsole.conn.getActor(options.frameActor);
    664  if (frameActor) {
    665    // If we've been given a frame actor in whose scope we should evaluate the
    666    // expression, be sure to use that frame's Debugger (that is, the JavaScript
    667    // debugger's Debugger) for the whole operation, not the console's Debugger.
    668    // (One Debugger will treat a different Debugger's Debugger.Object instances
    669    // as ordinary objects, not as references to be followed, so mixing
    670    // debuggers causes strange behaviors.)
    671    return { frame: frameActor.frame, dbg: frameActor.threadActor.dbg };
    672  }
    673  return DevToolsUtils.reportException(
    674    "evalWithDebugger",
    675    Error("The frame actor was not found: " + options.frameActor)
    676  );
    677 }
    678 
    679 /**
    680 * Get debugger object for given debugger and Web Console.
    681 *
    682 * @param object options
    683 *        See the `options` parameter of evalWithDebugger
    684 * @param {Debugger} dbg
    685 *        Debugger object
    686 * @param {WebConsoleActor} webConsole
    687 *        A reference to a webconsole actor which is used to get the target
    688 *        eval global and optionally the target actor
    689 * @return object
    690 *         An object that holds the following properties:
    691 *         - bindSelf: (optional) the self object for the evaluation
    692 *         - dbgGlobal: the global object reference in the debugger
    693 */
    694 function getDbgGlobal(options, dbg, webConsole) {
    695  let evalGlobal = webConsole.evalGlobal;
    696 
    697  if (options.innerWindowID) {
    698    const window = Services.wm.getCurrentInnerWindowWithId(
    699      options.innerWindowID
    700    );
    701 
    702    if (window) {
    703      evalGlobal = window;
    704    }
    705  }
    706 
    707  const dbgGlobal = dbg.makeGlobalObjectReference(evalGlobal);
    708 
    709  // If we have an object to bind to |_self|, create a Debugger.Object
    710  // referring to that object, belonging to dbg.
    711  if (!options.selectedObjectActor) {
    712    return { bindSelf: null, dbgGlobal };
    713  }
    714 
    715  // All the Object Actors are collected in the Target Actor's "objectsPool",
    716  // except for objects communicated by the thread actor on pause,
    717  // or by the JS Tracer.
    718  // But the "selected object actor" is generated via the console actor evaluation,
    719  // which stores its objects actor in the target's shared pool.
    720  const actor = webConsole.targetActor.objectsPool.getActorByID(
    721    options.selectedObjectActor
    722  );
    723 
    724  if (!actor) {
    725    return { bindSelf: null, dbgGlobal };
    726  }
    727 
    728  const jsVal = actor instanceof LongStringActor ? actor.str : actor.rawObj;
    729  if (!isObject(jsVal)) {
    730    return { bindSelf: jsVal, dbgGlobal };
    731  }
    732 
    733  // If we use the makeDebuggeeValue method of jsVal's own global, then
    734  // we'll get a D.O that sees jsVal as viewed from its own compartment -
    735  // that is, without wrappers. The evalWithBindings call will then wrap
    736  // jsVal appropriately for the evaluation compartment.
    737  const bindSelf = dbgGlobal.makeDebuggeeValue(jsVal);
    738  return { bindSelf, dbgGlobal };
    739 }