Debugger.Frame

一个 Debugger.Frame 实例表示一个可见的堆栈帧。给定一个 Debugger.Frame 实例,您可以找到该帧正在执行的脚本,遍历堆栈到旧的帧,找到正在执行的词法环境,等等。

对于给定的 Debugger 实例,SpiderMonkey 仅为给定的可见帧创建一个 Debugger.Frame 实例。在调试对象在给定帧中运行时调用的每个处理程序方法都将获得相同的帧对象。类似地,回溯到之前访问的帧会产生与之前相同的帧对象。调试器代码可以向帧对象添加自己的属性,并期望以后找到它们,使用 == 来确定两个表达式是否引用同一个帧,等等。

(如果多个 Debugger 实例调试相同的代码,则每个 Debugger 为给定的帧获取一个单独的 Debugger.Frame 实例。这允许使用每个 Debugger 实例的代码在其 Debugger.Frame 实例上放置任何它喜欢的属性,而无需担心干扰其他调试器。)

当调试对象弹出堆栈帧(例如,因为函数调用已返回或从中抛出异常)时,引用该帧的 Debugger.Frame 实例变为非活动状态:其 onStack 属性变为 false,并且访问其许多属性或调用其方法都会抛出异常。请注意,帧仅在调试器可预测的时间变为非活动状态:当调试对象运行时,或当调试器自己从堆栈中删除帧时。

可见帧

检查调用堆栈时,Debugger 不会显示堆栈上实际存在的全部帧:虽然它确实显示了所有运行调试对象代码的帧,但它会忽略运行调试器自身代码的帧,并且会忽略大多数运行非调试对象代码的帧。我们称这些堆栈帧为 Debugger 确实显示的可见帧

如果以下任何一项为真,则该帧为可见帧

  • 它正在运行 调试对象代码

  • 其直接调用者是运行调试对象代码的帧;或者

  • 它是一个 "debugger",表示由调试器调用的调试对象代码的延续。

“直接调用者”规则意味着,当调试对象代码调用非调试对象函数时,它看起来像是对原语的调用:您会看到一个对调试对象可访问的非调试对象函数的帧,但该函数进行的任何进一步调用都被视为内部细节,并从堆栈跟踪中省略。如果非调试对象函数最终回调到调试对象代码,则这些帧是可见的。

(请注意,调试对象不被认为是其触发的处理程序方法的“直接调用者”。即使调试对象和调试器共享相同的 JavaScript 堆栈,为 SpiderMonkey 调用处理程序方法以报告调试对象中的事件而推送的帧绝不被视为可见帧。)

调用函数和“debugger”帧

调用函数是此接口中任何允许调试器在调试对象中调用代码的函数:Debugger.Object.prototype.callDebugger.Frame.prototype.eval,等等。

虽然调用函数在要运行的代码和如何向其传递值方面有所不同,但它们都遵循以下一般过程

  1. older 为堆栈上最年轻的可见帧,如果不存在此类帧,则为 null。(这绝不是调试器自己的帧之一;这些帧永远不会显示为 Debugger.Frame 实例。)

  2. 在堆栈上推送一个 "debugger" 帧,其 older 属性为older

  3. 根据给定调用函数的需要调用调试对象代码,并将 "debugger" 帧作为其延续。例如,Debugger.Frame.prototype.eval 为其运行的代码推送一个 "eval" 帧,而 Debugger.Object.prototype.call 推送一个 "call" 帧。

  4. 当调试对象代码完成时,无论是通过返回、抛出异常还是终止,都弹出 "debugger" 帧,并从调用函数返回适当的 完成值 到调试器。

当调试器调用调用函数以运行调试对象代码时,该代码的延续是调试器,而不是下一个调试对象代码帧。推送 "debugger" 帧使这种延续明确,并使其更容易找到为调用创建的堆栈的范围。

挂起的帧

某些帧可以挂起

当生成器 yield 值时,或者当异步函数 await 值时,当前帧将被挂起并从堆栈中移除,其他 JS 代码有机会运行。稍后(例如,如果 await 的 Promise 变为已解析),SpiderMonkey 将恢复该帧。它将被放回堆栈,执行将从中断的地方继续。只有生成器和异步函数调用帧可以被挂起和恢复。

目前,帧的 onStack 属性在被挂起时为 false (bug 1448880)。

每次将生成器或异步函数调用放回堆栈时,SpiderMonkey 都使用相同的 Debugger.Frame 对象。这意味着 onStep 处理程序可用于跳过 yieldawait

