[go: nahoru, domu]

blob: f1057682f9309c29072fe833ebddd26208523796 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type * as Platform from '../../../../../front_end/core/platform/platform.js';
import * as Common from '../../../../../front_end/core/common/common.js';
import * as SDK from '../../../../../front_end/core/sdk/sdk.js';
import * as Protocol from '../../../../../front_end/generated/protocol.js';
import * as Bindings from '../../../../../front_end/models/bindings/bindings.js';
import * as Workspace from '../../../../../front_end/models/workspace/workspace.js';
import {createTarget} from '../../helpers/EnvironmentHelpers.js';
import {
describeWithMockConnection,
dispatchEvent,
setMockConnectionResponseHandler,
} from '../../helpers/MockConnection.js';
import {MockProtocolBackend} from '../../helpers/MockScopeChain.js';
const {assert} = chai;
const SCRIPT_ID_ONE = '1' as Protocol.Runtime.ScriptId;
const SCRIPT_ID_TWO = '2' as Protocol.Runtime.ScriptId;
describeWithMockConnection('DebuggerModel', () => {
describe('breakpoint activation', () => {
beforeEach(() => {
// Dummy handlers for unblocking target suspension.
setMockConnectionResponseHandler('Debugger.setAsyncCallStackDepth', () => ({}));
setMockConnectionResponseHandler('Debugger.disable', () => ({}));
setMockConnectionResponseHandler('DOM.disable', () => ({}));
setMockConnectionResponseHandler('CSS.disable', () => ({}));
setMockConnectionResponseHandler('Overlay.disable', () => ({}));
setMockConnectionResponseHandler('Overlay.setShowGridOverlays', () => ({}));
setMockConnectionResponseHandler('Overlay.setShowFlexOverlays', () => ({}));
setMockConnectionResponseHandler('Overlay.setShowScrollSnapOverlays', () => ({}));
setMockConnectionResponseHandler('Overlay.setShowContainerQueryOverlays', () => ({}));
setMockConnectionResponseHandler('Overlay.setShowIsolatedElements', () => ({}));
setMockConnectionResponseHandler('Overlay.setShowViewportSizeOnResize', () => ({}));
setMockConnectionResponseHandler('Target.setAutoAttach', () => ({}));
// Dummy handlers for unblocking target resumption.
setMockConnectionResponseHandler('Debugger.enable', () => ({}));
setMockConnectionResponseHandler('Debugger.setPauseOnExceptions', () => ({}));
setMockConnectionResponseHandler('DOM.enable', () => ({}));
setMockConnectionResponseHandler('Overlay.enable', () => ({}));
setMockConnectionResponseHandler('CSS.enable', () => ({}));
});
it('deactivates breakpoints on construction with inactive breakpoints', async () => {
let breakpointsDeactivated = false;
setMockConnectionResponseHandler('Debugger.setBreakpointsActive', request => {
if (request.active === false) {
breakpointsDeactivated = true;
}
return {};
});
Common.Settings.Settings.instance().moduleSetting('breakpointsActive').set(false);
createTarget();
assert.isTrue(breakpointsDeactivated);
});
it('deactivates breakpoints for suspended target', async () => {
let breakpointsDeactivated = false;
setMockConnectionResponseHandler('Debugger.setBreakpointsActive', request => {
if (request.active === false) {
breakpointsDeactivated = true;
}
return {};
});
const target = createTarget();
await target.suspend();
// Deactivate breakpoints while suspended.
Common.Settings.Settings.instance().moduleSetting('breakpointsActive').set(false);
// Verify that the backend received the message.
assert.isTrue(breakpointsDeactivated);
// Resume and verify that the setBreakpointsActive(false) is called again when the target resumes.
// This is only needed for older backends (before crbug.com/1357046 is fixed).
breakpointsDeactivated = false;
await target.resume();
assert.isTrue(breakpointsDeactivated);
});
it('activates breakpoints for suspended target', async () => {
let breakpointsDeactivated = false;
let breakpointsActivated = false;
setMockConnectionResponseHandler('Debugger.setBreakpointsActive', request => {
if (request.active) {
breakpointsActivated = true;
} else {
breakpointsDeactivated = true;
}
return {};
});
// Deactivate breakpoints befroe the target is created.
Common.Settings.Settings.instance().moduleSetting('breakpointsActive').set(false);
const target = createTarget();
assert.isTrue(breakpointsDeactivated);
await target.suspend();
// Activate breakpoints while suspended.
Common.Settings.Settings.instance().moduleSetting('breakpointsActive').set(true);
// Verify that the backend received the message.
assert.isTrue(breakpointsActivated);
});
});
describe('createRawLocationFromURL', () => {
it('yields correct location in the presence of multiple scripts with the same URL', async () => {
const target = createTarget();
const debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel);
const url = 'http://localhost/index.html';
dispatchEvent(target, 'Debugger.scriptParsed', {
scriptId: SCRIPT_ID_ONE,
url,
startLine: 0,
startColumn: 0,
endLine: 1,
endColumn: 10,
executionContextId: 1,
hash: '',
isLiveEdit: false,
sourceMapURL: undefined,
hasSourceURL: false,
length: 10,
});
dispatchEvent(target, 'Debugger.scriptParsed', {
scriptId: SCRIPT_ID_TWO,
url,
startLine: 20,
startColumn: 0,
endLine: 21,
endColumn: 10,
executionContextId: 1,
hash: '',
isLiveEdit: false,
sourceMapURL: undefined,
hasSourceURL: false,
length: 10,
});
assert.strictEqual(debuggerModel?.createRawLocationByURL(url, 0)?.scriptId, SCRIPT_ID_ONE);
assert.strictEqual(debuggerModel?.createRawLocationByURL(url, 20, 1)?.scriptId, SCRIPT_ID_TWO);
assert.strictEqual(debuggerModel?.createRawLocationByURL(url, 5, 5), null);
});
});
const breakpointId1 = 'fs.js:1' as Protocol.Debugger.BreakpointId;
const breakpointId2 = 'unsupported' as Protocol.Debugger.BreakpointId;
describe('setBreakpointByURL', () => {
it('correctly sets only a single breakpoint in Node.js internal scripts', async () => {
setMockConnectionResponseHandler(
'Debugger.setBreakpointByUrl', ({url}): Protocol.Debugger.SetBreakpointByUrlResponse => {
if (url === 'fs.js') {
return {
breakpointId: breakpointId1,
locations: [],
getError() {
return undefined;
},
};
}
return {
breakpointId: breakpointId2,
locations: [],
getError() {
return undefined;
},
};
});
const target = createTarget();
target.markAsNodeJSForTest();
const model = new SDK.DebuggerModel.DebuggerModel(target);
const {breakpointId} = await model.setBreakpointByURL('fs.js' as Platform.DevToolsPath.UrlString, 1);
assert.strictEqual(breakpointId, breakpointId1);
});
});
describe('scriptsForSourceURL', () => {
it('returns the latest script at the front of the result for scripts with the same URL', () => {
const target = createTarget();
const url = 'http://localhost/index.html';
dispatchEvent(target, 'Debugger.scriptParsed', {
scriptId: SCRIPT_ID_ONE,
url,
startLine: 0,
startColumn: 0,
endLine: 1,
endColumn: 10,
executionContextId: 1,
hash: '',
isLiveEdit: false,
sourceMapURL: undefined,
hasSourceURL: false,
length: 10,
});
dispatchEvent(target, 'Debugger.scriptParsed', {
scriptId: SCRIPT_ID_TWO,
url,
startLine: 20,
startColumn: 0,
endLine: 21,
endColumn: 10,
executionContextId: 1,
hash: '',
isLiveEdit: false,
sourceMapURL: undefined,
hasSourceURL: false,
length: 10,
});
const debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel);
const scripts = debuggerModel?.scriptsForSourceURL(url) || [];
assert.strictEqual(scripts[0].scriptId, SCRIPT_ID_TWO);
assert.strictEqual(scripts[1].scriptId, SCRIPT_ID_ONE);
});
});
describe('Scope', () => {
it('Scope.typeName covers every enum value', async () => {
const target = createTarget();
const debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel) as SDK.DebuggerModel.DebuggerModel;
const scriptUrl = 'https://script-host/script.js' as Platform.DevToolsPath.UrlString;
const script = new SDK.Script.Script(
debuggerModel, SCRIPT_ID_ONE, scriptUrl, 0, 0, 0, 0, 0, '', false, false, undefined, false, 0, null, null,
null, null, null, null);
const scopeTypes: Protocol.Debugger.ScopeType[] = [
Protocol.Debugger.ScopeType.Global,
Protocol.Debugger.ScopeType.Local,
Protocol.Debugger.ScopeType.With,
Protocol.Debugger.ScopeType.Closure,
Protocol.Debugger.ScopeType.Catch,
Protocol.Debugger.ScopeType.Block,
Protocol.Debugger.ScopeType.Script,
Protocol.Debugger.ScopeType.Eval,
Protocol.Debugger.ScopeType.Module,
Protocol.Debugger.ScopeType.WasmExpressionStack,
];
for (const scopeType of scopeTypes) {
const payload: Protocol.Debugger.CallFrame = {
callFrameId: '0' as Protocol.Debugger.CallFrameId,
functionName: 'test',
functionLocation: undefined,
location: {
scriptId: SCRIPT_ID_ONE,
lineNumber: 0,
columnNumber: 0,
},
url: 'test-url',
scopeChain: [{
type: scopeType,
object: {type: 'object'} as Protocol.Runtime.RemoteObject,
}],
this: {type: 'object'} as Protocol.Runtime.RemoteObject,
returnValue: undefined,
canBeRestarted: false,
};
const callFrame = new SDK.DebuggerModel.CallFrame(debuggerModel, script, payload, 0);
const scope = new SDK.DebuggerModel.Scope(callFrame, 0);
assert.notEqual('', scope.typeName());
}
});
});
describe('pause', () => {
let target: SDK.Target.Target;
let backend: MockProtocolBackend;
let debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding;
beforeEach(() => {
target = createTarget({id: 'main' as Protocol.Target.TargetID, name: 'main', type: SDK.Target.Type.Frame});
const targetManager = target.targetManager();
const workspace = Workspace.Workspace.WorkspaceImpl.instance();
const resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace);
debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(
{forceNew: false, resourceMapping, targetManager});
backend = new MockProtocolBackend();
Bindings.IgnoreListManager.IgnoreListManager.instance({forceNew: false, debuggerWorkspaceBinding});
});
it('with empty call frame list will invoke plain step-into', async () => {
const stepIntoRequestPromise = new Promise<void>(resolve => {
setMockConnectionResponseHandler('Debugger.stepInto', () => {
resolve();
return {};
});
});
backend.dispatchDebuggerPauseWithNoCallFrames(target, Protocol.Debugger.PausedEventReason.Other);
await stepIntoRequestPromise;
});
});
});
describe('DebuggerModel', () => {
describe('sortAndMergeRanges', () => {
function createRange(
scriptId: Protocol.Runtime.ScriptId, startLine: number, startColumn: number, endLine: number,
endColumn: number): Protocol.Debugger.LocationRange {
return {
scriptId,
start: {lineNumber: startLine, columnNumber: startColumn},
end: {lineNumber: endLine, columnNumber: endColumn},
};
}
function sortAndMerge(locationRange: Protocol.Debugger.LocationRange[]) {
return SDK.DebuggerModel.sortAndMergeRanges(locationRange.concat());
}
function assertIsMaximallyMerged(locationRange: Protocol.Debugger.LocationRange[]) {
for (let i = 1; i < locationRange.length; ++i) {
const prev = locationRange[i - 1];
const curr = locationRange[i];
assert.isTrue(prev.scriptId <= curr.scriptId);
if (prev.scriptId === curr.scriptId) {
assert.isTrue(prev.end.lineNumber <= curr.start.lineNumber);
if (prev.end.lineNumber === curr.start.lineNumber) {
assert.isTrue(prev.end.columnNumber <= curr.start.columnNumber);
}
}
}
}
it('can be reduced if equal', () => {
const testRange = createRange(SCRIPT_ID_ONE, 0, 3, 3, 3);
const locationRangesToBeReduced = [
testRange,
testRange,
];
const reduced = sortAndMerge(locationRangesToBeReduced);
assert.deepEqual(reduced, [testRange]);
assertIsMaximallyMerged(reduced);
});
it('can be reduced if overlapping (multiple ranges)', () => {
const locationRangesToBeReduced = [
createRange(SCRIPT_ID_ONE, 0, 5, 5, 3),
createRange(SCRIPT_ID_ONE, 0, 3, 3, 3),
createRange(SCRIPT_ID_ONE, 5, 3, 10, 10),
createRange(SCRIPT_ID_TWO, 5, 4, 10, 10),
];
const locationRangesExpected = [
createRange(SCRIPT_ID_ONE, 0, 3, 10, 10),
locationRangesToBeReduced[3],
];
const reduced = sortAndMerge(locationRangesToBeReduced);
assert.deepEqual(reduced, locationRangesExpected);
assertIsMaximallyMerged(reduced);
});
it('can be reduced if overlapping (same start, different end)', () => {
const locationRangesToBeReduced = [
createRange(SCRIPT_ID_ONE, 0, 5, 5, 3),
createRange(SCRIPT_ID_ONE, 0, 5, 3, 3),
];
const locationRangesExpected = [
createRange(SCRIPT_ID_ONE, 0, 5, 5, 3),
];
const reduced = sortAndMerge(locationRangesToBeReduced);
assert.deepEqual(reduced, locationRangesExpected);
assertIsMaximallyMerged(reduced);
});
it('can be reduced if overlapping (different start, same end)', () => {
const locationRangesToBeReduced = [
createRange(SCRIPT_ID_ONE, 0, 3, 5, 3),
createRange(SCRIPT_ID_ONE, 0, 5, 5, 3),
];
const locationRangesExpected = [
createRange(SCRIPT_ID_ONE, 0, 3, 5, 3),
];
const reduced = sortAndMerge(locationRangesToBeReduced);
assert.deepEqual(reduced, locationRangesExpected);
assertIsMaximallyMerged(reduced);
});
it('can be reduced if overlapping (start == other.end)', () => {
const locationRangesToBeReduced = [
createRange(SCRIPT_ID_ONE, 0, 3, 5, 3),
createRange(SCRIPT_ID_ONE, 5, 3, 10, 3),
];
const locationRangesExpected = [
createRange(SCRIPT_ID_ONE, 0, 3, 10, 3),
];
const reduced = sortAndMerge(locationRangesToBeReduced);
assert.deepEqual(reduced, locationRangesExpected);
assertIsMaximallyMerged(reduced);
});
});
});