chore: split ContextRecorder from inspector (#9250)
This commit is contained in:
parent
b126a5685b
commit
f3648a66a3
|
|
@ -35,28 +35,23 @@ import { CallLog, CallLogStatus, EventData, Mode, Source, UIState } from './reco
|
||||||
import { createGuid, isUnderTest, monotonicTime } from '../../utils/utils';
|
import { createGuid, isUnderTest, monotonicTime } from '../../utils/utils';
|
||||||
import { metadataToCallLog } from './recorder/recorderUtils';
|
import { metadataToCallLog } from './recorder/recorderUtils';
|
||||||
import { Debugger } from './debugger';
|
import { Debugger } from './debugger';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
type BindingSource = { frame: Frame, page: Page };
|
type BindingSource = { frame: Frame, page: Page };
|
||||||
|
|
||||||
const symbol = Symbol('RecorderSupplement');
|
const symbol = Symbol('RecorderSupplement');
|
||||||
|
|
||||||
export class RecorderSupplement implements InstrumentationListener {
|
export class RecorderSupplement implements InstrumentationListener {
|
||||||
private _generator: CodeGenerator;
|
|
||||||
private _pageAliases = new Map<Page, string>();
|
|
||||||
private _lastPopupOrdinal = 0;
|
|
||||||
private _lastDialogOrdinal = 0;
|
|
||||||
private _lastDownloadOrdinal = 0;
|
|
||||||
private _timers = new Set<NodeJS.Timeout>();
|
|
||||||
private _context: BrowserContext;
|
private _context: BrowserContext;
|
||||||
private _mode: Mode;
|
private _mode: Mode;
|
||||||
private _highlightedSelector = '';
|
private _highlightedSelector = '';
|
||||||
private _recorderApp: RecorderApp | null = null;
|
private _recorderApp: RecorderApp | null = null;
|
||||||
private _params: channels.BrowserContextRecorderSupplementEnableParams;
|
|
||||||
private _currentCallsMetadata = new Map<CallMetadata, SdkObject>();
|
private _currentCallsMetadata = new Map<CallMetadata, SdkObject>();
|
||||||
private _recorderSources: Source[];
|
private _recorderSources: Source[] = [];
|
||||||
private _userSources = new Map<string, Source>();
|
private _userSources = new Map<string, Source>();
|
||||||
private _allMetadatas = new Map<string, CallMetadata>();
|
private _allMetadatas = new Map<string, CallMetadata>();
|
||||||
private _debugger: Debugger;
|
private _debugger: Debugger;
|
||||||
|
private _contextRecorder: ContextRecorder;
|
||||||
|
|
||||||
static showInspector(context: BrowserContext) {
|
static showInspector(context: BrowserContext) {
|
||||||
RecorderSupplement.show(context, {}).catch(() => {});
|
RecorderSupplement.show(context, {}).catch(() => {});
|
||||||
|
|
@ -73,11 +68,235 @@ export class RecorderSupplement implements InstrumentationListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) {
|
constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) {
|
||||||
|
this._mode = params.startRecording ? 'recording' : 'none';
|
||||||
|
this._contextRecorder = new ContextRecorder(context, params);
|
||||||
this._context = context;
|
this._context = context;
|
||||||
this._debugger = Debugger.lookup(context)!;
|
this._debugger = Debugger.lookup(context)!;
|
||||||
context.instrumentation.addListener(this);
|
context.instrumentation.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async install() {
|
||||||
|
const recorderApp = await RecorderApp.open(this._context);
|
||||||
|
this._recorderApp = recorderApp;
|
||||||
|
recorderApp.once('close', () => {
|
||||||
|
this._debugger.resume(false);
|
||||||
|
this._recorderApp = null;
|
||||||
|
});
|
||||||
|
recorderApp.on('event', (data: EventData) => {
|
||||||
|
if (data.event === 'setMode') {
|
||||||
|
this._setMode(data.params.mode);
|
||||||
|
this._refreshOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.event === 'selectorUpdated') {
|
||||||
|
this._highlightedSelector = data.params.selector;
|
||||||
|
this._refreshOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.event === 'step') {
|
||||||
|
this._debugger.resume(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.event === 'resume') {
|
||||||
|
this._debugger.resume(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.event === 'pause') {
|
||||||
|
this._debugger.pauseOnNextStatement();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.event === 'clear') {
|
||||||
|
this._contextRecorder.clearScript();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
recorderApp.setMode(this._mode),
|
||||||
|
recorderApp.setPaused(this._debugger.isPaused()),
|
||||||
|
this._pushAllSources()
|
||||||
|
]);
|
||||||
|
|
||||||
|
this._context.once(BrowserContext.Events.Close, () => {
|
||||||
|
this._contextRecorder.dispose();
|
||||||
|
recorderApp.close().catch(() => {});
|
||||||
|
});
|
||||||
|
this._contextRecorder.on(ContextRecorder.Events.Change, (data: { sources: Source[], primaryFileName: string }) => {
|
||||||
|
this._recorderSources = data.sources;
|
||||||
|
this._pushAllSources();
|
||||||
|
this._recorderApp?.setFile(data.primaryFileName);
|
||||||
|
});
|
||||||
|
|
||||||
|
await this._context.exposeBinding('_playwrightRecorderState', false, source => {
|
||||||
|
let actionSelector = this._highlightedSelector;
|
||||||
|
let actionPoint: Point | undefined;
|
||||||
|
for (const [metadata, sdkObject] of this._currentCallsMetadata) {
|
||||||
|
if (source.page === sdkObject.attribution.page) {
|
||||||
|
actionPoint = metadata.point || actionPoint;
|
||||||
|
actionSelector = actionSelector || metadata.params.selector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const uiState: UIState = {
|
||||||
|
mode: this._mode,
|
||||||
|
actionPoint,
|
||||||
|
actionSelector,
|
||||||
|
};
|
||||||
|
return uiState;
|
||||||
|
});
|
||||||
|
|
||||||
|
await this._context.exposeBinding('_playwrightRecorderSetSelector', false, async (_, selector: string) => {
|
||||||
|
this._setMode('none');
|
||||||
|
await this._recorderApp?.setSelector(selector, true);
|
||||||
|
await this._recorderApp?.bringToFront();
|
||||||
|
});
|
||||||
|
|
||||||
|
await this._context.exposeBinding('_playwrightResume', false, () => {
|
||||||
|
this._debugger.resume(false);
|
||||||
|
});
|
||||||
|
await this._context.extendInjectedScript(consoleApiSource.source);
|
||||||
|
|
||||||
|
await this._contextRecorder.install();
|
||||||
|
|
||||||
|
if (this._debugger.isPaused())
|
||||||
|
this._pausedStateChanged();
|
||||||
|
this._debugger.on(Debugger.Events.PausedStateChanged, () => this._pausedStateChanged());
|
||||||
|
|
||||||
|
(this._context as any).recorderAppForTest = recorderApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pausedStateChanged() {
|
||||||
|
// If we are called upon page.pause, we don't have metadatas, populate them.
|
||||||
|
for (const { metadata, sdkObject } of this._debugger.pausedDetails()) {
|
||||||
|
if (!this._currentCallsMetadata.has(metadata))
|
||||||
|
this.onBeforeCall(sdkObject, metadata);
|
||||||
|
}
|
||||||
|
this._recorderApp?.setPaused(this._debugger.isPaused());
|
||||||
|
this._updateUserSources();
|
||||||
|
this.updateCallLog([...this._currentCallsMetadata.keys()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setMode(mode: Mode) {
|
||||||
|
this._mode = mode;
|
||||||
|
this._recorderApp?.setMode(this._mode);
|
||||||
|
this._contextRecorder.setEnabled(this._mode === 'recording');
|
||||||
|
this._debugger.setMuted(this._mode === 'recording');
|
||||||
|
if (this._mode !== 'none')
|
||||||
|
this._context.pages()[0].bringToFront().catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _refreshOverlay() {
|
||||||
|
for (const page of this._context.pages())
|
||||||
|
page.mainFrame().evaluateExpression('window._playwrightRefreshOverlay()', false, undefined, 'main').catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||||
|
if (this._mode === 'recording')
|
||||||
|
return;
|
||||||
|
this._currentCallsMetadata.set(metadata, sdkObject);
|
||||||
|
this._allMetadatas.set(metadata.id, metadata);
|
||||||
|
this._updateUserSources();
|
||||||
|
this.updateCallLog([metadata]);
|
||||||
|
if (metadata.params && metadata.params.selector) {
|
||||||
|
this._highlightedSelector = metadata.params.selector;
|
||||||
|
this._recorderApp?.setSelector(this._highlightedSelector).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||||
|
if (this._mode === 'recording')
|
||||||
|
return;
|
||||||
|
if (!metadata.error)
|
||||||
|
this._currentCallsMetadata.delete(metadata);
|
||||||
|
this._updateUserSources();
|
||||||
|
this.updateCallLog([metadata]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateUserSources() {
|
||||||
|
// Remove old decorations.
|
||||||
|
for (const source of this._userSources.values()) {
|
||||||
|
source.highlight = [];
|
||||||
|
source.revealLine = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply new decorations.
|
||||||
|
let fileToSelect = undefined;
|
||||||
|
for (const metadata of this._currentCallsMetadata.keys()) {
|
||||||
|
if (!metadata.stack || !metadata.stack[0])
|
||||||
|
continue;
|
||||||
|
const { file, line } = metadata.stack[0];
|
||||||
|
let source = this._userSources.get(file);
|
||||||
|
if (!source) {
|
||||||
|
source = { file, text: this._readSource(file), highlight: [], language: languageForFile(file) };
|
||||||
|
this._userSources.set(file, source);
|
||||||
|
}
|
||||||
|
if (line) {
|
||||||
|
const paused = this._debugger.isPaused(metadata);
|
||||||
|
source.highlight.push({ line, type: metadata.error ? 'error' : (paused ? 'paused' : 'running') });
|
||||||
|
source.revealLine = line;
|
||||||
|
fileToSelect = source.file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._pushAllSources();
|
||||||
|
if (fileToSelect)
|
||||||
|
this._recorderApp?.setFile(fileToSelect);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _pushAllSources() {
|
||||||
|
this._recorderApp?.setSources([...this._recorderSources, ...this._userSources.values()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async onCallLog(logName: string, message: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||||
|
this.updateCallLog([metadata]);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCallLog(metadatas: CallMetadata[]) {
|
||||||
|
if (this._mode === 'recording')
|
||||||
|
return;
|
||||||
|
const logs: CallLog[] = [];
|
||||||
|
for (const metadata of metadatas) {
|
||||||
|
if (!metadata.method)
|
||||||
|
continue;
|
||||||
|
let status: CallLogStatus = 'done';
|
||||||
|
if (this._currentCallsMetadata.has(metadata))
|
||||||
|
status = 'in-progress';
|
||||||
|
if (this._debugger.isPaused(metadata))
|
||||||
|
status = 'paused';
|
||||||
|
logs.push(metadataToCallLog(metadata, status));
|
||||||
|
}
|
||||||
|
this._recorderApp?.updateCallLogs(logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _readSource(fileName: string): string {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(fileName, 'utf-8');
|
||||||
|
} catch (e) {
|
||||||
|
return '// No source available';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContextRecorder extends EventEmitter {
|
||||||
|
static Events = {
|
||||||
|
Change: 'change'
|
||||||
|
};
|
||||||
|
|
||||||
|
private _generator: CodeGenerator;
|
||||||
|
private _pageAliases = new Map<Page, string>();
|
||||||
|
private _lastPopupOrdinal = 0;
|
||||||
|
private _lastDialogOrdinal = 0;
|
||||||
|
private _lastDownloadOrdinal = 0;
|
||||||
|
private _timers = new Set<NodeJS.Timeout>();
|
||||||
|
private _context: BrowserContext;
|
||||||
|
private _params: channels.BrowserContextRecorderSupplementEnableParams;
|
||||||
|
private _recorderSources: Source[];
|
||||||
|
|
||||||
|
constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) {
|
||||||
|
super();
|
||||||
|
this._context = context;
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._mode = params.startRecording ? 'recording' : 'none';
|
|
||||||
const language = params.language || context._browser.options.sdkLanguage;
|
const language = params.language || context._browser.options.sdkLanguage;
|
||||||
|
|
||||||
const languages = new Set([
|
const languages = new Set([
|
||||||
|
|
@ -112,8 +331,10 @@ export class RecorderSupplement implements InstrumentationListener {
|
||||||
if (languageGenerator === orderedLanguages[0])
|
if (languageGenerator === orderedLanguages[0])
|
||||||
text = source.text;
|
text = source.text;
|
||||||
}
|
}
|
||||||
this._pushAllSources();
|
this.emit(ContextRecorder.Events.Change, {
|
||||||
this._recorderApp?.setFile(primaryLanguage.fileName);
|
sources: this._recorderSources,
|
||||||
|
primaryFileName: primaryLanguage.fileName
|
||||||
|
});
|
||||||
});
|
});
|
||||||
if (params.outputFile) {
|
if (params.outputFile) {
|
||||||
context.on(BrowserContext.Events.BeforeClose, () => {
|
context.on(BrowserContext.Events.BeforeClose, () => {
|
||||||
|
|
@ -129,58 +350,10 @@ export class RecorderSupplement implements InstrumentationListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
async install() {
|
async install() {
|
||||||
const recorderApp = await RecorderApp.open(this._context);
|
|
||||||
this._recorderApp = recorderApp;
|
|
||||||
recorderApp.once('close', () => {
|
|
||||||
this._debugger.resume(false);
|
|
||||||
this._recorderApp = null;
|
|
||||||
});
|
|
||||||
recorderApp.on('event', (data: EventData) => {
|
|
||||||
if (data.event === 'setMode') {
|
|
||||||
this._setMode(data.params.mode);
|
|
||||||
this._refreshOverlay();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.event === 'selectorUpdated') {
|
|
||||||
this._highlightedSelector = data.params.selector;
|
|
||||||
this._refreshOverlay();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.event === 'step') {
|
|
||||||
this._debugger.resume(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.event === 'resume') {
|
|
||||||
this._debugger.resume(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.event === 'pause') {
|
|
||||||
this._debugger.pauseOnNextStatement();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.event === 'clear') {
|
|
||||||
this._clearScript();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
recorderApp.setMode(this._mode),
|
|
||||||
recorderApp.setPaused(this._debugger.isPaused()),
|
|
||||||
this._pushAllSources()
|
|
||||||
]);
|
|
||||||
|
|
||||||
this._context.on(BrowserContext.Events.Page, page => this._onPage(page));
|
this._context.on(BrowserContext.Events.Page, page => this._onPage(page));
|
||||||
for (const page of this._context.pages())
|
for (const page of this._context.pages())
|
||||||
this._onPage(page);
|
this._onPage(page);
|
||||||
|
|
||||||
this._context.once(BrowserContext.Events.Close, () => {
|
|
||||||
for (const timer of this._timers)
|
|
||||||
clearTimeout(timer);
|
|
||||||
this._timers.clear();
|
|
||||||
recorderApp.close().catch(() => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Input actions that potentially lead to navigation are intercepted on the page and are
|
// Input actions that potentially lead to navigation are intercepted on the page and are
|
||||||
// performed by the Playwright.
|
// performed by the Playwright.
|
||||||
await this._context.exposeBinding('_playwrightRecorderPerformAction', false,
|
await this._context.exposeBinding('_playwrightRecorderPerformAction', false,
|
||||||
|
|
@ -190,66 +363,17 @@ export class RecorderSupplement implements InstrumentationListener {
|
||||||
await this._context.exposeBinding('_playwrightRecorderRecordAction', false,
|
await this._context.exposeBinding('_playwrightRecorderRecordAction', false,
|
||||||
(source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action));
|
(source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action));
|
||||||
|
|
||||||
await this._context.exposeBinding('_playwrightRecorderState', false, source => {
|
|
||||||
let actionSelector = this._highlightedSelector;
|
|
||||||
let actionPoint: Point | undefined;
|
|
||||||
for (const [metadata, sdkObject] of this._currentCallsMetadata) {
|
|
||||||
if (source.page === sdkObject.attribution.page) {
|
|
||||||
actionPoint = metadata.point || actionPoint;
|
|
||||||
actionSelector = actionSelector || metadata.params.selector;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const uiState: UIState = {
|
|
||||||
mode: this._mode,
|
|
||||||
actionPoint,
|
|
||||||
actionSelector,
|
|
||||||
};
|
|
||||||
return uiState;
|
|
||||||
});
|
|
||||||
|
|
||||||
await this._context.exposeBinding('_playwrightRecorderSetSelector', false, async (_, selector: string) => {
|
|
||||||
this._setMode('none');
|
|
||||||
await this._recorderApp?.setSelector(selector, true);
|
|
||||||
await this._recorderApp?.bringToFront();
|
|
||||||
});
|
|
||||||
|
|
||||||
await this._context.exposeBinding('_playwrightResume', false, () => {
|
|
||||||
this._debugger.resume(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
await this._context.extendInjectedScript(recorderSource.source, { isUnderTest: isUnderTest() });
|
await this._context.extendInjectedScript(recorderSource.source, { isUnderTest: isUnderTest() });
|
||||||
await this._context.extendInjectedScript(consoleApiSource.source);
|
|
||||||
|
|
||||||
if (this._debugger.isPaused())
|
|
||||||
this._pausedStateChanged();
|
|
||||||
this._debugger.on(Debugger.Events.PausedStateChanged, () => this._pausedStateChanged());
|
|
||||||
|
|
||||||
(this._context as any).recorderAppForTest = recorderApp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_pausedStateChanged() {
|
setEnabled(enabled: boolean) {
|
||||||
// If we are called upon page.pause, we don't have metadatas, populate them.
|
this._generator.setEnabled(enabled);
|
||||||
for (const { metadata, sdkObject } of this._debugger.pausedDetails()) {
|
|
||||||
if (!this._currentCallsMetadata.has(metadata))
|
|
||||||
this.onBeforeCall(sdkObject, metadata);
|
|
||||||
}
|
|
||||||
this._recorderApp?.setPaused(this._debugger.isPaused());
|
|
||||||
this._updateUserSources();
|
|
||||||
this.updateCallLog([...this._currentCallsMetadata.keys()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setMode(mode: Mode) {
|
dispose() {
|
||||||
this._mode = mode;
|
for (const timer of this._timers)
|
||||||
this._recorderApp?.setMode(this._mode);
|
clearTimeout(timer);
|
||||||
this._generator.setEnabled(this._mode === 'recording');
|
this._timers.clear();
|
||||||
Debugger.lookup(this._context)!.setMuted(this._mode === 'recording');
|
|
||||||
if (this._mode !== 'none')
|
|
||||||
this._context.pages()[0].bringToFront().catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _refreshOverlay() {
|
|
||||||
for (const page of this._context.pages())
|
|
||||||
page.mainFrame().evaluateExpression('window._playwrightRefreshOverlay()', false, undefined, 'main').catch(() => {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onPage(page: Page) {
|
private async _onPage(page: Page) {
|
||||||
|
|
@ -290,7 +414,7 @@ export class RecorderSupplement implements InstrumentationListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _clearScript(): void {
|
clearScript(): void {
|
||||||
this._generator.restart();
|
this._generator.restart();
|
||||||
if (!!this._params.startRecording) {
|
if (!!this._params.startRecording) {
|
||||||
for (const page of this._context.pages())
|
for (const page of this._context.pages())
|
||||||
|
|
@ -389,6 +513,7 @@ export class RecorderSupplement implements InstrumentationListener {
|
||||||
const popupAlias = this._pageAliases.get(popup)!;
|
const popupAlias = this._pageAliases.get(popup)!;
|
||||||
this._generator.signal(pageAlias, page.mainFrame(), { name: 'popup', popupAlias });
|
this._generator.signal(pageAlias, page.mainFrame(), { name: 'popup', popupAlias });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onDownload(page: Page) {
|
private _onDownload(page: Page) {
|
||||||
const pageAlias = this._pageAliases.get(page)!;
|
const pageAlias = this._pageAliases.get(page)!;
|
||||||
this._generator.signal(pageAlias, page.mainFrame(), { name: 'download', downloadAlias: String(++this._lastDownloadOrdinal) });
|
this._generator.signal(pageAlias, page.mainFrame(), { name: 'download', downloadAlias: String(++this._lastDownloadOrdinal) });
|
||||||
|
|
@ -398,94 +523,6 @@ export class RecorderSupplement implements InstrumentationListener {
|
||||||
const pageAlias = this._pageAliases.get(page)!;
|
const pageAlias = this._pageAliases.get(page)!;
|
||||||
this._generator.signal(pageAlias, page.mainFrame(), { name: 'dialog', dialogAlias: String(++this._lastDialogOrdinal) });
|
this._generator.signal(pageAlias, page.mainFrame(), { name: 'dialog', dialogAlias: String(++this._lastDialogOrdinal) });
|
||||||
}
|
}
|
||||||
|
|
||||||
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
|
||||||
if (this._mode === 'recording')
|
|
||||||
return;
|
|
||||||
this._currentCallsMetadata.set(metadata, sdkObject);
|
|
||||||
this._allMetadatas.set(metadata.id, metadata);
|
|
||||||
this._updateUserSources();
|
|
||||||
this.updateCallLog([metadata]);
|
|
||||||
if (metadata.params && metadata.params.selector) {
|
|
||||||
this._highlightedSelector = metadata.params.selector;
|
|
||||||
this._recorderApp?.setSelector(this._highlightedSelector).catch(() => {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
|
||||||
if (this._mode === 'recording')
|
|
||||||
return;
|
|
||||||
if (!metadata.error)
|
|
||||||
this._currentCallsMetadata.delete(metadata);
|
|
||||||
this._updateUserSources();
|
|
||||||
this.updateCallLog([metadata]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _updateUserSources() {
|
|
||||||
// Remove old decorations.
|
|
||||||
for (const source of this._userSources.values()) {
|
|
||||||
source.highlight = [];
|
|
||||||
source.revealLine = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply new decorations.
|
|
||||||
let fileToSelect = undefined;
|
|
||||||
for (const metadata of this._currentCallsMetadata.keys()) {
|
|
||||||
if (!metadata.stack || !metadata.stack[0])
|
|
||||||
continue;
|
|
||||||
const { file, line } = metadata.stack[0];
|
|
||||||
let source = this._userSources.get(file);
|
|
||||||
if (!source) {
|
|
||||||
source = { file, text: this._readSource(file), highlight: [], language: languageForFile(file) };
|
|
||||||
this._userSources.set(file, source);
|
|
||||||
}
|
|
||||||
if (line) {
|
|
||||||
const paused = this._debugger.isPaused(metadata);
|
|
||||||
source.highlight.push({ line, type: metadata.error ? 'error' : (paused ? 'paused' : 'running') });
|
|
||||||
source.revealLine = line;
|
|
||||||
fileToSelect = source.file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._pushAllSources();
|
|
||||||
if (fileToSelect)
|
|
||||||
this._recorderApp?.setFile(fileToSelect);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _pushAllSources() {
|
|
||||||
this._recorderApp?.setSources([...this._recorderSources, ...this._userSources.values()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata) {
|
|
||||||
}
|
|
||||||
|
|
||||||
async onCallLog(logName: string, message: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
|
||||||
this.updateCallLog([metadata]);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCallLog(metadatas: CallMetadata[]) {
|
|
||||||
if (this._mode === 'recording')
|
|
||||||
return;
|
|
||||||
const logs: CallLog[] = [];
|
|
||||||
for (const metadata of metadatas) {
|
|
||||||
if (!metadata.method)
|
|
||||||
continue;
|
|
||||||
let status: CallLogStatus = 'done';
|
|
||||||
if (this._currentCallsMetadata.has(metadata))
|
|
||||||
status = 'in-progress';
|
|
||||||
if (this._debugger.isPaused(metadata))
|
|
||||||
status = 'paused';
|
|
||||||
logs.push(metadataToCallLog(metadata, status));
|
|
||||||
}
|
|
||||||
this._recorderApp?.updateCallLogs(logs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _readSource(fileName: string): string {
|
|
||||||
try {
|
|
||||||
return fs.readFileSync(fileName, 'utf-8');
|
|
||||||
} catch (e) {
|
|
||||||
return '// No source available';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function languageForFile(file: string) {
|
function languageForFile(file: string) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue