| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as Common from '../../core/common/common.js'; |
| import * as SDK from '../../core/sdk/sdk.js'; |
| |
| import {resolveScopeChain} from './NamesResolver.js'; |
| |
| /** |
| * This class is responsible for resolving / updating the scope chain for a specific {@link SDK.DebuggerModel.CallFrame} |
| * instance. |
| * |
| * There are several sources that can influence the scope view: |
| * - Debugger plugins can provide the whole scope info (e.g. from DWARF) |
| * - Source Maps can provide OR augment scope info |
| * |
| * Source maps can be enabled/disabled dynamically and debugger plugins can attach debug info after the fact. |
| * |
| * This class tracks all that and sends events with the latest scope chain for a specific call frame. |
| */ |
| export class ScopeChainModel extends Common.ObjectWrapper.ObjectWrapper<EventTypes> { |
| readonly #callFrame: SDK.DebuggerModel.CallFrame; |
| |
| /** We use the `Throttler` here to make sure that `#boundUpdate` is not run multiple times simultanously */ |
| readonly #throttler = new Common.Throttler.Throttler(5); |
| readonly #boundUpdate = this.#update.bind(this); |
| |
| constructor(callFrame: SDK.DebuggerModel.CallFrame) { |
| super(); |
| this.#callFrame = callFrame; |
| this.#callFrame.debuggerModel.addEventListener( |
| SDK.DebuggerModel.Events.DebugInfoAttached, this.#debugInfoAttached, this); |
| this.#callFrame.debuggerModel.sourceMapManager().addEventListener( |
| SDK.SourceMapManager.Events.SourceMapAttached, this.#sourceMapAttached, this); |
| |
| void this.#throttler.schedule(this.#boundUpdate); |
| } |
| |
| dispose(): void { |
| this.#callFrame.debuggerModel.removeEventListener( |
| SDK.DebuggerModel.Events.DebugInfoAttached, this.#debugInfoAttached, this); |
| this.#callFrame.debuggerModel.sourceMapManager().removeEventListener( |
| SDK.SourceMapManager.Events.SourceMapAttached, this.#sourceMapAttached, this); |
| this.listeners?.clear(); |
| } |
| |
| async #update(): Promise<void> { |
| const scopeChain = await resolveScopeChain(this.#callFrame); |
| this.dispatchEventToListeners(Events.SCOPE_CHAIN_UPDATED, new ScopeChain(scopeChain)); |
| } |
| |
| #debugInfoAttached(event: Common.EventTarget.EventTargetEvent<SDK.Script.Script>): void { |
| if (event.data === this.#callFrame.script) { |
| void this.#throttler.schedule(this.#boundUpdate); |
| } |
| } |
| |
| #sourceMapAttached(event: Common.EventTarget |
| .EventTargetEvent<{client: SDK.Script.Script, sourceMap: SDK.SourceMap.SourceMap}>): void { |
| if (event.data.client === this.#callFrame.script) { |
| void this.#throttler.schedule(this.#boundUpdate); |
| } |
| } |
| } |
| |
| export const enum Events { |
| SCOPE_CHAIN_UPDATED = 'ScopeChainUpdated', |
| } |
| |
| export interface EventTypes { |
| [Events.SCOPE_CHAIN_UPDATED]: ScopeChain; |
| } |
| |
| /** |
| * A scope chain ready to be shown in the UI with debugging info applied. |
| */ |
| export class ScopeChain { |
| readonly scopeChain: SDK.DebuggerModel.ScopeChainEntry[]; |
| |
| constructor(scopeChain: SDK.DebuggerModel.ScopeChainEntry[]) { |
| this.scopeChain = scopeChain; |
| } |
| } |