mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix(chromium): continue requests paused for the second time (#27429)
Sometimes Chromium restarts requests. This leads to multiple `Fetch.requestPaused` for a single `Network.requestWillBeSent`. Fixes #27294.
This commit is contained in:
parent
7dcba6f5f0
commit
d426f2fd4e
@ -206,6 +206,21 @@ export class CRNetworkManager {
|
|||||||
this._onRequest(sessionInfo, requestWillBeSentEvent, event);
|
this._onRequest(sessionInfo, requestWillBeSentEvent, event);
|
||||||
this._requestIdToRequestWillBeSentEvent.delete(requestId);
|
this._requestIdToRequestWillBeSentEvent.delete(requestId);
|
||||||
} else {
|
} else {
|
||||||
|
const existingRequest = this._requestIdToRequest.get(requestId);
|
||||||
|
const alreadyContinuedParams = existingRequest?._route?._alreadyContinuedParams;
|
||||||
|
if (alreadyContinuedParams) {
|
||||||
|
// Sometimes Chromium network stack restarts the request internally.
|
||||||
|
// For example, when no-cors request hits a "less public address space", it should be resent with cors.
|
||||||
|
// There are some more examples here: https://source.chromium.org/chromium/chromium/src/+/main:services/network/url_loader.cc;l=1205-1234;drc=d5dd931e0ad3d9ffe74888ec62a3cc106efd7ea6
|
||||||
|
// There are probably even more cases deep inside the network stack.
|
||||||
|
//
|
||||||
|
// Anyway, in this case, continue the request in the same way as before, and it should go through.
|
||||||
|
this._session._sendMayFail('Fetch.continueRequest', {
|
||||||
|
...alreadyContinuedParams,
|
||||||
|
requestId: event.requestId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._requestIdToRequestPausedEvent.set(requestId, event);
|
this._requestIdToRequestPausedEvent.set(requestId, event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,14 +383,20 @@ export class CRNetworkManager {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_deleteRequest(request: InterceptableRequest) {
|
||||||
|
this._requestIdToRequest.delete(request._requestId);
|
||||||
|
if (request._route)
|
||||||
|
request._route._alreadyContinuedParams = undefined;
|
||||||
|
if (request._interceptionId)
|
||||||
|
this._attemptedAuthentications.delete(request._interceptionId);
|
||||||
|
}
|
||||||
|
|
||||||
_handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response, timestamp: number, hasExtraInfo: boolean) {
|
_handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response, timestamp: number, hasExtraInfo: boolean) {
|
||||||
const response = this._createResponse(request, responsePayload, hasExtraInfo);
|
const response = this._createResponse(request, responsePayload, hasExtraInfo);
|
||||||
response.setTransferSize(null);
|
response.setTransferSize(null);
|
||||||
response.setEncodedBodySize(null);
|
response.setEncodedBodySize(null);
|
||||||
response._requestFinished((timestamp - request._timestamp) * 1000);
|
response._requestFinished((timestamp - request._timestamp) * 1000);
|
||||||
this._requestIdToRequest.delete(request._requestId);
|
this._deleteRequest(request);
|
||||||
if (request._interceptionId)
|
|
||||||
this._attemptedAuthentications.delete(request._interceptionId);
|
|
||||||
(this._page?._frameManager || this._serviceWorker)!.requestReceivedResponse(response);
|
(this._page?._frameManager || this._serviceWorker)!.requestReceivedResponse(response);
|
||||||
(this._page?._frameManager || this._serviceWorker)!.reportRequestFinished(request.request, response);
|
(this._page?._frameManager || this._serviceWorker)!.reportRequestFinished(request.request, response);
|
||||||
}
|
}
|
||||||
@ -423,9 +444,7 @@ export class CRNetworkManager {
|
|||||||
response.responseHeadersSize().then(size => response.setEncodedBodySize(event.encodedDataLength - size));
|
response.responseHeadersSize().then(size => response.setEncodedBodySize(event.encodedDataLength - size));
|
||||||
response._requestFinished(helper.secondsToRoundishMillis(event.timestamp - request._timestamp));
|
response._requestFinished(helper.secondsToRoundishMillis(event.timestamp - request._timestamp));
|
||||||
}
|
}
|
||||||
this._requestIdToRequest.delete(request._requestId);
|
this._deleteRequest(request);
|
||||||
if (request._interceptionId)
|
|
||||||
this._attemptedAuthentications.delete(request._interceptionId);
|
|
||||||
(this._page?._frameManager || this._serviceWorker)!.reportRequestFinished(request.request, response);
|
(this._page?._frameManager || this._serviceWorker)!.reportRequestFinished(request.request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,9 +477,7 @@ export class CRNetworkManager {
|
|||||||
response.setEncodedBodySize(null);
|
response.setEncodedBodySize(null);
|
||||||
response._requestFinished(helper.secondsToRoundishMillis(event.timestamp - request._timestamp));
|
response._requestFinished(helper.secondsToRoundishMillis(event.timestamp - request._timestamp));
|
||||||
}
|
}
|
||||||
this._requestIdToRequest.delete(request._requestId);
|
this._deleteRequest(request);
|
||||||
if (request._interceptionId)
|
|
||||||
this._attemptedAuthentications.delete(request._interceptionId);
|
|
||||||
request.request._setFailureText(event.errorText);
|
request.request._setFailureText(event.errorText);
|
||||||
(this._page?._frameManager || this._serviceWorker)!.requestFailed(request.request, !!event.canceled);
|
(this._page?._frameManager || this._serviceWorker)!.requestFailed(request.request, !!event.canceled);
|
||||||
}
|
}
|
||||||
@ -491,7 +508,7 @@ class InterceptableRequest {
|
|||||||
readonly _documentId: string | undefined;
|
readonly _documentId: string | undefined;
|
||||||
readonly _timestamp: number;
|
readonly _timestamp: number;
|
||||||
readonly _wallTime: number;
|
readonly _wallTime: number;
|
||||||
private _route: RouteImpl | null;
|
readonly _route: RouteImpl | null;
|
||||||
private _redirectedFrom: InterceptableRequest | null;
|
private _redirectedFrom: InterceptableRequest | null;
|
||||||
session: CRSession;
|
session: CRSession;
|
||||||
|
|
||||||
@ -529,18 +546,12 @@ class InterceptableRequest {
|
|||||||
|
|
||||||
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, type, method, postDataBuffer, headersObjectToArray(headers));
|
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, type, method, postDataBuffer, headersObjectToArray(headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
_routeForRedirectChain(): RouteImpl | null {
|
|
||||||
let request: InterceptableRequest = this;
|
|
||||||
while (request._redirectedFrom)
|
|
||||||
request = request._redirectedFrom;
|
|
||||||
return request._route;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RouteImpl implements network.RouteDelegate {
|
class RouteImpl implements network.RouteDelegate {
|
||||||
private readonly _session: CRSession;
|
private readonly _session: CRSession;
|
||||||
private _interceptionId: string;
|
private _interceptionId: string;
|
||||||
|
_alreadyContinuedParams: Protocol.Fetch.continueRequestParameters | undefined;
|
||||||
|
|
||||||
constructor(session: CRSession, interceptionId: string) {
|
constructor(session: CRSession, interceptionId: string) {
|
||||||
this._session = session;
|
this._session = session;
|
||||||
@ -548,15 +559,16 @@ class RouteImpl implements network.RouteDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async continue(request: network.Request, overrides: types.NormalizedContinueOverrides): Promise<void> {
|
async continue(request: network.Request, overrides: types.NormalizedContinueOverrides): Promise<void> {
|
||||||
// In certain cases, protocol will return error if the request was already canceled
|
this._alreadyContinuedParams = {
|
||||||
// or the page was closed. We should tolerate these errors.
|
|
||||||
await this._session._sendMayFail('Fetch.continueRequest', {
|
|
||||||
requestId: this._interceptionId!,
|
requestId: this._interceptionId!,
|
||||||
url: overrides.url,
|
url: overrides.url,
|
||||||
headers: overrides.headers,
|
headers: overrides.headers,
|
||||||
method: overrides.method,
|
method: overrides.method,
|
||||||
postData: overrides.postData ? overrides.postData.toString('base64') : undefined
|
postData: overrides.postData ? overrides.postData.toString('base64') : undefined
|
||||||
});
|
};
|
||||||
|
// In certain cases, protocol will return error if the request was already canceled
|
||||||
|
// or the page was closed. We should tolerate these errors.
|
||||||
|
await this._session._sendMayFail('Fetch.continueRequest', this._alreadyContinuedParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: types.NormalizedFulfillResponse) {
|
async fulfill(response: types.NormalizedFulfillResponse) {
|
||||||
|
@ -64,13 +64,6 @@ export class WKInterceptableRequest {
|
|||||||
resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers));
|
resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
_routeForRedirectChain(): WKRouteImpl | null {
|
|
||||||
let request: WKInterceptableRequest = this;
|
|
||||||
while (request._redirectedFrom)
|
|
||||||
request = request._redirectedFrom;
|
|
||||||
return request._route;
|
|
||||||
}
|
|
||||||
|
|
||||||
createResponse(responsePayload: Protocol.Network.Response): network.Response {
|
createResponse(responsePayload: Protocol.Network.Response): network.Response {
|
||||||
const getResponseBody = async () => {
|
const getResponseBody = async () => {
|
||||||
const response = await this._session.send('Network.getResponseBody', { requestId: this._requestId });
|
const response = await this._session.send('Network.getResponseBody', { requestId: this._requestId });
|
||||||
|
@ -25,6 +25,9 @@ export default defineConfig({
|
|||||||
use: {
|
use: {
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
ctViteConfig: {
|
ctViteConfig: {
|
||||||
|
build: {
|
||||||
|
assetsInlineLimit: 0,
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': resolve(__dirname, './src'),
|
'@': resolve(__dirname, './src'),
|
||||||
|
BIN
tests/components/ct-react-vite/src/assets/iconfont.woff2
Normal file
BIN
tests/components/ct-react-vite/src/assets/iconfont.woff2
Normal file
Binary file not shown.
@ -18,3 +18,12 @@ code {
|
|||||||
background-color: #1b1b1d;
|
background-color: #1b1b1d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'pwtest-iconfont';
|
||||||
|
/* See tests/assets/webfont/README.md */
|
||||||
|
src: url('./iconfont.woff2') format('woff2');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
.title-with-font {
|
||||||
|
font-family: pwtest-iconfont, sans-serif;
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import './TitleWithFont.css';
|
||||||
|
|
||||||
|
export default function TitleWithFont() {
|
||||||
|
return <div className='title-with-font'>+-</div>
|
||||||
|
}
|
22
tests/components/ct-react-vite/tests/route.spec.tsx
Normal file
22
tests/components/ct-react-vite/tests/route.spec.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { test, expect } from '@playwright/experimental-ct-react';
|
||||||
|
import TitleWithFont from '@/components/TitleWithFont';
|
||||||
|
|
||||||
|
test('should load font without routes', async ({ mount, page }) => {
|
||||||
|
const promise = page.waitForEvent('requestfinished', request => request.url().includes('iconfont'));
|
||||||
|
await mount(<TitleWithFont />);
|
||||||
|
const request = await promise;
|
||||||
|
const response = await request.response();
|
||||||
|
const body = await response!.body();
|
||||||
|
expect(body.length).toBe(2656);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should load font with routes', async ({ mount, page }) => {
|
||||||
|
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/27294' });
|
||||||
|
await page.route('**/*.json', r => r.continue());
|
||||||
|
const promise = page.waitForEvent('requestfinished', request => request.url().includes('iconfont'));
|
||||||
|
await mount(<TitleWithFont />);
|
||||||
|
const request = await promise;
|
||||||
|
const response = await request.response();
|
||||||
|
const body = await response!.body();
|
||||||
|
expect(body.length).toBe(2656);
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user