fix: make network manager multi session (#10793)
This commit is contained in:
parent
6f2e3db883
commit
085936bd7e
@ -113,7 +113,7 @@ export class FrameManager extends EventEmitter {
|
||||
super();
|
||||
this.#client = client;
|
||||
this.#page = page;
|
||||
this.#networkManager = new NetworkManager(client, ignoreHTTPSErrors, this);
|
||||
this.#networkManager = new NetworkManager(ignoreHTTPSErrors, this);
|
||||
this.#timeoutSettings = timeoutSettings;
|
||||
this.setupEventListeners(this.#client);
|
||||
client.once(CDPSessionEmittedEvents.Disconnected, () => {
|
||||
@ -176,12 +176,16 @@ export class FrameManager extends EventEmitter {
|
||||
this.#onClientDisconnect().catch(debugError);
|
||||
});
|
||||
await this.initialize(client);
|
||||
await this.#networkManager.updateClient(client);
|
||||
await this.#networkManager.addClient(client);
|
||||
if (frame) {
|
||||
frame.emit(FrameEmittedEvents.FrameSwappedByActivation);
|
||||
}
|
||||
}
|
||||
|
||||
async registerSecondaryPage(client: CDPSessionImpl): Promise<void> {
|
||||
await this.#networkManager.addClient(client);
|
||||
}
|
||||
|
||||
private setupEventListeners(session: CDPSession) {
|
||||
session.on('Page.frameAttached', event => {
|
||||
this.#onFrameAttached(session, event.frameId, event.parentFrameId);
|
||||
@ -222,13 +226,13 @@ export class FrameManager extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
async initialize(client: CDPSession = this.#client): Promise<void> {
|
||||
async initialize(client: CDPSession): Promise<void> {
|
||||
try {
|
||||
const networkInit = this.#networkManager.addClient(client);
|
||||
const result = await Promise.all([
|
||||
client.send('Page.enable'),
|
||||
client.send('Page.getFrameTree'),
|
||||
]);
|
||||
|
||||
const {frameTree} = result[1];
|
||||
this.#handleFrameTree(client, frameTree);
|
||||
await Promise.all([
|
||||
@ -236,10 +240,7 @@ export class FrameManager extends EventEmitter {
|
||||
client.send('Runtime.enable').then(() => {
|
||||
return this.#createIsolatedWorld(client, UTILITY_WORLD_NAME);
|
||||
}),
|
||||
// TODO: Network manager is not aware of OOP iframes yet.
|
||||
client === this.#client
|
||||
? this.#networkManager.initialize()
|
||||
: Promise.resolve(),
|
||||
networkInit,
|
||||
]);
|
||||
} catch (error) {
|
||||
// The target might have been closed before the initialization finished.
|
||||
@ -295,7 +296,7 @@ export class FrameManager extends EventEmitter {
|
||||
frame.updateClient(target._session()!);
|
||||
}
|
||||
this.setupEventListeners(target._session()!);
|
||||
void this.initialize(target._session());
|
||||
void this.initialize(target._session()!);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,11 +45,12 @@ class MockCDPSession extends EventEmitter {
|
||||
describe('NetworkManager', () => {
|
||||
it('should process extra info on multiple redirects', async () => {
|
||||
const mockCDPSession = new MockCDPSession();
|
||||
new NetworkManager(mockCDPSession, true, {
|
||||
const manager = new NetworkManager(true, {
|
||||
frame(): Frame | null {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
await manager.addClient(mockCDPSession);
|
||||
mockCDPSession.emit('Network.requestWillBeSent', {
|
||||
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
|
||||
loaderId: '7760711DEFCFA23132D98ABA6B4E175C',
|
||||
@ -476,11 +477,12 @@ describe('NetworkManager', () => {
|
||||
});
|
||||
it(`should handle "double pause" (crbug.com/1196004) Fetch.requestPaused events for the same Network.requestWillBeSent event`, async () => {
|
||||
const mockCDPSession = new MockCDPSession();
|
||||
const manager = new NetworkManager(mockCDPSession, true, {
|
||||
const manager = new NetworkManager(true, {
|
||||
frame(): Frame | null {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
await manager.addClient(mockCDPSession);
|
||||
await manager.setRequestInterception(true);
|
||||
|
||||
const requests: HTTPRequest[] = [];
|
||||
@ -562,11 +564,12 @@ describe('NetworkManager', () => {
|
||||
});
|
||||
it(`should handle Network.responseReceivedExtraInfo event after Network.responseReceived event (github.com/puppeteer/puppeteer/issues/8234)`, async () => {
|
||||
const mockCDPSession = new MockCDPSession();
|
||||
const manager = new NetworkManager(mockCDPSession, true, {
|
||||
const manager = new NetworkManager(true, {
|
||||
frame(): Frame | null {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
await manager.addClient(mockCDPSession);
|
||||
|
||||
const requests: HTTPRequest[] = [];
|
||||
manager.on(
|
||||
@ -680,11 +683,12 @@ describe('NetworkManager', () => {
|
||||
|
||||
it(`should resolve the response once the late responseReceivedExtraInfo event arrives`, async () => {
|
||||
const mockCDPSession = new MockCDPSession();
|
||||
const manager = new NetworkManager(mockCDPSession, true, {
|
||||
const manager = new NetworkManager(true, {
|
||||
frame(): Frame | null {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
await manager.addClient(mockCDPSession);
|
||||
|
||||
const finishedRequests: HTTPRequest[] = [];
|
||||
const pendingRequests: HTTPRequest[] = [];
|
||||
@ -832,11 +836,12 @@ describe('NetworkManager', () => {
|
||||
|
||||
it(`should send responses for iframe that don't receive loadingFinished event`, async () => {
|
||||
const mockCDPSession = new MockCDPSession();
|
||||
const manager = new NetworkManager(mockCDPSession, true, {
|
||||
const manager = new NetworkManager(true, {
|
||||
frame(): Frame | null {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
await manager.addClient(mockCDPSession);
|
||||
|
||||
const responses: HTTPResponse[] = [];
|
||||
const requests: HTTPRequest[] = [];
|
||||
@ -995,11 +1000,12 @@ describe('NetworkManager', () => {
|
||||
|
||||
it(`should send responses for iframe that don't receive loadingFinished event`, async () => {
|
||||
const mockCDPSession = new MockCDPSession();
|
||||
const manager = new NetworkManager(mockCDPSession, true, {
|
||||
const manager = new NetworkManager(true, {
|
||||
frame(): Frame | null {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
await manager.addClient(mockCDPSession);
|
||||
|
||||
const responses: HTTPResponse[] = [];
|
||||
const requests: HTTPRequest[] = [];
|
||||
@ -1141,11 +1147,12 @@ describe('NetworkManager', () => {
|
||||
|
||||
it(`should handle cached redirects`, async () => {
|
||||
const mockCDPSession = new MockCDPSession();
|
||||
const manager = new NetworkManager(mockCDPSession, true, {
|
||||
const manager = new NetworkManager(true, {
|
||||
frame(): Frame | null {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
await manager.addClient(mockCDPSession);
|
||||
|
||||
const responses: HTTPResponse[] = [];
|
||||
const requests: HTTPRequest[] = [];
|
||||
|
@ -16,13 +16,11 @@
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {Frame} from '../api/Frame.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {createDebuggableDeferred} from '../util/DebuggableDeferred.js';
|
||||
import {Deferred} from '../util/Deferred.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {CDPSession, CDPSessionEmittedEvents} from './Connection.js';
|
||||
import {EventEmitter, Handler} from './EventEmitter.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {HTTPRequest} from './HTTPRequest.js';
|
||||
import {HTTPResponse} from './HTTPResponse.js';
|
||||
import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js';
|
||||
@ -68,102 +66,103 @@ export const NetworkManagerEmittedEvents = {
|
||||
RequestFinished: Symbol('NetworkManager.RequestFinished'),
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface FrameProvider {
|
||||
frame(id: string): Frame | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class NetworkManager extends EventEmitter {
|
||||
#client: CDPSession;
|
||||
#ignoreHTTPSErrors: boolean;
|
||||
#frameManager: Pick<FrameManager, 'frame'>;
|
||||
#frameManager: FrameProvider;
|
||||
#networkEventManager = new NetworkEventManager();
|
||||
#extraHTTPHeaders: Record<string, string> = {};
|
||||
#extraHTTPHeaders?: Record<string, string>;
|
||||
#credentials?: Credentials;
|
||||
#attemptedAuthentications = new Set<string>();
|
||||
#userRequestInterceptionEnabled = false;
|
||||
#protocolRequestInterceptionEnabled = false;
|
||||
#userCacheDisabled = false;
|
||||
#emulatedNetworkConditions: InternalNetworkConditions = {
|
||||
offline: false,
|
||||
upload: -1,
|
||||
download: -1,
|
||||
latency: 0,
|
||||
};
|
||||
#deferredInit?: Deferred<void>;
|
||||
#userCacheDisabled?: boolean;
|
||||
#emulatedNetworkConditions?: InternalNetworkConditions;
|
||||
#userAgent?: string;
|
||||
#userAgentMetadata?: Protocol.Emulation.UserAgentMetadata;
|
||||
|
||||
#handlers = new Map<string, Handler<any>>([
|
||||
['Fetch.requestPaused', this.#onRequestPaused.bind(this)],
|
||||
['Fetch.authRequired', this.#onAuthRequired.bind(this)],
|
||||
['Network.requestWillBeSent', this.#onRequestWillBeSent.bind(this)],
|
||||
[
|
||||
'Network.requestServedFromCache',
|
||||
this.#onRequestServedFromCache.bind(this),
|
||||
],
|
||||
['Network.responseReceived', this.#onResponseReceived.bind(this)],
|
||||
['Network.loadingFinished', this.#onLoadingFinished.bind(this)],
|
||||
['Network.loadingFailed', this.#onLoadingFailed.bind(this)],
|
||||
[
|
||||
'Network.responseReceivedExtraInfo',
|
||||
this.#onResponseReceivedExtraInfo.bind(this),
|
||||
],
|
||||
#handlers = new Map<string, Function>([
|
||||
['Fetch.requestPaused', this.#onRequestPaused],
|
||||
['Fetch.authRequired', this.#onAuthRequired],
|
||||
['Network.requestWillBeSent', this.#onRequestWillBeSent],
|
||||
['Network.requestServedFromCache', this.#onRequestServedFromCache],
|
||||
['Network.responseReceived', this.#onResponseReceived],
|
||||
['Network.loadingFinished', this.#onLoadingFinished],
|
||||
['Network.loadingFailed', this.#onLoadingFailed],
|
||||
['Network.responseReceivedExtraInfo', this.#onResponseReceivedExtraInfo],
|
||||
]);
|
||||
|
||||
constructor(
|
||||
client: CDPSession,
|
||||
ignoreHTTPSErrors: boolean,
|
||||
frameManager: Pick<FrameManager, 'frame'>
|
||||
) {
|
||||
#clients = new Map<
|
||||
CDPSession,
|
||||
Array<{event: string | symbol; handler: Handler}>
|
||||
>();
|
||||
|
||||
constructor(ignoreHTTPSErrors: boolean, frameManager: FrameProvider) {
|
||||
super();
|
||||
this.#client = client;
|
||||
this.#ignoreHTTPSErrors = ignoreHTTPSErrors;
|
||||
this.#frameManager = frameManager;
|
||||
}
|
||||
|
||||
async addClient(client: CDPSession): Promise<void> {
|
||||
if (this.#clients.has(client)) {
|
||||
return;
|
||||
}
|
||||
const listeners: Array<{event: string | symbol; handler: Handler}> = [];
|
||||
this.#clients.set(client, listeners);
|
||||
for (const [event, handler] of this.#handlers) {
|
||||
this.#client.on(event, handler);
|
||||
listeners.push({
|
||||
event,
|
||||
handler: handler.bind(this, client),
|
||||
});
|
||||
client.on(event, listeners.at(-1)!.handler);
|
||||
}
|
||||
}
|
||||
|
||||
async updateClient(client: CDPSession): Promise<void> {
|
||||
this.#client = client;
|
||||
for (const [event, handler] of this.#handlers) {
|
||||
this.#client.on(event, handler);
|
||||
}
|
||||
this.#deferredInit = undefined;
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize calls should avoid async dependencies between CDP calls as those
|
||||
* might not resolve until after the target is resumed causing a deadlock.
|
||||
*/
|
||||
initialize(): Promise<void> {
|
||||
if (this.#deferredInit) {
|
||||
return this.#deferredInit.valueOrThrow();
|
||||
}
|
||||
this.#deferredInit = createDebuggableDeferred(
|
||||
'NetworkManager initialization timed out'
|
||||
);
|
||||
const init = Promise.all([
|
||||
listeners.push({
|
||||
event: CDPSessionEmittedEvents.Disconnected,
|
||||
handler: this.#removeClient.bind(this, client),
|
||||
});
|
||||
client.on(CDPSessionEmittedEvents.Disconnected, listeners.at(-1)!.handler);
|
||||
await Promise.all([
|
||||
this.#ignoreHTTPSErrors
|
||||
? this.#client.send('Security.setIgnoreCertificateErrors', {
|
||||
? client.send('Security.setIgnoreCertificateErrors', {
|
||||
ignore: true,
|
||||
})
|
||||
: null,
|
||||
this.#client.send('Network.enable'),
|
||||
client.send('Network.enable'),
|
||||
this.#applyExtraHTTPHeaders(client),
|
||||
this.#applyNetworkConditions(client),
|
||||
this.#applyProtocolCacheDisabled(client),
|
||||
this.#applyProtocolRequestInterception(client),
|
||||
this.#applyUserAgent(client),
|
||||
]);
|
||||
const deferredInitPromise = this.#deferredInit;
|
||||
init
|
||||
.then(() => {
|
||||
deferredInitPromise.resolve();
|
||||
})
|
||||
.catch(err => {
|
||||
deferredInitPromise.reject(err);
|
||||
});
|
||||
return this.#deferredInit.valueOrThrow();
|
||||
}
|
||||
|
||||
async #removeClient(client: CDPSession) {
|
||||
const listeners = this.#clients.get(client);
|
||||
for (const {event, handler} of listeners || []) {
|
||||
client.off(event, handler);
|
||||
}
|
||||
this.#clients.delete(client);
|
||||
}
|
||||
|
||||
async authenticate(credentials?: Credentials): Promise<void> {
|
||||
this.#credentials = credentials;
|
||||
await this.#updateProtocolRequestInterception();
|
||||
const enabled = this.#userRequestInterceptionEnabled || !!this.#credentials;
|
||||
if (enabled === this.#protocolRequestInterceptionEnabled) {
|
||||
return;
|
||||
}
|
||||
this.#protocolRequestInterceptionEnabled = enabled;
|
||||
await this.#applyToAllClients(
|
||||
this.#applyProtocolRequestInterception.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(
|
||||
@ -178,7 +177,15 @@ export class NetworkManager extends EventEmitter {
|
||||
);
|
||||
this.#extraHTTPHeaders[key.toLowerCase()] = value;
|
||||
}
|
||||
await this.#client.send('Network.setExtraHTTPHeaders', {
|
||||
|
||||
await this.#applyToAllClients(this.#applyExtraHTTPHeaders.bind(this));
|
||||
}
|
||||
|
||||
async #applyExtraHTTPHeaders(client: CDPSession) {
|
||||
if (this.#extraHTTPHeaders === undefined) {
|
||||
return;
|
||||
}
|
||||
await client.send('Network.setExtraHTTPHeaders', {
|
||||
headers: this.#extraHTTPHeaders,
|
||||
});
|
||||
}
|
||||
@ -192,13 +199,29 @@ export class NetworkManager extends EventEmitter {
|
||||
}
|
||||
|
||||
async setOfflineMode(value: boolean): Promise<void> {
|
||||
if (!this.#emulatedNetworkConditions) {
|
||||
this.#emulatedNetworkConditions = {
|
||||
offline: false,
|
||||
upload: -1,
|
||||
download: -1,
|
||||
latency: 0,
|
||||
};
|
||||
}
|
||||
this.#emulatedNetworkConditions.offline = value;
|
||||
await this.#updateNetworkConditions();
|
||||
await this.#applyToAllClients(this.#applyNetworkConditions.bind(this));
|
||||
}
|
||||
|
||||
async emulateNetworkConditions(
|
||||
networkConditions: NetworkConditions | null
|
||||
): Promise<void> {
|
||||
if (!this.#emulatedNetworkConditions) {
|
||||
this.#emulatedNetworkConditions = {
|
||||
offline: false,
|
||||
upload: -1,
|
||||
download: -1,
|
||||
latency: 0,
|
||||
};
|
||||
}
|
||||
this.#emulatedNetworkConditions.upload = networkConditions
|
||||
? networkConditions.upload
|
||||
: -1;
|
||||
@ -209,11 +232,22 @@ export class NetworkManager extends EventEmitter {
|
||||
? networkConditions.latency
|
||||
: 0;
|
||||
|
||||
await this.#updateNetworkConditions();
|
||||
await this.#applyToAllClients(this.#applyNetworkConditions.bind(this));
|
||||
}
|
||||
|
||||
async #updateNetworkConditions(): Promise<void> {
|
||||
await this.#client.send('Network.emulateNetworkConditions', {
|
||||
async #applyToAllClients(fn: (client: CDPSession) => Promise<unknown>) {
|
||||
await Promise.all(
|
||||
Array.from(this.#clients.keys()).map(client => {
|
||||
return fn(client);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async #applyNetworkConditions(client: CDPSession): Promise<void> {
|
||||
if (this.#emulatedNetworkConditions === undefined) {
|
||||
return;
|
||||
}
|
||||
await client.send('Network.emulateNetworkConditions', {
|
||||
offline: this.#emulatedNetworkConditions.offline,
|
||||
latency: this.#emulatedNetworkConditions.latency,
|
||||
uploadThroughput: this.#emulatedNetworkConditions.upload,
|
||||
@ -225,55 +259,71 @@ export class NetworkManager extends EventEmitter {
|
||||
userAgent: string,
|
||||
userAgentMetadata?: Protocol.Emulation.UserAgentMetadata
|
||||
): Promise<void> {
|
||||
await this.#client.send('Network.setUserAgentOverride', {
|
||||
userAgent: userAgent,
|
||||
userAgentMetadata: userAgentMetadata,
|
||||
this.#userAgent = userAgent;
|
||||
this.#userAgentMetadata = userAgentMetadata;
|
||||
await this.#applyToAllClients(this.#applyUserAgent.bind(this));
|
||||
}
|
||||
|
||||
async #applyUserAgent(client: CDPSession) {
|
||||
if (this.#userAgent === undefined) {
|
||||
return;
|
||||
}
|
||||
await client.send('Network.setUserAgentOverride', {
|
||||
userAgent: this.#userAgent,
|
||||
userAgentMetadata: this.#userAgentMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
async setCacheEnabled(enabled: boolean): Promise<void> {
|
||||
this.#userCacheDisabled = !enabled;
|
||||
await this.#updateProtocolCacheDisabled();
|
||||
await this.#applyToAllClients(this.#applyProtocolCacheDisabled.bind(this));
|
||||
}
|
||||
|
||||
async setRequestInterception(value: boolean): Promise<void> {
|
||||
this.#userRequestInterceptionEnabled = value;
|
||||
await this.#updateProtocolRequestInterception();
|
||||
}
|
||||
|
||||
async #updateProtocolRequestInterception(): Promise<void> {
|
||||
const enabled = this.#userRequestInterceptionEnabled || !!this.#credentials;
|
||||
if (enabled === this.#protocolRequestInterceptionEnabled) {
|
||||
return;
|
||||
}
|
||||
this.#protocolRequestInterceptionEnabled = enabled;
|
||||
if (enabled) {
|
||||
await this.#applyToAllClients(
|
||||
this.#applyProtocolRequestInterception.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
async #applyProtocolRequestInterception(client: CDPSession): Promise<void> {
|
||||
if (this.#userCacheDisabled === undefined) {
|
||||
this.#userCacheDisabled = false;
|
||||
}
|
||||
if (this.#protocolRequestInterceptionEnabled) {
|
||||
await Promise.all([
|
||||
this.#updateProtocolCacheDisabled(),
|
||||
this.#client.send('Fetch.enable', {
|
||||
this.#applyProtocolCacheDisabled(client),
|
||||
client.send('Fetch.enable', {
|
||||
handleAuthRequests: true,
|
||||
patterns: [{urlPattern: '*'}],
|
||||
}),
|
||||
]);
|
||||
} else {
|
||||
await Promise.all([
|
||||
this.#updateProtocolCacheDisabled(),
|
||||
this.#client.send('Fetch.disable'),
|
||||
this.#applyProtocolCacheDisabled(client),
|
||||
client.send('Fetch.disable'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#cacheDisabled(): boolean {
|
||||
return this.#userCacheDisabled;
|
||||
async #applyProtocolCacheDisabled(client: CDPSession): Promise<void> {
|
||||
if (this.#userCacheDisabled === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
async #updateProtocolCacheDisabled(): Promise<void> {
|
||||
await this.#client.send('Network.setCacheDisabled', {
|
||||
cacheDisabled: this.#cacheDisabled(),
|
||||
await client.send('Network.setCacheDisabled', {
|
||||
cacheDisabled: this.#userCacheDisabled,
|
||||
});
|
||||
}
|
||||
|
||||
#onRequestWillBeSent(event: Protocol.Network.RequestWillBeSentEvent): void {
|
||||
#onRequestWillBeSent(
|
||||
client: CDPSession,
|
||||
event: Protocol.Network.RequestWillBeSentEvent
|
||||
): void {
|
||||
// Request interception doesn't happen for data URLs with Network Service.
|
||||
if (
|
||||
this.#userRequestInterceptionEnabled &&
|
||||
@ -291,16 +341,19 @@ export class NetworkManager extends EventEmitter {
|
||||
if (requestPausedEvent) {
|
||||
const {requestId: fetchRequestId} = requestPausedEvent;
|
||||
this.#patchRequestEventHeaders(event, requestPausedEvent);
|
||||
this.#onRequest(event, fetchRequestId);
|
||||
this.#onRequest(client, event, fetchRequestId);
|
||||
this.#networkEventManager.forgetRequestPaused(networkRequestId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
this.#onRequest(event, undefined);
|
||||
this.#onRequest(client, event, undefined);
|
||||
}
|
||||
|
||||
#onAuthRequired(event: Protocol.Fetch.AuthRequiredEvent): void {
|
||||
#onAuthRequired(
|
||||
client: CDPSession,
|
||||
event: Protocol.Fetch.AuthRequiredEvent
|
||||
): void {
|
||||
let response: Protocol.Fetch.AuthChallengeResponse['response'] = 'Default';
|
||||
if (this.#attemptedAuthentications.has(event.requestId)) {
|
||||
response = 'CancelAuth';
|
||||
@ -312,7 +365,7 @@ export class NetworkManager extends EventEmitter {
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
};
|
||||
this.#client
|
||||
client
|
||||
.send('Fetch.continueWithAuth', {
|
||||
requestId: event.requestId,
|
||||
authChallengeResponse: {response, username, password},
|
||||
@ -327,12 +380,15 @@ export class NetworkManager extends EventEmitter {
|
||||
* CDP may send multiple Fetch.requestPaused
|
||||
* for the same Network.requestWillBeSent.
|
||||
*/
|
||||
#onRequestPaused(event: Protocol.Fetch.RequestPausedEvent): void {
|
||||
#onRequestPaused(
|
||||
client: CDPSession,
|
||||
event: Protocol.Fetch.RequestPausedEvent
|
||||
): void {
|
||||
if (
|
||||
!this.#userRequestInterceptionEnabled &&
|
||||
this.#protocolRequestInterceptionEnabled
|
||||
) {
|
||||
this.#client
|
||||
client
|
||||
.send('Fetch.continueRequest', {
|
||||
requestId: event.requestId,
|
||||
})
|
||||
@ -342,7 +398,7 @@ export class NetworkManager extends EventEmitter {
|
||||
const {networkId: networkRequestId, requestId: fetchRequestId} = event;
|
||||
|
||||
if (!networkRequestId) {
|
||||
this.#onRequestWithoutNetworkInstrumentation(event);
|
||||
this.#onRequestWithoutNetworkInstrumentation(client, event);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -364,7 +420,7 @@ export class NetworkManager extends EventEmitter {
|
||||
|
||||
if (requestWillBeSentEvent) {
|
||||
this.#patchRequestEventHeaders(requestWillBeSentEvent, event);
|
||||
this.#onRequest(requestWillBeSentEvent, fetchRequestId);
|
||||
this.#onRequest(client, requestWillBeSentEvent, fetchRequestId);
|
||||
} else {
|
||||
this.#networkEventManager.storeRequestPaused(networkRequestId, event);
|
||||
}
|
||||
@ -382,6 +438,7 @@ export class NetworkManager extends EventEmitter {
|
||||
}
|
||||
|
||||
#onRequestWithoutNetworkInstrumentation(
|
||||
client: CDPSession,
|
||||
event: Protocol.Fetch.RequestPausedEvent
|
||||
): void {
|
||||
// If an event has no networkId it should not have any network events. We
|
||||
@ -391,7 +448,7 @@ export class NetworkManager extends EventEmitter {
|
||||
: null;
|
||||
|
||||
const request = new HTTPRequest(
|
||||
this.#client,
|
||||
client,
|
||||
frame,
|
||||
event.requestId,
|
||||
this.#userRequestInterceptionEnabled,
|
||||
@ -403,6 +460,7 @@ export class NetworkManager extends EventEmitter {
|
||||
}
|
||||
|
||||
#onRequest(
|
||||
client: CDPSession,
|
||||
event: Protocol.Network.RequestWillBeSentEvent,
|
||||
fetchRequestId?: FetchRequestId
|
||||
): void {
|
||||
@ -434,6 +492,7 @@ export class NetworkManager extends EventEmitter {
|
||||
// requestWillBeSent event.
|
||||
if (request) {
|
||||
this.#handleRequestRedirect(
|
||||
client,
|
||||
request,
|
||||
event.redirectResponse,
|
||||
redirectResponseExtraInfo
|
||||
@ -446,7 +505,7 @@ export class NetworkManager extends EventEmitter {
|
||||
: null;
|
||||
|
||||
const request = new HTTPRequest(
|
||||
this.#client,
|
||||
client,
|
||||
frame,
|
||||
fetchRequestId,
|
||||
this.#userRequestInterceptionEnabled,
|
||||
@ -459,6 +518,7 @@ export class NetworkManager extends EventEmitter {
|
||||
}
|
||||
|
||||
#onRequestServedFromCache(
|
||||
_client: CDPSession,
|
||||
event: Protocol.Network.RequestServedFromCacheEvent
|
||||
): void {
|
||||
const request = this.#networkEventManager.getRequest(event.requestId);
|
||||
@ -469,12 +529,13 @@ export class NetworkManager extends EventEmitter {
|
||||
}
|
||||
|
||||
#handleRequestRedirect(
|
||||
client: CDPSession,
|
||||
request: HTTPRequest,
|
||||
responsePayload: Protocol.Network.Response,
|
||||
extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null
|
||||
): void {
|
||||
const response = new HTTPResponse(
|
||||
this.#client,
|
||||
client,
|
||||
request,
|
||||
responsePayload,
|
||||
extraInfo
|
||||
@ -490,6 +551,7 @@ export class NetworkManager extends EventEmitter {
|
||||
}
|
||||
|
||||
#emitResponseEvent(
|
||||
client: CDPSession,
|
||||
responseReceived: Protocol.Network.ResponseReceivedEvent,
|
||||
extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null
|
||||
): void {
|
||||
@ -521,7 +583,7 @@ export class NetworkManager extends EventEmitter {
|
||||
}
|
||||
|
||||
const response = new HTTPResponse(
|
||||
this.#client,
|
||||
client,
|
||||
request,
|
||||
responseReceived.response,
|
||||
extraInfo
|
||||
@ -530,7 +592,10 @@ export class NetworkManager extends EventEmitter {
|
||||
this.emit(NetworkManagerEmittedEvents.Response, response);
|
||||
}
|
||||
|
||||
#onResponseReceived(event: Protocol.Network.ResponseReceivedEvent): void {
|
||||
#onResponseReceived(
|
||||
client: CDPSession,
|
||||
event: Protocol.Network.ResponseReceivedEvent
|
||||
): void {
|
||||
const request = this.#networkEventManager.getRequest(event.requestId);
|
||||
let extraInfo = null;
|
||||
if (request && !request._fromMemoryCache && event.hasExtraInfo) {
|
||||
@ -545,10 +610,11 @@ export class NetworkManager extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.#emitResponseEvent(event, extraInfo);
|
||||
this.#emitResponseEvent(client, event, extraInfo);
|
||||
}
|
||||
|
||||
#onResponseReceivedExtraInfo(
|
||||
client: CDPSession,
|
||||
event: Protocol.Network.ResponseReceivedExtraInfoEvent
|
||||
): void {
|
||||
// We may have skipped a redirect response/request pair due to waiting for
|
||||
@ -559,7 +625,7 @@ export class NetworkManager extends EventEmitter {
|
||||
);
|
||||
if (redirectInfo) {
|
||||
this.#networkEventManager.responseExtraInfo(event.requestId).push(event);
|
||||
this.#onRequest(redirectInfo.event, redirectInfo.fetchRequestId);
|
||||
this.#onRequest(client, redirectInfo.event, redirectInfo.fetchRequestId);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -570,7 +636,11 @@ export class NetworkManager extends EventEmitter {
|
||||
);
|
||||
if (queuedEvents) {
|
||||
this.#networkEventManager.forgetQueuedEventGroup(event.requestId);
|
||||
this.#emitResponseEvent(queuedEvents.responseReceivedEvent, event);
|
||||
this.#emitResponseEvent(
|
||||
client,
|
||||
queuedEvents.responseReceivedEvent,
|
||||
event
|
||||
);
|
||||
if (queuedEvents.loadingFinishedEvent) {
|
||||
this.#emitLoadingFinished(queuedEvents.loadingFinishedEvent);
|
||||
}
|
||||
@ -597,7 +667,10 @@ export class NetworkManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
#onLoadingFinished(event: Protocol.Network.LoadingFinishedEvent): void {
|
||||
#onLoadingFinished(
|
||||
_client: CDPSession,
|
||||
event: Protocol.Network.LoadingFinishedEvent
|
||||
): void {
|
||||
// If the response event for this request is still waiting on a
|
||||
// corresponding ExtraInfo event, then wait to emit this event too.
|
||||
const queuedEvents = this.#networkEventManager.getQueuedEventGroup(
|
||||
@ -627,7 +700,10 @@ export class NetworkManager extends EventEmitter {
|
||||
this.emit(NetworkManagerEmittedEvents.RequestFinished, request);
|
||||
}
|
||||
|
||||
#onLoadingFailed(event: Protocol.Network.LoadingFailedEvent): void {
|
||||
#onLoadingFailed(
|
||||
_client: CDPSession,
|
||||
event: Protocol.Network.LoadingFailedEvent
|
||||
): void {
|
||||
// If the response event for this request is still waiting on a
|
||||
// corresponding ExtraInfo event, then wait to emit this event too.
|
||||
const queuedEvents = this.#networkEventManager.getQueuedEventGroup(
|
||||
|
@ -329,6 +329,15 @@ export class CDPPage extends Page {
|
||||
await this.#frameManager.swapFrameTree(newSession);
|
||||
this.#setupEventListeners();
|
||||
});
|
||||
this.#tabSession?.on(
|
||||
CDPSessionEmittedEvents.Ready,
|
||||
(session: CDPSessionImpl) => {
|
||||
if (session._target()._subtype() !== 'prerender') {
|
||||
return;
|
||||
}
|
||||
this.#frameManager.registerSecondaryPage(session).catch(debugError);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#setupEventListeners() {
|
||||
@ -399,7 +408,7 @@ export class CDPPage extends Page {
|
||||
async #initialize(): Promise<void> {
|
||||
try {
|
||||
await Promise.all([
|
||||
this.#frameManager.initialize(),
|
||||
this.#frameManager.initialize(this.#client),
|
||||
this.#client.send('Performance.enable'),
|
||||
this.#client.send('Log.enable'),
|
||||
]);
|
||||
|
@ -3911,6 +3911,12 @@
|
||||
"parameters": ["firefox", "webDriverBiDi"],
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[prerender.spec] Prerender with network requests can receive requests from the prerendered page",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["firefox", "webDriverBiDi"],
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[proxy.spec] request proxy in incognito browser context should proxy requests when configured at browser level",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
|
@ -1,2 +1,5 @@
|
||||
<a id="navigate-within-document" href="#nav">Navigate within document</a>
|
||||
<a name="nav"></a>
|
||||
<script>
|
||||
fetch('oopif.html?requestFromOOPIF')
|
||||
</script>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<head></head>
|
||||
<head>
|
||||
<script>fetch('target.html?fromPrerendered')</script>
|
||||
</head>
|
||||
<body>target</body>
|
||||
|
@ -307,17 +307,23 @@ describeWithDebugLogs('OOPIF', function () {
|
||||
it('should load oopif iframes with subresources and request interception', async () => {
|
||||
const {server, page, context} = state;
|
||||
|
||||
const frame = page.waitForFrame(frame => {
|
||||
const framePromise = page.waitForFrame(frame => {
|
||||
return frame.url().endsWith('/oopif.html');
|
||||
});
|
||||
await page.setRequestInterception(true);
|
||||
page.on('request', request => {
|
||||
return request.continue();
|
||||
void request.continue();
|
||||
});
|
||||
await page.setRequestInterception(true);
|
||||
const requestPromise = page.waitForRequest(request => {
|
||||
return request.url().includes('requestFromOOPIF');
|
||||
});
|
||||
await page.goto(server.PREFIX + '/dynamic-oopif.html');
|
||||
await frame;
|
||||
const frame = await framePromise;
|
||||
const request = await requestPromise;
|
||||
expect(oopifs(context)).toHaveLength(1);
|
||||
expect(request.frame()).toBe(frame);
|
||||
});
|
||||
|
||||
it('should support frames within OOP iframes', async () => {
|
||||
const {server, page} = state;
|
||||
|
||||
|
@ -89,4 +89,44 @@ describe('Prerender', function () {
|
||||
expect(mainFrame).toBe(page.mainFrame());
|
||||
});
|
||||
});
|
||||
|
||||
describe('with network requests', () => {
|
||||
it('can receive requests from the prerendered page', async () => {
|
||||
const {page, server} = await getTestState();
|
||||
|
||||
const urls: string[] = [];
|
||||
page.on('request', request => {
|
||||
urls.push(request.url());
|
||||
});
|
||||
|
||||
await page.goto(server.PREFIX + '/prerender/index.html');
|
||||
const button = await page.waitForSelector('button');
|
||||
await button?.click();
|
||||
const mainFrame = page.mainFrame();
|
||||
const link = await mainFrame.waitForSelector('a');
|
||||
await Promise.all([mainFrame.waitForNavigation(), link?.click()]);
|
||||
expect(mainFrame).toBe(page.mainFrame());
|
||||
expect(
|
||||
await mainFrame.evaluate(() => {
|
||||
return document.body.innerText;
|
||||
})
|
||||
).toBe('target');
|
||||
expect(mainFrame).toBe(page.mainFrame());
|
||||
expect(
|
||||
urls.find(url => {
|
||||
return url.endsWith('prerender/target.html');
|
||||
})
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
urls.find(url => {
|
||||
return url.includes('prerender/index.html');
|
||||
})
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
urls.find(url => {
|
||||
return url.includes('prerender/target.html?fromPrerendered');
|
||||
})
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user