fix(route): make sure Route.fetch works for popup main request (#26590)

References #24603.
This commit is contained in:
Dmitry Gozman 2023-08-21 16:48:51 -07:00 committed by GitHub
parent c3c3c7f53c
commit 72bdd43e69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 23 additions and 11 deletions

View File

@ -190,6 +190,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
}
async _onRoute(route: network.Route) {
route._context = this;
const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) {
if (!routeHandler.matches(route.request().url()))

View File

@ -33,6 +33,7 @@ import { urlMatches } from '../utils/network';
import { MultiMap } from '../utils/multimap';
import { APIResponse } from './fetch';
import type { Serializable } from '../../types/structs';
import type { BrowserContext } from './browserContext';
export type NetworkCookie = {
name: string,
@ -158,11 +159,6 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
return this._provisionalHeaders.headers();
}
_context() {
// TODO: make sure this works for service worker requests.
return this.frame().page().context();
}
_actualHeaders(): Promise<RawHeaders> {
if (this._fallbackOverrides.headers)
return Promise.resolve(RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers));
@ -277,6 +273,7 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
export class Route extends ChannelOwner<channels.RouteChannel> implements api.Route {
private _handlingPromise: ManualPromise<boolean> | null = null;
_context!: BrowserContext;
static from(route: channels.RouteChannel): Route {
return (route as any)._object;
@ -322,8 +319,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
async fetch(options: FallbackOverrides & { maxRedirects?: number, timeout?: number } = {}): Promise<APIResponse> {
return await this._wrapApiCall(async () => {
const context = this.request()._context();
return context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
return this._context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
});
}

View File

@ -171,6 +171,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
}
private async _onRoute(route: Route) {
route._context = this.context();
const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) {
if (!routeHandler.matches(route.request().url()))

View File

@ -128,13 +128,12 @@ export class RouteDispatcher extends Dispatcher<Route, channels.RouteChannel, Re
private constructor(scope: RequestDispatcher, route: Route) {
super(scope, route, 'Route', {
// Context route can point to a non-reported request.
// Context route can point to a non-reported request, so we send the request in the initializer.
request: scope
});
}
async continue(params: channels.RouteContinueParams, metadata: CallMetadata): Promise<channels.RouteContinueResult> {
// Used to discriminate between continue in tracing.
await this._object.continue({
url: params.url,
method: params.method,
@ -145,12 +144,10 @@ export class RouteDispatcher extends Dispatcher<Route, channels.RouteChannel, Re
}
async fulfill(params: channels.RouteFulfillParams, metadata: CallMetadata): Promise<void> {
// Used to discriminate between fulfills in tracing.
await this._object.fulfill(params);
}
async abort(params: channels.RouteAbortParams, metadata: CallMetadata): Promise<void> {
// Used to discriminate between abort in tracing.
await this._object.abort(params.errorCode || 'failed');
}

View File

@ -121,6 +121,7 @@ it('should work with clicking target=_blank', async ({ page, server }) => {
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true);
expect(popup.mainFrame().page()).toBe(popup);
});
it('should work with fake-clicking target=_blank and rel=noopener', async ({ page, server }) => {

View File

@ -265,3 +265,19 @@ it('should intercept with post data override', async ({ page, server, isElectron
const request = await requestPromise;
expect((await request.postBody).toString()).toBe(JSON.stringify({ 'foo': 'bar' }));
});
it('should fulfill popup main request using alias', async ({ page, server, isElectron, isAndroid }) => {
it.fixme(isElectron, 'error: Browser context management is not supported.');
it.skip(isAndroid, 'The internal Android localhost (10.0.0.2) != the localhost on the host');
await page.context().route('**/*', async route => {
const response = await route.fetch();
await route.fulfill({ response, body: 'hello' });
});
await page.setContent(`<a target=_blank href="${server.EMPTY_PAGE}">click me</a>`);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.getByText('click me').click(),
]);
await expect(popup.locator('body')).toHaveText('hello');
});