diff --git a/package.json b/package.json index 17f31f7b24..38b0354ac8 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "main": "index.js", "playwright": { "chromium_revision": "724623", - "firefox_revision": "1008", + "firefox_revision": "1009", "webkit_revision": "1055" }, "scripts": { diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index ee5c98142e..ee1cc50fcc 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -69,7 +69,6 @@ export class CRBrowser extends browser.Browser { } _createBrowserContext(contextId: string | null, options: BrowserContextOptions): BrowserContext { - let overrides: CROverrides | null = null; const context = new BrowserContext({ pages: async (): Promise => { const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === 'page'); @@ -81,28 +80,7 @@ export class CRBrowser extends browser.Browser { const { targetId } = await this._client.send('Target.createTarget', { url: 'about:blank', browserContextId: contextId || undefined }); const target = this._targets.get(targetId); assert(await target._initializedPromise, 'Failed to create target for page'); - const page = await target.page(); - const crPage = target._crPage; - const session = crPage._client; - const promises: Promise[] = [ overrides._applyOverrides(crPage) ]; - if (options.bypassCSP) - promises.push(session.send('Page.setBypassCSP', { enabled: true })); - if (options.ignoreHTTPSErrors) - promises.push(session.send('Security.setIgnoreCertificateErrors', { ignore: true })); - if (options.viewport) - promises.push(crPage.setViewport(options.viewport)); - if (options.javaScriptEnabled === false) - promises.push(session.send('Emulation.setScriptExecutionDisabled', { value: true })); - if (options.userAgent) - crPage._networkManager.setUserAgent(options.userAgent); - if (options.mediaType || options.colorScheme) { - const features = options.colorScheme ? [{ name: 'prefers-color-scheme', value: options.colorScheme }] : []; - promises.push(session.send('Emulation.setEmulatedMedia', { media: options.mediaType || '', features })); - } - if (options.timezoneId) - promises.push(emulateTimezone(session, options.timezoneId)); - await Promise.all(promises); - return page; + return target.page(); }, close: async (): Promise => { @@ -162,8 +140,7 @@ export class CRBrowser extends browser.Browser { } }, options); - overrides = new CROverrides(context); - (context as any).overrides = overrides; + (context as any).overrides = new CROverrides(context); return context; } @@ -317,13 +294,3 @@ export class CRBrowser extends browser.Browser { return !this._connection._closed; } } - -async function emulateTimezone(session: CRSession, timezoneId: string) { - try { - await session.send('Emulation.setTimezoneOverride', { timezoneId: timezoneId }); - } catch (exception) { - if (exception.message.includes('Invalid timezone')) - throw new Error(`Invalid timezone ID: ${timezoneId}`); - throw exception; - } -} diff --git a/src/chromium/crPage.ts b/src/chromium/crPage.ts index 33dec12721..b30af7b70c 100644 --- a/src/chromium/crPage.ts +++ b/src/chromium/crPage.ts @@ -38,6 +38,7 @@ import { BrowserContext } from '../browserContext'; import * as types from '../types'; import * as input from '../input'; import { ConsoleMessage } from '../console'; +import { CROverrides } from './features/crOverrides'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; @@ -86,13 +87,32 @@ export class CRPage implements PageDelegate { this._client.send('Page.getFrameTree'), ]); this._handleFrameTree(frameTree); - await Promise.all([ + const promises: Promise[] = [ this._client.send('Log.enable', {}), this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}), this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }), this._client.send('Runtime.enable', {}).then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)), this._networkManager.initialize(), - ]); + ((this._page.browserContext() as any).overrides as CROverrides)._applyOverrides(this), + ]; + const options = this._page.browserContext()._options; + if (options.bypassCSP) + promises.push(this._client.send('Page.setBypassCSP', { enabled: true })); + if (options.ignoreHTTPSErrors) + promises.push(this._client.send('Security.setIgnoreCertificateErrors', { ignore: true })); + if (options.viewport) + promises.push(this.setViewport(options.viewport)); + if (options.javaScriptEnabled === false) + promises.push(this._client.send('Emulation.setScriptExecutionDisabled', { value: true })); + if (options.userAgent) + this._networkManager.setUserAgent(options.userAgent); + if (options.mediaType || options.colorScheme) { + const features = options.colorScheme ? [{ name: 'prefers-color-scheme', value: options.colorScheme }] : []; + promises.push(this._client.send('Emulation.setEmulatedMedia', { media: options.mediaType || '', features })); + } + if (options.timezoneId) + promises.push(emulateTimezone(this._client, options.timezoneId)); + await Promise.all(promises); } didClose() { @@ -488,3 +508,13 @@ export class ChromiumPage extends Page { function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject { return handle._remoteObject as Protocol.Runtime.RemoteObject; } + +async function emulateTimezone(session: CRSession, timezoneId: string) { + try { + await session.send('Emulation.setTimezoneOverride', { timezoneId: timezoneId }); + } catch (exception) { + if (exception.message.includes('Invalid timezone')) + throw new Error(`Invalid timezone ID: ${timezoneId}`); + throw exception; + } +} diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index 651ec3dee0..211ab1eb30 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -155,21 +155,7 @@ export class FFBrowser extends browser.Browser { browserContextId: browserContextId || undefined }); const target = this._targets.get(targetId); - const page = await target.page(); - const session = target._ffPage._session; - const promises: Promise[] = []; - if (options.viewport) - promises.push(page._delegate.setViewport(options.viewport)); - if (options.bypassCSP) - promises.push(session.send('Page.setBypassCSP', { enabled: true })); - if (options.javaScriptEnabled === false) - promises.push(session.send('Page.setJavascriptEnabled', { enabled: false })); - if (options.userAgent) - promises.push(session.send('Page.setUserAgent', { userAgent: options.userAgent })); - if (options.mediaType || options.colorScheme) - promises.push(session.send('Page.setEmulatedMedia', { type: options.mediaType, colorScheme: options.colorScheme })); - await Promise.all(promises); - return page; + return target.page(); }, close: async (): Promise => { diff --git a/src/firefox/ffPage.ts b/src/firefox/ffPage.ts index bc8330b545..318bf2e062 100644 --- a/src/firefox/ffPage.ts +++ b/src/firefox/ffPage.ts @@ -68,12 +68,24 @@ export class FFPage implements PageDelegate { } async _initialize() { - await Promise.all([ + const promises: Promise[] = [ this._session.send('Runtime.enable'), this._session.send('Network.enable'), this._session.send('Page.enable'), this._session.send('Page.setInterceptFileChooserDialog', { enabled: true }) - ]); + ]; + const options = this._page.browserContext()._options; + if (options.viewport) + promises.push(this.setViewport(options.viewport)); + if (options.bypassCSP) + promises.push(this._session.send('Page.setBypassCSP', { enabled: true })); + if (options.javaScriptEnabled === false) + promises.push(this._session.send('Page.setJavascriptEnabled', { enabled: false })); + if (options.userAgent) + promises.push(this._session.send('Page.setUserAgent', { userAgent: options.userAgent })); + if (options.mediaType || options.colorScheme) + promises.push(this._session.send('Page.setEmulatedMedia', { type: options.mediaType, colorScheme: options.colorScheme })); + await Promise.all(promises); } _onExecutionContextCreated({executionContextId, auxData}) { diff --git a/src/webkit/wkExecutionContext.ts b/src/webkit/wkExecutionContext.ts index 678eae53c0..ba5b4798f8 100644 --- a/src/webkit/wkExecutionContext.ts +++ b/src/webkit/wkExecutionContext.ts @@ -30,6 +30,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { _contextId: number; private _contextDestroyedCallback: () => void; private _executionContextDestroyedPromise: Promise; + _jsonObjectId: Protocol.Runtime.RemoteObjectId | undefined; constructor(client: WKTargetSession, contextPayload: Protocol.Runtime.ExecutionContextDescription) { this._session = client; @@ -218,7 +219,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { } private _returnObjectByValue(objectId: Protocol.Runtime.RemoteObjectId) { - const serializeFunction = function() { + const serializeFunction = function(JSON: { stringify: (o: any) => string }) { try { return JSON.stringify(this); } catch (e) { @@ -231,6 +232,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { // Serialize object using standard JSON implementation to correctly pass 'undefined'. functionDeclaration: serializeFunction + '\n' + suffix + '\n', objectId: objectId, + arguments: [ { objectId: this._jsonObjectId } ], returnByValue: true }).catch(e => { if (isSwappedOutError(e)) diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts index ddc167bcc6..df8bddbbcd 100644 --- a/src/webkit/wkPage.ts +++ b/src/webkit/wkPage.ts @@ -36,6 +36,8 @@ import { PNG } from 'pngjs'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; const BINDING_CALL_MESSAGE = '__playwright_binding_call__'; +const JSON_CALL_MESSAGE = '__playwright_json_call__'; +const JSON_SAVE_SCRIPT = `console.debug('${JSON_CALL_MESSAGE}', JSON)`; export class WKPage implements PageDelegate { readonly rawMouse: RawMouseImpl; @@ -47,7 +49,7 @@ export class WKPage implements PageDelegate { private readonly _contextIdToContext: Map; private _isolatedWorlds: Set; private _sessionListeners: RegisteredListener[] = []; - private readonly _bootstrapScripts: string[] = []; + private readonly _bootstrapScripts: string[] = [ JSON_SAVE_SCRIPT ]; constructor(browser: WKBrowser, browserContext: BrowserContext) { this._browser = browser; @@ -70,7 +72,7 @@ export class WKPage implements PageDelegate { this._isolatedWorlds = new Set(); // New bootstrap scripts may have been added during provisional load, push them // again to be on the safe side. - if (this._setBootstrapScripts.length) + if (this._bootstrapScripts.length) this._setBootstrapScripts(session).catch(e => debugError(e)); } @@ -107,6 +109,8 @@ export class WKPage implements PageDelegate { promises.push(this._setExtraHTTPHeaders(session, this._page._state.extraHTTPHeaders)); if (this._page._state.viewport) promises.push(WKPage._setViewport(session, this._page._state.viewport)); + if (contextOptions.javaScriptEnabled !== false) + promises.push(session.send('Page.setBootstrapScript', { source: JSON_SAVE_SCRIPT })); await Promise.all(promises); } @@ -217,6 +221,12 @@ export class WKPage implements PageDelegate { this._page._onBindingCalled(parameters[2].value, context); return; } + if (level === 'debug' && parameters && parameters[0].value === JSON_CALL_MESSAGE) { + const parsedObjectId = JSON.parse(parameters[1].objectId); + const context = this._contextIdToContext.get(parsedObjectId.injectedScriptId); + (context._delegate as WKExecutionContext)._jsonObjectId = parameters[1].objectId; + return; + } let derivedType: string = type; if (type === 'log') derivedType = level; diff --git a/test/evaluation.spec.js b/test/evaluation.spec.js index 8859f6ce95..7fd2730575 100644 --- a/test/evaluation.spec.js +++ b/test/evaluation.spec.js @@ -259,10 +259,10 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { })).catch(e => error = e); expect(error.message).toContain('Error in promise'); }); - it.skip(FFOX || WEBKIT)('should work even when JSON is set to null', async({page, server}) => { + it('should work even when JSON is set to null', async ({ page }) => { await page.evaluate(() => { window.JSON.stringify = null; window.JSON = null; }); const result = await page.evaluate(() => ({abc: 123})); - expect(result).toEqual({abc: 123}); + expect(result).toEqual({abc: 123}); }) });