2021-04-21 20:46:45 -07:00
|
|
|
/**
|
|
|
|
* Copyright (c) Microsoft Corporation.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import { EventEmitter } from 'events';
|
|
|
|
import { debugMode, isUnderTest, monotonicTime } from '../../utils/utils';
|
|
|
|
import { BrowserContext } from '../browserContext';
|
|
|
|
import { CallMetadata, InstrumentationListener, SdkObject } from '../instrumentation';
|
2021-04-23 18:34:52 -07:00
|
|
|
import { debugLogger } from '../../utils/debugLogger';
|
2021-04-21 20:46:45 -07:00
|
|
|
|
2021-04-23 18:34:52 -07:00
|
|
|
const symbol = Symbol('Debugger');
|
2021-04-21 20:46:45 -07:00
|
|
|
|
2021-04-23 18:34:52 -07:00
|
|
|
export class Debugger extends EventEmitter implements InstrumentationListener {
|
2021-04-21 20:46:45 -07:00
|
|
|
private _pauseOnNextStatement = false;
|
|
|
|
private _pausedCallsMetadata = new Map<CallMetadata, { resolve: () => void, sdkObject: SdkObject }>();
|
|
|
|
private _enabled: boolean;
|
2021-04-23 18:34:52 -07:00
|
|
|
private _context: BrowserContext;
|
2021-04-21 20:46:45 -07:00
|
|
|
|
|
|
|
static Events = {
|
|
|
|
PausedStateChanged: 'pausedstatechanged'
|
|
|
|
};
|
2021-05-26 15:49:30 -07:00
|
|
|
private _muted = false;
|
2021-04-21 20:46:45 -07:00
|
|
|
|
2021-04-23 18:34:52 -07:00
|
|
|
constructor(context: BrowserContext) {
|
2021-04-21 20:46:45 -07:00
|
|
|
super();
|
2021-04-23 18:34:52 -07:00
|
|
|
this._context = context;
|
|
|
|
(this._context as any)[symbol] = this;
|
2021-04-21 20:46:45 -07:00
|
|
|
this._enabled = debugMode() === 'inspector';
|
|
|
|
if (this._enabled)
|
|
|
|
this.pauseOnNextStatement();
|
|
|
|
}
|
|
|
|
|
2021-04-23 18:34:52 -07:00
|
|
|
static lookup(context?: BrowserContext): Debugger | undefined {
|
2021-04-21 20:46:45 -07:00
|
|
|
if (!context)
|
|
|
|
return;
|
2021-04-23 18:34:52 -07:00
|
|
|
return (context as any)[symbol] as Debugger | undefined;
|
|
|
|
}
|
|
|
|
|
2021-05-26 15:49:30 -07:00
|
|
|
async setMuted(muted: boolean) {
|
|
|
|
this._muted = muted;
|
|
|
|
}
|
|
|
|
|
2021-04-21 20:46:45 -07:00
|
|
|
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
2021-05-26 15:49:30 -07:00
|
|
|
if (this._muted)
|
|
|
|
return;
|
|
|
|
if (shouldPauseOnCall(sdkObject, metadata) || (this._pauseOnNextStatement && shouldPauseOnNonInputStep(sdkObject, metadata)))
|
2021-04-21 20:46:45 -07:00
|
|
|
await this.pause(sdkObject, metadata);
|
|
|
|
}
|
|
|
|
|
|
|
|
async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
2021-05-26 15:49:30 -07:00
|
|
|
if (this._muted)
|
|
|
|
return;
|
2021-04-21 20:46:45 -07:00
|
|
|
if (this._enabled && this._pauseOnNextStatement)
|
|
|
|
await this.pause(sdkObject, metadata);
|
|
|
|
}
|
|
|
|
|
2021-04-23 18:34:52 -07:00
|
|
|
async onCallLog(logName: string, message: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
|
|
|
debugLogger.log(logName as any, message);
|
|
|
|
}
|
|
|
|
|
2021-04-21 20:46:45 -07:00
|
|
|
async pause(sdkObject: SdkObject, metadata: CallMetadata) {
|
2021-05-26 15:49:30 -07:00
|
|
|
if (this._muted)
|
|
|
|
return;
|
2021-04-21 20:46:45 -07:00
|
|
|
this._enabled = true;
|
|
|
|
metadata.pauseStartTime = monotonicTime();
|
|
|
|
const result = new Promise<void>(resolve => {
|
|
|
|
this._pausedCallsMetadata.set(metadata, { resolve, sdkObject });
|
|
|
|
});
|
2021-04-23 18:34:52 -07:00
|
|
|
this.emit(Debugger.Events.PausedStateChanged);
|
2021-04-21 20:46:45 -07:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
resume(step: boolean) {
|
|
|
|
this._pauseOnNextStatement = step;
|
|
|
|
const endTime = monotonicTime();
|
|
|
|
for (const [metadata, { resolve }] of this._pausedCallsMetadata) {
|
|
|
|
metadata.pauseEndTime = endTime;
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
this._pausedCallsMetadata.clear();
|
2021-04-23 18:34:52 -07:00
|
|
|
this.emit(Debugger.Events.PausedStateChanged);
|
2021-04-21 20:46:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pauseOnNextStatement() {
|
|
|
|
this._pauseOnNextStatement = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
isPaused(metadata?: CallMetadata): boolean {
|
|
|
|
if (metadata)
|
|
|
|
return this._pausedCallsMetadata.has(metadata);
|
|
|
|
return !!this._pausedCallsMetadata.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
pausedDetails(): { metadata: CallMetadata, sdkObject: SdkObject }[] {
|
|
|
|
const result: { metadata: CallMetadata, sdkObject: SdkObject }[] = [];
|
|
|
|
for (const [metadata, { sdkObject }] of this._pausedCallsMetadata)
|
|
|
|
result.push({ metadata, sdkObject });
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function shouldPauseOnCall(sdkObject: SdkObject, metadata: CallMetadata): boolean {
|
|
|
|
if (!sdkObject.attribution.browser?.options.headful && !isUnderTest())
|
|
|
|
return false;
|
|
|
|
return metadata.method === 'pause';
|
|
|
|
}
|
|
|
|
|
2021-05-26 15:49:30 -07:00
|
|
|
const nonInputActionsToStep = new Set(['close', 'evaluate', 'evaluateHandle', 'goto', 'setContent']);
|
|
|
|
|
|
|
|
function shouldPauseOnNonInputStep(sdkObject: SdkObject, metadata: CallMetadata): boolean {
|
|
|
|
return nonInputActionsToStep.has(metadata.method);
|
2021-04-21 20:46:45 -07:00
|
|
|
}
|