每次帧被挂起时都会调用 frame.onPop 处理程序,每次帧被恢复时都会调用 Debugger.onEnterFrame 处理程序。(这意味着这些事件可以对同一个 Frame 对象触发多次,这很奇怪,但准确地传达了正在发生的事情。)

传递给挂起的 frame.onPop 处理程序的 完成值 包含其他属性以阐明正在发生的事情。有关详细信息,请参阅完成值的文档。

进入生成器: “初始 yield”

当调用调试对象生成器时,会发生一些奇怪的事情。.onEnterFrame 钩子会触发,就像我们正在进入生成器一样。但是生成器内部的代码不会运行。相反,它会立即返回。然后,我们有时会为同一个生成器获得另一个 .onEnterFrame 事件。发生了什么事?

为了解释这一点,我们首先必须描述根据 ECMAScript 语言规范生成器调用是如何工作的。请注意,除了步骤 3 之外,它与常规函数调用完全相同。

  1. “执行上下文”(我们称之为 Frame)被推送到堆栈。

  2. 创建一个环境(用于参数和局部变量)。如果有任何参数默认值表达式,则对其进行计算。

  3. 创建一个生成器对象,最初在生成器主体开始时被挂起。

  4. 堆栈帧被弹出,生成器对象被返回给调用者。

JavaScript 引擎实际上按此顺序执行这些步骤。因此,当调用调试对象生成器时,您将观察到以下情况

  1. debugger.onEnterFrame 钩子触发。

  2. 调试器可以遍历参数默认值代码(如果有)。

  3. 生成器的主体尚未运行。相反,将创建一个生成器对象并将其挂起(不会触发任何调试器事件)。

  4. frame.onPop 钩子触发,完成值为 {return: (新的生成器对象) }

在 SpiderMonkey 中,此挂起和返回新生成器对象的流程称为“初始 yield”。

如果调用者随后使用生成器的 .next() 方法,这可能会立即发生,也可能不会立即发生,具体取决于调试对象代码,则挂起的生成器将被恢复,再次触发 .onEnterFrame

Debugger.Frame 原型对象的访问器属性

一个 Debugger.Frame 实例从其原型继承以下访问器属性

type

描述此帧类型的字符串

  • "call":运行函数调用的帧。(我们可能无法获取对主机函数调用的帧。)

  • "eval":运行传递给 eval 的代码的帧。

  • "global":运行全局代码的帧(既不是上述两种情况的 JavaScript)。

  • "module":在模块顶层运行代码的帧。

  • "wasmcall":运行 WebAssembly 函数调用的帧。

  • "debugger":调试器调用的用户代码调用帧(请参阅下面的 eval 方法)。

如果 .terminated == true,则访问此属性将抛出错误。

implementation

描述此帧在 JavaScript 引擎的哪个层级中执行的字符串。

  • "interpreter":在解释器中运行的帧。

  • "baseline":在非优化基线 JIT 中运行的帧。

  • "ion":在优化 JIT 中运行的帧。

  • "wasm":在 WebAssembly 基线 JIT 中运行的帧。

如果 .onStack == false,则访问此属性将抛出错误。

this

此帧的 this 值(一个调试目标值)。对于 wasmcall 帧,此属性会抛出 TypeError

如果 .terminated == true,则访问此属性将抛出错误。

older

下一个较旧的可视帧的 Debugger.Frame,当此帧完成时,控制权将恢复到该帧。如果没有较旧的帧,则为 null。如果在此帧上方显式插入了异步堆栈跟踪,则为 null,因为显式保存的帧优先。如果此帧是挂起的生成器或异步调用,则这也将为 null

如果 .terminated == true,则访问此属性将抛出错误。

olderSavedFrame

如果此帧没有 older 帧,则此字段可能保存一个 SavedFrame 对象,该对象表示触发此 Debugger.Frame 实例执行的已保存异步堆栈。

如果 .terminated == true,则访问此属性将抛出错误。

onStack

如果此 Debugger.Frame 实例引用的帧仍在堆栈上,则为 true;如果它已完成执行或以其他方式弹出,则为 false。请注意,无论帧处于什么状态,都可以访问此属性,因此可用于验证是否可以安全地访问需要堆栈上帧的其他属性。

terminated

如果此 Debugger.Frame 实例引用的帧将永远不会再次运行,则为 true;如果它在堆栈上或是一个稍后可能恢复的挂起生成器/异步调用,则为 false。请注意,无论帧处于什么状态,都可以访问此属性,因此可用于验证是否可以安全地访问需要非终止帧的其他属性。

