mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: evaluate UtilityScript
lazily (#36019)
This commit is contained in:
parent
92d4ce30c6
commit
6af41232b8
@ -219,9 +219,21 @@ const noBooleanCompareRules = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const noWebGlobalsRuleList = [
|
const noWebGlobalsRuleList = [
|
||||||
|
// Keep in sync with builtins from utilityScript.ts
|
||||||
{ name: "window", message: "Use InjectedScript.window instead" },
|
{ name: "window", message: "Use InjectedScript.window instead" },
|
||||||
{ name: "document", message: "Use InjectedScript.document instead" },
|
{ name: "document", message: "Use InjectedScript.document instead" },
|
||||||
{ name: "globalThis", message: "Use InjectedScript.window instead" },
|
{ name: "globalThis", message: "Use InjectedScript.window instead" },
|
||||||
|
{ name: "setTimeout", message: "Use InjectedScript.utils.builtins.setTimeout instead" },
|
||||||
|
{ name: "clearTimeout", message: "Use InjectedScript.utils.builtins.clearTimeout instead" },
|
||||||
|
{ name: "setInterval", message: "Use InjectedScript.utils.builtins.setInterval instead" },
|
||||||
|
{ name: "clearInterval", message: "Use InjectedScript.utils.builtins.clearInterval instead" },
|
||||||
|
{ name: "requestAnimationFrame", message: "Use InjectedScript.utils.builtins.requestAnimationFrame instead" },
|
||||||
|
{ name: "cancelAnimationFrame", message: "Use InjectedScript.utils.builtins.cancelAnimationFrame instead" },
|
||||||
|
{ name: "requestIdleCallback", message: "Use InjectedScript.utils.builtins.requestIdleCallback instead" },
|
||||||
|
{ name: "cancelIdleCallback", message: "Use InjectedScript.utils.builtins.cancelIdleCallback instead" },
|
||||||
|
{ name: "Date", message: "Use InjectedScript.utils.builtins.Date instead" },
|
||||||
|
{ name: "Intl", message: "Use InjectedScript.utils.builtins.Intl instead" },
|
||||||
|
{ name: "performance", message: "Use InjectedScript.utils.builtins.performance instead" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const noNodeGlobalsRuleList = [{ name: "process" }];
|
const noNodeGlobalsRuleList = [{ name: "process" }];
|
||||||
|
@ -10,26 +10,14 @@
|
|||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type ClockMethods = {
|
import type { Builtins } from './utilityScript';
|
||||||
Date: DateConstructor;
|
|
||||||
setTimeout: Window['setTimeout'];
|
|
||||||
clearTimeout: Window['clearTimeout'];
|
|
||||||
setInterval: Window['setInterval'];
|
|
||||||
clearInterval: Window['clearInterval'];
|
|
||||||
requestAnimationFrame?: Window['requestAnimationFrame'];
|
|
||||||
cancelAnimationFrame?: (id: number) => void;
|
|
||||||
requestIdleCallback?: Window['requestIdleCallback'];
|
|
||||||
cancelIdleCallback?: (id: number) => void;
|
|
||||||
Intl?: typeof Intl;
|
|
||||||
performance?: Window['performance'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ClockConfig = {
|
export type ClockConfig = {
|
||||||
now?: number | Date;
|
now?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InstallConfig = ClockConfig & {
|
export type InstallConfig = ClockConfig & {
|
||||||
toFake?: (keyof ClockMethods)[];
|
toFake?: (keyof Builtins)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
enum TimerType {
|
enum TimerType {
|
||||||
@ -427,7 +415,7 @@ export class ClockController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mirrorDateProperties(target: any, source: DateConstructor): DateConstructor & Date {
|
function mirrorDateProperties(target: any, source: Builtins['Date']): Builtins['Date'] {
|
||||||
for (const prop in source) {
|
for (const prop in source) {
|
||||||
if (source.hasOwnProperty(prop))
|
if (source.hasOwnProperty(prop))
|
||||||
target[prop] = (source as any)[prop];
|
target[prop] = (source as any)[prop];
|
||||||
@ -441,7 +429,8 @@ function mirrorDateProperties(target: any, source: DateConstructor): DateConstru
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDate(clock: ClockController, NativeDate: DateConstructor): DateConstructor & Date {
|
function createDate(clock: ClockController, NativeDate: Builtins['Date']): Builtins['Date'] {
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
function ClockDate(this: typeof ClockDate, year: number, month: number, date: number, hour: number, minute: number, second: number, ms: number): Date | string {
|
function ClockDate(this: typeof ClockDate, year: number, month: number, date: number, hour: number, minute: number, second: number, ms: number): Date | string {
|
||||||
// the Date constructor called as a function, ref Ecma-262 Edition 5.1, section 15.9.2.
|
// the Date constructor called as a function, ref Ecma-262 Edition 5.1, section 15.9.2.
|
||||||
// This remains so in the 10th edition of 2019 as well.
|
// This remains so in the 10th edition of 2019 as well.
|
||||||
@ -497,17 +486,18 @@ function createDate(clock: ClockController, NativeDate: DateConstructor): DateCo
|
|||||||
* but we need to take control of those that have a
|
* but we need to take control of those that have a
|
||||||
* dependency on the current clock.
|
* dependency on the current clock.
|
||||||
*/
|
*/
|
||||||
function createIntl(clock: ClockController, NativeIntl: typeof Intl): typeof Intl {
|
function createIntl(clock: ClockController, NativeIntl: Builtins['Intl']): Builtins['Intl'] {
|
||||||
const ClockIntl: any = {};
|
const ClockIntl: any = {};
|
||||||
/*
|
/*
|
||||||
* All properties of Intl are non-enumerable, so we need
|
* All properties of Intl are non-enumerable, so we need
|
||||||
* to do a bit of work to get them out.
|
* to do a bit of work to get them out.
|
||||||
*/
|
*/
|
||||||
for (const key of Object.getOwnPropertyNames(NativeIntl) as (keyof typeof Intl)[])
|
for (const key of Object.getOwnPropertyNames(NativeIntl) as (keyof Builtins['Intl'])[])
|
||||||
ClockIntl[key] = NativeIntl[key];
|
ClockIntl[key] = NativeIntl[key];
|
||||||
|
|
||||||
ClockIntl.DateTimeFormat = function(...args: any[]) {
|
ClockIntl.DateTimeFormat = function(...args: any[]) {
|
||||||
const realFormatter = new NativeIntl.DateTimeFormat(...args);
|
const realFormatter = new NativeIntl.DateTimeFormat(...args);
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
const formatter: Intl.DateTimeFormat = {
|
const formatter: Intl.DateTimeFormat = {
|
||||||
formatRange: realFormatter.formatRange.bind(realFormatter),
|
formatRange: realFormatter.formatRange.bind(realFormatter),
|
||||||
formatRangeToParts: realFormatter.formatRangeToParts.bind(realFormatter),
|
formatRangeToParts: realFormatter.formatRangeToParts.bind(realFormatter),
|
||||||
@ -560,8 +550,8 @@ function compareTimers(a: Timer, b: Timer) {
|
|||||||
const maxTimeout = Math.pow(2, 31) - 1; // see https://heycam.github.io/webidl/#abstract-opdef-converttoint
|
const maxTimeout = Math.pow(2, 31) - 1; // see https://heycam.github.io/webidl/#abstract-opdef-converttoint
|
||||||
const idCounterStart = 1e12; // arbitrarily large number to avoid collisions with native timer IDs
|
const idCounterStart = 1e12; // arbitrarily large number to avoid collisions with native timer IDs
|
||||||
|
|
||||||
function platformOriginals(globalObject: WindowOrWorkerGlobalScope): { raw: ClockMethods, bound: ClockMethods } {
|
function platformOriginals(globalObject: WindowOrWorkerGlobalScope): { raw: Builtins, bound: Builtins } {
|
||||||
const raw: ClockMethods = {
|
const raw: Builtins = {
|
||||||
setTimeout: globalObject.setTimeout,
|
setTimeout: globalObject.setTimeout,
|
||||||
clearTimeout: globalObject.clearTimeout,
|
clearTimeout: globalObject.clearTimeout,
|
||||||
setInterval: globalObject.setInterval,
|
setInterval: globalObject.setInterval,
|
||||||
@ -575,7 +565,7 @@ function platformOriginals(globalObject: WindowOrWorkerGlobalScope): { raw: Cloc
|
|||||||
Intl: (globalObject as any).Intl,
|
Intl: (globalObject as any).Intl,
|
||||||
};
|
};
|
||||||
const bound = { ...raw };
|
const bound = { ...raw };
|
||||||
for (const key of Object.keys(bound) as (keyof ClockMethods)[]) {
|
for (const key of Object.keys(bound) as (keyof Builtins)[]) {
|
||||||
if (key !== 'Date' && typeof bound[key] === 'function')
|
if (key !== 'Date' && typeof bound[key] === 'function')
|
||||||
bound[key] = (bound[key] as any).bind(globalObject);
|
bound[key] = (bound[key] as any).bind(globalObject);
|
||||||
}
|
}
|
||||||
@ -592,7 +582,7 @@ function getScheduleHandler(type: TimerType) {
|
|||||||
return `set${type}`;
|
return `set${type}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createApi(clock: ClockController, originals: ClockMethods): ClockMethods {
|
function createApi(clock: ClockController, originals: Builtins): Builtins {
|
||||||
return {
|
return {
|
||||||
setTimeout: (func: TimerHandler, timeout?: number | undefined, ...args: any[]) => {
|
setTimeout: (func: TimerHandler, timeout?: number | undefined, ...args: any[]) => {
|
||||||
const delay = timeout ? +timeout : timeout;
|
const delay = timeout ? +timeout : timeout;
|
||||||
@ -646,9 +636,9 @@ function createApi(clock: ClockController, originals: ClockMethods): ClockMethod
|
|||||||
if (timerId)
|
if (timerId)
|
||||||
return clock.clearTimer(timerId, TimerType.IdleCallback);
|
return clock.clearTimer(timerId, TimerType.IdleCallback);
|
||||||
},
|
},
|
||||||
Intl: originals.Intl ? createIntl(clock, originals.Intl) : undefined,
|
Intl: originals.Intl ? createIntl(clock, originals.Intl) : (undefined as unknown as Builtins['Intl']),
|
||||||
Date: createDate(clock, originals.Date),
|
Date: createDate(clock, originals.Date),
|
||||||
performance: originals.performance ? fakePerformance(clock, originals.performance) : undefined,
|
performance: originals.performance ? fakePerformance(clock, originals.performance) : (undefined as unknown as Builtins['performance']),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,7 +649,7 @@ function getClearHandler(type: TimerType) {
|
|||||||
return `clear${type}`;
|
return `clear${type}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fakePerformance(clock: ClockController, performance: Performance): Performance {
|
function fakePerformance(clock: ClockController, performance: Builtins['performance']): Builtins['performance'] {
|
||||||
const result: any = {
|
const result: any = {
|
||||||
now: () => clock.performanceNow(),
|
now: () => clock.performanceNow(),
|
||||||
};
|
};
|
||||||
@ -676,7 +666,7 @@ function fakePerformance(clock: ClockController, performance: Performance): Perf
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createClock(globalObject: WindowOrWorkerGlobalScope): { clock: ClockController, api: ClockMethods, originals: ClockMethods } {
|
export function createClock(globalObject: WindowOrWorkerGlobalScope): { clock: ClockController, api: Builtins, originals: Builtins } {
|
||||||
const originals = platformOriginals(globalObject);
|
const originals = platformOriginals(globalObject);
|
||||||
const embedder: Embedder = {
|
const embedder: Embedder = {
|
||||||
dateNow: () => originals.raw.Date.now(),
|
dateNow: () => originals.raw.Date.now(),
|
||||||
@ -696,7 +686,7 @@ export function createClock(globalObject: WindowOrWorkerGlobalScope): { clock: C
|
|||||||
return { clock, api, originals: originals.raw };
|
return { clock, api, originals: originals.raw };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function install(globalObject: WindowOrWorkerGlobalScope, config: InstallConfig = {}): { clock: ClockController, api: ClockMethods, originals: ClockMethods } {
|
export function install(globalObject: WindowOrWorkerGlobalScope, config: InstallConfig = {}): { clock: ClockController, api: Builtins, originals: Builtins } {
|
||||||
if ((globalObject as any).Date?.isFake) {
|
if ((globalObject as any).Date?.isFake) {
|
||||||
// Timers are already faked; this is a problem.
|
// Timers are already faked; this is a problem.
|
||||||
// Make the user reset timers before continuing.
|
// Make the user reset timers before continuing.
|
||||||
@ -704,7 +694,7 @@ export function install(globalObject: WindowOrWorkerGlobalScope, config: Install
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { clock, api, originals } = createClock(globalObject);
|
const { clock, api, originals } = createClock(globalObject);
|
||||||
const toFake = config.toFake?.length ? config.toFake : Object.keys(originals) as (keyof ClockMethods)[];
|
const toFake = config.toFake?.length ? config.toFake : Object.keys(originals) as (keyof Builtins)[];
|
||||||
|
|
||||||
for (const method of toFake) {
|
for (const method of toFake) {
|
||||||
if (method === 'Date') {
|
if (method === 'Date') {
|
||||||
@ -735,12 +725,12 @@ export function install(globalObject: WindowOrWorkerGlobalScope, config: Install
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function inject(globalObject: WindowOrWorkerGlobalScope) {
|
export function inject(globalObject: WindowOrWorkerGlobalScope) {
|
||||||
const builtin = platformOriginals(globalObject).bound;
|
const builtins = platformOriginals(globalObject).bound;
|
||||||
const { clock: controller } = install(globalObject);
|
const { clock: controller } = install(globalObject);
|
||||||
controller.resume();
|
controller.resume();
|
||||||
return {
|
return {
|
||||||
controller,
|
controller,
|
||||||
builtin,
|
builtins,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ export class Highlight {
|
|||||||
|
|
||||||
runHighlightOnRaf(selector: ParsedSelector) {
|
runHighlightOnRaf(selector: ParsedSelector) {
|
||||||
if (this._rafRequest)
|
if (this._rafRequest)
|
||||||
cancelAnimationFrame(this._rafRequest);
|
this._injectedScript.utils.builtins.cancelAnimationFrame(this._rafRequest);
|
||||||
const elements = this._injectedScript.querySelectorAll(selector, this._injectedScript.document.documentElement);
|
const elements = this._injectedScript.querySelectorAll(selector, this._injectedScript.document.documentElement);
|
||||||
const locator = asLocator(this._language, stringifySelector(selector));
|
const locator = asLocator(this._language, stringifySelector(selector));
|
||||||
const color = elements.length > 1 ? '#f6b26b7f' : '#6fa8dc7f';
|
const color = elements.length > 1 ? '#f6b26b7f' : '#6fa8dc7f';
|
||||||
@ -108,12 +108,12 @@ export class Highlight {
|
|||||||
const suffix = elements.length > 1 ? ` [${index + 1} of ${elements.length}]` : '';
|
const suffix = elements.length > 1 ? ` [${index + 1} of ${elements.length}]` : '';
|
||||||
return { element, color, tooltipText: locator + suffix };
|
return { element, color, tooltipText: locator + suffix };
|
||||||
}));
|
}));
|
||||||
this._rafRequest = requestAnimationFrame(() => this.runHighlightOnRaf(selector));
|
this._rafRequest = this._injectedScript.utils.builtins.requestAnimationFrame(() => this.runHighlightOnRaf(selector));
|
||||||
}
|
}
|
||||||
|
|
||||||
uninstall() {
|
uninstall() {
|
||||||
if (this._rafRequest)
|
if (this._rafRequest)
|
||||||
cancelAnimationFrame(this._rafRequest);
|
this._injectedScript.utils.builtins.cancelAnimationFrame(this._rafRequest);
|
||||||
this._glassPaneElement.remove();
|
this._glassPaneElement.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ import { elementMatchesText, elementText, getElementLabels } from './selectorUti
|
|||||||
import { createVueEngine } from './vueSelectorEngine';
|
import { createVueEngine } from './vueSelectorEngine';
|
||||||
import { XPathEngine } from './xpathSelectorEngine';
|
import { XPathEngine } from './xpathSelectorEngine';
|
||||||
import { ConsoleAPI } from './consoleApi';
|
import { ConsoleAPI } from './consoleApi';
|
||||||
import { ensureUtilityScript } from './utilityScript';
|
import { UtilityScript } from './utilityScript';
|
||||||
|
|
||||||
import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot';
|
import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot';
|
||||||
import type { CSSComplexSelectorList } from '@isomorphic/cssParser';
|
import type { CSSComplexSelectorList } from '@isomorphic/cssParser';
|
||||||
@ -108,6 +108,7 @@ export class InjectedScript {
|
|||||||
isInsideScope,
|
isInsideScope,
|
||||||
normalizeWhiteSpace,
|
normalizeWhiteSpace,
|
||||||
parseAriaSnapshot,
|
parseAriaSnapshot,
|
||||||
|
// Builtins protect injected code from clock emulation.
|
||||||
builtins: null as unknown as Builtins,
|
builtins: null as unknown as Builtins,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -125,7 +126,7 @@ export class InjectedScript {
|
|||||||
this.document = window.document;
|
this.document = window.document;
|
||||||
// Make sure builtins are created from "window". This is important for InjectedScript instantiated
|
// Make sure builtins are created from "window". This is important for InjectedScript instantiated
|
||||||
// inside a trace viewer snapshot, where "window" differs from "globalThis".
|
// inside a trace viewer snapshot, where "window" differs from "globalThis".
|
||||||
const utilityScript = ensureUtilityScript(window);
|
const utilityScript = new UtilityScript(window);
|
||||||
this.isUnderTest = options.isUnderTest ?? utilityScript.isUnderTest;
|
this.isUnderTest = options.isUnderTest ?? utilityScript.isUnderTest;
|
||||||
this.utils.builtins = utilityScript.builtins;
|
this.utils.builtins = utilityScript.builtins;
|
||||||
this._sdkLanguage = options.sdkLanguage;
|
this._sdkLanguage = options.sdkLanguage;
|
||||||
@ -564,7 +565,7 @@ export class InjectedScript {
|
|||||||
observer.observe(element);
|
observer.observe(element);
|
||||||
// Firefox doesn't call IntersectionObserver callback unless
|
// Firefox doesn't call IntersectionObserver callback unless
|
||||||
// there are rafs.
|
// there are rafs.
|
||||||
requestAnimationFrame(() => {});
|
this.utils.builtins.requestAnimationFrame(() => {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -645,7 +646,7 @@ export class InjectedScript {
|
|||||||
return 'error:notconnected';
|
return 'error:notconnected';
|
||||||
|
|
||||||
// Drop frames that are shorter than 16ms - WebKit Win bug.
|
// Drop frames that are shorter than 16ms - WebKit Win bug.
|
||||||
const time = performance.now();
|
const time = this.utils.builtins.performance.now();
|
||||||
if (this._stableRafCount > 1 && time - lastTime < 15)
|
if (this._stableRafCount > 1 && time - lastTime < 15)
|
||||||
return continuePolling;
|
return continuePolling;
|
||||||
lastTime = time;
|
lastTime = time;
|
||||||
@ -673,12 +674,12 @@ export class InjectedScript {
|
|||||||
if (success !== continuePolling)
|
if (success !== continuePolling)
|
||||||
fulfill(success);
|
fulfill(success);
|
||||||
else
|
else
|
||||||
requestAnimationFrame(raf);
|
this.utils.builtins.requestAnimationFrame(raf);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
requestAnimationFrame(raf);
|
this.utils.builtins.requestAnimationFrame(raf);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ class RecordActionTool implements RecorderTool {
|
|||||||
|
|
||||||
constructor(recorder: Recorder) {
|
constructor(recorder: Recorder) {
|
||||||
this._recorder = recorder;
|
this._recorder = recorder;
|
||||||
this._performingActions = new recorder.injectedScript.utils.builtins.Set();
|
this._performingActions = new Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor() {
|
cursor() {
|
||||||
@ -603,7 +603,7 @@ class TextAssertionTool implements RecorderTool {
|
|||||||
|
|
||||||
constructor(recorder: Recorder, kind: 'text' | 'value' | 'snapshot') {
|
constructor(recorder: Recorder, kind: 'text' | 'value' | 'snapshot') {
|
||||||
this._recorder = recorder;
|
this._recorder = recorder;
|
||||||
this._textCache = new recorder.injectedScript.utils.builtins.Map();
|
this._textCache = new Map();
|
||||||
this._kind = kind;
|
this._kind = kind;
|
||||||
this._dialog = new Dialog(recorder);
|
this._dialog = new Dialog(recorder);
|
||||||
}
|
}
|
||||||
|
@ -18,15 +18,10 @@ import { parseEvaluationResultValue, serializeAsCallArgument } from '@isomorphic
|
|||||||
|
|
||||||
// --- This section should match javascript.ts and generated_injected_builtins.js ---
|
// --- This section should match javascript.ts and generated_injected_builtins.js ---
|
||||||
|
|
||||||
// This runtime guid is replaced by the actual guid at runtime in all generated sources.
|
|
||||||
const kRuntimeGuid = '$runtime_guid$';
|
|
||||||
// This flag is replaced by true/false at runtime in all generated sources.
|
// This flag is replaced by true/false at runtime in all generated sources.
|
||||||
const kUtilityScriptIsUnderTest = false;
|
const kUtilityScriptIsUnderTest = false;
|
||||||
|
|
||||||
// The name of the global property that stores the UtilityScript instance,
|
// Keep in sync with eslint.config.mjs
|
||||||
// referenced by generated_injected_builtins.js.
|
|
||||||
const kUtilityScriptGlobalProperty = `__playwright_utility_script__${kRuntimeGuid}`;
|
|
||||||
|
|
||||||
export type Builtins = {
|
export type Builtins = {
|
||||||
setTimeout: Window['setTimeout'],
|
setTimeout: Window['setTimeout'],
|
||||||
clearTimeout: Window['clearTimeout'],
|
clearTimeout: Window['clearTimeout'],
|
||||||
@ -35,18 +30,12 @@ export type Builtins = {
|
|||||||
requestAnimationFrame: Window['requestAnimationFrame'],
|
requestAnimationFrame: Window['requestAnimationFrame'],
|
||||||
cancelAnimationFrame: Window['cancelAnimationFrame'],
|
cancelAnimationFrame: Window['cancelAnimationFrame'],
|
||||||
requestIdleCallback: Window['requestIdleCallback'],
|
requestIdleCallback: Window['requestIdleCallback'],
|
||||||
cancelIdleCallback: (id: number) => void,
|
cancelIdleCallback: Window['cancelIdleCallback'],
|
||||||
performance: Window['performance'],
|
performance: Window['performance'],
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
eval: typeof window['eval'],
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
Intl: typeof window['Intl'],
|
Intl: typeof window['Intl'],
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
Date: typeof window['Date'],
|
Date: typeof window['Date'],
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
Map: typeof window['Map'],
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
Set: typeof window['Set'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- End of the matching section ---
|
// --- End of the matching section ---
|
||||||
@ -54,6 +43,7 @@ export type Builtins = {
|
|||||||
export class UtilityScript {
|
export class UtilityScript {
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
readonly global: typeof globalThis;
|
readonly global: typeof globalThis;
|
||||||
|
// Builtins protect injected code from clock emulation.
|
||||||
readonly builtins: Builtins;
|
readonly builtins: Builtins;
|
||||||
readonly isUnderTest: boolean;
|
readonly isUnderTest: boolean;
|
||||||
|
|
||||||
@ -61,29 +51,23 @@ export class UtilityScript {
|
|||||||
constructor(global: typeof globalThis) {
|
constructor(global: typeof globalThis) {
|
||||||
this.global = global;
|
this.global = global;
|
||||||
this.isUnderTest = kUtilityScriptIsUnderTest;
|
this.isUnderTest = kUtilityScriptIsUnderTest;
|
||||||
// UtilityScript is evaluated in every page as an InitScript, and saves builtins
|
if ((global as any).__pwClock) {
|
||||||
// from the global object, before the page has a chance to temper with them.
|
this.builtins = (global as any).__pwClock.builtins;
|
||||||
//
|
} else {
|
||||||
// Later on, any compiled script replaces global invocations of builtins, e.g. setTimeout,
|
this.builtins = {
|
||||||
// with a version exported by generate_injected_builtins.js. That file tries to
|
setTimeout: global.setTimeout?.bind(global),
|
||||||
// get original builtins saved on the instance of UtilityScript, and falls back
|
clearTimeout: global.clearTimeout?.bind(global),
|
||||||
// to the global object just in case something goes wrong with InitScript that creates UtilityScript.
|
setInterval: global.setInterval?.bind(global),
|
||||||
this.builtins = {
|
clearInterval: global.clearInterval?.bind(global),
|
||||||
setTimeout: global.setTimeout?.bind(global),
|
requestAnimationFrame: global.requestAnimationFrame?.bind(global),
|
||||||
clearTimeout: global.clearTimeout?.bind(global),
|
cancelAnimationFrame: global.cancelAnimationFrame?.bind(global),
|
||||||
setInterval: global.setInterval?.bind(global),
|
requestIdleCallback: global.requestIdleCallback?.bind(global),
|
||||||
clearInterval: global.clearInterval?.bind(global),
|
cancelIdleCallback: global.cancelIdleCallback?.bind(global),
|
||||||
requestAnimationFrame: global.requestAnimationFrame?.bind(global),
|
performance: global.performance,
|
||||||
cancelAnimationFrame: global.cancelAnimationFrame?.bind(global),
|
Intl: global.Intl,
|
||||||
requestIdleCallback: global.requestIdleCallback?.bind(global),
|
Date: global.Date,
|
||||||
cancelIdleCallback: global.cancelIdleCallback?.bind(global),
|
} satisfies Builtins;
|
||||||
performance: global.performance,
|
}
|
||||||
eval: global.eval?.bind(global),
|
|
||||||
Intl: global.Intl,
|
|
||||||
Date: global.Date,
|
|
||||||
Map: global.Map,
|
|
||||||
Set: global.Set,
|
|
||||||
};
|
|
||||||
if (this.isUnderTest)
|
if (this.isUnderTest)
|
||||||
(global as any).builtins = this.builtins;
|
(global as any).builtins = this.builtins;
|
||||||
}
|
}
|
||||||
@ -95,7 +79,7 @@ export class UtilityScript {
|
|||||||
for (let i = 0; i < args.length; i++)
|
for (let i = 0; i < args.length; i++)
|
||||||
parameters[i] = parseEvaluationResultValue(args[i], handles);
|
parameters[i] = parseEvaluationResultValue(args[i], handles);
|
||||||
|
|
||||||
let result = eval(expression);
|
let result = this.global.eval(expression);
|
||||||
if (isFunction === true) {
|
if (isFunction === true) {
|
||||||
result = result(...parameters);
|
result = result(...parameters);
|
||||||
} else if (isFunction === false) {
|
} else if (isFunction === false) {
|
||||||
@ -137,16 +121,3 @@ export class UtilityScript {
|
|||||||
return safeJson(value);
|
return safeJson(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
export function ensureUtilityScript(global?: typeof globalThis): UtilityScript {
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
global = global ?? globalThis;
|
|
||||||
let utilityScript: UtilityScript = (global as any)[kUtilityScriptGlobalProperty];
|
|
||||||
if (utilityScript)
|
|
||||||
return utilityScript;
|
|
||||||
|
|
||||||
utilityScript = new UtilityScript(global);
|
|
||||||
Object.defineProperty(global, kUtilityScriptGlobalProperty, { value: utilityScript, configurable: false, enumerable: false, writable: false });
|
|
||||||
return utilityScript;
|
|
||||||
}
|
|
||||||
|
@ -21,7 +21,6 @@ import * as network from '../network';
|
|||||||
import { BidiConnection } from './bidiConnection';
|
import { BidiConnection } from './bidiConnection';
|
||||||
import { bidiBytesValueToString } from './bidiNetworkManager';
|
import { bidiBytesValueToString } from './bidiNetworkManager';
|
||||||
import { BidiPage, kPlaywrightBindingChannel } from './bidiPage';
|
import { BidiPage, kPlaywrightBindingChannel } from './bidiPage';
|
||||||
import { kUtilityInitScript } from '../page';
|
|
||||||
import { kPlaywrightBinding } from '../javascript';
|
import { kPlaywrightBinding } from '../javascript';
|
||||||
import * as bidi from './third_party/bidiProtocol';
|
import * as bidi from './third_party/bidiProtocol';
|
||||||
|
|
||||||
@ -222,7 +221,6 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
override async _initialize() {
|
override async _initialize() {
|
||||||
const promises: Promise<any>[] = [
|
const promises: Promise<any>[] = [
|
||||||
super._initialize(),
|
super._initialize(),
|
||||||
this._installUtilityScript(),
|
|
||||||
];
|
];
|
||||||
if (this._options.viewport) {
|
if (this._options.viewport) {
|
||||||
promises.push(this._browser._browserSession.send('browsingContext.setViewport', {
|
promises.push(this._browser._browserSession.send('browsingContext.setViewport', {
|
||||||
@ -239,13 +237,6 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _installUtilityScript() {
|
|
||||||
await this._browser._browserSession.send('script.addPreloadScript', {
|
|
||||||
functionDeclaration: `() => { return${kUtilityInitScript.source} }`,
|
|
||||||
userContexts: [this._userContextId()],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
override possiblyUninitializedPages(): Page[] {
|
override possiblyUninitializedPages(): Page[] {
|
||||||
return this._bidiPages().map(bidiPage => bidiPage._page);
|
return this._bidiPages().map(bidiPage => bidiPage._page);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import { BrowserContext, verifyGeolocation } from '../browserContext';
|
|||||||
import { TargetClosedError } from '../errors';
|
import { TargetClosedError } from '../errors';
|
||||||
import { kPlaywrightBinding } from '../javascript';
|
import { kPlaywrightBinding } from '../javascript';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { kUtilityInitScript } from '../page';
|
|
||||||
import { ConnectionEvents, FFConnection } from './ffConnection';
|
import { ConnectionEvents, FFConnection } from './ffConnection';
|
||||||
import { FFPage } from './ffPage';
|
import { FFPage } from './ffPage';
|
||||||
|
|
||||||
@ -383,7 +382,7 @@ export class FFBrowserContext extends BrowserContext {
|
|||||||
if (this.bindingsInitScript)
|
if (this.bindingsInitScript)
|
||||||
bindingScripts.unshift(this.bindingsInitScript.source);
|
bindingScripts.unshift(this.bindingsInitScript.source);
|
||||||
const initScripts = this.initScripts.map(script => script.source);
|
const initScripts = this.initScripts.map(script => script.source);
|
||||||
await this._browser.session.send('Browser.setInitScripts', { browserContextId: this._browserContextId, scripts: [kUtilityInitScript.source, ...bindingScripts, ...initScripts].map(script => ({ script })) });
|
await this._browser.session.send('Browser.setInitScripts', { browserContextId: this._browserContextId, scripts: [...bindingScripts, ...initScripts].map(script => ({ script })) });
|
||||||
}
|
}
|
||||||
|
|
||||||
async doUpdateRequestInterception(): Promise<void> {
|
async doUpdateRequestInterception(): Promise<void> {
|
||||||
|
@ -131,7 +131,7 @@ export class ExecutionContext extends SdkObject {
|
|||||||
(() => {
|
(() => {
|
||||||
const module = {};
|
const module = {};
|
||||||
${kUtilityScriptSource}
|
${kUtilityScriptSource}
|
||||||
return (module.exports.ensureUtilityScript())();
|
return new (module.exports.UtilityScript())(globalThis);
|
||||||
})();`;
|
})();`;
|
||||||
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this.delegate.rawEvaluateHandle(this, source))
|
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this.delegate.rawEvaluateHandle(this, source))
|
||||||
.then(handle => {
|
.then(handle => {
|
||||||
|
@ -760,7 +760,7 @@ export class Page extends SdkObject {
|
|||||||
const bindings = [...this.browserContext._pageBindings.values(), ...this._pageBindings.values()].map(binding => binding.initScript);
|
const bindings = [...this.browserContext._pageBindings.values(), ...this._pageBindings.values()].map(binding => binding.initScript);
|
||||||
if (this.browserContext.bindingsInitScript)
|
if (this.browserContext.bindingsInitScript)
|
||||||
bindings.unshift(this.browserContext.bindingsInitScript);
|
bindings.unshift(this.browserContext.bindingsInitScript);
|
||||||
return [kUtilityInitScript, ...bindings, ...this.browserContext.initScripts, ...this.initScripts];
|
return [...bindings, ...this.browserContext.initScripts, ...this.initScripts];
|
||||||
}
|
}
|
||||||
|
|
||||||
getBinding(name: string) {
|
getBinding(name: string) {
|
||||||
@ -902,14 +902,6 @@ export class InitScript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const kUtilityInitScript = new InitScript(`
|
|
||||||
(() => {
|
|
||||||
const module = {};
|
|
||||||
${js.kUtilityScriptSource}
|
|
||||||
(module.exports.ensureUtilityScript())();
|
|
||||||
})();
|
|
||||||
`, true /* internal */);
|
|
||||||
|
|
||||||
class FrameThrottler {
|
class FrameThrottler {
|
||||||
private _acks: (() => void)[] = [];
|
private _acks: (() => void)[] = [];
|
||||||
private _defaultInterval: number;
|
private _defaultInterval: number;
|
||||||
|
@ -14,6 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Hopefully, this file is never used in injected sources,
|
||||||
|
// because it does not use `builtins.performance`,
|
||||||
|
// and can break when clock emulation is engaged.
|
||||||
|
|
||||||
|
/* eslint-disable no-restricted-globals */
|
||||||
|
|
||||||
let _timeOrigin = performance.timeOrigin;
|
let _timeOrigin = performance.timeOrigin;
|
||||||
let _timeShift = 0;
|
let _timeShift = 0;
|
||||||
|
|
||||||
|
@ -14,6 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Hopefully, this file is never used in injected sources,
|
||||||
|
// because it does not use `builtins.setTimeout` and similar,
|
||||||
|
// and can break when clock emulation is engaged.
|
||||||
|
|
||||||
|
/* eslint-disable no-restricted-globals */
|
||||||
|
|
||||||
import { monotonicTime } from './time';
|
import { monotonicTime } from './time';
|
||||||
|
|
||||||
export async function raceAgainstDeadline<T>(cb: () => Promise<T>, deadline: number): Promise<{ result: T, timedOut: false } | { timedOut: true }> {
|
export async function raceAgainstDeadline<T>(cb: () => Promise<T>, deadline: number): Promise<{ result: T, timedOut: false } | { timedOut: true }> {
|
||||||
|
@ -45,8 +45,10 @@ function isRegExp(obj: any): obj is RegExp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
function isDate(obj: any): obj is Date {
|
function isDate(obj: any): obj is Date {
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
return obj instanceof Date || Object.prototype.toString.call(obj) === '[object Date]';
|
return obj instanceof Date || Object.prototype.toString.call(obj) === '[object Date]';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
@ -132,8 +134,10 @@ export function parseEvaluationResultValue(value: SerializedValue, handles: any[
|
|||||||
return -0;
|
return -0;
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if ('d' in value)
|
if ('d' in value) {
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
return new Date(value.d);
|
return new Date(value.d);
|
||||||
|
}
|
||||||
if ('u' in value)
|
if ('u' in value)
|
||||||
return new URL(value.u);
|
return new URL(value.u);
|
||||||
if ('bi' in value)
|
if ('bi' in value)
|
||||||
|
@ -16,21 +16,22 @@
|
|||||||
|
|
||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
import { createClock as rawCreateClock, install as rawInstall } from '../../../packages/injected/src/clock';
|
import { createClock as rawCreateClock, install as rawInstall } from '../../../packages/injected/src/clock';
|
||||||
import type { InstallConfig, ClockController, ClockMethods } from '../../../packages/injected/src/clock';
|
import type { InstallConfig, ClockController } from '../../../packages/injected/src/clock';
|
||||||
|
import type { Builtins } from '../../../packages/injected/src/utilityScript';
|
||||||
|
|
||||||
const createClock = (now?: number): ClockController & ClockMethods => {
|
const createClock = (now?: number): ClockController & Builtins => {
|
||||||
const { clock, api } = rawCreateClock(globalThis);
|
const { clock, api } = rawCreateClock(globalThis);
|
||||||
clock.setSystemTime(now || 0);
|
clock.setSystemTime(now || 0);
|
||||||
for (const key of Object.keys(api))
|
for (const key of Object.keys(api))
|
||||||
clock[key] = api[key];
|
clock[key] = api[key];
|
||||||
return clock as ClockController & ClockMethods;
|
return clock as ClockController & Builtins;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ClockFixtures = {
|
type ClockFixtures = {
|
||||||
clock: ClockController & ClockMethods;
|
clock: ClockController & Builtins;
|
||||||
now: number | undefined;
|
now: number | undefined;
|
||||||
install: (now?: number) => ClockController & ClockMethods;
|
install: (now?: number) => ClockController & Builtins;
|
||||||
installEx: (config?: InstallConfig) => { clock: ClockController, api: ClockMethods, originals: ClockMethods };
|
installEx: (config?: InstallConfig) => { clock: ClockController, api: Builtins, originals: Builtins };
|
||||||
};
|
};
|
||||||
|
|
||||||
const it = test.extend<ClockFixtures>({
|
const it = test.extend<ClockFixtures>({
|
||||||
@ -42,14 +43,14 @@ const it = test.extend<ClockFixtures>({
|
|||||||
now: undefined,
|
now: undefined,
|
||||||
|
|
||||||
install: async ({}, use) => {
|
install: async ({}, use) => {
|
||||||
let clockObject: ClockController & ClockMethods;
|
let clockObject: ClockController & Builtins;
|
||||||
const install = (now?: number) => {
|
const install = (now?: number) => {
|
||||||
const { clock, api } = rawInstall(globalThis);
|
const { clock, api } = rawInstall(globalThis);
|
||||||
if (now)
|
if (now)
|
||||||
clock.setSystemTime(now);
|
clock.setSystemTime(now);
|
||||||
for (const key of Object.keys(api))
|
for (const key of Object.keys(api))
|
||||||
clock[key] = api[key];
|
clock[key] = api[key];
|
||||||
clockObject = clock as ClockController & ClockMethods;
|
clockObject = clock as ClockController & Builtins;
|
||||||
return clockObject;
|
return clockObject;
|
||||||
};
|
};
|
||||||
await use(install);
|
await use(install);
|
||||||
|
@ -83,15 +83,3 @@ it('should work with bogus Array.from', async ({ page, server }) => {
|
|||||||
const divsCount = await page.$$eval('css=div', divs => divs.length);
|
const divsCount = await page.$$eval('css=div', divs => divs.length);
|
||||||
expect(divsCount).toBe(3);
|
expect(divsCount).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with broken Map', async ({ page, server }) => {
|
|
||||||
await page.setContent(`
|
|
||||||
<script>
|
|
||||||
window.Map = () => {};
|
|
||||||
</script>
|
|
||||||
<button>Click me</button>
|
|
||||||
<button>And me</button>
|
|
||||||
`);
|
|
||||||
const count = await page.$$eval('role=button', els => els.length);
|
|
||||||
expect(count).toBe(2);
|
|
||||||
});
|
|
||||||
|
@ -109,3 +109,18 @@ it('init script should run only once in popup', async ({ page, browserName }) =>
|
|||||||
]);
|
]);
|
||||||
expect(await popup.evaluate('callCount')).toEqual(1);
|
expect(await popup.evaluate('callCount')).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('init script should not observe playwright internals', async ({ server, page }) => {
|
||||||
|
it.skip(!!process.env.PW_CLOCK, 'clock installs globalThis.__pwClock');
|
||||||
|
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
window['check'] = () => {
|
||||||
|
const keys = Reflect.ownKeys(globalThis).map(k => k.toString());
|
||||||
|
return keys.find(name => name.includes('playwright') || name.includes('_pw')) || 'none';
|
||||||
|
};
|
||||||
|
window['found'] = window['check']();
|
||||||
|
});
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
expect(await page.evaluate(() => window['found'])).toBe('none');
|
||||||
|
expect(await page.evaluate(() => window['check']())).toBe('none');
|
||||||
|
});
|
||||||
|
@ -850,38 +850,6 @@ it('should work with Array.from/map', async ({ page }) => {
|
|||||||
})).toBe('([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})');
|
})).toBe('([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with overridden eval', {
|
|
||||||
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34628' },
|
|
||||||
}, async ({ page, server }) => {
|
|
||||||
server.setRoute('/page', (req, res) => {
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.end(`
|
|
||||||
<script>
|
|
||||||
window.eval = () => 42;
|
|
||||||
</script>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
await page.goto(server.PREFIX + '/page');
|
|
||||||
expect(await page.evaluate(x => ({ value: 2 * x }), 17)).toEqual({ value: 34 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work with deleted Map', {
|
|
||||||
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34443' },
|
|
||||||
}, async ({ page, server }) => {
|
|
||||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34443' });
|
|
||||||
|
|
||||||
server.setRoute('/page', (req, res) => {
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.end(`
|
|
||||||
<script>
|
|
||||||
delete window.Map;
|
|
||||||
</script>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
await page.goto(server.PREFIX + '/page');
|
|
||||||
expect(await page.evaluate(x => ({ value: 2 * x }), 17)).toEqual({ value: 34 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should ignore dangerous object keys', async ({ page }) => {
|
it('should ignore dangerous object keys', async ({ page }) => {
|
||||||
const input = {
|
const input = {
|
||||||
__proto__: { polluted: true },
|
__proto__: { polluted: true },
|
||||||
|
@ -309,41 +309,3 @@ it('should fail with busted Array.prototype.toJSON', async ({ page }) => {
|
|||||||
|
|
||||||
expect.soft(await page.evaluate(() => ([] as any).toJSON())).toBe('"[]"');
|
expect.soft(await page.evaluate(() => ([] as any).toJSON())).toBe('"[]"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with overridden eval', {
|
|
||||||
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34628' },
|
|
||||||
}, async ({ page, server }) => {
|
|
||||||
await page.exposeFunction('add', (a, b) => a + b);
|
|
||||||
|
|
||||||
server.setRoute('/page', (req, res) => {
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.end(`
|
|
||||||
<script>
|
|
||||||
window.eval = () => 42;
|
|
||||||
</script>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
await page.goto(server.PREFIX + '/page');
|
|
||||||
expect(await page.evaluate(async () => {
|
|
||||||
return { value: await (window as any)['add'](5, 6) };
|
|
||||||
})).toEqual({ value: 11 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work with deleted Map', {
|
|
||||||
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34443' },
|
|
||||||
}, async ({ page, server }) => {
|
|
||||||
await page.exposeFunction('add', (a, b) => a + b);
|
|
||||||
|
|
||||||
server.setRoute('/page', (req, res) => {
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.end(`
|
|
||||||
<script>
|
|
||||||
delete window.Map;
|
|
||||||
</script>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
await page.goto(server.PREFIX + '/page');
|
|
||||||
expect(await page.evaluate(async () => {
|
|
||||||
return { value: await (window as any)['add'](5, 6) };
|
|
||||||
})).toEqual({ value: 11 });
|
|
||||||
});
|
|
||||||
|
@ -136,7 +136,6 @@ const inlineCSSPlugin = {
|
|||||||
platform: 'browser',
|
platform: 'browser',
|
||||||
target: 'ES2019',
|
target: 'ES2019',
|
||||||
plugins: [inlineCSSPlugin],
|
plugins: [inlineCSSPlugin],
|
||||||
inject: hasExports ? [require.resolve('./generate_injected_builtins.js')] : [],
|
|
||||||
});
|
});
|
||||||
for (const message of [...buildOutput.errors, ...buildOutput.warnings])
|
for (const message of [...buildOutput.errors, ...buildOutput.warnings])
|
||||||
console.log(message.text);
|
console.log(message.text);
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// IMPORTANT: This file should match javascript.ts and utilityScript.ts
|
|
||||||
const gSetTimeout = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.setTimeout ?? globalThis.setTimeout;
|
|
||||||
const gClearTimeout = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.clearTimeout ?? globalThis.clearTimeout;
|
|
||||||
const gSetInterval = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.setInterval ?? globalThis.setInterval;
|
|
||||||
const gClearInterval = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.clearInterval ?? globalThis.clearInterval;
|
|
||||||
const gRequestAnimationFrame = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.requestAnimationFrame ?? globalThis.requestAnimationFrame;
|
|
||||||
const gCancelAnimationFrame = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.cancelAnimationFrame ?? globalThis.cancelAnimationFrame;
|
|
||||||
const gRequestIdleCallback = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.requestIdleCallback ?? globalThis.requestIdleCallback;
|
|
||||||
const gCancelIdleCallback = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.cancelIdleCallback ?? globalThis.cancelIdleCallback;
|
|
||||||
const gPerformance = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.performance ?? globalThis.performance;
|
|
||||||
const gEval = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.eval ?? globalThis.eval;
|
|
||||||
const gIntl = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.Intl ?? globalThis.Intl;
|
|
||||||
const gDate = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.Date ?? globalThis.Date;
|
|
||||||
const gMap = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.Map ?? globalThis.Map;
|
|
||||||
const gSet = globalThis.__playwright_utility_script__$runtime_guid$?.builtins.Set ?? globalThis.Set;
|
|
||||||
|
|
||||||
export {
|
|
||||||
gSetTimeout as 'setTimeout',
|
|
||||||
gClearTimeout as 'clearTimeout',
|
|
||||||
gSetInterval as 'setInterval',
|
|
||||||
gClearInterval as 'clearInterval',
|
|
||||||
gRequestAnimationFrame as 'requestAnimationFrame',
|
|
||||||
gCancelAnimationFrame as 'cancelAnimationFrame',
|
|
||||||
gRequestIdleCallback as 'requestIdleCallback',
|
|
||||||
gCancelIdleCallback as 'cancelIdleCallback',
|
|
||||||
gPerformance as 'performance',
|
|
||||||
gEval as 'eval',
|
|
||||||
gIntl as 'Intl',
|
|
||||||
gDate as 'Date',
|
|
||||||
gMap as 'Map',
|
|
||||||
gSet as 'Set',
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user