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 }