script

在此帧中执行的脚本(一个 Debugger.Script 实例),或在不表示对调试目标代码调用的帧上为 null。在 callee 属性不为 null 的帧上,这等于 callee.script

如果 .terminated == true,则访问此属性将抛出错误。

offset

当前在 script 中执行的字节码指令的偏移量,或者如果帧的 script 属性为 null,则为 undefined。对于 wasmcall 帧,此属性会抛出 TypeError

如果这用于挂起的函数帧,则偏移量将引用帧将恢复到的偏移量。

如果 .terminated == true,则访问此属性将抛出错误。

environment

正在进行评估的词法环境(一个 Debugger.Environment 实例),或在不表示调试目标代码评估的帧上为 null,例如对非调试目标函数、主机函数或 "debugger" 帧的调用。

如果 .terminated == true,则访问此属性将抛出错误。

callee

创建此帧的函数应用,作为调试目标值,或者如果这不是 "call" 帧,则为 null

如果 .terminated == true,则访问此属性将抛出错误。

constructing

如果此帧用于作为构造函数调用的函数,则为 true,否则为 false。

如果 .terminated == true,则访问此属性将抛出错误。

arguments

传递给当前帧的参数,或者如果这不是 "call" 帧,则为 null。当不为 null 时,这是一个对象,分配在与调试器相同的全局范围内,其原型链上具有 Array.prototype,一个不可写的 length 属性,以及名称为数组索引的属性。每个属性都是一个只读访问器属性,其 getter 返回相应参数的当前值。当引用帧被弹出时,参数值的属性的 getter 会抛出错误。

如果 .onStack == false,则访问此属性将抛出错误。

asyncPromise

如果帧不是异步(生成器)函数,则这将是 undefined

对于异步函数,这将是一个 Debugger.Object,其引用是异步函数调用的返回值的 Promise。请注意,如果在 onEnterFrame 期间访问此属性,则此属性将为 null,因为此时 Promise 还不存在。

对于异步生成器函数,这将是一个 Debugger.Object,其引用是当前迭代的“value”+“done”对象的 Promise,该 Promise 将在生成器下次抛出/yield/返回时解析。请注意,如果在初始生成器 onEnterFrame/onPop(在第一次 .next 调用之前)期间访问此属性,则这将为 null,因为此时还没有 Promise。

如果 .terminated == true,则访问此属性将抛出错误。

Debugger.Frame 实例的处理程序方法

每个 Debugger.Frame 实例都继承访问器属性,这些属性保存处理程序函数,供 SpiderMonkey 在帧中发生给定事件时调用。

对帧的处理程序方法的调用是跨区间的线程内调用:调用发生在帧所属的线程中,并在处理程序方法所属的区间中运行。

Debugger.Frame 实例继承以下处理程序方法属性

onStep

此属性必须是 undefined 或函数。如果它是一个函数,则当此帧中的执行取得少量进展时,SpiderMonkey 会调用它,不传递任何参数,并将此 Debugger.Frame 实例作为 this 值提供。该函数应返回一个 恢复值,指定调试目标的执行应如何继续。

“少量进展”的构成因实现而异,但它足够细粒度以实现有用的“步进”和“下一步”行为。

如果多个 Debugger 实例都具有给定堆栈帧的 Debugger.Frame 实例,并且都设置了 onStep 处理程序,则它们的处理程序将以未指定的顺序运行。如果任何 onStep 处理程序强制帧提前返回(通过返回除 undefined 之外的恢复值),则任何剩余的调试器的 onStep 处理程序都不会运行。

此属性在不执行调试目标代码的帧上被忽略,例如对主机函数的调用 "call" 帧和 "debugger" 帧。

无论帧当前是否在堆栈上/挂起/终止,都可以访问和重新分配此属性。

onPop

此属性必须是 undefined 或函数。如果它是一个函数,则 SpiderMonkey 会在弹出或挂起此帧之前调用它,传递一个 完成值 指示原因,并将此 Debugger.Frame 实例作为 this 值提供。该函数应返回一个 恢复值 指示执行应如何继续。在新创建的帧上,此属性的值为 undefined

当调用此处理程序时,此帧的当前执行位置(如其 offsetenvironment 属性中反映的那样)是导致其展开的操作。在返回或抛出异常的帧中,该位置通常是 return 或 throw 语句。在传播异常的帧中,该位置是调用。在生成器或异步函数帧中,该位置可能是 yieldawait 表达式。

