2020-06-26 01:05:36 +02:00
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
|
2020-08-24 23:48:03 +02:00
|
|
|
import { BrowserContext } from '../server/browserContext';
|
2020-07-14 01:03:24 +02:00
|
|
|
import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher';
|
2020-07-09 06:36:03 +02:00
|
|
|
import { PageDispatcher, BindingCallDispatcher, WorkerDispatcher } from './pageDispatcher';
|
2021-08-24 23:29:04 +02:00
|
|
|
import { playwrightFetch } from '../server/fetch';
|
2021-08-16 21:49:10 +02:00
|
|
|
import { FrameDispatcher } from './frameDispatcher';
|
2020-08-24 23:48:03 +02:00
|
|
|
import * as channels from '../protocol/channels';
|
2021-05-13 19:29:14 +02:00
|
|
|
import { RouteDispatcher, RequestDispatcher, ResponseDispatcher } from './networkDispatchers';
|
2020-08-24 23:48:03 +02:00
|
|
|
import { CRBrowserContext } from '../server/chromium/crBrowser';
|
2020-07-09 06:36:03 +02:00
|
|
|
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
2021-01-25 04:21:19 +01:00
|
|
|
import { RecorderSupplement } from '../server/supplements/recorderSupplement';
|
2021-02-09 23:44:48 +01:00
|
|
|
import { CallMetadata } from '../server/instrumentation';
|
2021-03-31 19:38:05 +02:00
|
|
|
import { ArtifactDispatcher } from './artifactDispatcher';
|
|
|
|
|
import { Artifact } from '../server/artifact';
|
2021-05-13 19:29:14 +02:00
|
|
|
import { Request, Response } from '../server/network';
|
2021-08-24 23:29:04 +02:00
|
|
|
import { headersArrayToObject } from '../utils/utils';
|
2020-06-26 01:05:36 +02:00
|
|
|
|
2021-08-30 18:43:18 +02:00
|
|
|
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer, channels.BrowserContextEvents> implements channels.BrowserContextChannel {
|
2020-08-19 19:31:59 +02:00
|
|
|
private _context: BrowserContext;
|
2020-06-26 01:05:36 +02:00
|
|
|
|
2020-08-19 19:31:59 +02:00
|
|
|
constructor(scope: DispatcherScope, context: BrowserContext) {
|
2021-01-30 01:00:56 +01:00
|
|
|
super(scope, context, 'BrowserContext', { isChromium: context._browser.options.isChromium }, true);
|
2020-06-26 01:05:36 +02:00
|
|
|
this._context = context;
|
2021-03-31 19:38:05 +02:00
|
|
|
// Note: when launching persistent context, dispatcher is created very late,
|
|
|
|
|
// so we can already have pages, videos and everything else.
|
|
|
|
|
|
|
|
|
|
const onVideo = (artifact: Artifact) => {
|
|
|
|
|
// Note: Video must outlive Page and BrowserContext, so that client can saveAs it
|
|
|
|
|
// after closing the context. We use |scope| for it.
|
|
|
|
|
const artifactDispatcher = new ArtifactDispatcher(scope, artifact);
|
|
|
|
|
this._dispatchEvent('video', { artifact: artifactDispatcher });
|
|
|
|
|
};
|
|
|
|
|
context.on(BrowserContext.Events.VideoStarted, onVideo);
|
|
|
|
|
for (const video of context._browser._idToVideo.values()) {
|
|
|
|
|
if (video.context === context)
|
|
|
|
|
onVideo(video.artifact);
|
|
|
|
|
}
|
2020-07-14 00:26:09 +02:00
|
|
|
|
|
|
|
|
for (const page of context.pages())
|
2020-07-15 03:26:50 +02:00
|
|
|
this._dispatchEvent('page', { page: new PageDispatcher(this._scope, page) });
|
2020-08-22 01:26:33 +02:00
|
|
|
context.on(BrowserContext.Events.Page, page => this._dispatchEvent('page', { page: new PageDispatcher(this._scope, page) }));
|
|
|
|
|
context.on(BrowserContext.Events.Close, () => {
|
2020-06-26 01:05:36 +02:00
|
|
|
this._dispatchEvent('close');
|
2020-07-11 01:24:11 +02:00
|
|
|
this._dispose();
|
2020-06-26 01:05:36 +02:00
|
|
|
});
|
2020-07-14 00:26:09 +02:00
|
|
|
|
2021-01-30 01:00:56 +01:00
|
|
|
if (context._browser.options.name === 'chromium') {
|
2020-07-14 00:26:09 +02:00
|
|
|
for (const page of (context as CRBrowserContext).backgroundPages())
|
2021-04-02 03:47:14 +02:00
|
|
|
this._dispatchEvent('backgroundPage', { page: new PageDispatcher(this._scope, page) });
|
|
|
|
|
context.on(CRBrowserContext.CREvents.BackgroundPage, page => this._dispatchEvent('backgroundPage', { page: new PageDispatcher(this._scope, page) }));
|
2020-07-14 00:26:09 +02:00
|
|
|
for (const serviceWorker of (context as CRBrowserContext).serviceWorkers())
|
2021-04-02 03:47:14 +02:00
|
|
|
this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this._scope, serviceWorker)});
|
|
|
|
|
context.on(CRBrowserContext.CREvents.ServiceWorker, serviceWorker => this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this._scope, serviceWorker) }));
|
2020-07-14 00:26:09 +02:00
|
|
|
}
|
2021-05-13 19:29:14 +02:00
|
|
|
context.on(BrowserContext.Events.Request, (request: Request) => {
|
|
|
|
|
return this._dispatchEvent('request', {
|
|
|
|
|
request: RequestDispatcher.from(this._scope, request),
|
|
|
|
|
page: PageDispatcher.fromNullable(this._scope, request.frame()._page.initializedOrUndefined())
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
context.on(BrowserContext.Events.Response, (response: Response) => this._dispatchEvent('response', {
|
|
|
|
|
response: ResponseDispatcher.from(this._scope, response),
|
|
|
|
|
page: PageDispatcher.fromNullable(this._scope, response.frame()._page.initializedOrUndefined())
|
|
|
|
|
}));
|
|
|
|
|
context.on(BrowserContext.Events.RequestFailed, (request: Request) => this._dispatchEvent('requestFailed', {
|
|
|
|
|
request: RequestDispatcher.from(this._scope, request),
|
2021-08-30 18:43:18 +02:00
|
|
|
failureText: request._failureText || undefined,
|
2021-05-13 19:29:14 +02:00
|
|
|
responseEndTiming: request._responseEndTiming,
|
|
|
|
|
page: PageDispatcher.fromNullable(this._scope, request.frame()._page.initializedOrUndefined())
|
|
|
|
|
}));
|
2021-08-30 18:58:44 +02:00
|
|
|
context.on(BrowserContext.Events.RequestFinished, ({ request, response}: { request: Request, response: Response | null }) => this._dispatchEvent('requestFinished', {
|
2021-05-13 19:29:14 +02:00
|
|
|
request: RequestDispatcher.from(scope, request),
|
2021-08-30 18:58:44 +02:00
|
|
|
response: ResponseDispatcher.fromNullable(scope, response),
|
2021-05-13 19:29:14 +02:00
|
|
|
responseEndTiming: request._responseEndTiming,
|
2021-08-27 22:53:57 +02:00
|
|
|
page: PageDispatcher.fromNullable(this._scope, request.frame()._page.initializedOrUndefined()),
|
2021-05-13 19:29:14 +02:00
|
|
|
}));
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async setDefaultNavigationTimeoutNoReply(params: channels.BrowserContextSetDefaultNavigationTimeoutNoReplyParams) {
|
2020-06-26 01:05:36 +02:00
|
|
|
this._context.setDefaultNavigationTimeout(params.timeout);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async setDefaultTimeoutNoReply(params: channels.BrowserContextSetDefaultTimeoutNoReplyParams) {
|
2020-06-26 01:05:36 +02:00
|
|
|
this._context.setDefaultTimeout(params.timeout);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async exposeBinding(params: channels.BrowserContextExposeBindingParams): Promise<void> {
|
2020-10-02 07:47:31 +02:00
|
|
|
await this._context.exposeBinding(params.name, !!params.needsHandle, (source, ...args) => {
|
|
|
|
|
const binding = new BindingCallDispatcher(this._scope, params.name, !!params.needsHandle, source, args);
|
2020-07-15 03:26:50 +02:00
|
|
|
this._dispatchEvent('bindingCall', { binding });
|
|
|
|
|
return binding.promise();
|
2021-09-08 23:27:05 +02:00
|
|
|
});
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-24 23:29:04 +02:00
|
|
|
async fetch(params: channels.BrowserContextFetchParams): Promise<channels.BrowserContextFetchResult> {
|
|
|
|
|
const { fetchResponse, error } = await playwrightFetch(this._context, {
|
|
|
|
|
url: params.url,
|
|
|
|
|
method: params.method,
|
|
|
|
|
headers: params.headers ? headersArrayToObject(params.headers, false) : undefined,
|
|
|
|
|
postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined,
|
2021-09-08 19:01:40 +02:00
|
|
|
timeout: params.timeout,
|
2021-08-24 23:29:04 +02:00
|
|
|
});
|
|
|
|
|
let response;
|
|
|
|
|
if (fetchResponse) {
|
|
|
|
|
response = {
|
|
|
|
|
url: fetchResponse.url,
|
|
|
|
|
status: fetchResponse.status,
|
|
|
|
|
statusText: fetchResponse.statusText,
|
|
|
|
|
headers: fetchResponse.headers,
|
2021-09-08 22:40:07 +02:00
|
|
|
fetchUid: fetchResponse.fetchUid
|
2021-08-24 23:29:04 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { response, error };
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-08 22:40:07 +02:00
|
|
|
async fetchResponseBody(params: channels.BrowserContextFetchResponseBodyParams): Promise<channels.BrowserContextFetchResponseBodyResult> {
|
|
|
|
|
const buffer = this._context.fetchResponses.get(params.fetchUid);
|
|
|
|
|
return { binary: buffer ? buffer.toString('base64') : undefined };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async disposeFetchResponse(params: channels.BrowserContextDisposeFetchResponseParams): Promise<channels.BrowserContextDisposeFetchResponseResult> {
|
|
|
|
|
this._context.fetchResponses.delete(params.fetchUid);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-19 18:33:24 +01:00
|
|
|
async newPage(params: channels.BrowserContextNewPageParams, metadata: CallMetadata): Promise<channels.BrowserContextNewPageResult> {
|
|
|
|
|
return { page: lookupDispatcher<PageDispatcher>(await this._context.newPage(metadata)) };
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async cookies(params: channels.BrowserContextCookiesParams): Promise<channels.BrowserContextCookiesResult> {
|
2020-07-15 03:26:50 +02:00
|
|
|
return { cookies: await this._context.cookies(params.urls) };
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async addCookies(params: channels.BrowserContextAddCookiesParams): Promise<void> {
|
2020-06-26 01:05:36 +02:00
|
|
|
await this._context.addCookies(params.cookies);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async clearCookies(): Promise<void> {
|
|
|
|
|
await this._context.clearCookies();
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async grantPermissions(params: channels.BrowserContextGrantPermissionsParams): Promise<void> {
|
|
|
|
|
await this._context.grantPermissions(params.permissions, params.origin);
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async clearPermissions(): Promise<void> {
|
|
|
|
|
await this._context.clearPermissions();
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async setGeolocation(params: channels.BrowserContextSetGeolocationParams): Promise<void> {
|
2020-08-18 01:19:21 +02:00
|
|
|
await this._context.setGeolocation(params.geolocation);
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async setExtraHTTPHeaders(params: channels.BrowserContextSetExtraHTTPHeadersParams): Promise<void> {
|
2020-08-19 00:38:29 +02:00
|
|
|
await this._context.setExtraHTTPHeaders(params.headers);
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async setOffline(params: channels.BrowserContextSetOfflineParams): Promise<void> {
|
2020-06-26 01:05:36 +02:00
|
|
|
await this._context.setOffline(params.offline);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async setHTTPCredentials(params: channels.BrowserContextSetHTTPCredentialsParams): Promise<void> {
|
2020-08-18 01:19:21 +02:00
|
|
|
await this._context.setHTTPCredentials(params.httpCredentials);
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async addInitScript(params: channels.BrowserContextAddInitScriptParams): Promise<void> {
|
2020-06-26 01:05:36 +02:00
|
|
|
await this._context._doAddInitScript(params.source);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-20 23:19:27 +02:00
|
|
|
async setNetworkInterceptionEnabled(params: channels.BrowserContextSetNetworkInterceptionEnabledParams): Promise<void> {
|
2020-06-26 20:51:47 +02:00
|
|
|
if (!params.enabled) {
|
2020-08-19 02:34:04 +02:00
|
|
|
await this._context._setRequestInterceptor(undefined);
|
2020-06-26 20:51:47 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2021-03-12 01:04:08 +01:00
|
|
|
await this._context._setRequestInterceptor((route, request) => {
|
2021-04-21 08:03:56 +02:00
|
|
|
this._dispatchEvent('route', { route: RouteDispatcher.from(this._scope, route), request: RequestDispatcher.from(this._scope, request) });
|
2020-06-26 20:51:47 +02:00
|
|
|
});
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|
|
|
|
|
|
2021-02-09 23:44:48 +01:00
|
|
|
async storageState(params: channels.BrowserContextStorageStateParams, metadata: CallMetadata): Promise<channels.BrowserContextStorageStateResult> {
|
|
|
|
|
return await this._context.storageState(metadata);
|
2020-11-13 23:24:53 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-19 18:33:24 +01:00
|
|
|
async close(params: channels.BrowserContextCloseParams, metadata: CallMetadata): Promise<void> {
|
|
|
|
|
await this._context.close(metadata);
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|
2020-07-09 06:36:03 +02:00
|
|
|
|
2021-01-25 04:21:19 +01:00
|
|
|
async recorderSupplementEnable(params: channels.BrowserContextRecorderSupplementEnableParams): Promise<void> {
|
2021-04-22 05:46:45 +02:00
|
|
|
await RecorderSupplement.show(this._context, params);
|
2021-01-25 23:49:26 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-12 19:11:30 +01:00
|
|
|
async pause(params: channels.BrowserContextPauseParams, metadata: CallMetadata) {
|
|
|
|
|
// Inspector controller will take care of this.
|
2020-12-23 23:15:16 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-02 03:47:14 +02:00
|
|
|
async newCDPSession(params: channels.BrowserContextNewCDPSessionParams): Promise<channels.BrowserContextNewCDPSessionResult> {
|
2021-01-30 01:00:56 +01:00
|
|
|
if (!this._object._browser.options.isChromium)
|
2020-08-28 19:23:02 +02:00
|
|
|
throw new Error(`CDP session is only available in Chromium`);
|
2021-08-16 21:49:10 +02:00
|
|
|
if (!params.page && !params.frame || params.page && params.frame)
|
|
|
|
|
throw new Error(`CDP session must be initiated with either Page or Frame, not none or both`);
|
2020-07-09 06:36:03 +02:00
|
|
|
const crBrowserContext = this._object as CRBrowserContext;
|
2021-08-16 21:49:10 +02:00
|
|
|
return { session: new CDPSessionDispatcher(this._scope, await crBrowserContext.newCDPSession((params.page ? params.page as PageDispatcher : params.frame as FrameDispatcher)._object)) };
|
2020-07-09 06:36:03 +02:00
|
|
|
}
|
2021-04-24 05:39:09 +02:00
|
|
|
|
2021-04-25 05:39:48 +02:00
|
|
|
async tracingStart(params: channels.BrowserContextTracingStartParams): Promise<channels.BrowserContextTracingStartResult> {
|
|
|
|
|
await this._context.tracing.start(params);
|
2021-04-24 05:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-01 02:03:31 +02:00
|
|
|
async tracingStartChunk(params: channels.BrowserContextTracingStartChunkParams): Promise<channels.BrowserContextTracingStartChunkResult> {
|
|
|
|
|
await this._context.tracing.startChunk();
|
2021-04-25 05:39:48 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-01 02:03:31 +02:00
|
|
|
async tracingStopChunk(params: channels.BrowserContextTracingStopChunkParams): Promise<channels.BrowserContextTracingStopChunkResult> {
|
|
|
|
|
const artifact = await this._context.tracing.stopChunk(params.save);
|
|
|
|
|
return { artifact: artifact ? new ArtifactDispatcher(this._scope, artifact) : undefined };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async tracingStop(params: channels.BrowserContextTracingStopParams): Promise<channels.BrowserContextTracingStopResult> {
|
|
|
|
|
await this._context.tracing.stop();
|
2021-04-24 05:39:09 +02:00
|
|
|
}
|
2021-08-25 22:32:56 +02:00
|
|
|
|
|
|
|
|
async harExport(params: channels.BrowserContextHarExportParams): Promise<channels.BrowserContextHarExportResult> {
|
|
|
|
|
const artifact = await this._context._harRecorder?.export();
|
|
|
|
|
if (!artifact)
|
|
|
|
|
throw new Error('No HAR artifact. Ensure record.harPath is set.');
|
|
|
|
|
return { artifact: new ArtifactDispatcher(this._scope, artifact) };
|
|
|
|
|
}
|
2020-06-26 01:05:36 +02:00
|
|
|
}
|