api(chromium): add ChromiumBrowserContext.serviceWorkers() (#1416)
This commit is contained in:
parent
c6696746dc
commit
3ed9970b33
|
|
@ -3810,6 +3810,7 @@ const backgroundPage = await backroundPageTarget.page();
|
|||
- [event: 'serviceworker'](#event-serviceworker)
|
||||
- [chromiumBrowserContext.backgroundPages()](#chromiumbrowsercontextbackgroundpages)
|
||||
- [chromiumBrowserContext.newCDPSession(page)](#chromiumbrowsercontextnewcdpsessionpage)
|
||||
- [chromiumBrowserContext.serviceWorkers()](#chromiumbrowsercontextserviceworkers)
|
||||
<!-- GEN:stop -->
|
||||
<!-- GEN:toc-extends-BrowserContext -->
|
||||
- [event: 'close'](#event-close)
|
||||
|
|
@ -3853,6 +3854,9 @@ Emitted when new service worker is created in the context.
|
|||
- `page` <[Page]> Page to create new session for.
|
||||
- returns: <[Promise]<[CDPSession]>> Promise that resolves to the newly created session.
|
||||
|
||||
#### chromiumBrowserContext.serviceWorkers()
|
||||
- returns: <[Array]<[Worker]>> All existing service workers in the context.
|
||||
|
||||
### class: ChromiumCoverage
|
||||
|
||||
Coverage gathers information about parts of JavaScript and CSS that were used by the page.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, Bro
|
|||
import { Events as CommonEvents } from '../events';
|
||||
import { assert, debugError, helper } from '../helper';
|
||||
import * as network from '../network';
|
||||
import { Page, PageBinding, PageEvent } from '../page';
|
||||
import { Page, PageBinding, PageEvent, Worker } from '../page';
|
||||
import * as platform from '../platform';
|
||||
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||
import * as types from '../types';
|
||||
|
|
@ -53,6 +53,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
|||
const promises = [
|
||||
session.send('Target.setDiscoverTargets', { discover: true }),
|
||||
session.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }),
|
||||
session.send('Target.setDiscoverTargets', { discover: false }),
|
||||
];
|
||||
const existingPageAttachPromises: Promise<any>[] = [];
|
||||
if (isPersistent) {
|
||||
|
|
@ -82,9 +83,8 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
|||
context._browserClosed();
|
||||
this.emit(CommonEvents.Browser.Disconnected);
|
||||
});
|
||||
this._session.on('Target.targetCreated', this._targetCreated.bind(this));
|
||||
this._session.on('Target.targetDestroyed', this._targetDestroyed.bind(this));
|
||||
this._session.on('Target.attachedToTarget', this._onAttachedToTarget.bind(this));
|
||||
this._session.on('Target.detachedFromTarget', this._onDetachedFromTarget.bind(this));
|
||||
this._firstPagePromise = new Promise(f => this._firstPageCallback = f);
|
||||
}
|
||||
|
||||
|
|
@ -116,8 +116,8 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
|||
|
||||
_onAttachedToTarget({targetInfo, sessionId, waitingForDebugger}: Protocol.Target.attachedToTargetPayload) {
|
||||
const session = this._connection.session(sessionId)!;
|
||||
if (!CRTarget.isPageType(targetInfo.type)) {
|
||||
assert(targetInfo.type === 'service_worker' || targetInfo.type === 'browser' || targetInfo.type === 'other');
|
||||
if (!CRTarget.isPageType(targetInfo.type) && targetInfo.type !== 'service_worker') {
|
||||
assert(targetInfo.type === 'browser' || targetInfo.type === 'other');
|
||||
if (waitingForDebugger) {
|
||||
// Ideally, detaching should resume any target, but there is a bug in the backend.
|
||||
session.send('Runtime.runIfWaitingForDebugger').catch(debugError).then(() => {
|
||||
|
|
@ -128,34 +128,32 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
|||
}
|
||||
const { context, target } = this._createTarget(targetInfo, session);
|
||||
|
||||
if (!CRTarget.isPageType(targetInfo.type))
|
||||
if (CRTarget.isPageType(targetInfo.type)) {
|
||||
const pageEvent = new PageEvent(context, target.pageOrError());
|
||||
target.pageOrError().then(async () => {
|
||||
if (targetInfo.type === 'page') {
|
||||
this._firstPageCallback();
|
||||
context.emit(CommonEvents.BrowserContext.Page, pageEvent);
|
||||
const opener = target.opener();
|
||||
if (!opener)
|
||||
return;
|
||||
const openerPage = await opener.pageOrError();
|
||||
if (openerPage instanceof Page && !openerPage.isClosed())
|
||||
openerPage.emit(CommonEvents.Page.Popup, pageEvent);
|
||||
} else if (targetInfo.type === 'background_page') {
|
||||
context.emit(Events.CRBrowserContext.BackgroundPage, pageEvent);
|
||||
}
|
||||
});
|
||||
return;
|
||||
const pageEvent = new PageEvent(context, target.pageOrError());
|
||||
target.pageOrError().then(async () => {
|
||||
if (targetInfo.type === 'page') {
|
||||
this._firstPageCallback();
|
||||
context.emit(CommonEvents.BrowserContext.Page, pageEvent);
|
||||
const opener = target.opener();
|
||||
if (!opener)
|
||||
return;
|
||||
const openerPage = await opener.pageOrError();
|
||||
if (openerPage instanceof Page && !openerPage.isClosed())
|
||||
openerPage.emit(CommonEvents.Page.Popup, pageEvent);
|
||||
} else if (targetInfo.type === 'background_page') {
|
||||
context.emit(Events.CRBrowserContext.BackgroundPage, pageEvent);
|
||||
}
|
||||
}
|
||||
assert(targetInfo.type === 'service_worker');
|
||||
target.serviceWorkerOrError().then(workerOrError => {
|
||||
if (workerOrError instanceof Worker)
|
||||
context.emit(Events.CRBrowserContext.ServiceWorker, workerOrError);
|
||||
});
|
||||
}
|
||||
|
||||
async _targetCreated({targetInfo}: Protocol.Target.targetCreatedPayload) {
|
||||
if (targetInfo.type !== 'service_worker')
|
||||
return;
|
||||
const { context, target } = this._createTarget(targetInfo, null);
|
||||
const serviceWorker = await target.serviceWorker();
|
||||
context.emit(Events.CRBrowserContext.ServiceWorker, serviceWorker);
|
||||
}
|
||||
|
||||
private _createTarget(targetInfo: Protocol.Target.TargetInfo, session: CRSession | null) {
|
||||
private _createTarget(targetInfo: Protocol.Target.TargetInfo, session: CRSession) {
|
||||
const {browserContextId} = targetInfo;
|
||||
const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId)! : this._defaultContext;
|
||||
let hasInitialAboutBlank = false;
|
||||
|
|
@ -169,17 +167,17 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
|||
hasInitialAboutBlank = true;
|
||||
}
|
||||
}
|
||||
const target = new CRTarget(this, targetInfo, context, session, () => this._connection.createSession(targetInfo), hasInitialAboutBlank);
|
||||
const target = new CRTarget(this, targetInfo, context, session, hasInitialAboutBlank);
|
||||
assert(!this._targets.has(targetInfo.targetId), 'Target should not exist before targetCreated');
|
||||
this._targets.set(targetInfo.targetId, target);
|
||||
return { context, target };
|
||||
}
|
||||
|
||||
async _targetDestroyed(event: { targetId: string; }) {
|
||||
const target = this._targets.get(event.targetId)!;
|
||||
_onDetachedFromTarget({targetId}: Protocol.Target.detachFromTargetParameters) {
|
||||
const target = this._targets.get(targetId!)!;
|
||||
if (!target)
|
||||
return;
|
||||
this._targets.delete(event.targetId);
|
||||
this._targets.delete(targetId!);
|
||||
target._didClose();
|
||||
}
|
||||
|
||||
|
|
@ -417,6 +415,10 @@ export class CRBrowserContext extends BrowserContextBase {
|
|||
return this._targets().filter(target => target.type() === 'background_page').map(target => target._initializedPage).filter(pageOrNull => !!pageOrNull) as Page[];
|
||||
}
|
||||
|
||||
serviceWorkers(): Worker[] {
|
||||
return this._targets().filter(target => target.type() === 'service_worker').map(target => target._initializedWorker).filter(workerOrNull => !!workerOrNull) as any as Worker[];
|
||||
}
|
||||
|
||||
async newCDPSession(page: Page): Promise<CRSession> {
|
||||
const targetId = CRTarget.fromPage(page)._targetId;
|
||||
const rootSession = await this._browser._clientRootSession();
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert, helper } from '../helper';
|
||||
import { Page, Worker } from '../page';
|
||||
import { CRBrowser, CRBrowserContext } from './crBrowser';
|
||||
import { CRSession, CRSessionEvents } from './crConnection';
|
||||
import { Page, Worker } from '../page';
|
||||
import { Protocol } from './protocol';
|
||||
import { debugError, assert, helper } from '../helper';
|
||||
import { CRPage } from './crPage';
|
||||
import { CRExecutionContext } from './crExecutionContext';
|
||||
import { CRPage } from './crPage';
|
||||
import { Protocol } from './protocol';
|
||||
|
||||
const targetSymbol = Symbol('target');
|
||||
|
||||
|
|
@ -30,11 +30,11 @@ export class CRTarget {
|
|||
private readonly _browser: CRBrowser;
|
||||
private readonly _browserContext: CRBrowserContext;
|
||||
readonly _targetId: string;
|
||||
readonly sessionFactory: () => Promise<CRSession>;
|
||||
private readonly _pagePromise: Promise<Page | Error> | null = null;
|
||||
readonly _crPage: CRPage | null = null;
|
||||
_initializedPage: Page | null = null;
|
||||
private _workerPromise: Promise<Worker> | null = null;
|
||||
private readonly _workerPromise: Promise<Worker | Error> | null = null;
|
||||
_initializedWorker: Worker | null = null;
|
||||
|
||||
static fromPage(page: Page): CRTarget {
|
||||
return (page as any)[targetSymbol];
|
||||
|
|
@ -48,22 +48,23 @@ export class CRTarget {
|
|||
browser: CRBrowser,
|
||||
targetInfo: Protocol.Target.TargetInfo,
|
||||
browserContext: CRBrowserContext,
|
||||
session: CRSession | null,
|
||||
sessionFactory: () => Promise<CRSession>,
|
||||
session: CRSession,
|
||||
hasInitialAboutBlank: boolean) {
|
||||
this._targetInfo = targetInfo;
|
||||
this._browser = browser;
|
||||
this._browserContext = browserContext;
|
||||
this._targetId = targetInfo.targetId;
|
||||
this.sessionFactory = sessionFactory;
|
||||
if (CRTarget.isPageType(targetInfo.type)) {
|
||||
assert(session, 'Page target must be created with existing session');
|
||||
this._crPage = new CRPage(session, this._browser, this._browserContext);
|
||||
helper.addEventListener(session, 'Page.windowOpen', event => browser._onWindowOpen(targetInfo.targetId, event));
|
||||
const page = this._crPage.page();
|
||||
(page as any)[targetSymbol] = this;
|
||||
session.once(CRSessionEvents.Disconnected, () => page._didDisconnect());
|
||||
this._pagePromise = this._crPage.initialize(hasInitialAboutBlank).then(() => this._initializedPage = page).catch(e => e);
|
||||
} else if (targetInfo.type === 'service_worker') {
|
||||
this._workerPromise = this._initializeServiceWorker(session);
|
||||
} else {
|
||||
assert(false, 'Unsupported target type: ' + targetInfo.type);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,22 +79,28 @@ export class CRTarget {
|
|||
throw new Error('Not a page.');
|
||||
}
|
||||
|
||||
async serviceWorker(): Promise<Worker | null> {
|
||||
if (this._targetInfo.type !== 'service_worker')
|
||||
return null;
|
||||
if (!this._workerPromise) {
|
||||
// TODO(einbinder): Make workers send their console logs.
|
||||
this._workerPromise = this.sessionFactory().then(session => {
|
||||
const worker = new Worker(this._targetInfo.url);
|
||||
session.once('Runtime.executionContextCreated', async event => {
|
||||
worker._createExecutionContext(new CRExecutionContext(session, event.context));
|
||||
});
|
||||
// This might fail if the target is closed before we receive all execution contexts.
|
||||
session.send('Runtime.enable', {}).catch(debugError);
|
||||
return worker;
|
||||
});
|
||||
private async _initializeServiceWorker(session: CRSession): Promise<Worker | Error> {
|
||||
const worker = new Worker(this._targetInfo.url);
|
||||
session.once('Runtime.executionContextCreated', event => {
|
||||
worker._createExecutionContext(new CRExecutionContext(session, event.context));
|
||||
});
|
||||
try {
|
||||
// This might fail if the target is closed before we receive all execution contexts.
|
||||
await Promise.all([
|
||||
session.send('Runtime.enable', {}),
|
||||
session.send('Runtime.runIfWaitingForDebugger'),
|
||||
]);
|
||||
this._initializedWorker = worker;
|
||||
return worker;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
return this._workerPromise;
|
||||
}
|
||||
|
||||
serviceWorkerOrError(): Promise<Worker | Error> {
|
||||
if (this.type() === 'service_worker')
|
||||
return this._workerPromise!;
|
||||
throw new Error('Not a service worker.');
|
||||
}
|
||||
|
||||
type(): 'page' | 'background_page' | 'service_worker' | 'shared_worker' | 'other' | 'browser' {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,23 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
|
|||
]);
|
||||
expect(await worker.evaluate(() => self.toString())).toBe('[object ServiceWorkerGlobalScope]');
|
||||
});
|
||||
it('serviceWorkers() should return current workers', async({browser, page, server, context}) => {
|
||||
const [worker1] = await Promise.all([
|
||||
context.waitForEvent('serviceworker'),
|
||||
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
|
||||
]);
|
||||
let workers = context.serviceWorkers();
|
||||
expect(workers.length).toBe(1);
|
||||
|
||||
const [worker2] = await Promise.all([
|
||||
context.waitForEvent('serviceworker'),
|
||||
page.goto(server.CROSS_PROCESS_PREFIX + '/serviceworkers/empty/sw.html')
|
||||
]);
|
||||
workers = context.serviceWorkers();
|
||||
expect(workers.length).toBe(2);
|
||||
expect(workers).toContain(worker1);
|
||||
expect(workers).toContain(worker2);
|
||||
});
|
||||
it('should not create a worker from a shared worker', async({browser, page, server, context}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
let serviceWorkerCreated;
|
||||
|
|
|
|||
Loading…
Reference in a new issue