mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: make CRNetworkManager handle multiple sessions (#29721)
It was already handling worker sessions, but not OOPIFs. As a result, some functionality was properly implemented only for OOPIFs and not for workers. This change removes OOPIFs fanout for network-related calls from CRPage and moves that to the CRNetworkManager, now also covering workers.
This commit is contained in:
parent
52b803ecf5
commit
aedd7ca0be
@ -562,7 +562,7 @@ export class CRBrowserContext extends BrowserContext {
|
|||||||
|
|
||||||
override async clearCache(): Promise<void> {
|
override async clearCache(): Promise<void> {
|
||||||
for (const page of this._crPages())
|
for (const page of this._crPages())
|
||||||
await page._mainFrameSession._networkManager.clearCache();
|
await page._networkManager.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelDownload(guid: string) {
|
async cancelDownload(guid: string) {
|
||||||
|
@ -26,70 +26,88 @@ import type * as contexts from '../browserContext';
|
|||||||
import type * as frames from '../frames';
|
import type * as frames from '../frames';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
import type { CRPage } from './crPage';
|
import type { CRPage } from './crPage';
|
||||||
import { assert, headersObjectToArray } from '../../utils';
|
import { assert, headersArrayToObject, headersObjectToArray } from '../../utils';
|
||||||
import type { CRServiceWorker } from './crServiceWorker';
|
import type { CRServiceWorker } from './crServiceWorker';
|
||||||
import { isProtocolError } from '../protocolError';
|
import { isProtocolError, isSessionClosedError } from '../protocolError';
|
||||||
|
|
||||||
type SessionInfo = {
|
type SessionInfo = {
|
||||||
session: CRSession;
|
session: CRSession;
|
||||||
|
isMain?: boolean;
|
||||||
workerFrame?: frames.Frame;
|
workerFrame?: frames.Frame;
|
||||||
|
eventListeners: RegisteredListener[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CRNetworkManager {
|
export class CRNetworkManager {
|
||||||
private _session: CRSession;
|
|
||||||
private _page: Page | null;
|
private _page: Page | null;
|
||||||
private _serviceWorker: CRServiceWorker | null;
|
private _serviceWorker: CRServiceWorker | null;
|
||||||
private _parentManager: CRNetworkManager | null;
|
|
||||||
private _requestIdToRequest = new Map<string, InterceptableRequest>();
|
private _requestIdToRequest = new Map<string, InterceptableRequest>();
|
||||||
private _requestIdToRequestWillBeSentEvent = new Map<string, Protocol.Network.requestWillBeSentPayload>();
|
private _requestIdToRequestWillBeSentEvent = new Map<string, Protocol.Network.requestWillBeSentPayload>();
|
||||||
private _credentials: {origin?: string, username: string, password: string} | null = null;
|
private _credentials: {origin?: string, username: string, password: string} | null = null;
|
||||||
private _attemptedAuthentications = new Set<string>();
|
private _attemptedAuthentications = new Set<string>();
|
||||||
private _userRequestInterceptionEnabled = false;
|
private _userRequestInterceptionEnabled = false;
|
||||||
private _protocolRequestInterceptionEnabled = false;
|
private _protocolRequestInterceptionEnabled = false;
|
||||||
|
private _offline = false;
|
||||||
|
private _extraHTTPHeaders: types.HeadersArray = [];
|
||||||
private _requestIdToRequestPausedEvent = new Map<string, Protocol.Fetch.requestPausedPayload>();
|
private _requestIdToRequestPausedEvent = new Map<string, Protocol.Fetch.requestPausedPayload>();
|
||||||
private _eventListeners: RegisteredListener[];
|
|
||||||
private _responseExtraInfoTracker = new ResponseExtraInfoTracker();
|
private _responseExtraInfoTracker = new ResponseExtraInfoTracker();
|
||||||
|
private _sessions = new Map<CRSession, SessionInfo>();
|
||||||
|
|
||||||
constructor(session: CRSession, page: Page | null, serviceWorker: CRServiceWorker | null, parentManager: CRNetworkManager | null) {
|
constructor(page: Page | null, serviceWorker: CRServiceWorker | null) {
|
||||||
this._session = session;
|
|
||||||
this._page = page;
|
this._page = page;
|
||||||
this._serviceWorker = serviceWorker;
|
this._serviceWorker = serviceWorker;
|
||||||
this._parentManager = parentManager;
|
|
||||||
this._eventListeners = this.instrumentNetworkEvents({ session });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instrumentNetworkEvents(sessionInfo: SessionInfo): RegisteredListener[] {
|
async addSession(session: CRSession, workerFrame?: frames.Frame, isMain?: boolean) {
|
||||||
const listeners = [
|
const sessionInfo: SessionInfo = { session, isMain, workerFrame, eventListeners: [] };
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Fetch.requestPaused', this._onRequestPaused.bind(this, sessionInfo)),
|
sessionInfo.eventListeners = [
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Fetch.authRequired', this._onAuthRequired.bind(this)),
|
eventsHelper.addEventListener(session, 'Fetch.requestPaused', this._onRequestPaused.bind(this, sessionInfo)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, sessionInfo)),
|
eventsHelper.addEventListener(session, 'Fetch.authRequired', this._onAuthRequired.bind(this, sessionInfo)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)),
|
eventsHelper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, sessionInfo)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)),
|
eventsHelper.addEventListener(session, 'Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.responseReceived', this._onResponseReceived.bind(this, sessionInfo)),
|
eventsHelper.addEventListener(session, 'Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)),
|
eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this, sessionInfo)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.loadingFinished', this._onLoadingFinished.bind(this)),
|
eventsHelper.addEventListener(session, 'Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.loadingFailed', this._onLoadingFailed.bind(this, sessionInfo)),
|
eventsHelper.addEventListener(session, 'Network.loadingFinished', this._onLoadingFinished.bind(this, sessionInfo)),
|
||||||
|
eventsHelper.addEventListener(session, 'Network.loadingFailed', this._onLoadingFailed.bind(this, sessionInfo)),
|
||||||
];
|
];
|
||||||
if (this._page) {
|
if (this._page) {
|
||||||
listeners.push(...[
|
sessionInfo.eventListeners.push(...[
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketCreated', e => this._page!._frameManager.onWebSocketCreated(e.requestId, e.url)),
|
eventsHelper.addEventListener(session, 'Network.webSocketCreated', e => this._page!._frameManager.onWebSocketCreated(e.requestId, e.url)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketWillSendHandshakeRequest', e => this._page!._frameManager.onWebSocketRequest(e.requestId)),
|
eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page!._frameManager.onWebSocketRequest(e.requestId)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketHandshakeResponseReceived', e => this._page!._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)),
|
eventsHelper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page!._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page!._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)),
|
eventsHelper.addEventListener(session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page!._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page!._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)),
|
eventsHelper.addEventListener(session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page!._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketClosed', e => this._page!._frameManager.webSocketClosed(e.requestId)),
|
eventsHelper.addEventListener(session, 'Network.webSocketClosed', e => this._page!._frameManager.webSocketClosed(e.requestId)),
|
||||||
eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketFrameError', e => this._page!._frameManager.webSocketError(e.requestId, e.errorMessage)),
|
eventsHelper.addEventListener(session, 'Network.webSocketFrameError', e => this._page!._frameManager.webSocketError(e.requestId, e.errorMessage)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return listeners;
|
this._sessions.set(session, sessionInfo);
|
||||||
|
await Promise.all([
|
||||||
|
session.send('Network.enable'),
|
||||||
|
this._updateProtocolRequestInterceptionForSession(sessionInfo, true /* initial */),
|
||||||
|
this._setOfflineForSession(sessionInfo, true /* initial */),
|
||||||
|
this._setExtraHTTPHeadersForSession(sessionInfo, true /* initial */),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize() {
|
removeSession(session: CRSession) {
|
||||||
await this._session.send('Network.enable');
|
const info = this._sessions.get(session);
|
||||||
|
if (info)
|
||||||
|
eventsHelper.removeEventListeners(info.eventListeners);
|
||||||
|
this._sessions.delete(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
private async _forEachSession(cb: (sessionInfo: SessionInfo) => Promise<any>) {
|
||||||
eventsHelper.removeEventListeners(this._eventListeners);
|
await Promise.all([...this._sessions.values()].map(info => {
|
||||||
|
if (info.isMain)
|
||||||
|
return cb(info);
|
||||||
|
return cb(info).catch(e => {
|
||||||
|
// Broadcasting a message to the closed target should be a noop.
|
||||||
|
if (isSessionClosedError(e))
|
||||||
|
return;
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticate(credentials: types.Credentials | null) {
|
async authenticate(credentials: types.Credentials | null) {
|
||||||
@ -98,8 +116,20 @@ export class CRNetworkManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setOffline(offline: boolean) {
|
async setOffline(offline: boolean) {
|
||||||
await this._session.send('Network.emulateNetworkConditions', {
|
if (offline === this._offline)
|
||||||
offline,
|
return;
|
||||||
|
this._offline = offline;
|
||||||
|
await this._forEachSession(info => this._setOfflineForSession(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _setOfflineForSession(info: SessionInfo, initial?: boolean) {
|
||||||
|
if (initial && !this._offline)
|
||||||
|
return;
|
||||||
|
// Workers are affected by the owner frame's Network.emulateNetworkConditions.
|
||||||
|
if (info.workerFrame)
|
||||||
|
return;
|
||||||
|
await info.session.send('Network.emulateNetworkConditions', {
|
||||||
|
offline: this._offline,
|
||||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
latency: 0,
|
latency: 0,
|
||||||
downloadThroughput: -1,
|
downloadThroughput: -1,
|
||||||
@ -117,28 +147,46 @@ export class CRNetworkManager {
|
|||||||
if (enabled === this._protocolRequestInterceptionEnabled)
|
if (enabled === this._protocolRequestInterceptionEnabled)
|
||||||
return;
|
return;
|
||||||
this._protocolRequestInterceptionEnabled = enabled;
|
this._protocolRequestInterceptionEnabled = enabled;
|
||||||
if (enabled) {
|
await this._forEachSession(info => this._updateProtocolRequestInterceptionForSession(info));
|
||||||
await Promise.all([
|
}
|
||||||
this._session.send('Network.setCacheDisabled', { cacheDisabled: true }),
|
|
||||||
this._session.send('Fetch.enable', {
|
private async _updateProtocolRequestInterceptionForSession(info: SessionInfo, initial?: boolean) {
|
||||||
handleAuthRequests: true,
|
const enabled = this._protocolRequestInterceptionEnabled;
|
||||||
patterns: [{ urlPattern: '*', requestStage: 'Request' }],
|
if (initial && !enabled)
|
||||||
}),
|
return;
|
||||||
]);
|
const cachePromise = info.session.send('Network.setCacheDisabled', { cacheDisabled: enabled });
|
||||||
} else {
|
let fetchPromise = Promise.resolve<any>(undefined);
|
||||||
await Promise.all([
|
if (!info.workerFrame) {
|
||||||
this._session.send('Network.setCacheDisabled', { cacheDisabled: false }),
|
if (enabled)
|
||||||
this._session.send('Fetch.disable')
|
fetchPromise = info.session.send('Fetch.enable', { handleAuthRequests: true, patterns: [{ urlPattern: '*', requestStage: 'Request' }] });
|
||||||
]);
|
else
|
||||||
|
fetchPromise = info.session.send('Fetch.disable');
|
||||||
}
|
}
|
||||||
|
await Promise.all([cachePromise, fetchPromise]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setExtraHTTPHeaders(extraHTTPHeaders: types.HeadersArray) {
|
||||||
|
if (!this._extraHTTPHeaders.length && !extraHTTPHeaders.length)
|
||||||
|
return;
|
||||||
|
this._extraHTTPHeaders = extraHTTPHeaders;
|
||||||
|
await this._forEachSession(info => this._setExtraHTTPHeadersForSession(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _setExtraHTTPHeadersForSession(info: SessionInfo, initial?: boolean) {
|
||||||
|
if (initial && !this._extraHTTPHeaders.length)
|
||||||
|
return;
|
||||||
|
await info.session.send('Network.setExtraHTTPHeaders', { headers: headersArrayToObject(this._extraHTTPHeaders, false /* lowerCase */) });
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearCache() {
|
async clearCache() {
|
||||||
// Sending 'Network.setCacheDisabled' with 'cacheDisabled = true' will clear the MemoryCache.
|
await this._forEachSession(async info => {
|
||||||
await this._session.send('Network.setCacheDisabled', { cacheDisabled: true });
|
// Sending 'Network.setCacheDisabled' with 'cacheDisabled = true' will clear the MemoryCache.
|
||||||
if (!this._protocolRequestInterceptionEnabled)
|
await info.session.send('Network.setCacheDisabled', { cacheDisabled: true });
|
||||||
await this._session.send('Network.setCacheDisabled', { cacheDisabled: false });
|
if (!this._protocolRequestInterceptionEnabled)
|
||||||
await this._session.send('Network.clearBrowserCache');
|
await info.session.send('Network.setCacheDisabled', { cacheDisabled: false });
|
||||||
|
if (!info.workerFrame)
|
||||||
|
await info.session.send('Network.clearBrowserCache');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRequestWillBeSent(sessionInfo: SessionInfo, event: Protocol.Network.requestWillBeSentPayload) {
|
_onRequestWillBeSent(sessionInfo: SessionInfo, event: Protocol.Network.requestWillBeSentPayload) {
|
||||||
@ -165,7 +213,7 @@ export class CRNetworkManager {
|
|||||||
this._responseExtraInfoTracker.requestWillBeSentExtraInfo(event);
|
this._responseExtraInfoTracker.requestWillBeSentExtraInfo(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAuthRequired(event: Protocol.Fetch.authRequiredPayload) {
|
_onAuthRequired(sessionInfo: SessionInfo, event: Protocol.Fetch.authRequiredPayload) {
|
||||||
let response: 'Default' | 'CancelAuth' | 'ProvideCredentials' = 'Default';
|
let response: 'Default' | 'CancelAuth' | 'ProvideCredentials' = 'Default';
|
||||||
const shouldProvideCredentials = this._shouldProvideCredentials(event.request.url);
|
const shouldProvideCredentials = this._shouldProvideCredentials(event.request.url);
|
||||||
if (this._attemptedAuthentications.has(event.requestId)) {
|
if (this._attemptedAuthentications.has(event.requestId)) {
|
||||||
@ -175,7 +223,7 @@ export class CRNetworkManager {
|
|||||||
this._attemptedAuthentications.add(event.requestId);
|
this._attemptedAuthentications.add(event.requestId);
|
||||||
}
|
}
|
||||||
const { username, password } = shouldProvideCredentials && this._credentials ? this._credentials : { username: undefined, password: undefined };
|
const { username, password } = shouldProvideCredentials && this._credentials ? this._credentials : { username: undefined, password: undefined };
|
||||||
this._session._sendMayFail('Fetch.continueWithAuth', {
|
sessionInfo.session._sendMayFail('Fetch.continueWithAuth', {
|
||||||
requestId: event.requestId,
|
requestId: event.requestId,
|
||||||
authChallengeResponse: { response, username, password },
|
authChallengeResponse: { response, username, password },
|
||||||
});
|
});
|
||||||
@ -191,7 +239,7 @@ export class CRNetworkManager {
|
|||||||
if (!event.networkId) {
|
if (!event.networkId) {
|
||||||
// Fetch without networkId means that request was not recognized by inspector, and
|
// Fetch without networkId means that request was not recognized by inspector, and
|
||||||
// it will never receive Network.requestWillBeSent. Continue the request to not affect it.
|
// it will never receive Network.requestWillBeSent. Continue the request to not affect it.
|
||||||
this._session._sendMayFail('Fetch.continueRequest', { requestId: event.requestId });
|
sessionInfo.session._sendMayFail('Fetch.continueRequest', { requestId: event.requestId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.request.url.startsWith('data:'))
|
if (event.request.url.startsWith('data:'))
|
||||||
@ -215,7 +263,7 @@ export class CRNetworkManager {
|
|||||||
//
|
//
|
||||||
// Note: make sure not to prematurely continue the redirect, which shares the
|
// Note: make sure not to prematurely continue the redirect, which shares the
|
||||||
// `networkId` between the original request and the redirect.
|
// `networkId` between the original request and the redirect.
|
||||||
this._session._sendMayFail('Fetch.continueRequest', {
|
sessionInfo.session._sendMayFail('Fetch.continueRequest', {
|
||||||
...alreadyContinuedParams,
|
...alreadyContinuedParams,
|
||||||
requestId: event.requestId,
|
requestId: event.requestId,
|
||||||
});
|
});
|
||||||
@ -266,7 +314,7 @@ export class CRNetworkManager {
|
|||||||
];
|
];
|
||||||
if (requestHeaders['Access-Control-Request-Headers'])
|
if (requestHeaders['Access-Control-Request-Headers'])
|
||||||
responseHeaders.push({ name: 'Access-Control-Allow-Headers', value: requestHeaders['Access-Control-Request-Headers'] });
|
responseHeaders.push({ name: 'Access-Control-Allow-Headers', value: requestHeaders['Access-Control-Request-Headers'] });
|
||||||
this._session._sendMayFail('Fetch.fulfillRequest', {
|
sessionInfo.session._sendMayFail('Fetch.fulfillRequest', {
|
||||||
requestId: requestPausedEvent.requestId,
|
requestId: requestPausedEvent.requestId,
|
||||||
responseCode: 204,
|
responseCode: 204,
|
||||||
responsePhrase: network.STATUS_TEXTS['204'],
|
responsePhrase: network.STATUS_TEXTS['204'],
|
||||||
@ -279,7 +327,7 @@ export class CRNetworkManager {
|
|||||||
// Non-service-worker requests MUST have a frame—if they don't, we pretend there was no request
|
// Non-service-worker requests MUST have a frame—if they don't, we pretend there was no request
|
||||||
if (!frame && !this._serviceWorker) {
|
if (!frame && !this._serviceWorker) {
|
||||||
if (requestPausedEvent)
|
if (requestPausedEvent)
|
||||||
this._session._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId });
|
sessionInfo.session._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,9 +337,9 @@ export class CRNetworkManager {
|
|||||||
if (redirectedFrom || (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled)) {
|
if (redirectedFrom || (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled)) {
|
||||||
// Chromium does not preserve header overrides between redirects, so we have to do it ourselves.
|
// Chromium does not preserve header overrides between redirects, so we have to do it ourselves.
|
||||||
const headers = redirectedFrom?._originalRequestRoute?._alreadyContinuedParams?.headers;
|
const headers = redirectedFrom?._originalRequestRoute?._alreadyContinuedParams?.headers;
|
||||||
this._session._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId, headers });
|
sessionInfo.session._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId, headers });
|
||||||
} else {
|
} else {
|
||||||
route = new RouteImpl(this._session, requestPausedEvent.requestId);
|
route = new RouteImpl(sessionInfo.session, requestPausedEvent.requestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
|
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
|
||||||
@ -426,16 +474,15 @@ export class CRNetworkManager {
|
|||||||
(this._page?._frameManager || this._serviceWorker)!.requestReceivedResponse(response);
|
(this._page?._frameManager || this._serviceWorker)!.requestReceivedResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onLoadingFinished(event: Protocol.Network.loadingFinishedPayload) {
|
_onLoadingFinished(sessionInfo: SessionInfo, event: Protocol.Network.loadingFinishedPayload) {
|
||||||
this._responseExtraInfoTracker.loadingFinished(event);
|
this._responseExtraInfoTracker.loadingFinished(event);
|
||||||
|
|
||||||
let request = this._requestIdToRequest.get(event.requestId);
|
const request = this._requestIdToRequest.get(event.requestId);
|
||||||
if (!request)
|
|
||||||
request = this._maybeAdoptMainRequest(event.requestId);
|
|
||||||
// For certain requestIds we never receive requestWillBeSent event.
|
// For certain requestIds we never receive requestWillBeSent event.
|
||||||
// @see https://crbug.com/750469
|
// @see https://crbug.com/750469
|
||||||
if (!request)
|
if (!request)
|
||||||
return;
|
return;
|
||||||
|
this._maybeUpdateOOPIFMainRequest(sessionInfo, request);
|
||||||
|
|
||||||
// Under certain conditions we never get the Network.responseReceived
|
// Under certain conditions we never get the Network.responseReceived
|
||||||
// event from protocol. @see https://crbug.com/883475
|
// event from protocol. @see https://crbug.com/883475
|
||||||
@ -453,8 +500,6 @@ export class CRNetworkManager {
|
|||||||
this._responseExtraInfoTracker.loadingFailed(event);
|
this._responseExtraInfoTracker.loadingFailed(event);
|
||||||
|
|
||||||
let request = this._requestIdToRequest.get(event.requestId);
|
let request = this._requestIdToRequest.get(event.requestId);
|
||||||
if (!request)
|
|
||||||
request = this._maybeAdoptMainRequest(event.requestId);
|
|
||||||
|
|
||||||
if (!request) {
|
if (!request) {
|
||||||
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId);
|
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId);
|
||||||
@ -472,6 +517,7 @@ export class CRNetworkManager {
|
|||||||
// @see https://crbug.com/750469
|
// @see https://crbug.com/750469
|
||||||
if (!request)
|
if (!request)
|
||||||
return;
|
return;
|
||||||
|
this._maybeUpdateOOPIFMainRequest(sessionInfo, request);
|
||||||
const response = request.request._existingResponse();
|
const response = request.request._existingResponse();
|
||||||
if (response) {
|
if (response) {
|
||||||
response.setTransferSize(null);
|
response.setTransferSize(null);
|
||||||
@ -483,22 +529,12 @@ export class CRNetworkManager {
|
|||||||
(this._page?._frameManager || this._serviceWorker)!.requestFailed(request.request, !!event.canceled);
|
(this._page?._frameManager || this._serviceWorker)!.requestFailed(request.request, !!event.canceled);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _maybeAdoptMainRequest(requestId: Protocol.Network.RequestId): InterceptableRequest | undefined {
|
private _maybeUpdateOOPIFMainRequest(sessionInfo: SessionInfo, request: InterceptableRequest) {
|
||||||
// OOPIF has a main request that starts in the parent session but finishes in the child session.
|
// OOPIF has a main request that starts in the parent session but finishes in the child session.
|
||||||
if (!this._parentManager)
|
// We check for the main request by matching loaderId and requestId, and if it now belongs to
|
||||||
return;
|
// a child session, migrate it there.
|
||||||
const request = this._parentManager._requestIdToRequest.get(requestId);
|
if (request.session !== sessionInfo.session && !sessionInfo.isMain && request._documentId === request._requestId)
|
||||||
// Main requests have matching loaderId and requestId.
|
request.session = sessionInfo.session;
|
||||||
if (!request || request._documentId !== requestId)
|
|
||||||
return;
|
|
||||||
this._requestIdToRequest.set(requestId, request);
|
|
||||||
request.session = this._session;
|
|
||||||
this._parentManager._requestIdToRequest.delete(requestId);
|
|
||||||
if (request._interceptionId && this._parentManager._attemptedAuthentications.has(request._interceptionId)) {
|
|
||||||
this._parentManager._attemptedAuthentications.delete(request._interceptionId);
|
|
||||||
this._attemptedAuthentications.add(request._interceptionId);
|
|
||||||
}
|
|
||||||
return request;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import type { RegisteredListener } from '../../utils/eventsHelper';
|
|||||||
import { eventsHelper } from '../../utils/eventsHelper';
|
import { eventsHelper } from '../../utils/eventsHelper';
|
||||||
import { registry } from '../registry';
|
import { registry } from '../registry';
|
||||||
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||||
import { assert, createGuid, headersArrayToObject } from '../../utils';
|
import { assert, createGuid } from '../../utils';
|
||||||
import * as dialog from '../dialog';
|
import * as dialog from '../dialog';
|
||||||
import * as dom from '../dom';
|
import * as dom from '../dom';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
@ -61,6 +61,7 @@ export class CRPage implements PageDelegate {
|
|||||||
readonly rawTouchscreen: RawTouchscreenImpl;
|
readonly rawTouchscreen: RawTouchscreenImpl;
|
||||||
readonly _targetId: string;
|
readonly _targetId: string;
|
||||||
readonly _opener: CRPage | null;
|
readonly _opener: CRPage | null;
|
||||||
|
readonly _networkManager: CRNetworkManager;
|
||||||
private readonly _pdf: CRPDF;
|
private readonly _pdf: CRPDF;
|
||||||
private readonly _coverage: CRCoverage;
|
private readonly _coverage: CRCoverage;
|
||||||
readonly _browserContext: CRBrowserContext;
|
readonly _browserContext: CRBrowserContext;
|
||||||
@ -92,6 +93,13 @@ export class CRPage implements PageDelegate {
|
|||||||
this._coverage = new CRCoverage(client);
|
this._coverage = new CRCoverage(client);
|
||||||
this._browserContext = browserContext;
|
this._browserContext = browserContext;
|
||||||
this._page = new Page(this, browserContext);
|
this._page = new Page(this, browserContext);
|
||||||
|
this._networkManager = new CRNetworkManager(this._page, null);
|
||||||
|
// Sync any browser context state to the network manager. This does not talk over CDP because
|
||||||
|
// we have not connected any sessions to the network manager yet.
|
||||||
|
this.updateOffline();
|
||||||
|
this.updateExtraHTTPHeaders();
|
||||||
|
this.updateHttpCredentials();
|
||||||
|
this.updateRequestInterception();
|
||||||
this._mainFrameSession = new FrameSession(this, client, targetId, null);
|
this._mainFrameSession = new FrameSession(this, client, targetId, null);
|
||||||
this._sessions.set(targetId, this._mainFrameSession);
|
this._sessions.set(targetId, this._mainFrameSession);
|
||||||
if (opener && !browserContext._options.noDefaultViewport) {
|
if (opener && !browserContext._options.noDefaultViewport) {
|
||||||
@ -184,7 +192,11 @@ export class CRPage implements PageDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateExtraHTTPHeaders(): Promise<void> {
|
async updateExtraHTTPHeaders(): Promise<void> {
|
||||||
await this._forAllFrameSessions(frame => frame._updateExtraHTTPHeaders(false));
|
const headers = network.mergeHeaders([
|
||||||
|
this._browserContext._options.extraHTTPHeaders,
|
||||||
|
this._page.extraHTTPHeaders()
|
||||||
|
]);
|
||||||
|
await this._networkManager.setExtraHTTPHeaders(headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateGeolocation(): Promise<void> {
|
async updateGeolocation(): Promise<void> {
|
||||||
@ -192,11 +204,11 @@ export class CRPage implements PageDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateOffline(): Promise<void> {
|
async updateOffline(): Promise<void> {
|
||||||
await this._forAllFrameSessions(frame => frame._updateOffline(false));
|
await this._networkManager.setOffline(!!this._browserContext._options.offline);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateHttpCredentials(): Promise<void> {
|
async updateHttpCredentials(): Promise<void> {
|
||||||
await this._forAllFrameSessions(frame => frame._updateHttpCredentials(false));
|
await this._networkManager.authenticate(this._browserContext._options.httpCredentials || null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateEmulatedViewportSize(preserveWindowBoundaries?: boolean): Promise<void> {
|
async updateEmulatedViewportSize(preserveWindowBoundaries?: boolean): Promise<void> {
|
||||||
@ -216,7 +228,7 @@ export class CRPage implements PageDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateRequestInterception(): Promise<void> {
|
async updateRequestInterception(): Promise<void> {
|
||||||
await this._forAllFrameSessions(frame => frame._updateRequestInterception());
|
await this._networkManager.setRequestInterception(this._page.needsRequestInterception());
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFileChooserInterception() {
|
async updateFileChooserInterception() {
|
||||||
@ -392,7 +404,6 @@ class FrameSession {
|
|||||||
readonly _client: CRSession;
|
readonly _client: CRSession;
|
||||||
readonly _crPage: CRPage;
|
readonly _crPage: CRPage;
|
||||||
readonly _page: Page;
|
readonly _page: Page;
|
||||||
readonly _networkManager: CRNetworkManager;
|
|
||||||
private readonly _parentSession: FrameSession | null;
|
private readonly _parentSession: FrameSession | null;
|
||||||
private readonly _childSessions = new Set<FrameSession>();
|
private readonly _childSessions = new Set<FrameSession>();
|
||||||
private readonly _contextIdToContext = new Map<number, dom.FrameExecutionContext>();
|
private readonly _contextIdToContext = new Map<number, dom.FrameExecutionContext>();
|
||||||
@ -418,7 +429,6 @@ class FrameSession {
|
|||||||
this._crPage = crPage;
|
this._crPage = crPage;
|
||||||
this._page = crPage._page;
|
this._page = crPage._page;
|
||||||
this._targetId = targetId;
|
this._targetId = targetId;
|
||||||
this._networkManager = new CRNetworkManager(client, this._page, null, parentSession ? parentSession._networkManager : null);
|
|
||||||
this._parentSession = parentSession;
|
this._parentSession = parentSession;
|
||||||
if (parentSession)
|
if (parentSession)
|
||||||
parentSession._childSessions.add(this);
|
parentSession._childSessions.add(this);
|
||||||
@ -533,7 +543,7 @@ class FrameSession {
|
|||||||
source: '',
|
source: '',
|
||||||
worldName: UTILITY_WORLD_NAME,
|
worldName: UTILITY_WORLD_NAME,
|
||||||
}),
|
}),
|
||||||
this._networkManager.initialize(),
|
this._crPage._networkManager.addSession(this._client, undefined, this._isMainFrame()),
|
||||||
this._client.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }),
|
this._client.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }),
|
||||||
];
|
];
|
||||||
if (!isSettingStorageState) {
|
if (!isSettingStorageState) {
|
||||||
@ -559,10 +569,6 @@ class FrameSession {
|
|||||||
if (!this._crPage._browserContext._browser.options.headful)
|
if (!this._crPage._browserContext._browser.options.headful)
|
||||||
promises.push(this._setDefaultFontFamilies(this._client));
|
promises.push(this._setDefaultFontFamilies(this._client));
|
||||||
promises.push(this._updateGeolocation(true));
|
promises.push(this._updateGeolocation(true));
|
||||||
promises.push(this._updateExtraHTTPHeaders(true));
|
|
||||||
promises.push(this._updateRequestInterception());
|
|
||||||
promises.push(this._updateOffline(true));
|
|
||||||
promises.push(this._updateHttpCredentials(true));
|
|
||||||
promises.push(this._updateEmulateMedia());
|
promises.push(this._updateEmulateMedia());
|
||||||
promises.push(this._updateFileChooserInterception(true));
|
promises.push(this._updateFileChooserInterception(true));
|
||||||
for (const binding of this._crPage._page.allBindings())
|
for (const binding of this._crPage._page.allBindings())
|
||||||
@ -586,7 +592,7 @@ class FrameSession {
|
|||||||
if (this._parentSession)
|
if (this._parentSession)
|
||||||
this._parentSession._childSessions.delete(this);
|
this._parentSession._childSessions.delete(this);
|
||||||
eventsHelper.removeEventListeners(this._eventListeners);
|
eventsHelper.removeEventListeners(this._eventListeners);
|
||||||
this._networkManager.dispose();
|
this._crPage._networkManager.removeSession(this._client);
|
||||||
this._crPage._sessions.delete(this._targetId);
|
this._crPage._sessions.delete(this._targetId);
|
||||||
this._client.dispose();
|
this._client.dispose();
|
||||||
}
|
}
|
||||||
@ -752,7 +758,8 @@ class FrameSession {
|
|||||||
});
|
});
|
||||||
// This might fail if the target is closed before we initialize.
|
// This might fail if the target is closed before we initialize.
|
||||||
session._sendMayFail('Runtime.enable');
|
session._sendMayFail('Runtime.enable');
|
||||||
session._sendMayFail('Network.enable');
|
// TODO: attribute workers to the right frame.
|
||||||
|
this._crPage._networkManager.addSession(session, this._page._frameManager.frame(this._targetId) ?? undefined).catch(() => {});
|
||||||
session._sendMayFail('Runtime.runIfWaitingForDebugger');
|
session._sendMayFail('Runtime.runIfWaitingForDebugger');
|
||||||
session._sendMayFail('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true });
|
session._sendMayFail('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true });
|
||||||
session.on('Target.attachedToTarget', event => this._onAttachedToTarget(event));
|
session.on('Target.attachedToTarget', event => this._onAttachedToTarget(event));
|
||||||
@ -762,8 +769,6 @@ class FrameSession {
|
|||||||
this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace));
|
this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace));
|
||||||
});
|
});
|
||||||
session.on('Runtime.exceptionThrown', exception => this._page.emitOnContextOnceInitialized(BrowserContext.Events.PageError, exceptionToError(exception.exceptionDetails), this._page));
|
session.on('Runtime.exceptionThrown', exception => this._page.emitOnContextOnceInitialized(BrowserContext.Events.PageError, exceptionToError(exception.exceptionDetails), this._page));
|
||||||
// TODO: attribute workers to the right frame.
|
|
||||||
this._networkManager.instrumentNetworkEvents({ session, workerFrame: this._page._frameManager.frame(this._targetId) ?? undefined });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDetachedFromTarget(event: Protocol.Target.detachedFromTargetPayload) {
|
_onDetachedFromTarget(event: Protocol.Target.detachedFromTargetPayload) {
|
||||||
@ -981,33 +986,12 @@ class FrameSession {
|
|||||||
await this._client._sendMayFail('Page.stopScreencast');
|
await this._client._sendMayFail('Page.stopScreencast');
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateExtraHTTPHeaders(initial: boolean): Promise<void> {
|
|
||||||
const headers = network.mergeHeaders([
|
|
||||||
this._crPage._browserContext._options.extraHTTPHeaders,
|
|
||||||
this._page.extraHTTPHeaders()
|
|
||||||
]);
|
|
||||||
if (!initial || headers.length)
|
|
||||||
await this._client.send('Network.setExtraHTTPHeaders', { headers: headersArrayToObject(headers, false /* lowerCase */) });
|
|
||||||
}
|
|
||||||
|
|
||||||
async _updateGeolocation(initial: boolean): Promise<void> {
|
async _updateGeolocation(initial: boolean): Promise<void> {
|
||||||
const geolocation = this._crPage._browserContext._options.geolocation;
|
const geolocation = this._crPage._browserContext._options.geolocation;
|
||||||
if (!initial || geolocation)
|
if (!initial || geolocation)
|
||||||
await this._client.send('Emulation.setGeolocationOverride', geolocation || {});
|
await this._client.send('Emulation.setGeolocationOverride', geolocation || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateOffline(initial: boolean): Promise<void> {
|
|
||||||
const offline = !!this._crPage._browserContext._options.offline;
|
|
||||||
if (!initial || offline)
|
|
||||||
await this._networkManager.setOffline(offline);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _updateHttpCredentials(initial: boolean): Promise<void> {
|
|
||||||
const credentials = this._crPage._browserContext._options.httpCredentials || null;
|
|
||||||
if (!initial || credentials)
|
|
||||||
await this._networkManager.authenticate(credentials);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _updateViewport(preserveWindowBoundaries?: boolean): Promise<void> {
|
async _updateViewport(preserveWindowBoundaries?: boolean): Promise<void> {
|
||||||
if (this._crPage._browserContext._browser.isClank())
|
if (this._crPage._browserContext._browser.isClank())
|
||||||
return;
|
return;
|
||||||
@ -1106,10 +1090,6 @@ class FrameSession {
|
|||||||
await session.send('Page.setFontFamilies', fontFamilies);
|
await session.send('Page.setFontFamilies', fontFamilies);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateRequestInterception(): Promise<void> {
|
|
||||||
await this._networkManager.setRequestInterception(this._page.needsRequestInterception());
|
|
||||||
}
|
|
||||||
|
|
||||||
async _updateFileChooserInterception(initial: boolean) {
|
async _updateFileChooserInterception(initial: boolean) {
|
||||||
const enabled = this._page.fileChooserIntercepted();
|
const enabled = this._page.fileChooserIntercepted();
|
||||||
if (initial && !enabled)
|
if (initial && !enabled)
|
||||||
|
@ -34,13 +34,13 @@ export class CRServiceWorker extends Worker {
|
|||||||
this._session = session;
|
this._session = session;
|
||||||
this._browserContext = browserContext;
|
this._browserContext = browserContext;
|
||||||
if (!!process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS)
|
if (!!process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS)
|
||||||
this._networkManager = new CRNetworkManager(session, null, this, null);
|
this._networkManager = new CRNetworkManager(null, this);
|
||||||
session.once('Runtime.executionContextCreated', event => {
|
session.once('Runtime.executionContextCreated', event => {
|
||||||
this._createExecutionContext(new CRExecutionContext(session, event.context));
|
this._createExecutionContext(new CRExecutionContext(session, event.context));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this._networkManager && this._isNetworkInspectionEnabled()) {
|
if (this._networkManager && this._isNetworkInspectionEnabled()) {
|
||||||
this._networkManager.initialize().catch(() => {});
|
this._networkManager.addSession(session, undefined, true /* isMain */).catch(() => {});
|
||||||
this.updateRequestInterception();
|
this.updateRequestInterception();
|
||||||
this.updateExtraHTTPHeaders(true);
|
this.updateExtraHTTPHeaders(true);
|
||||||
this.updateHttpCredentials(true);
|
this.updateHttpCredentials(true);
|
||||||
@ -56,6 +56,7 @@ export class CRServiceWorker extends Worker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override didClose() {
|
override didClose() {
|
||||||
|
this._networkManager?.removeSession(this._session);
|
||||||
this._session.dispose();
|
this._session.dispose();
|
||||||
super.didClose();
|
super.didClose();
|
||||||
}
|
}
|
||||||
|
@ -224,3 +224,32 @@ it('should report and intercept network from nested worker', async function({ pa
|
|||||||
|
|
||||||
await expect.poll(() => messages).toEqual(['{"foo":"not bar"}', '{"foo":"not bar"}']);
|
await expect.poll(() => messages).toEqual(['{"foo":"not bar"}', '{"foo":"not bar"}']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support extra http headers', async ({ page, server }) => {
|
||||||
|
await page.setExtraHTTPHeaders({ foo: 'bar' });
|
||||||
|
const [worker, request1] = await Promise.all([
|
||||||
|
page.waitForEvent('worker'),
|
||||||
|
server.waitForRequest('/worker/worker.js'),
|
||||||
|
page.goto(server.PREFIX + '/worker/worker.html'),
|
||||||
|
]);
|
||||||
|
const [request2] = await Promise.all([
|
||||||
|
server.waitForRequest('/one-style.css'),
|
||||||
|
worker.evaluate(url => fetch(url), server.PREFIX + '/one-style.css'),
|
||||||
|
]);
|
||||||
|
expect(request1.headers['foo']).toBe('bar');
|
||||||
|
expect(request2.headers['foo']).toBe('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support offline', async ({ page, server, browserName }) => {
|
||||||
|
it.fixme(browserName === 'firefox');
|
||||||
|
|
||||||
|
const [worker] = await Promise.all([
|
||||||
|
page.waitForEvent('worker'),
|
||||||
|
page.goto(server.PREFIX + '/worker/worker.html'),
|
||||||
|
]);
|
||||||
|
await page.context().setOffline(true);
|
||||||
|
expect(await worker.evaluate(() => navigator.onLine)).toBe(false);
|
||||||
|
expect(await worker.evaluate(() => fetch('/one-style.css').catch(e => 'error'))).toBe('error');
|
||||||
|
await page.context().setOffline(false);
|
||||||
|
expect(await worker.evaluate(() => navigator.onLine)).toBe(true);
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user