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) { async _onRoute(route: network.Route) {
route._context = this;
const routeHandlers = this._routes.slice(); const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) { for (const routeHandler of routeHandlers) {
if (!routeHandler.matches(route.request().url())) if (!routeHandler.matches(route.request().url()))

View File

@ -33,6 +33,7 @@ import { urlMatches } from '../utils/network';
import { MultiMap } from '../utils/multimap'; import { MultiMap } from '../utils/multimap';
import { APIResponse } from './fetch'; import { APIResponse } from './fetch';
import type { Serializable } from '../../types/structs'; import type { Serializable } from '../../types/structs';
import type { BrowserContext } from './browserContext';
export type NetworkCookie = { export type NetworkCookie = {
name: string, name: string,
@ -158,11 +159,6 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
return this._provisionalHeaders.headers(); return this._provisionalHeaders.headers();
} }
_context() {
// TODO: make sure this works for service worker requests.
return this.frame().page().context();
}
_actualHeaders(): Promise<RawHeaders> { _actualHeaders(): Promise<RawHeaders> {
if (this._fallbackOverrides.headers) if (this._fallbackOverrides.headers)
return Promise.resolve(RawHeaders._fromHeadersObjectLossy(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 { export class Route extends ChannelOwner<channels.RouteChannel> implements api.Route {
private _handlingPromise: ManualPromise<boolean> | null = null; private _handlingPromise: ManualPromise<boolean> | null = null;
_context!: BrowserContext;
static from(route: channels.RouteChannel): Route { static from(route: channels.RouteChannel): Route {
return (route as any)._object; 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> { async fetch(options: FallbackOverrides & { maxRedirects?: number, timeout?: number } = {}): Promise<APIResponse> {
return await this._wrapApiCall(async () => { return await this._wrapApiCall(async () => {
const context = this.request()._context(); return this._context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
return 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) { private async _onRoute(route: Route) {
route._context = this.context();
const routeHandlers = this._routes.slice(); const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) { for (const routeHandler of routeHandlers) {
if (!routeHandler.matches(route.request().url())) 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) { private constructor(scope: RequestDispatcher, route: Route) {
super(scope, 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 request: scope
}); });
} }
async continue(params: channels.RouteContinueParams, metadata: CallMetadata): Promise<channels.RouteContinueResult> { async continue(params: channels.RouteContinueParams, metadata: CallMetadata): Promise<channels.RouteContinueResult> {
// Used to discriminate between continue in tracing.
await this._object.continue({ await this._object.continue({
url: params.url, url: params.url,
method: params.method, 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> { async fulfill(params: channels.RouteFulfillParams, metadata: CallMetadata): Promise<void> {
// Used to discriminate between fulfills in tracing.
await this._object.fulfill(params); await this._object.fulfill(params);
} }
async abort(params: channels.RouteAbortParams, metadata: CallMetadata): Promise<void> { async abort(params: channels.RouteAbortParams, metadata: CallMetadata): Promise<void> {
// Used to discriminate between abort in tracing.
await this._object.abort(params.errorCode || 'failed'); 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 page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true); 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 }) => { 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; const request = await requestPromise;
expect((await request.postBody).toString()).toBe(JSON.stringify({ 'foo': 'bar' })); 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');
});