diff --git a/docs/api.md b/docs/api.md index 7cf55959c6900..29f75fd06fcde 100644 --- a/docs/api.md +++ b/docs/api.md @@ -78,8 +78,6 @@ * [event: 'requestfailed'](#event-requestfailed) * [event: 'requestfinished'](#event-requestfinished) * [event: 'response'](#event-response) - * [event: 'workercreated'](#event-workercreated) - * [event: 'workerdestroyed'](#event-workerdestroyed) * [page.$(selector)](#pageselector) * [page.$$(selector)](#pageselector-1) * [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args) @@ -155,12 +153,16 @@ * [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options) * [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) * [page.waitForXPath(xpath[, options])](#pagewaitforxpathxpath-options) - * [page.workers()](#pageworkers) + * [page.workers](#pageworkers) - [class: Worker](#class-worker) * [worker.evaluate(pageFunction[, ...args])](#workerevaluatepagefunction-args) * [worker.evaluateHandle(pageFunction[, ...args])](#workerevaluatehandlepagefunction-args) * [worker.executionContext()](#workerexecutioncontext) * [worker.url()](#workerurl) +- [class: Workers](#class-workers) + * [event: 'workercreated'](#event-workercreated) + * [event: 'workerdestroyed'](#event-workerdestroyed) + * [workers.list()](#workerslist) - [class: Accessibility](#class-accessibility) * [accessibility.snapshot([options])](#accessibilitysnapshotoptions) - [class: Keyboard](#class-keyboard) @@ -1065,16 +1067,6 @@ Emitted when a request finishes successfully. Emitted when a [response] is received. -#### event: 'workercreated' -- <[Worker]> - -Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is spawned by the page. - -#### event: 'workerdestroyed' -- <[Worker]> - -Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is terminated. - #### page.$(selector) - `selector` <[string]> A [selector] to query page for - returns: <[Promise]> @@ -2177,9 +2169,8 @@ const playwright = require('playwright'); ``` Shortcut for [page.mainFrame().waitForXPath(xpath[, options])](#framewaitforxpathxpath-options). -#### page.workers() -- returns: <[Array]<[Worker]>> -This method returns all of the dedicated [WebWorkers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) associated with the page. +#### page.workers +- returns: <[Workers]> > **NOTE** This does not contain ServiceWorkers @@ -2225,6 +2216,26 @@ Shortcut for [(await worker.executionContext()).evaluateHandle(pageFunction, ... #### worker.url() - returns: <[string]> +### class: Workers + +The Workers class represents a [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) collection. + +#### event: 'workercreated' +- <[Worker]> + +Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is spawned by the page. + +#### event: 'workerdestroyed' +- <[Worker]> + +Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is terminated. + +#### workers.list() +- returns: <[Array]<[Worker]>> +This method returns all of the dedicated [WebWorkers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) associated with the page. + +> **NOTE** This does not contain ServiceWorkers + ### class: Accessibility The Accessibility class provides methods for inspecting Chromium's accessibility tree. The accessibility tree is used by assistive technology such as [screen readers](https://en.wikipedia.org/wiki/Screen_reader) or [switches](https://en.wikipedia.org/wiki/Switch_access). @@ -2495,22 +2506,22 @@ Dispatches a `mouseup` event. > **NOTE** Generating a pdf is currently only supported in Chrome headless. -`page.pdf()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call [page.emulateMedia('screen')](#pageemulatemediamediatype) before calling `page.pdf()`: +`pdf.generate()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call [page.emulateMedia('screen')](#pageemulatemediamediatype) before calling `pdf.generate()`: -> **NOTE** By default, `page.pdf()` generates a pdf with modified colors for printing. Use the [`-webkit-print-color-adjust`](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-print-color-adjust) property to force rendering of exact colors. +> **NOTE** By default, `pdf.generate()` generates a pdf with modified colors for printing. Use the [`-webkit-print-color-adjust`](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-print-color-adjust) property to force rendering of exact colors. ```js // Generates a PDF with 'screen' media type. await page.emulateMedia('screen'); -await page.pdf({path: 'page.pdf'}); +await page.pdf.generate({path: 'page.pdf'}); ``` The `width`, `height`, and `margin` options accept values labeled with units. Unlabeled values are treated as pixels. A few examples: -- `page.pdf({width: 100})` - prints with width set to 100 pixels -- `page.pdf({width: '100px'})` - prints with width set to 100 pixels -- `page.pdf({width: '10cm'})` - prints with width set to 10 centimeters. +- `page.pdf.generate({width: 100})` - prints with width set to 100 pixels +- `page.pdf.generate({width: '100px'})` - prints with width set to 100 pixels +- `page.pdf.generate({width: '10cm'})` - prints with width set to 10 centimeters. All possible units are: - `px` - pixel diff --git a/src/Events.ts b/src/Events.ts index 580565de1c788..d028e660cd36d 100644 --- a/src/Events.ts +++ b/src/Events.ts @@ -35,8 +35,6 @@ export const Events = { Load: 'load', Metrics: 'metrics', Popup: 'popup', - WorkerCreated: 'workercreated', - WorkerDestroyed: 'workerdestroyed', }, Browser: { @@ -52,4 +50,8 @@ export const Events = { TargetChanged: 'targetchanged', }, + Workers: { + WorkerCreated: 'workercreated', + WorkerDestroyed: 'workerdestroyed', + } }; diff --git a/src/api.ts b/src/api.ts index 4eb787f4e0aac..1c318b2d6f771 100644 --- a/src/api.ts +++ b/src/api.ts @@ -41,7 +41,8 @@ export = { TimeoutError: require('./Errors').TimeoutError, Touchscreen: require('./chromium/Input').Touchscreen, Tracing: require('./chromium/features/tracing').Tracing, - Worker: require('./chromium/Worker').Worker, + Worker: require('./chromium/features/workers').Worker, + Workers: require('./chromium/features/workers').Workers, }, Firefox: { Accessibility: require('./firefox/Accessibility').Accessibility, diff --git a/src/chromium/Page.ts b/src/chromium/Page.ts index 0eef7557161bb..9e1af37636f16 100644 --- a/src/chromium/Page.ts +++ b/src/chromium/Page.ts @@ -25,7 +25,7 @@ import { TimeoutSettings } from '../TimeoutSettings'; import { Accessibility } from './features/accessibility'; import { Browser } from './Browser'; import { BrowserContext } from './BrowserContext'; -import { CDPSession, CDPSessionEvents, Connection } from './Connection'; +import { CDPSession, CDPSessionEvents } from './Connection'; import { Coverage } from './features/coverage'; import { Dialog, DialogType } from './Dialog'; import { EmulationManager } from './EmulationManager'; @@ -40,7 +40,7 @@ import { getExceptionMessage, releaseObject, valueFromRemoteObject } from './pro import { Target } from './Target'; import { TaskQueue } from './TaskQueue'; import { Tracing } from './features/tracing'; -import { Worker } from './Worker'; +import { Workers } from './features/workers'; const writeFileAsync = helper.promisify(fs.writeFile); @@ -66,12 +66,12 @@ export class Page extends EventEmitter { readonly accessibility: Accessibility; readonly coverage: Coverage; readonly pdf: PDF; + readonly workers: Workers; readonly tracing: Tracing; private _pageBindings = new Map(); _javascriptEnabled = true; private _viewport: Viewport | null = null; private _screenshotTaskQueue: TaskQueue; - private _workers = new Map(); private _fileChooserInterceptionIsDisabled = false; private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>(); private _disconnectPromise: Promise | undefined; @@ -98,6 +98,7 @@ export class Page extends EventEmitter { this.tracing = new Tracing(client); this.coverage = new Coverage(client); this.pdf = new PDF(client); + this.workers = new Workers(client, this._addConsoleMessage.bind(this), this._handleException.bind(this)); this._screenshotTaskQueue = screenshotTaskQueue; @@ -109,17 +110,6 @@ export class Page extends EventEmitter { }).catch(debugError); return; } - const session = Connection.fromSession(client).session(event.sessionId); - const worker = new Worker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this)); - this._workers.set(event.sessionId, worker); - this.emit(Events.Page.WorkerCreated, worker); - }); - client.on('Target.detachedFromTarget', event => { - const worker = this._workers.get(event.sessionId); - if (!worker) - return; - this.emit(Events.Page.WorkerDestroyed, worker); - this._workers.delete(event.sessionId); }); this._frameManager.on(FrameManagerEvents.FrameAttached, event => this.emit(Events.Page.FrameAttached, event)); @@ -238,10 +228,6 @@ export class Page extends EventEmitter { return this._frameManager.frames(); } - workers(): Worker[] { - return Array.from(this._workers.values()); - } - async setRequestInterception(value: boolean) { return this._frameManager.networkManager().setRequestInterception(value); } diff --git a/src/chromium/Target.ts b/src/chromium/Target.ts index d3810651cb6f1..6d12659b1134b 100644 --- a/src/chromium/Target.ts +++ b/src/chromium/Target.ts @@ -21,7 +21,6 @@ import { BrowserContext } from './BrowserContext'; import { CDPSession } from './Connection'; import { Page, Viewport } from './Page'; import { TaskQueue } from './TaskQueue'; -import { Worker } from './Worker'; import { Protocol } from './protocol'; export class Target { @@ -33,7 +32,6 @@ export class Target { private _defaultViewport: Viewport; private _screenshotTaskQueue: TaskQueue; private _pagePromise: Promise | null = null; - private _workerPromise: Promise | null = null; _initializedPromise: Promise; _initializedCallback: (value?: unknown) => void; _isClosedPromise: Promise; @@ -85,17 +83,6 @@ export class Target { return this._pagePromise; } - async worker(): Promise { - if (this._targetInfo.type !== 'service_worker' && this._targetInfo.type !== 'shared_worker') - return null; - if (!this._workerPromise) { - // TODO(einbinder): Make workers send their console logs. - this._workerPromise = this._sessionFactory() - .then(client => new Worker(client, this._targetInfo.url, () => {} /* consoleAPICalled */, () => {} /* exceptionThrown */)); - } - return this._workerPromise; - } - url(): string { return this._targetInfo.url; } diff --git a/test/worker.spec.js b/src/chromium/features/workers.spec.js similarity index 82% rename from test/worker.spec.js rename to src/chromium/features/workers.spec.js index f44796dd90461..5cc6c0e469f1a 100644 --- a/test/worker.spec.js +++ b/src/chromium/features/workers.spec.js @@ -1,4 +1,4 @@ -const utils = require('./utils'); +const utils = require('../../../test/utils'); const {waitEvent} = utils; module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { @@ -9,22 +9,22 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { describe.skip(FFOX || WEBKIT)('Workers', function() { it('Page.workers', async function({page, server}) { await Promise.all([ - new Promise(x => page.once('workercreated', x)), + new Promise(x => page.workers.once('workercreated', x)), page.goto(server.PREFIX + '/worker/worker.html')]); - const worker = page.workers()[0]; + const worker = page.workers.list()[0]; expect(worker.url()).toContain('worker.js'); - expect(await worker.evaluate(() => self.workerFunction())).toBe('worker function result'); + expect(await worker.evaluate(() => self['workerFunction']())).toBe('worker function result'); await page.goto(server.EMPTY_PAGE); - expect(page.workers().length).toBe(0); + expect(page.workers.list().length).toBe(0); }); it('should emit created and destroyed events', async function({page}) { - const workerCreatedPromise = new Promise(x => page.once('workercreated', x)); + const workerCreatedPromise = new Promise(x => page.workers.once('workercreated', x)); const workerObj = await page.evaluateHandle(() => new Worker('data:text/javascript,1')); const worker = await workerCreatedPromise; const workerThisObj = await worker.evaluateHandle(() => this); - const workerDestroyedPromise = new Promise(x => page.once('workerdestroyed', x)); + const workerDestroyedPromise = new Promise(x => page.workers.once('workerdestroyed', x)); await page.evaluate(workerObj => workerObj.terminate(), workerObj); expect(await workerDestroyedPromise).toBe(worker); const error = await workerThisObj.getProperty('self').catch(error => error); @@ -51,7 +51,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { expect(await (await log.args()[3].getProperty('origin')).jsonValue()).toBe('null'); }); it('should have an execution context', async function({page}) { - const workerCreatedPromise = new Promise(x => page.once('workercreated', x)); + const workerCreatedPromise = new Promise(x => page.workers.once('workercreated', x)); await page.evaluate(() => new Worker(`data:text/javascript,console.log(1)`)); const worker = await workerCreatedPromise; expect(await (await worker.executionContext()).evaluate('1+1')).toBe(2); diff --git a/src/chromium/Worker.ts b/src/chromium/features/workers.ts similarity index 54% rename from src/chromium/Worker.ts rename to src/chromium/features/workers.ts index 4030a1180e26c..654344707b23c 100644 --- a/src/chromium/Worker.ts +++ b/src/chromium/features/workers.ts @@ -15,11 +15,43 @@ * limitations under the License. */ import { EventEmitter } from 'events'; -import { CDPSession } from './Connection'; -import { ExecutionContext } from './ExecutionContext'; -import { debugError } from '../helper'; -import { JSHandle } from './JSHandle'; -import { Protocol } from './protocol'; +import { CDPSession, Connection } from '../Connection'; +import { ExecutionContext } from '../ExecutionContext'; +import { debugError } from '../../helper'; +import { JSHandle } from '../JSHandle'; +import { Protocol } from '../protocol'; +import { Events } from '../../Events'; + +type AddToConsoleCallback = (type: string, args: JSHandle[], stackTrace: Protocol.Runtime.StackTrace | undefined) => void; +type HandleExceptionCallback = (exceptionDetails: Protocol.Runtime.ExceptionDetails) => void; + +export class Workers extends EventEmitter { + private _workers = new Map(); + + constructor(client: CDPSession, addToConsole: AddToConsoleCallback, handleException: HandleExceptionCallback) { + super(); + + client.on('Target.attachedToTarget', event => { + if (event.targetInfo.type !== 'worker') + return; + const session = Connection.fromSession(client).session(event.sessionId); + const worker = new Worker(session, event.targetInfo.url, addToConsole, handleException); + this._workers.set(event.sessionId, worker); + this.emit(Events.Workers.WorkerCreated, worker); + }); + client.on('Target.detachedFromTarget', event => { + const worker = this._workers.get(event.sessionId); + if (!worker) + return; + this.emit(Events.Workers.WorkerDestroyed, worker); + this._workers.delete(event.sessionId); + }); + } + + list(): Worker[] { + return Array.from(this._workers.values()); + } +} export class Worker extends EventEmitter { private _client: CDPSession; @@ -27,7 +59,7 @@ export class Worker extends EventEmitter { private _executionContextPromise: Promise; private _executionContextCallback: (value?: ExecutionContext) => void; - constructor(client: CDPSession, url: string, consoleAPICalled: (arg0: string, arg1: JSHandle[], arg2: Protocol.Runtime.StackTrace | undefined) => void, exceptionThrown: (arg0: Protocol.Runtime.ExceptionDetails) => void) { + constructor(client: CDPSession, url: string, addToConsole: AddToConsoleCallback, handleException: HandleExceptionCallback) { super(); this._client = client; this._url = url; @@ -41,8 +73,8 @@ export class Worker extends EventEmitter { // This might fail if the target is closed before we recieve all execution contexts. this._client.send('Runtime.enable', {}).catch(debugError); - this._client.on('Runtime.consoleAPICalled', event => consoleAPICalled(event.type, event.args.map(jsHandleFactory), event.stackTrace)); - this._client.on('Runtime.exceptionThrown', exception => exceptionThrown(exception.exceptionDetails)); + this._client.on('Runtime.consoleAPICalled', event => addToConsole(event.type, event.args.map(jsHandleFactory), event.stackTrace)); + this._client.on('Runtime.exceptionThrown', exception => handleException(exception.exceptionDetails)); } url(): string { diff --git a/test/playwright.spec.js b/test/playwright.spec.js index e8e2bb158752f..e582c4e12907e 100644 --- a/test/playwright.spec.js +++ b/test/playwright.spec.js @@ -154,7 +154,7 @@ module.exports.addTests = ({testRunner, product, playwrightPath}) => { require('./target.spec.js').addTests(testOptions); require('./touchscreen.spec.js').addTests(testOptions); require('./waittask.spec.js').addTests(testOptions); - require('./worker.spec.js').addTests(testOptions); + require('../src/chromium/features/workers.spec.js').addTests(testOptions); if (CHROME) { require('./CDPSession.spec.js').addTests(testOptions); require('../src/chromium/features/coverage.spec.js').addTests(testOptions);