| // 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 type * as Protocol from '../../generated/protocol.js'; |
| import {createTarget} from '../../testing/EnvironmentHelpers.js'; |
| import {describeWithMockConnection} from '../../testing/MockConnection.js'; |
| import type * as ScopesCodec from '../../third_party/source-map-scopes-codec/source-map-scopes-codec.js'; |
| |
| import * as SDK from './sdk.js'; |
| |
| describeWithMockConnection('SourceMapScopeRemoteObject', () => { |
| let callFrame: sinon.SinonStubbedInstance<SDK.DebuggerModel.CallFrame>; |
| |
| beforeEach(() => { |
| callFrame = sinon.createStubInstance(SDK.DebuggerModel.CallFrame); |
| const target = createTarget(); |
| callFrame.debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel)!; |
| }); |
| |
| it('returns "value unavailable" for unavailable scope variables', async () => { |
| const originalScope: ScopesCodec.OriginalScope = { |
| start: {line: 0, column: 0}, |
| end: {line: 20, column: 0}, |
| isStackFrame: false, |
| kind: 'global', |
| variables: ['variable1', 'variable2'], |
| children: [], |
| }; |
| |
| const entry = new SDK.SourceMapScopeChainEntry.SourceMapScopeChainEntry( |
| callFrame, originalScope, undefined, false, undefined); |
| const {properties} = |
| await entry.object().getAllProperties(/* accessorPropertiesOnly */ false, /* generatePreview */ true); |
| |
| assert.isNotNull(properties); |
| assert.lengthOf(properties, 2); |
| assert.strictEqual(properties[0].name, 'variable1'); |
| assert.isUndefined(properties[0].value); |
| assert.strictEqual(properties[1].name, 'variable2'); |
| assert.isUndefined(properties[1].value); |
| }); |
| |
| it('resolves variable values using binding expressions and evaluateOnCallFrame', async () => { |
| const originalScope: ScopesCodec.OriginalScope = { |
| start: {line: 0, column: 0}, |
| end: {line: 20, column: 0}, |
| isStackFrame: false, |
| kind: 'global', |
| variables: ['variable1'], |
| children: [], |
| }; |
| const range: ScopesCodec.GeneratedRange = { |
| start: {line: 0, column: 0}, |
| end: {line: 0, column: 200}, |
| isStackFrame: false, |
| isHidden: false, |
| values: ['a'], |
| children: [], |
| }; |
| callFrame.location.returns( |
| new SDK.DebuggerModel.Location(callFrame.debuggerModel, '0' as Protocol.Runtime.ScriptId, 0, 50)); |
| callFrame.evaluate.callsFake(({expression}) => { |
| assert.strictEqual(expression, 'a'); |
| return Promise.resolve({object: new SDK.RemoteObject.LocalJSONObject(42)}); |
| }); |
| |
| const entry = |
| new SDK.SourceMapScopeChainEntry.SourceMapScopeChainEntry(callFrame, originalScope, range, false, undefined); |
| const {properties} = |
| await entry.object().getAllProperties(/* accessorPropertiesOnly */ false, /* generatePreview */ true); |
| |
| assert.isNotNull(properties); |
| assert.lengthOf(properties, 1); |
| assert.strictEqual(properties[0].name, 'variable1'); |
| assert.strictEqual(properties[0].value?.value, 42); |
| }); |
| |
| it('uses the right binding expression when resolving variable values when ranges are split', async () => { |
| const originalScope: ScopesCodec.OriginalScope = { |
| start: {line: 0, column: 0}, |
| end: {line: 20, column: 0}, |
| isStackFrame: false, |
| kind: 'global', |
| variables: ['variable1'], |
| children: [], |
| }; |
| const range: ScopesCodec.GeneratedRange = { |
| start: {line: 0, column: 0}, |
| end: {line: 0, column: 200}, |
| isStackFrame: false, |
| isHidden: false, |
| values: [[ |
| {from: {line: 0, column: 0}, to: {line: 0, column: 50}, value: 'a'}, // From 0..50 available as 'a'. |
| {from: {line: 0, column: 50}, to: {line: 0, column: 150}}, // From 50..150 unavailable. |
| {from: {line: 0, column: 150}, to: {line: 0, column: 200}, value: 'b'}, // From 150..200 available as 'b'. |
| |
| ]], |
| children: [], |
| }; |
| |
| // We simulate 3 pauses in the 3 sub-ranges. |
| const pauseLocations = [ |
| new SDK.DebuggerModel.Location(callFrame.debuggerModel, '0' as Protocol.Runtime.ScriptId, 0, 25), |
| new SDK.DebuggerModel.Location(callFrame.debuggerModel, '0' as Protocol.Runtime.ScriptId, 0, 100), |
| new SDK.DebuggerModel.Location(callFrame.debuggerModel, '0' as Protocol.Runtime.ScriptId, 0, 175), |
| ]; |
| const expectedExpressions = ['a', 'does not matter since it must not be called', 'b']; |
| const values = [42, undefined, 21]; |
| |
| for (let i = 0; i < 3; ++i) { |
| callFrame.location.returns(pauseLocations[i]); |
| callFrame.evaluate.callsFake(({expression}) => { |
| assert.strictEqual(expression, expectedExpressions[i]); |
| return Promise.resolve({object: new SDK.RemoteObject.LocalJSONObject(values[i])}); |
| }); |
| |
| const entry = |
| new SDK.SourceMapScopeChainEntry.SourceMapScopeChainEntry(callFrame, originalScope, range, false, undefined); |
| const {properties} = |
| await entry.object().getAllProperties(/* accessorPropertiesOnly */ false, /* generatePreview */ true); |
| |
| assert.isNotNull(properties); |
| assert.lengthOf(properties, 1); |
| assert.strictEqual(properties[0].name, 'variable1'); |
| assert.strictEqual(properties[0].value?.value, values[i]); |
| } |
| }); |
| }); |