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

Commit

Permalink
Refactoring code
Browse files Browse the repository at this point in the history
  • Loading branch information
Raghav Katyal committed Oct 30, 2017
1 parent c0e2d38 commit 330568e
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 173 deletions.
193 changes: 193 additions & 0 deletions src/chrome/breakOnLoadHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import {logger} from 'vscode-debugadapter';
import {ISetBreakpointResult} from '../debugAdapterInterfaces';

import Crdp from '../../crdp/crdp';
import {ChromeDebugAdapter} from './chromeDebugAdapter';

import * as path from 'path';

export class BreakOnLoadHelper {

public userBreakpointOnLine1Col1: boolean = false;
private _instrumentationBreakpointSet: boolean = false;

// 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
private _stopOnEntryBreakpointIdToRequestedFileName = new Map<string, [string, Set<string>]>();
private _stopOnEntryRequestedFileNameToBreakpointId = new Map<string, string>();
private _stopOnEntryRegexToBreakpointId = new Map<string, string>();

private _chromeDebugAdapter: ChromeDebugAdapter;

public constructor(chromeDebugAdapter: ChromeDebugAdapter) {
this._chromeDebugAdapter = chromeDebugAdapter;
}

public get stopOnEntryRequestedFileNameToBreakpointId(): Map<string, string> {
return this._stopOnEntryRequestedFileNameToBreakpointId;
}

public get stopOnEntryBreakpointIdToRequestedFileName(): Map<string, [string, Set<string>]> {
return this._stopOnEntryBreakpointIdToRequestedFileName;
}

public get instrumentationBreakpointSet(): boolean {
return this._instrumentationBreakpointSet;
}

/**
* Checks and resolves the pending breakpoints of a script. If any breakpoints were resolved returns true, else false.
* Used when break on load active, either through Chrome's Instrumentation Breakpoint API or the regex approach
*/
public async resolvePendingBreakpointsOfPausedScript(scriptId: string): Promise<boolean> {
const pausedScriptUrl = this._chromeDebugAdapter.scriptsById.get(scriptId).url;
const mappedUrl = await this._chromeDebugAdapter.pathTransformer.scriptParsed(pausedScriptUrl);

const pendingBreakpoints = this._chromeDebugAdapter.pendingBreakpointsByUrl.get(mappedUrl);
// If the file has unbound breakpoints, resolve them and return true
if (pendingBreakpoints !== undefined) {
await this._chromeDebugAdapter.resolvePendingBreakpoint(pendingBreakpoints);
return true;
} else {
// If no pending breakpoints, return false
return false;
}
}

/**
* Returns whether we should continue on hitting a stopOnEntry breakpoint
* Only used when using regex approach for break on load
*/
private async shouldContinueOnStopOnEntryBreakpoint(scriptId: string): Promise<boolean> {
// 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
let shouldContinue = true;
let anyPendingBreakpointsResolved = await this.resolvePendingBreakpointsOfPausedScript(scriptId);

// If there were any pending breakpoints resolved and any of them was at (1,1) we shouldn't continue
if (anyPendingBreakpointsResolved && this.userBreakpointOnLine1Col1) {
// Here we need to store this information per file, but since we can safely assume that scriptParsed would immediately be followed by onPaused event
// for the breakonload files, this implementation should be fine
this.userBreakpointOnLine1Col1 = false;
shouldContinue = false;
}

return shouldContinue;
}

/**
* Handles a script with a stop on entry breakpoint and returns whether we should continue or not on hitting that breakpoint
* Only used when using regex approach for break on load
*/
public async handleStopOnEntryBreakpointAndContinue(notification: Crdp.Debugger.PausedEvent): Promise<boolean> {
const hitBreakpoints = notification.hitBreakpoints;
let allStopOnEntryBreakpoints = true;

// If there is a breakpoint which is not a stopOnEntry breakpoint, we appear as if we hit that one
// This is particularly done for cases when we end up with a user breakpoint and a stopOnEntry breakpoint on the same line
hitBreakpoints.forEach(bp => {
if (!this._stopOnEntryBreakpointIdToRequestedFileName.has(bp)) {
notification.hitBreakpoints = [bp];
allStopOnEntryBreakpoints = false;
}
});

// If all the breakpoints on this point are stopOnEntry breakpoints
// This will be true in cases where it's a single breakpoint and it's a stopOnEntry breakpoint
// 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
// 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
// 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
// in itself. So when the file is actually loaded, we would have 2 stopOnEntry breakpoints */

if (allStopOnEntryBreakpoints) {
const pausedScriptId = notification.callFrames[0].location.scriptId;
let shouldContinue = await this.shouldContinueOnStopOnEntryBreakpoint(pausedScriptId);
if (shouldContinue) {
return true;
}
}
return false;
}

/**
* Adds a stopOnEntry breakpoint for the given script url
* Only used when using regex approach for break on load
*/
public async addStopOnEntryBreakpoint(url: string): Promise<ISetBreakpointResult[]> {
let responsePs: ISetBreakpointResult[];
// Check if file already has a stop on entry breakpoint
if (!this._stopOnEntryRequestedFileNameToBreakpointId.has(url)) {

// Generate regex we need for the file
const urlRegex = this.getUrlRegexForBreakOnLoad(url);

// 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
let breakpointId: string;
breakpointId = this._stopOnEntryRegexToBreakpointId.get(urlRegex);

// If breakpointId is undefined it means the breakpoint doesn't exist yet so we add it
if (breakpointId === undefined) {
let result;
try {
result = await this.setStopOnEntryBreakpoint(urlRegex);
} catch (e) {
logger.log(`Exception occured while trying to set stop on entry breakpoint ${e.message}.`);
}
if (result) {
breakpointId = result.breakpointId;
this._stopOnEntryRegexToBreakpointId.set(urlRegex, breakpointId);
} else {
logger.log(`BreakpointId was null when trying to set on urlregex ${urlRegex}. This normally happens if the breakpoint already exists.`);
}
responsePs = [result];
} else {
responsePs = [];
}

// Store the new breakpointId and the file name in the right mappings
this._stopOnEntryRequestedFileNameToBreakpointId.set(url, breakpointId);

let regexAndFileNames = this._stopOnEntryBreakpointIdToRequestedFileName.get(breakpointId);

// If there already exists an entry for the breakpoint Id, we add this file to the list of file mappings
if (regexAndFileNames !== undefined) {
regexAndFileNames[1].add(url);
} else { // else create an entry for this breakpoint id
const fileSet = new Set<string>();
fileSet.add(url);
this._stopOnEntryBreakpointIdToRequestedFileName.set(breakpointId, [urlRegex, fileSet]);
}
} else {
responsePs = [];
}
return Promise.all(responsePs);
}

/**
* Tells Chrome to set instrumentation breakpoint to stop on all the scripts before execution
* Only used when using instrument approach for break on load
*/
public async setInstrumentationBreakpoint(): Promise<void> {
this._chromeDebugAdapter.chrome.DOMDebugger.setInstrumentationBreakpoint({eventName: "scriptFirstStatement"});
this._instrumentationBreakpointSet = true;
}

// Sets a breakpoint on (0,0) for the files matching the given regex
private async setStopOnEntryBreakpoint(urlRegex: string): Promise<Crdp.Debugger.SetBreakpointByUrlResponse> {
let result = await this._chromeDebugAdapter.chrome.Debugger.setBreakpointByUrl({ urlRegex, lineNumber: 0, columnNumber: 0, condition: '' });
return result;
}

/* Constructs the regex for files to enable break on load
For example, for a file index.js the regex will match urls containing index.js, index.ts, abc/index.ts, index.bin.js etc
It won't match index100.js, indexabc.ts etc */
private getUrlRegexForBreakOnLoad(url: string): string {
const fileNameWithoutFullPath = path.parse(url).base;
const fileNameWithoutExtension = path.parse(fileNameWithoutFullPath).name;
const escapedFileName = fileNameWithoutExtension.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');

return ".*[\\\\\\/]" + escapedFileName + "([^A-z^0-9].*)?$";
}
}
Loading

0 comments on commit 330568e

Please sign in to comment.