diff --git a/src/page.ts b/src/page.ts index 290b65990b..8bc7b78d6d 100644 --- a/src/page.ts +++ b/src/page.ts @@ -275,12 +275,12 @@ export class Page arg.dispose()); return; } - this.emit(Events.Page.Console, new console.ConsoleMessage(type, undefined, args, location)); + this.emit(Events.Page.Console, new console.ConsoleMessage(type, text, args, location)); } url(): string { diff --git a/src/webkit/Browser.ts b/src/webkit/Browser.ts index 7025b37009..85d9a9ea46 100644 --- a/src/webkit/Browser.ts +++ b/src/webkit/Browser.ts @@ -20,7 +20,7 @@ import { EventEmitter } from 'events'; import { assert, helper, RegisteredListener, debugError } from '../helper'; import { filterCookies, NetworkCookie, rewriteCookies, SetNetworkCookieParam } from '../network'; import { Connection, ConnectionEvents, TargetSession } from './Connection'; -import { Page } from './Page'; +import { Page } from '../page'; import { Target } from './Target'; import { Protocol } from './protocol'; import * as types from '../types'; @@ -98,11 +98,11 @@ export class Browser extends EventEmitter { this._contexts.delete(browserContextId); } - async newPage(): Promise { + async newPage(): Promise> { return this._createPageInContext(this._defaultContext._id); } - async _createPageInContext(browserContextId?: string): Promise { + async _createPageInContext(browserContextId?: string): Promise> { const { targetId } = await this._connection.send('Browser.createPage', { browserContextId }); const target = this._targets.get(targetId); return await target.page(); @@ -136,7 +136,7 @@ export class Browser extends EventEmitter { } } - async pages(): Promise { + async pages(): Promise[]> { const contextPages = await Promise.all(this.browserContexts().map(context => context.pages())); // Flatten array. return contextPages.reduce((acc, x) => acc.concat(x), []); @@ -166,19 +166,19 @@ export class Browser extends EventEmitter { target._didClose(); } - _closePage(page: Page) { + _closePage(page: Page) { this._connection.send('Target.close', { targetId: Target.fromPage(page)._targetId }).catch(debugError); } - async _pages(context: BrowserContext): Promise { + async _pages(context: BrowserContext): Promise[]> { const targets = this.targets().filter(target => target._browserContext === context && target._type === 'page'); const pages = await Promise.all(targets.map(target => target.page())); return pages.filter(page => !!page); } - async _activatePage(page: Page): Promise { + async _activatePage(page: Page): Promise { await this._connection.send('Target.activate', { targetId: Target.fromPage(page)._targetId }); } @@ -211,7 +211,7 @@ export class BrowserContext { this._id = contextId; } - pages(): Promise { + pages(): Promise[]> { return this._browser._pages(this); } @@ -219,7 +219,7 @@ export class BrowserContext { return !!this._id; } - newPage(): Promise { + newPage(): Promise> { return this._browser._createPageInContext(this._id); } diff --git a/src/webkit/FrameManager.ts b/src/webkit/FrameManager.ts index 1f6de40b05..45c6e13e2d 100644 --- a/src/webkit/FrameManager.ts +++ b/src/webkit/FrameManager.ts @@ -22,15 +22,20 @@ import { assert, debugError, helper, RegisteredListener } from '../helper'; import * as js from '../javascript'; import * as dom from '../dom'; import * as network from '../network'; -import { TimeoutSettings } from '../TimeoutSettings'; import { TargetSession } from './Connection'; import { Events } from './events'; +import { Events as CommonEvents } from '../events'; import { ExecutionContextDelegate } from './ExecutionContext'; import { NetworkManager, NetworkManagerEvents } from './NetworkManager'; -import { Page } from './Page'; +import { Page, PageDelegate } from '../page'; import { Protocol } from './protocol'; import { DOMWorldDelegate } from './JSHandle'; import * as dialog from '../dialog'; +import { Browser, BrowserContext } from './Browser'; +import { RawMouseImpl, RawKeyboardImpl } from './Input'; +import { WKScreenshotDelegate } from './Screenshotter'; +import * as input from '../input'; +import * as types from '../types'; export const FrameManagerEvents = { FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'), @@ -45,31 +50,43 @@ type FrameData = { id: string, }; -export class FrameManager extends EventEmitter implements frames.FrameDelegate { +export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate { + readonly rawMouse: RawMouseImpl; + readonly rawKeyboard: RawKeyboardImpl; + readonly screenshotterDelegate: WKScreenshotDelegate; _session: TargetSession; - _page: Page; + _page: Page; _networkManager: NetworkManager; - _timeoutSettings: TimeoutSettings; _frames: Map; _contextIdToContext: Map; _isolatedWorlds: Set; _sessionListeners: RegisteredListener[] = []; _mainFrame: frames.Frame; + private _bootstrapScripts: string[] = []; - constructor(page: Page, timeoutSettings: TimeoutSettings) { + constructor(browserContext: BrowserContext) { super(); - this._page = page; + this.rawKeyboard = new RawKeyboardImpl(); + this.rawMouse = new RawMouseImpl(); + this.screenshotterDelegate = new WKScreenshotDelegate(); this._networkManager = new NetworkManager(this); - this._timeoutSettings = timeoutSettings; this._frames = new Map(); this._contextIdToContext = new Map(); this._isolatedWorlds = new Set(); + this._page = new Page(this, browserContext); + this._networkManager.on(NetworkManagerEvents.Request, event => this._page.emit(CommonEvents.Page.Request, event)); + this._networkManager.on(NetworkManagerEvents.Response, event => this._page.emit(CommonEvents.Page.Response, event)); + this._networkManager.on(NetworkManagerEvents.RequestFailed, event => this._page.emit(CommonEvents.Page.RequestFailed, event)); + this._networkManager.on(NetworkManagerEvents.RequestFinished, event => this._page.emit(CommonEvents.Page.RequestFinished, event)); } async initialize(session: TargetSession) { helper.removeEventListeners(this._sessionListeners); this.disconnectFromTarget(); this._session = session; + this.rawKeyboard.setSession(session); + this.rawMouse.setSession(session); + this.screenshotterDelegate.initialize(session); this._addSessionListeners(); this.emit(FrameManagerEvents.TargetSwappedOnNavigation); const [,{frameTree}] = await Promise.all([ @@ -85,12 +102,17 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate { this._session.send('Page.setInterceptFileChooserDialog', { enabled: true }), this._networkManager.initialize(session), ]); - if (this._page._userAgent !== null) - await this._session.send('Page.overrideUserAgent', { value: this._page._userAgent }); - if (this._page._emulatedMediaType !== undefined) - await this._session.send('Page.setEmulatedMedia', { media: this._page._emulatedMediaType || '' }); - if (!this._page._javascriptEnabled) - await this._session.send('Emulation.setJavaScriptEnabled', { enabled: this._page._javascriptEnabled }); + if (this._page._state.userAgent !== null) + await this._session.send('Page.overrideUserAgent', { value: this._page._state.userAgent }); + if (this._page._state.mediaType !== null) + await this._session.send('Page.setEmulatedMedia', { media: this._page._state.mediaType || '' }); + if (this._page._state.javascriptEnabled !== null) + await this._session.send('Emulation.setJavaScriptEnabled', { enabled: this._page._state.javascriptEnabled }); + } + + didClose() { + helper.removeEventListeners(this._sessionListeners); + this.disconnectFromTarget(); } _addSessionListeners() { @@ -137,7 +159,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate { this._handleFrameTree(child); } - page(): Page { + page(): Page { return this._page; } @@ -162,13 +184,14 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate { return; assert(parentFrameId); const parentFrame = this._frames.get(parentFrameId); - const frame = new frames.Frame(this, this._timeoutSettings, parentFrame); + const frame = new frames.Frame(this, this._page._timeoutSettings, parentFrame); const data: FrameData = { id: frameId, }; frame[frameDataSymbol] = data; this._frames.set(frameId, frame); this.emit(FrameManagerEvents.FrameAttached, frame); + this._page.emit(CommonEvents.Page.FrameAttached, frame); return frame; } @@ -189,7 +212,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate { } } else if (isMainFrame) { // Initial frame navigation. - frame = new frames.Frame(this, this._timeoutSettings, null); + frame = new frames.Frame(this, this._page._timeoutSettings, null); const data: FrameData = { id: framePayload.id, }; @@ -215,6 +238,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate { } this.emit(FrameManagerEvents.FrameNavigated, frame); + this._page.emit(CommonEvents.Page.FrameNavigated, frame); } _onFrameNavigatedWithinDocument(frameId: string, url: string) { @@ -224,6 +248,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate { frame._navigated(url, frame.name()); this.emit(FrameManagerEvents.FrameNavigatedWithinDocument, frame); this.emit(FrameManagerEvents.FrameNavigated, frame); + this._page.emit(CommonEvents.Page.FrameNavigated, frame); } _onFrameDetached(frameId: string) { @@ -264,11 +289,12 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate { frame._detach(); this._frames.delete(this._frameData(frame).id); this.emit(FrameManagerEvents.FrameDetached, frame); + this._page.emit(CommonEvents.Page.FrameDetached, frame); } async navigateFrame(frame: frames.Frame, url: string, options: { referer?: string; timeout?: number; waitUntil?: string | Array; } | undefined = {}): Promise { const { - timeout = this._timeoutSettings.navigationTimeout(), + timeout = this._page._timeoutSettings.navigationTimeout(), } = options; const watchDog = new NextNavigationWatchdog(this, frame, timeout); await this._session.send('Page.navigate', {url}); @@ -329,16 +355,83 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate { this._page._onFileChooserOpened(handle); } - async setUserAgent(userAgent: string) { + setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise { + return this._networkManager.setExtraHTTPHeaders(extraHTTPHeaders); + } + + async setUserAgent(userAgent: string): Promise { await this._session.send('Page.overrideUserAgent', { value: userAgent }); } - async setEmulatedMedia(type?: string | null) { - await this._session.send('Page.setEmulatedMedia', { media: type || '' }); + async setJavaScriptEnabled(enabled: boolean): Promise { + await this._session.send('Emulation.setJavaScriptEnabled', { enabled }); } - async setJavaScriptEnabled(enabled: boolean) { - await this._session.send('Emulation.setJavaScriptEnabled', { enabled }); + setBypassCSP(enabled: boolean): Promise { + throw new Error('Not implemented'); + } + + async setViewport(viewport: types.Viewport): Promise { + if (viewport.isMobile || viewport.isLandscape || viewport.hasTouch) + throw new Error('Not implemented'); + const width = viewport.width; + const height = viewport.height; + await this._session.send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: viewport.deviceScaleFactor || 1 }); + } + + async setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise { + if (mediaColorScheme !== null) + throw new Error('Not implemented'); + await this._session.send('Page.setEmulatedMedia', { media: mediaType || '' }); + } + + setCacheEnabled(enabled: boolean): Promise { + return this._networkManager.setCacheEnabled(enabled); + } + + async reload(options?: frames.NavigateOptions): Promise { + const [response] = await Promise.all([ + this._page.waitForNavigation(options), + this._session.send('Page.reload') + ]); + return response; + } + + async _go(command: T, options?: frames.NavigateOptions): Promise { + const [response] = await Promise.all([ + this._page.waitForNavigation(options), + this._session.send(command).then(() => null), + ]).catch(error => { + if (error instanceof Error && error.message.includes(`Protocol error (${command}): Failed to go`)) + return [null]; + throw error; + }); + return response; + } + + goBack(options?: frames.NavigateOptions): Promise { + return this._go('Page.goBack', options); + } + + goForward(options?: frames.NavigateOptions): Promise { + return this._go('Page.goForward', options); + } + + exposeBinding(name: string, bindingFunction: string): Promise { + throw new Error('Not implemented'); + } + + async evaluateOnNewDocument(script: string): Promise { + this._bootstrapScripts.push(script); + const source = this._bootstrapScripts.join(';'); + // TODO(yurys): support process swap on navigation. + await this._session.send('Page.setBootstrapScript', { source }); + } + + async closePage(runBeforeUnload: boolean): Promise { + if (runBeforeUnload) + throw new Error('Not implemented'); + this._page.browser()._closePage(this._page); } } diff --git a/src/webkit/Input.ts b/src/webkit/Input.ts index 9b59121ebb..c364b742c7 100644 --- a/src/webkit/Input.ts +++ b/src/webkit/Input.ts @@ -35,7 +35,7 @@ function toModifiersMask(modifiers: Set): number { export class RawKeyboardImpl implements input.RawKeyboard { private _session: TargetSession; - constructor(session: TargetSession) { + setSession(session: TargetSession) { this._session = session; } @@ -72,7 +72,7 @@ export class RawKeyboardImpl implements input.RawKeyboard { export class RawMouseImpl implements input.RawMouse { private _client: TargetSession; - constructor(client: TargetSession) { + setSession(client: TargetSession) { this._client = client; } diff --git a/src/webkit/JSHandle.ts b/src/webkit/JSHandle.ts index 8e4e643d99..004b94c0ed 100644 --- a/src/webkit/JSHandle.ts +++ b/src/webkit/JSHandle.ts @@ -44,7 +44,7 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate { } isJavascriptEnabled(): boolean { - return this._frameManager.page()._javascriptEnabled; + return !!this._frameManager.page()._state.javascriptEnabled; } isElement(remoteObject: any): boolean { diff --git a/src/webkit/Page.ts b/src/webkit/Page.ts deleted file mode 100644 index 33c36119bb..0000000000 --- a/src/webkit/Page.ts +++ /dev/null @@ -1,410 +0,0 @@ -/** - * Copyright 2017 Google Inc. All rights reserved. - * Modifications copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { EventEmitter } from 'events'; -import * as console from '../console'; -import * as dom from '../dom'; -import * as frames from '../frames'; -import { assert, helper } from '../helper'; -import * as input from '../input'; -import { ClickOptions, mediaColorSchemes, mediaTypes, MultiClickOptions } from '../input'; -import * as js from '../javascript'; -import * as network from '../network'; -import { Screenshotter } from '../screenshotter'; -import { TimeoutSettings } from '../TimeoutSettings'; -import * as types from '../types'; -import { Browser, BrowserContext } from './Browser'; -import { TargetSession } from './Connection'; -import { Events } from './events'; -import { FrameManager, FrameManagerEvents } from './FrameManager'; -import { RawKeyboardImpl, RawMouseImpl } from './Input'; -import { NetworkManagerEvents } from './NetworkManager'; -import { Protocol } from './protocol'; -import { WKScreenshotDelegate } from './Screenshotter'; - -export class Page extends EventEmitter { - private _closed = false; - private _closedCallback: () => void; - private _closedPromise: Promise; - private _disconnected = false; - private _disconnectedCallback: (e: Error) => void; - private _disconnectedPromise: Promise; - _session: TargetSession; - private _browserContext: BrowserContext; - private _keyboard: input.Keyboard; - private _mouse: input.Mouse; - private _timeoutSettings: TimeoutSettings; - private _frameManager: FrameManager; - private _bootstrapScripts: string[] = []; - _javascriptEnabled = true; - _userAgent: string | null = null; - _emulatedMediaType: string | undefined; - private _viewport: types.Viewport | null = null; - _screenshotter: Screenshotter; - private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>(); - - constructor(browserContext: BrowserContext) { - super(); - this._closedPromise = new Promise(f => this._closedCallback = f); - this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f); - this._timeoutSettings = new TimeoutSettings(); - this._frameManager = new FrameManager(this, this._timeoutSettings); - - this._browserContext = browserContext; - - this._frameManager.on(FrameManagerEvents.FrameAttached, event => this.emit(Events.Page.FrameAttached, event)); - this._frameManager.on(FrameManagerEvents.FrameDetached, event => this.emit(Events.Page.FrameDetached, event)); - this._frameManager.on(FrameManagerEvents.FrameNavigated, event => this.emit(Events.Page.FrameNavigated, event)); - - const networkManager = this._frameManager.networkManager(); - networkManager.on(NetworkManagerEvents.Request, event => this.emit(Events.Page.Request, event)); - networkManager.on(NetworkManagerEvents.Response, event => this.emit(Events.Page.Response, event)); - networkManager.on(NetworkManagerEvents.RequestFailed, event => this.emit(Events.Page.RequestFailed, event)); - networkManager.on(NetworkManagerEvents.RequestFinished, event => this.emit(Events.Page.RequestFinished, event)); - } - - _didClose() { - assert(!this._closed, 'Page closed twice'); - this._closed = true; - this.emit(Events.Page.Close); - this._closedCallback(); - } - - _didDisconnect() { - assert(!this._disconnected, 'Page disconnected twice'); - this._disconnected = true; - this._frameManager.disconnectFromTarget(); - this._disconnectedCallback(new Error('Target closed')); - } - - _initialize(session: TargetSession) { - this._session = session; - this._keyboard = new input.Keyboard(new RawKeyboardImpl(session)); - this._mouse = new input.Mouse(new RawMouseImpl(session), this._keyboard); - this._screenshotter = new Screenshotter(this, new WKScreenshotDelegate(session), this._browserContext.browser()); - return this._frameManager.initialize(session); - } - - browser(): Browser { - return this._browserContext.browser(); - } - - browserContext(): BrowserContext { - return this._browserContext; - } - - _onTargetCrashed() { - this.emit('error', new Error('Page crashed!')); - } - - _addConsoleMessage(type: string, args: js.JSHandle[], location: console.ConsoleMessageLocation, text?: string) { - if (!this.listenerCount(Events.Page.Console)) { - args.forEach(arg => arg.dispose()); - return; - } - this.emit(Events.Page.Console, new console.ConsoleMessage(type, text, args, location)); - } - - mainFrame(): frames.Frame { - return this._frameManager.mainFrame(); - } - - get keyboard(): input.Keyboard { - return this._keyboard; - } - - frames(): frames.Frame[] { - return this._frameManager.frames(); - } - - setDefaultNavigationTimeout(timeout: number) { - this._timeoutSettings.setDefaultNavigationTimeout(timeout); - } - - setDefaultTimeout(timeout: number) { - this._timeoutSettings.setDefaultTimeout(timeout); - } - - async $(selector: string | types.Selector): Promise { - return this.mainFrame().$(selector); - } - - evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => { - const context = await this.mainFrame().executionContext(); - return context.evaluateHandle(pageFunction, ...args as any); - } - - $eval: types.$Eval = (selector, pageFunction, ...args) => { - return this.mainFrame().$eval(selector, pageFunction, ...args as any); - } - - $$eval: types.$$Eval = (selector, pageFunction, ...args) => { - return this.mainFrame().$$eval(selector, pageFunction, ...args as any); - } - - async $$(selector: string | types.Selector): Promise { - return this.mainFrame().$$(selector); - } - - async $x(expression: string): Promise { - return this.mainFrame().$x(expression); - } - - async addScriptTag(options: { url?: string; path?: string; content?: string; type?: string; }): Promise { - return this.mainFrame().addScriptTag(options); - } - - async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise { - return this.mainFrame().addStyleTag(options); - } - - async setExtraHTTPHeaders(headers: { [s: string]: string; }) { - return this._frameManager.networkManager().setExtraHTTPHeaders(headers); - } - - async setUserAgent(userAgent: string) { - this._userAgent = userAgent; - this._frameManager.setUserAgent(userAgent); - } - - url(): string { - return this.mainFrame().url(); - } - - async content(): Promise { - return await this._frameManager.mainFrame().content(); - } - - async setContent(html: string, options: { timeout?: number; waitUntil?: string | string[]; } = {}) { - await this._frameManager.mainFrame().setContent(html, options); - } - - async goto(url: string, options: { referer?: string; timeout?: number; waitUntil?: string | string[]; } = {}): Promise { - return await this._frameManager.mainFrame().goto(url, options); - } - - async reload(): Promise { - const [response] = await Promise.all([ - this.waitForNavigation(), - this._session.send('Page.reload') - ]); - return response; - } - - async goBack(): Promise { - return await this._go('Page.goBack'); - } - - async goForward(): Promise { - return await this._go('Page.goForward'); - } - - async _go(command: T): Promise { - const [response] = await Promise.all([ - this.waitForNavigation(), - this._session.send(command).then(() => null), - ]).catch(error => { - if (error instanceof Error && error.message.includes(`Protocol error (${command}): Failed to go`)) - return [null]; - throw error; - }); - return response; - } - - async waitForNavigation(): Promise { - return await this._frameManager.mainFrame().waitForNavigation(); - } - - async waitForRequest(urlOrPredicate: (string | Function), options: { timeout?: number; } = {}): Promise { - const { - timeout = this._timeoutSettings.timeout(), - } = options; - return helper.waitForEvent(this._frameManager.networkManager(), NetworkManagerEvents.Request, request => { - if (helper.isString(urlOrPredicate)) - return (urlOrPredicate === request.url()); - if (typeof urlOrPredicate === 'function') - return !!(urlOrPredicate(request)); - return false; - }, timeout, this._disconnectedPromise); - } - - async waitForResponse(urlOrPredicate: (string | Function), options: { timeout?: number; } = {}): Promise { - const { - timeout = this._timeoutSettings.timeout(), - } = options; - return helper.waitForEvent(this._frameManager.networkManager(), NetworkManagerEvents.Response, response => { - if (helper.isString(urlOrPredicate)) - return (urlOrPredicate === response.url()); - if (typeof urlOrPredicate === 'function') - return !!(urlOrPredicate(response)); - return false; - }, timeout, this._disconnectedPromise); - } - - async emulate(options: { viewport: types.Viewport; userAgent: string; }) { - await Promise.all([ - this.setViewport(options.viewport), - this.setUserAgent(options.userAgent) - ]); - } - - async emulateMedia(options: { - type?: input.MediaType | null, - colorScheme?: input.MediaColorScheme | null }) { - assert(!options.type || mediaTypes.has(options.type), 'Unsupported media type: ' + options.type); - assert(!options.colorScheme || mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme); - assert(!options.colorScheme, 'Media feature emulation is not supported'); - this._emulatedMediaType = typeof options.type === 'undefined' ? this._emulatedMediaType : options.type; - this._frameManager.setEmulatedMedia(this._emulatedMediaType); - } - - async setViewport(viewport: types.Viewport) { - this._viewport = viewport; - const width = viewport.width; - const height = viewport.height; - await this._session.send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: viewport.deviceScaleFactor || 1 }); - } - - viewport(): types.Viewport | null { - return this._viewport; - } - - evaluate: types.Evaluate = (pageFunction, ...args) => { - return this._frameManager.mainFrame().evaluate(pageFunction, ...args as any); - } - - async evaluateOnNewDocument(pageFunction: Function | string, ...args: Array) { - const script = helper.evaluationString(pageFunction, ...args); - this._bootstrapScripts.push(script); - const source = this._bootstrapScripts.join(';'); - // TODO(yurys): support process swap on navigation. - await this._session.send('Page.setBootstrapScript', { source }); - } - - setJavaScriptEnabled(enabled: boolean) { - if (this._javascriptEnabled === enabled) - return; - this._javascriptEnabled = enabled; - return this._frameManager.setJavaScriptEnabled(enabled); - } - - async setCacheEnabled(enabled: boolean = true) { - await this._frameManager.networkManager().setCacheEnabled(enabled); - } - - screenshot(options?: types.ScreenshotOptions): Promise { - return this._screenshotter.screenshotPage(options); - } - - async title(): Promise { - return this.mainFrame().title(); - } - - async close() { - assert(!this._disconnected, 'Protocol error: Connection closed. Most likely the page has been closed.'); - this.browser()._closePage(this); - await this._closedPromise; - } - - isClosed(): boolean { - return this._closed; - } - - async waitForFileChooser(options: { timeout?: number; } = {}): Promise { - const { - timeout = this._timeoutSettings.timeout(), - } = options; - let callback; - const promise = new Promise(x => callback = x); - this._fileChooserInterceptors.add(callback); - return helper.waitWithTimeout(promise, 'waiting for file chooser', timeout).catch(e => { - this._fileChooserInterceptors.delete(callback); - throw e; - }); - } - - async _onFileChooserOpened(handle: dom.ElementHandle) { - if (!this._fileChooserInterceptors.size) { - await handle.dispose(); - return; - } - const interceptors = Array.from(this._fileChooserInterceptors); - this._fileChooserInterceptors.clear(); - const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple); - const fileChooser = { element: handle, multiple }; - for (const interceptor of interceptors) - interceptor.call(null, fileChooser); - this.emit(Events.Page.FileChooser, fileChooser); - } - - get mouse(): input.Mouse { - return this._mouse; - } - - click(selector: string | types.Selector, options?: ClickOptions) { - return this.mainFrame().click(selector, options); - } - - dblclick(selector: string | types.Selector, options?: MultiClickOptions) { - return this.mainFrame().dblclick(selector, options); - } - - tripleclick(selector: string | types.Selector, options?: MultiClickOptions) { - return this.mainFrame().tripleclick(selector, options); - } - - hover(selector: string | types.Selector) { - return this.mainFrame().hover(selector); - } - - fill(selector: string | types.Selector, value: string) { - return this.mainFrame().fill(selector, value); - } - - focus(selector: string | types.Selector) { - return this.mainFrame().focus(selector); - } - - select(selector: string | types.Selector, ...values: string[]): Promise { - return this.mainFrame().select(selector, ...values); - } - - type(selector: string | types.Selector, text: string, options?: { delay: (number | undefined); }) { - return this.mainFrame().type(selector, text, options); - } - - waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: { visible?: boolean; hidden?: boolean; timeout?: number; polling?: string | number; }, ...args: any[]): Promise { - return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args); - } - - waitForSelector(selector: string | types.Selector, options?: types.TimeoutOptions): Promise { - return this.mainFrame().waitForSelector(selector, options); - } - - waitForXPath(xpath: string, options?: types.TimeoutOptions): Promise { - return this.mainFrame().waitForXPath(xpath, options); - } - - waitForFunction(pageFunction: Function | string, options?: types.WaitForFunctionOptions, ...args: any[]): Promise { - return this.mainFrame().waitForFunction(pageFunction, options, ...args); - } -} - -type FileChooser = { - element: dom.ElementHandle, - multiple: boolean -}; diff --git a/src/webkit/Screenshotter.ts b/src/webkit/Screenshotter.ts index 1030fefb7f..45ead0e207 100644 --- a/src/webkit/Screenshotter.ts +++ b/src/webkit/Screenshotter.ts @@ -11,7 +11,7 @@ import { TargetSession } from './Connection'; export class WKScreenshotDelegate implements ScreenshotterDelegate { private _session: TargetSession; - constructor(session: TargetSession) { + initialize(session: TargetSession) { this._session = session; } diff --git a/src/webkit/Target.ts b/src/webkit/Target.ts index fcd7037b78..d52b315c60 100644 --- a/src/webkit/Target.ts +++ b/src/webkit/Target.ts @@ -15,10 +15,11 @@ * limitations under the License. */ -import { BrowserContext } from './Browser'; -import { Page } from './Page'; +import { BrowserContext, Browser } from './Browser'; +import { Page } from '../page'; import { Protocol } from './protocol'; import { isSwappedOutError, TargetSession, TargetSessionEvents } from './Connection'; +import { FrameManager } from './FrameManager'; const targetSymbol = Symbol('target'); @@ -27,10 +28,10 @@ export class Target { readonly _targetId: string; readonly _type: 'page' | 'service-worker' | 'worker'; private readonly _session: TargetSession; - private _pagePromise: Promise | null = null; - private _page: Page | null = null; + private _pagePromise: Promise> | null = null; + private _page: Page | null = null; - static fromPage(page: Page): Target { + static fromPage(page: Page): Target { return (page as any)[targetSymbol]; } @@ -68,7 +69,7 @@ export class Target { if (this._page) this._page._didDisconnect(); }); - await this._page._initialize(this._session).catch(e => { + await (this._page._delegate as FrameManager).initialize(this._session).catch(e => { // Swallow initialization errors due to newer target swap in, // since we will reinitialize again. if (!isSwappedOutError(e)) @@ -76,12 +77,13 @@ export class Target { }); } - async page(): Promise { + async page(): Promise> { if (this._type === 'page' && !this._pagePromise) { const browser = this._browserContext.browser(); // Reference local page variable as _page may be // cleared on swap. - const page = new Page(this._browserContext); + const frameManager = new FrameManager(this._browserContext); + const page = frameManager._page; this._page = page; this._pagePromise = new Promise(async f => { await this._adoptPage(); diff --git a/src/webkit/api.ts b/src/webkit/api.ts index 60c5522886..0f014a9b54 100644 --- a/src/webkit/api.ts +++ b/src/webkit/api.ts @@ -9,7 +9,7 @@ export { ElementHandle } from '../dom'; export { Frame } from '../frames'; export { Mouse, Keyboard } from '../input'; export { Request, Response } from '../network'; -export { Page } from './Page'; +export { Page } from '../page'; export { Playwright } from './Playwright'; export { Dialog } from '../dialog'; export { ConsoleMessage } from '../console'; diff --git a/test/emulation.spec.js b/test/emulation.spec.js index 3c33e44a4a..c1bdf51bf2 100644 --- a/test/emulation.spec.js +++ b/test/emulation.spec.js @@ -86,7 +86,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME expect(await page.evaluate(() => window.innerWidth)).toBe(375); expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone'); }); - it('should support clicking', async({page, server}) => { + it.skip(WEBKIT)('should support clicking', async({page, server}) => { await page.emulate(iPhone); await page.goto(server.PREFIX + '/input/button.html'); const button = await page.$('button');