onPop 调用报告构造调用(即通过 new 运算符调用的函数)的完成时,传递给处理程序的完成值描述了函数体返回的值。如果此值不是对象,则它可能与 new 表达式生成的值不同,后者将是帧的 this 属性的值。(在 ECMAScript 术语中,onPop 处理程序接收 [[Call]] 方法返回的值,而不是 [[Construct]] 方法返回的值。)

当调试器处理程序函数通过返回 { return:... }{ throw:... }null 恢复值来强制帧提前完成时,SpiderMonkey 会调用帧的 onPop 处理程序(如果有)。在这种情况下传递的完成值反映导致帧完成的恢复值。

当 SpiderMonkey 调用正在抛出异常或被终止的帧的 onPop 处理程序,并且处理程序返回 undefined 时,则 SpiderMonkey 会继续进行异常或终止。也就是说,undefined 恢复值不会干扰帧的抛出和终止过程。

如果多个 Debugger 实例都具有给定堆栈帧的 Debugger.Frame 实例,并且都设置了 onPop 处理程序,则它们的处理程序将以未指定的顺序运行。每个处理程序返回的恢复值都会为下一个处理程序建立报告的完成值。

对于给定的 Debugger.FrameonPop 处理程序通常只调用一次,之后帧变为非活动状态。但是,在 生成器和异步函数 的情况下,每次挂起帧时,onPop 都会触发。

此处理器不会在 "debugger" 帧上调用。当由于过度递归或内存不足异常而展开帧时,也不会调用它。

无论帧当前是否在堆栈上/挂起/终止,都可以访问和重新分配此属性。

调试器.帧原型对象的函数属性

下面描述的函数只能使用一个 this 值来调用,该值引用 Debugger.Frame 实例;它们不能用作其他类型对象的的方法。

eval(code, [options])

在该帧的执行上下文中评估code,并返回一个 完成值,描述其完成方式。Code 是一个字符串。如果此帧的 environment 属性为 nulltype 属性为 wasmcall,则抛出 TypeError。在调用期间,所有现有的处理程序方法、断点等都保持活动状态。此函数遵循 调用函数约定

code包含 Use Strict 指令或在此帧中执行的代码为严格模式代码时,Code被解释为严格模式代码。

如果code不是严格模式代码,则code中的变量声明会影响此帧的环境。(在 ECMAScript 规范中使用的术语中,eval 代码的执行上下文的 VariableEnvironment 是此帧表示的执行上下文的 VariableEnvironment。)如果实现限制阻止 SpiderMonkey 按要求扩展此帧的环境,则此调用会抛出 Error 异常。

如果给出,options应该是一个对象,其属性指定评估应如何发生的细节。eval 方法识别以下属性

  • url

    我们应该将code归因于的文件名或 URL。如果省略此属性,则 URL 默认为 "debugger eval code"

  • lineNumber

    评估代码应在url中声称开始的行号。

如果 .onStack == false,则访问此属性将抛出错误。

evalWithBindings(code, bindings, [options])

类似于 eval,但在该帧的环境中评估code,并使用来自对象bindings的绑定扩展。对于bindings命名的每个自己的可枚举属性name,其值为value,在评估code的环境中包含一个名为name的变量,其值为value。每个value必须是调试器值。(这不像 with 语句:code可以访问、分配和删除引入的绑定,而不会对bindings对象产生任何影响。)

此方法允许调试器代码引入对给定调试器代码可见的临时绑定,并且这些绑定引用调试器持有的调试器值,并且不会修改任何现有的调试器环境。

请注意,与 eval 类似,传递给 evalWithBindingscode中的声明会影响此帧的环境,即使该环境通过code中可见的绑定扩展也是如此。(在 ECMAScript 规范中使用的术语中,eval 代码的执行上下文的 VariableEnvironment 是此帧表示的执行上下文的 VariableEnvironment,并且bindings出现在一个新的声明性环境中,它是 eval 代码的 LexicalEnvironment。)如果实现限制阻止 SpiderMonkey 按要求扩展此帧的环境,则此调用会抛出 Error 异常。

options参数与上面描述的 Debugger.Frame.prototype.eval相同。也像 eval 一样,如果此帧的 environment 属性为 nulltype 属性为 wasmcall,则抛出 TypeError

注意:如果此方法在拥有 Debugger 对象的对象上调用,并且该对象具有 onNativeCall 处理程序,则在评估期间只会调用与该调试器关联的对象上的钩子。

如果 .onStack == false,则访问此属性将抛出错误。