Skip to content
This repository was archived by the owner on Oct 2, 2021. It is now read-only.

Commit 330568e

Browse files
author
Raghav Katyal
committed
Refactoring code
1 parent c0e2d38 commit 330568e

File tree

3 files changed

+246
-173
lines changed

3 files changed

+246
-173
lines changed

src/chrome/breakOnLoadHelper.ts

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*---------------------------------------------------------
2+
* Copyright (C) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------*/
4+
5+
import {logger} from 'vscode-debugadapter';
6+
import {ISetBreakpointResult} from '../debugAdapterInterfaces';
7+
8+
import Crdp from '../../crdp/crdp';
9+
import {ChromeDebugAdapter} from './chromeDebugAdapter';
10+
11+
import * as path from 'path';
12+
13+
export class BreakOnLoadHelper {
14+
15+
public userBreakpointOnLine1Col1: boolean = false;
16+
private _instrumentationBreakpointSet: boolean = false;
17+
18+
// Break on load: Store some mapping between the requested file names, the regex for the file, and the chrome breakpoint id to perform lookup operations efficiently
19+
private _stopOnEntryBreakpointIdToRequestedFileName = new Map<string, [string, Set<string>]>();
20+
private _stopOnEntryRequestedFileNameToBreakpointId = new Map<string, string>();
21+
private _stopOnEntryRegexToBreakpointId = new Map<string, string>();
22+
23+
private _chromeDebugAdapter: ChromeDebugAdapter;
24+
25+
public constructor(chromeDebugAdapter: ChromeDebugAdapter) {
26+
this._chromeDebugAdapter = chromeDebugAdapter;
27+
}
28+
29+
public get stopOnEntryRequestedFileNameToBreakpointId(): Map<string, string> {
30+
return this._stopOnEntryRequestedFileNameToBreakpointId;
31+
}
32+
33+
public get stopOnEntryBreakpointIdToRequestedFileName(): Map<string, [string, Set<string>]> {
34+
return this._stopOnEntryBreakpointIdToRequestedFileName;
35+
}
36+
37+
public get instrumentationBreakpointSet(): boolean {
38+
return this._instrumentationBreakpointSet;
39+
}
40+
41+
/**
42+
* Checks and resolves the pending breakpoints of a script. If any breakpoints were resolved returns true, else false.
43+
* Used when break on load active, either through Chrome's Instrumentation Breakpoint API or the regex approach
44+
*/
45+
public async resolvePendingBreakpointsOfPausedScript(scriptId: string): Promise<boolean> {
46+
const pausedScriptUrl = this._chromeDebugAdapter.scriptsById.get(scriptId).url;
47+
const mappedUrl = await this._chromeDebugAdapter.pathTransformer.scriptParsed(pausedScriptUrl);
48+
49+
const pendingBreakpoints = this._chromeDebugAdapter.pendingBreakpointsByUrl.get(mappedUrl);
50+
// If the file has unbound breakpoints, resolve them and return true
51+
if (pendingBreakpoints !== undefined) {
52+
await this._chromeDebugAdapter.resolvePendingBreakpoint(pendingBreakpoints);
53+
return true;
54+
} else {
55+
// If no pending breakpoints, return false
56+
return false;
57+
}
58+
}
59+
60+
/**
61+
* Returns whether we should continue on hitting a stopOnEntry breakpoint
62+
* Only used when using regex approach for break on load
63+
*/
64+
private async shouldContinueOnStopOnEntryBreakpoint(scriptId: string): Promise<boolean> {
65+
// If the file has no unbound breakpoints or none of the resolved breakpoints are at (1,1), we should continue after hitting the stopOnEntry breakpoint
66+
let shouldContinue = true;
67+
let anyPendingBreakpointsResolved = await this.resolvePendingBreakpointsOfPausedScript(scriptId);
68+
69+
// If there were any pending breakpoints resolved and any of them was at (1,1) we shouldn't continue
70+
if (anyPendingBreakpointsResolved && this.userBreakpointOnLine1Col1) {
71+
// Here we need to store this information per file, but since we can safely assume that scriptParsed would immediately be followed by onPaused event
72+
// for the breakonload files, this implementation should be fine
73+
this.userBreakpointOnLine1Col1 = false;
74+
shouldContinue = false;
75+
}
76+
77+
return shouldContinue;
78+
}
79+
80+
/**
81+
* Handles a script with a stop on entry breakpoint and returns whether we should continue or not on hitting that breakpoint
82+
* Only used when using regex approach for break on load
83+
*/
84+
public async handleStopOnEntryBreakpointAndContinue(notification: Crdp.Debugger.PausedEvent): Promise<boolean> {
85+
const hitBreakpoints = notification.hitBreakpoints;
86+
let allStopOnEntryBreakpoints = true;
87+
88+
// If there is a breakpoint which is not a stopOnEntry breakpoint, we appear as if we hit that one
89+
// This is particularly done for cases when we end up with a user breakpoint and a stopOnEntry breakpoint on the same line
90+
hitBreakpoints.forEach(bp => {
91+
if (!this._stopOnEntryBreakpointIdToRequestedFileName.has(bp)) {
92+
notification.hitBreakpoints = [bp];
93+
allStopOnEntryBreakpoints = false;
94+
}
95+
});
96+
97+
// If all the breakpoints on this point are stopOnEntry breakpoints
98+
// This will be true in cases where it's a single breakpoint and it's a stopOnEntry breakpoint
99+
// This can also be true when we have multiple breakpoints and all of them are stopOnEntry breakpoints, for example in cases like index.js and index.bin.js
100+
// Suppose user puts breakpoints in both index.js and index.bin.js files, when the setBreakpoints function is called for index.js it will set a stopOnEntry
101+
// breakpoint on index.* files which will also match index.bin.js. Now when setBreakpoints is called for index.bin.js it will again put a stopOnEntry breakpoint
102+
// in itself. So when the file is actually loaded, we would have 2 stopOnEntry breakpoints */
103+
104+
if (allStopOnEntryBreakpoints) {
105+
const pausedScriptId = notification.callFrames[0].location.scriptId;
106+
let shouldContinue = await this.shouldContinueOnStopOnEntryBreakpoint(pausedScriptId);
107+
if (shouldContinue) {
108+
return true;
109+
}
110+
}
111+
return false;
112+
}
113+
114+
/**
115+
* Adds a stopOnEntry breakpoint for the given script url
116+
* Only used when using regex approach for break on load
117+
*/
118+
public async addStopOnEntryBreakpoint(url: string): Promise<ISetBreakpointResult[]> {
119+
let responsePs: ISetBreakpointResult[];
120+
// Check if file already has a stop on entry breakpoint
121+
if (!this._stopOnEntryRequestedFileNameToBreakpointId.has(url)) {
122+
123+
// Generate regex we need for the file
124+
const urlRegex = this.getUrlRegexForBreakOnLoad(url);
125+
126+
// Check if we already have a breakpoint for this regexp since two different files like script.ts and script.js may have the same regexp
127+
let breakpointId: string;
128+
breakpointId = this._stopOnEntryRegexToBreakpointId.get(urlRegex);
129+
130+
// If breakpointId is undefined it means the breakpoint doesn't exist yet so we add it
131+
if (breakpointId === undefined) {
132+
let result;
133+
try {
134+
result = await this.setStopOnEntryBreakpoint(urlRegex);
135+
} catch (e) {
136+
logger.log(`Exception occured while trying to set stop on entry breakpoint ${e.message}.`);
137+
}
138+
if (result) {
139+
breakpointId = result.breakpointId;
140+
this._stopOnEntryRegexToBreakpointId.set(urlRegex, breakpointId);
141+
} else {
142+
logger.log(`BreakpointId was null when trying to set on urlregex ${urlRegex}. This normally happens if the breakpoint already exists.`);
143+
}
144+
responsePs = [result];
145+
} else {
146+
responsePs = [];
147+
}
148+
149+
// Store the new breakpointId and the file name in the right mappings
150+
this._stopOnEntryRequestedFileNameToBreakpointId.set(url, breakpointId);
151+
152+
let regexAndFileNames = this._stopOnEntryBreakpointIdToRequestedFileName.get(breakpointId);
153+
154+
// If there already exists an entry for the breakpoint Id, we add this file to the list of file mappings
155+
if (regexAndFileNames !== undefined) {
156+
regexAndFileNames[1].add(url);
157+
} else { // else create an entry for this breakpoint id
158+
const fileSet = new Set<string>();
159+
fileSet.add(url);
160+
this._stopOnEntryBreakpointIdToRequestedFileName.set(breakpointId, [urlRegex, fileSet]);
161+
}
162+
} else {
163+
responsePs = [];
164+
}
165+
return Promise.all(responsePs);
166+
}
167+
168+
/**
169+
* Tells Chrome to set instrumentation breakpoint to stop on all the scripts before execution
170+
* Only used when using instrument approach for break on load
171+
*/
172+
public async setInstrumentationBreakpoint(): Promise<void> {
173+
this._chromeDebugAdapter.chrome.DOMDebugger.setInstrumentationBreakpoint({eventName: "scriptFirstStatement"});
174+
this._instrumentationBreakpointSet = true;
175+
}
176+
177+
// Sets a breakpoint on (0,0) for the files matching the given regex
178+
private async setStopOnEntryBreakpoint(urlRegex: string): Promise<Crdp.Debugger.SetBreakpointByUrlResponse> {
179+
let result = await this._chromeDebugAdapter.chrome.Debugger.setBreakpointByUrl({ urlRegex, lineNumber: 0, columnNumber: 0, condition: '' });
180+
return result;
181+
}
182+
183+
/* Constructs the regex for files to enable break on load
184+
For example, for a file index.js the regex will match urls containing index.js, index.ts, abc/index.ts, index.bin.js etc
185+
It won't match index100.js, indexabc.ts etc */
186+
private getUrlRegexForBreakOnLoad(url: string): string {
187+
const fileNameWithoutFullPath = path.parse(url).base;
188+
const fileNameWithoutExtension = path.parse(fileNameWithoutFullPath).name;
189+
const escapedFileName = fileNameWithoutExtension.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
190+
191+
return ".*[\\\\\\/]" + escapedFileName + "([^A-z^0-9].*)?$";
192+
}
193+
}

0 commit comments

Comments
 (0)