diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index 3604aec222..fd5f1bc822 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -30,7 +30,7 @@ import { Events } from './events'; import { Protocol } from './protocol'; import { CRExecutionContext } from './crExecutionContext'; import { logError } from '../logger'; -import { CRDevTools } from './crDevTools'; +import { CRDevTools } from '../debug/crDevTools'; export class CRBrowser extends BrowserBase { readonly _connection: CRConnection; diff --git a/src/chromium/crCoverage.ts b/src/chromium/crCoverage.ts index 616ef1f885..6b9a90d02b 100644 --- a/src/chromium/crCoverage.ts +++ b/src/chromium/crCoverage.ts @@ -18,8 +18,8 @@ import { CRSession } from './crConnection'; import { assert, helper, RegisteredListener } from '../helper'; import { Protocol } from './protocol'; -import * as js from '../javascript'; import * as types from '../types'; +import * as debugSupport from '../debug/debugSupport'; import { logError, InnerLogger } from '../logger'; type JSRange = { @@ -125,7 +125,7 @@ class JSCoverage { async _onScriptParsed(event: Protocol.Debugger.scriptParsedPayload) { // Ignore playwright-injected scripts - if (js.isPlaywrightSourceUrl(event.url)) + if (debugSupport.isPlaywrightSourceUrl(event.url)) return; this._scriptIds.add(event.scriptId); // Ignore other anonymous scripts unless the reportAnonymousScripts option is true. diff --git a/src/chromium/crExecutionContext.ts b/src/chromium/crExecutionContext.ts index aa07c130df..3bacb2a129 100644 --- a/src/chromium/crExecutionContext.ts +++ b/src/chromium/crExecutionContext.ts @@ -20,6 +20,7 @@ import { helper } from '../helper'; import { valueFromRemoteObject, getExceptionMessage, releaseObject } from './crProtocolHelper'; import { Protocol } from './protocol'; import * as js from '../javascript'; +import * as debugSupport from '../debug/debugSupport'; export class CRExecutionContext implements js.ExecutionContextDelegate { _client: CRSession; @@ -32,7 +33,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate { async rawEvaluate(expression: string): Promise { const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { - expression: js.ensureSourceUrl(expression), + expression: debugSupport.ensureSourceUrl(expression), contextId: this._contextId, }).catch(rewriteError); if (exceptionDetails) @@ -44,7 +45,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate { if (helper.isString(pageFunction)) { return this._callOnUtilityScript(context, `evaluate`, [ - { value: js.ensureSourceUrl(pageFunction) }, + { value: debugSupport.ensureSourceUrl(pageFunction) }, ], returnByValue, () => { }); } @@ -85,7 +86,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate { try { const utilityScript = await context.utilityScript(); const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', { - functionDeclaration: `function (...args) { return this.${method}(...args) }${js.generateSourceUrl()}`, + functionDeclaration: `function (...args) { return this.${method}(...args) }` + debugSupport.generateSourceUrl(), objectId: utilityScript._remoteObject.objectId, arguments: [ { value: returnByValue }, @@ -128,7 +129,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate { const remoteObject = toRemoteObject(handle); if (remoteObject.objectId) { const response = await this._client.send('Runtime.callFunctionOn', { - functionDeclaration: 'function() { return this; }', + functionDeclaration: 'function() { return this; }' + debugSupport.generateSourceUrl(), objectId: remoteObject.objectId, returnByValue: true, awaitPromise: true, diff --git a/src/chromium/crPage.ts b/src/chromium/crPage.ts index 347f3f3cf9..280d354201 100644 --- a/src/chromium/crPage.ts +++ b/src/chromium/crPage.ts @@ -16,7 +16,6 @@ */ import * as dom from '../dom'; -import * as js from '../javascript'; import * as frames from '../frames'; import { helper, RegisteredListener, assert } from '../helper'; import * as network from '../network'; @@ -38,6 +37,7 @@ import * as types from '../types'; import { ConsoleMessage } from '../console'; import { NotConnectedError } from '../errors'; import { logError } from '../logger'; +import * as debugSupport from '../debug/debugSupport'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; @@ -432,7 +432,7 @@ class FrameSession { lifecycleEventsEnabled = this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }), this._client.send('Runtime.enable', {}), this._client.send('Page.addScriptToEvaluateOnNewDocument', { - source: js.generateSourceUrl(), + source: debugSupport.generateSourceUrl(), worldName: UTILITY_WORLD_NAME, }), this._networkManager.initialize(), diff --git a/src/chromium/crDevTools.ts b/src/debug/crDevTools.ts similarity index 96% rename from src/chromium/crDevTools.ts rename to src/debug/crDevTools.ts index dd4e06e82f..082f35117b 100644 --- a/src/chromium/crDevTools.ts +++ b/src/debug/crDevTools.ts @@ -16,11 +16,11 @@ import * as fs from 'fs'; import * as util from 'util'; -import { CRSession } from './crConnection'; +import { CRSession } from '../chromium/crConnection'; const kBindingName = '__pw_devtools__'; -// This method intercepts preferences-related DevTools embedder methods +// This class intercepts preferences-related DevTools embedder methods // and stores preferences as a json file in the browser installation directory. export class CRDevTools { private _preferencesPath: string; diff --git a/src/debug/debugSupport.ts b/src/debug/debugSupport.ts new file mode 100644 index 0000000000..a186d89904 --- /dev/null +++ b/src/debug/debugSupport.ts @@ -0,0 +1,47 @@ +/** + * 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 * as sourceMap from './sourceMap'; +import { getFromENV } from '../helper'; + +let debugMode: boolean | undefined; +export function isDebugMode(): boolean { + if (debugMode === undefined) + debugMode = !!getFromENV('PLAYWRIGHT_DEBUG_UI'); + return debugMode; +} + +let sourceUrlCounter = 0; +const playwrightSourceUrlPrefix = '__playwright_evaluation_script__'; +const sourceUrlRegex = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; +export function generateSourceUrl(): string { + return `\n//# sourceURL=${playwrightSourceUrlPrefix}${sourceUrlCounter++}\n`; +} + +export function isPlaywrightSourceUrl(s: string): boolean { + return s.startsWith(playwrightSourceUrlPrefix); +} + +export function ensureSourceUrl(expression: string): string { + return sourceUrlRegex.test(expression) ? expression : expression + generateSourceUrl(); +} + +export async function generateSourceMapUrl(functionText: string, generatedText: string): Promise { + if (!isDebugMode()) + return generateSourceUrl(); + const sourceMapUrl = await sourceMap.generateSourceMapUrl(functionText, generatedText); + return sourceMapUrl || generateSourceUrl(); +} diff --git a/src/debug/sourceMap.ts b/src/debug/sourceMap.ts new file mode 100644 index 0000000000..b2762f66c4 --- /dev/null +++ b/src/debug/sourceMap.ts @@ -0,0 +1,145 @@ +/** + * 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 * as fs from 'fs'; +import * as util from 'util'; +import * as path from 'path'; + +// NOTE: update this to point to playwright/lib when moving this file. +const PLAYWRIGHT_LIB_PATH = path.normalize(path.join(__dirname, '..')); + +type Position = { + line: number; + column: number; +}; + +export async function generateSourceMapUrl(functionText: string, generatedText: string): Promise { + const filePath = getCallerFilePath(); + if (!filePath) + return; + try { + const generatedIndex = generatedText.indexOf(functionText); + if (generatedIndex === -1) + return; + const compiledPosition = findPosition(generatedText, generatedIndex); + const source = await util.promisify(fs.readFile)(filePath, 'utf8'); + const sourceIndex = source.indexOf(functionText); + if (sourceIndex === -1) + return; + const sourcePosition = findPosition(source, sourceIndex); + const delta = findPosition(functionText, functionText.length); + const sourceMap = generateSourceMap(filePath, sourcePosition, compiledPosition, delta); + return `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(sourceMap).toString('base64')}\n`; + } catch (e) { + } +} + +const VLQ_BASE_SHIFT = 5; +const VLQ_BASE = 1 << VLQ_BASE_SHIFT; +const VLQ_BASE_MASK = VLQ_BASE - 1; +const VLQ_CONTINUATION_BIT = VLQ_BASE; +const BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + +function base64VLQ(value: number): string { + if (value < 0) + value = ((-value) << 1) | 1; + else + value <<= 1; + let result = ''; + do { + let digit = value & VLQ_BASE_MASK; + value >>>= VLQ_BASE_SHIFT; + if (value > 0) + digit |= VLQ_CONTINUATION_BIT; + result += BASE64_DIGITS[digit]; + } while (value > 0); + return result; +} + +function generateSourceMap(filePath: string, sourcePosition: Position, compiledPosition: Position, delta: Position): any { + const mappings = []; + let lastCompiled = { line: 0, column: 0 }; + let lastSource = { line: 0, column: 0 }; + for (let line = 0; line < delta.line; line++) { + // We need at least a mapping per line. This will yield an execution line at the start of each line. + // Note: for more granular mapping, we can do word-by-word. + const source = advancePosition(sourcePosition, { line, column: 0 }); + const compiled = advancePosition(compiledPosition, { line, column: 0 }); + while (lastCompiled.line < compiled.line) { + mappings.push(';'); + lastCompiled.line++; + lastCompiled.column = 0; + } + mappings.push(base64VLQ(compiled.column - lastCompiled.column)); + mappings.push(base64VLQ(0)); // Source index. + mappings.push(base64VLQ(source.line - lastSource.line)); + mappings.push(base64VLQ(source.column - lastSource.column)); + lastCompiled = compiled; + lastSource = source; + } + return JSON.stringify({ + version: 3, + sources: ['file://' + filePath], + names: [], + mappings: mappings.join(''), + }); +} + +function findPosition(source: string, offset: number): Position { + const result: Position = { line: 0, column: 0 }; + let index = 0; + while (true) { + const newline = source.indexOf('\n', index); + if (newline === -1 || newline >= offset) + break; + result.line++; + index = newline + 1; + } + result.column = offset - index; + return result; +} + +function advancePosition(position: Position, delta: Position) { + return { + line: position.line + delta.line, + column: delta.column + (delta.line ? 0 : position.column), + }; +} + +function getCallerFilePath(): string | null { + const error = new Error(); + const stackFrames = (error.stack || '').split('\n').slice(1); + // Find first stackframe that doesn't point to PLAYWRIGHT_LIB_PATH. + for (let frame of stackFrames) { + frame = frame.trim(); + if (!frame.startsWith('at ')) + return null; + if (frame.endsWith(')')) { + const from = frame.indexOf('('); + frame = frame.substring(from + 1, frame.length - 1); + } else { + frame = frame.substring('at '.length); + } + const match = frame.match(/^(?:async )?(.*):(\d+):(\d+)$/); + if (!match) + return null; + const filePath = match[1]; + if (filePath.startsWith(PLAYWRIGHT_LIB_PATH)) + continue; + return filePath; + } + return null; +} diff --git a/src/firefox/ffExecutionContext.ts b/src/firefox/ffExecutionContext.ts index 80420c30d5..7fb7c98fe6 100644 --- a/src/firefox/ffExecutionContext.ts +++ b/src/firefox/ffExecutionContext.ts @@ -19,6 +19,7 @@ import { helper } from '../helper'; import * as js from '../javascript'; import { FFSession } from './ffConnection'; import { Protocol } from './protocol'; +import * as debugSupport from '../debug/debugSupport'; export class FFExecutionContext implements js.ExecutionContextDelegate { _session: FFSession; @@ -31,7 +32,7 @@ export class FFExecutionContext implements js.ExecutionContextDelegate { async rawEvaluate(expression: string): Promise { const payload = await this._session.send('Runtime.evaluate', { - expression: js.ensureSourceUrl(expression), + expression: debugSupport.ensureSourceUrl(expression), returnByValue: false, executionContextId: this._executionContextId, }).catch(rewriteError); @@ -43,7 +44,7 @@ export class FFExecutionContext implements js.ExecutionContextDelegate { if (helper.isString(pageFunction)) { return this._callOnUtilityScript(context, `evaluate`, [ - { value: pageFunction }, + { value: debugSupport.ensureSourceUrl(pageFunction) }, ], returnByValue, () => {}); } if (typeof pageFunction !== 'function') @@ -75,7 +76,7 @@ export class FFExecutionContext implements js.ExecutionContextDelegate { try { const utilityScript = await context.utilityScript(); const payload = await this._session.send('Runtime.callFunction', { - functionDeclaration: `(utilityScript, ...args) => utilityScript.${method}(...args)`, + functionDeclaration: `(utilityScript, ...args) => utilityScript.${method}(...args)` + debugSupport.generateSourceUrl(), args: [ { objectId: utilityScript._remoteObject.objectId, value: undefined }, { value: returnByValue }, @@ -123,7 +124,7 @@ export class FFExecutionContext implements js.ExecutionContextDelegate { const simpleValue = await this._session.send('Runtime.callFunction', { executionContextId: this._executionContextId, returnByValue: true, - functionDeclaration: ((e: any) => e).toString() + js.generateSourceUrl(), + functionDeclaration: ((e: any) => e).toString() + debugSupport.generateSourceUrl(), args: [this._toCallArgument(payload)], }); return deserializeValue(simpleValue.result!); diff --git a/src/helper.ts b/src/helper.ts index 92b3809509..ad1b925de2 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -23,9 +23,6 @@ import { TimeoutError } from './errors'; import * as types from './types'; import { ChildProcess, execSync } from 'child_process'; -// NOTE: update this to point to playwright/lib when moving this file. -const PLAYWRIGHT_LIB_PATH = __dirname; - export type RegisteredListener = { emitter: EventEmitter; eventName: (string | symbol); @@ -397,38 +394,6 @@ export function logPolitely(toBeLogged: string) { console.log(toBeLogged); // eslint-disable-line no-console } -export function getCallerFilePath(ignorePrefix = PLAYWRIGHT_LIB_PATH): string | null { - const error = new Error(); - const stackFrames = (error.stack || '').split('\n').slice(1); - // Find first stackframe that doesn't point to ignorePrefix. - for (let frame of stackFrames) { - frame = frame.trim(); - if (!frame.startsWith('at ')) - return null; - if (frame.endsWith(')')) { - const from = frame.indexOf('('); - frame = frame.substring(from + 1, frame.length - 1); - } else { - frame = frame.substring('at '.length); - } - const match = frame.match(/^(?:async )?(.*):(\d+):(\d+)$/); - if (!match) - return null; - const filePath = match[1]; - if (filePath.startsWith(ignorePrefix)) - continue; - return filePath; - } - return null; -} - -let debugMode: boolean | undefined; -export function isDebugMode(): boolean { - if (debugMode === undefined) - debugMode = !!getFromENV('PLAYWRIGHT_DEBUG_UI'); - return debugMode; -} - const escapeGlobChars = new Set(['/', '$', '^', '+', '.', '(', ')', '=', '!', '|']); export const helper = Helper; diff --git a/src/javascript.ts b/src/javascript.ts index b3cf874aa8..1f87c22bb4 100644 --- a/src/javascript.ts +++ b/src/javascript.ts @@ -16,12 +16,10 @@ import * as types from './types'; import * as dom from './dom'; -import * as fs from 'fs'; -import * as util from 'util'; -import * as js from './javascript'; +import { helper } from './helper'; import * as utilityScriptSource from './generated/utilityScriptSource'; -import { helper, getCallerFilePath, isDebugMode } from './helper'; import { InnerLogger } from './logger'; +import * as debugSupport from './debug/debugSupport'; export interface ExecutionContextDelegate { evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: string | Function, ...args: any[]): Promise; @@ -35,7 +33,7 @@ export interface ExecutionContextDelegate { export class ExecutionContext { readonly _delegate: ExecutionContextDelegate; readonly _logger: InnerLogger; - private _utilityScriptPromise: Promise | undefined; + private _utilityScriptPromise: Promise | undefined; constructor(delegate: ExecutionContextDelegate, logger: InnerLogger) { this._delegate = delegate; @@ -62,7 +60,7 @@ export class ExecutionContext { return this.doEvaluateInternal(false /* returnByValue */, true /* waitForNavigations */, pageFunction, ...args); } - utilityScript(): Promise { + utilityScript(): Promise { if (!this._utilityScriptPromise) { const source = `new (${utilityScriptSource.source})()`; this._utilityScriptPromise = this._delegate.rawEvaluate(source).then(object => this.createHandle(object)); @@ -237,119 +235,6 @@ export async function prepareFunctionCall( toDispose.map(handlePromise => handlePromise.then(handle => handle.dispose())); }; - const sourceMapUrl = await generateSourceMapUrl(originalText); - functionText += sourceMapUrl; + functionText += await debugSupport.generateSourceMapUrl(originalText, functionText); return { functionText, values: [ args.length, ...args, guids.length, ...guids ], handles: resultHandles, dispose }; } - -let sourceUrlCounter = 0; -const playwrightSourceUrlPrefix = '__playwright_evaluation_script__'; -const sourceUrlRegex = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; -export function generateSourceUrl(): string { - return `\n//# sourceURL=${playwrightSourceUrlPrefix}${sourceUrlCounter++}\n`; -} - -export function isPlaywrightSourceUrl(s: string): boolean { - return s.startsWith(playwrightSourceUrlPrefix); -} - -export function ensureSourceUrl(expression: string): string { - return sourceUrlRegex.test(expression) ? expression : expression + generateSourceUrl(); -} - -type Position = { - line: number; - column: number; -}; - -async function generateSourceMapUrl(functionText: string): Promise { - if (!isDebugMode()) - return generateSourceUrl(); - const filePath = getCallerFilePath(); - if (!filePath) - return generateSourceUrl(); - try { - const source = await util.promisify(fs.readFile)(filePath, 'utf8'); - const index = source.indexOf(functionText); - if (index === -1) - return generateSourceUrl(); - const sourcePosition = findPosition(source, index); - const delta = findPosition(functionText, functionText.length); - const sourceMap = generateSourceMap(filePath, sourcePosition, { line: 0, column: 0 }, delta); - return `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(sourceMap).toString('base64')}\n`; - } catch (e) { - return generateSourceUrl(); - } -} - -const VLQ_BASE_SHIFT = 5; -const VLQ_BASE = 1 << VLQ_BASE_SHIFT; -const VLQ_BASE_MASK = VLQ_BASE - 1; -const VLQ_CONTINUATION_BIT = VLQ_BASE; -const BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - -function base64VLQ(value: number): string { - if (value < 0) - value = ((-value) << 1) | 1; - else - value <<= 1; - let result = ''; - do { - let digit = value & VLQ_BASE_MASK; - value >>>= VLQ_BASE_SHIFT; - if (value > 0) - digit |= VLQ_CONTINUATION_BIT; - result += BASE64_DIGITS[digit]; - } while (value > 0); - return result; -} - -function generateSourceMap(filePath: string, sourcePosition: Position, compiledPosition: Position, delta: Position): any { - const mappings = []; - let lastCompiled = { line: 0, column: 0 }; - let lastSource = { line: 0, column: 0 }; - for (let line = 0; line < delta.line; line++) { - // We need at least a mapping per line. This will yield an execution line at the start of each line. - // Note: for more granular mapping, we can do word-by-word. - const source = advancePosition(sourcePosition, { line, column: 0 }); - const compiled = advancePosition(compiledPosition, { line, column: 0 }); - while (lastCompiled.line < compiled.line) { - mappings.push(';'); - lastCompiled.line++; - lastCompiled.column = 0; - } - mappings.push(base64VLQ(compiled.column - lastCompiled.column)); - mappings.push(base64VLQ(0)); // Source index. - mappings.push(base64VLQ(source.line - lastSource.line)); - mappings.push(base64VLQ(source.column - lastSource.column)); - lastCompiled = compiled; - lastSource = source; - } - return JSON.stringify({ - version: 3, - sources: ['file://' + filePath], - names: [], - mappings: mappings.join(''), - }); -} - -function findPosition(source: string, offset: number): Position { - const result: Position = { line: 0, column: 0 }; - let index = 0; - while (true) { - const newline = source.indexOf('\n', index); - if (newline === -1 || newline >= offset) - break; - result.line++; - index = newline + 1; - } - result.column = offset - index; - return result; -} - -function advancePosition(position: Position, delta: Position) { - return { - line: position.line + delta.line, - column: delta.column + (delta.line ? 0 : position.column), - }; -} diff --git a/src/server/chromium.ts b/src/server/chromium.ts index 7438bb8827..4d910ae684 100644 --- a/src/server/chromium.ts +++ b/src/server/chromium.ts @@ -16,7 +16,7 @@ */ import * as path from 'path'; -import { helper, assert, isDebugMode } from '../helper'; +import { helper, assert } from '../helper'; import { CRBrowser } from '../chromium/crBrowser'; import * as ws from 'ws'; import { Env } from './processLauncher'; @@ -26,7 +26,8 @@ import { WebSocketWrapper } from './browserServer'; import { ConnectionTransport, ProtocolRequest } from '../transport'; import { InnerLogger, logError } from '../logger'; import { BrowserDescriptor } from '../install/browserPaths'; -import { CRDevTools } from '../chromium/crDevTools'; +import { CRDevTools } from '../debug/crDevTools'; +import * as debugSupport from '../debug/debugSupport'; import { BrowserOptions } from '../browser'; export class Chromium extends BrowserTypeBase { @@ -34,7 +35,7 @@ export class Chromium extends BrowserTypeBase { constructor(packagePath: string, browser: BrowserDescriptor) { super(packagePath, browser, null /* use pipe not websocket */); - if (isDebugMode()) + if (debugSupport.isDebugMode()) this._devtools = this._createDevTools(); } diff --git a/src/webkit/wkExecutionContext.ts b/src/webkit/wkExecutionContext.ts index b79a7fa994..ab8eadb225 100644 --- a/src/webkit/wkExecutionContext.ts +++ b/src/webkit/wkExecutionContext.ts @@ -20,6 +20,7 @@ import { helper } from '../helper'; import { valueFromRemoteObject, releaseObject } from './wkProtocolHelper'; import { Protocol } from './protocol'; import * as js from '../javascript'; +import * as debugSupport from '../debug/debugSupport'; type MaybeCallArgument = Protocol.Runtime.CallArgument | { unserializable: any }; @@ -44,7 +45,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { async rawEvaluate(expression: string): Promise { const contextId = this._contextId; const response = await this._session.send('Runtime.evaluate', { - expression: js.ensureSourceUrl(expression), + expression: debugSupport.ensureSourceUrl(expression), contextId, returnByValue: false }); @@ -82,11 +83,11 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { private async _evaluateRemoteObject(context: js.ExecutionContext, pageFunction: Function | string, args: any[], returnByValue: boolean): Promise { if (helper.isString(pageFunction)) { const utilityScript = await context.utilityScript(); - const functionDeclaration = `function (returnByValue, pageFunction) { return this.evaluate(returnByValue, pageFunction); }${js.generateSourceUrl()}`; + const functionDeclaration = `function (returnByValue, pageFunction) { return this.evaluate(returnByValue, pageFunction); }` + debugSupport.generateSourceUrl(); return await this._session.send('Runtime.callFunctionOn', { functionDeclaration, objectId: utilityScript._remoteObject.objectId!, - arguments: [ { value: returnByValue }, { value: pageFunction } ], + arguments: [ { value: returnByValue }, { value: debugSupport.ensureSourceUrl(pageFunction) } ], returnByValue: false, // We need to return real Promise if that is a promise. emulateUserGesture: true }); @@ -113,7 +114,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { const utilityScript = await context.utilityScript(); const callParams = this._serializeFunctionAndArguments(functionText, values, handles, returnByValue); return await this._session.send('Runtime.callFunctionOn', { - functionDeclaration: callParams.functionText, + functionDeclaration: callParams.functionText + debugSupport.generateSourceUrl(), objectId: utilityScript._remoteObject.objectId!, arguments: callParams.callArguments, returnByValue: false, // We need to return real Promise if that is a promise. @@ -126,7 +127,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { private _serializeFunctionAndArguments(originalText: string, values: any[], handles: MaybeCallArgument[], returnByValue: boolean): { functionText: string, callArguments: Protocol.Runtime.CallArgument[]} { const callArguments: Protocol.Runtime.CallArgument[] = values.map(value => ({ value })); - let functionText = `function (returnByValue, functionText, ...args) { return this.callFunction(returnByValue, functionText, ...args); }${js.generateSourceUrl()}`; + let functionText = `function (returnByValue, functionText, ...args) { return this.callFunction(returnByValue, functionText, ...args); }`; if (handles.some(handle => 'unserializable' in handle)) { const paramStrings = []; for (let i = 0; i < callArguments.length; i++) @@ -139,7 +140,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { callArguments.push(handle); } } - functionText = `function (returnByValue, functionText, ...a) { return this.callFunction(returnByValue, functionText, ${paramStrings.join(',')}); }${js.generateSourceUrl()}`; + functionText = `function (returnByValue, functionText, ...a) { return this.callFunction(returnByValue, functionText, ${paramStrings.join(',')}); }`; } else { callArguments.push(...(handles as Protocol.Runtime.CallArgument[])); } @@ -167,7 +168,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { try { const serializeResponse = await this._session.send('Runtime.callFunctionOn', { // Serialize object using standard JSON implementation to correctly pass 'undefined'. - functionDeclaration: 'function(){return this}\n' + js.generateSourceUrl(), + functionDeclaration: 'function(){return this}\n' + debugSupport.generateSourceUrl(), objectId: objectId, returnByValue: true }); @@ -206,7 +207,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { const remoteObject = handle._remoteObject; if (remoteObject.objectId) { const response = await this._session.send('Runtime.callFunctionOn', { - functionDeclaration: 'function() { return this; }', + functionDeclaration: 'function() { return this; }' + debugSupport.generateSourceUrl(), objectId: remoteObject.objectId, returnByValue: true });