feat(chromium)!: roll to Chromium 97.0.4692.0 (r938248)

Issues: #7458
This commit is contained in:
Alex Rudenko 2021-11-23 08:19:14 +01:00 committed by GitHub
parent 4c3caaa3f9
commit ac162c561e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 794 additions and 20 deletions

View File

@ -59,7 +59,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"debug": "4.3.2", "debug": "4.3.2",
"devtools-protocol": "0.0.901419", "devtools-protocol": "0.0.937139",
"extract-zip": "2.0.1", "extract-zip": "2.0.1",
"https-proxy-agent": "5.0.0", "https-proxy-agent": "5.0.0",
"node-fetch": "2.6.5", "node-fetch": "2.6.5",

View File

@ -199,7 +199,7 @@ export class FrameManager extends EventEmitter {
} }
watcher.dispose(); watcher.dispose();
if (error) throw error; if (error) throw error;
return watcher.navigationResponse(); return await watcher.navigationResponse();
async function navigate( async function navigate(
client: CDPSession, client: CDPSession,
@ -243,7 +243,7 @@ export class FrameManager extends EventEmitter {
]); ]);
watcher.dispose(); watcher.dispose();
if (error) throw error; if (error) throw error;
return watcher.navigationResponse(); return await watcher.navigationResponse();
} }
private async _onAttachedToTarget( private async _onAttachedToTarget(

View File

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { CDPSession } from './Connection.js'; import { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js';
import { EventEmitter } from './EventEmitter.js';
import { Frame } from './FrameManager.js'; import { Frame } from './FrameManager.js';
import { HTTPResponse } from './HTTPResponse.js'; import { HTTPResponse } from './HTTPResponse.js';
import { assert } from './assert.js'; import { assert } from './assert.js';
@ -56,6 +58,13 @@ export interface ResponseForRequest {
*/ */
export type ResourceType = Lowercase<Protocol.Network.ResourceType>; export type ResourceType = Lowercase<Protocol.Network.ResourceType>;
interface CDPSession extends EventEmitter {
send<T extends keyof ProtocolMapping.Commands>(
method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): Promise<ProtocolMapping.Commands[T]['returnType']>;
}
/** /**
* *
* Represents an HTTP request sent by a page. * Represents an HTTP request sent by a page.

View File

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { CDPSession } from './Connection.js'; import { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js';
import { EventEmitter } from './EventEmitter.js';
import { Frame } from './FrameManager.js'; import { Frame } from './FrameManager.js';
import { HTTPRequest } from './HTTPRequest.js'; import { HTTPRequest } from './HTTPRequest.js';
import { SecurityDetails } from './SecurityDetails.js'; import { SecurityDetails } from './SecurityDetails.js';
@ -28,6 +30,13 @@ export interface RemoteAddress {
port: number; port: number;
} }
interface CDPSession extends EventEmitter {
send<T extends keyof ProtocolMapping.Commands>(
method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): Promise<ProtocolMapping.Commands[T]['returnType']>;
}
/** /**
* The HTTPResponse class represents responses which are received by the * The HTTPResponse class represents responses which are received by the
* {@link Page} class. * {@link Page} class.
@ -55,7 +64,8 @@ export class HTTPResponse {
constructor( constructor(
client: CDPSession, client: CDPSession,
request: HTTPRequest, request: HTTPRequest,
responsePayload: Protocol.Network.Response responsePayload: Protocol.Network.Response,
extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null
) { ) {
this._client = client; this._client = client;
this._request = request; this._request = request;
@ -68,13 +78,17 @@ export class HTTPResponse {
ip: responsePayload.remoteIPAddress, ip: responsePayload.remoteIPAddress,
port: responsePayload.remotePort, port: responsePayload.remotePort,
}; };
this._status = responsePayload.status; // TODO extract statusText from extraInfo.headersText instead if present
this._statusText = responsePayload.statusText; this._statusText = responsePayload.statusText;
this._url = request.url(); this._url = request.url();
this._fromDiskCache = !!responsePayload.fromDiskCache; this._fromDiskCache = !!responsePayload.fromDiskCache;
this._fromServiceWorker = !!responsePayload.fromServiceWorker; this._fromServiceWorker = !!responsePayload.fromServiceWorker;
for (const key of Object.keys(responsePayload.headers))
this._headers[key.toLowerCase()] = responsePayload.headers[key]; this._status = extraInfo ? extraInfo.statusCode : responsePayload.status;
const headers = extraInfo ? extraInfo.headers : responsePayload.headers;
for (const key of Object.keys(headers))
this._headers[key.toLowerCase()] = headers[key];
this._securityDetails = responsePayload.securityDetails this._securityDetails = responsePayload.securityDetails
? new SecurityDetails(responsePayload.securityDetails) ? new SecurityDetails(responsePayload.securityDetails)
: null; : null;

View File

@ -171,7 +171,8 @@ export class LifecycleWatcher {
this._checkLifecycleComplete(); this._checkLifecycleComplete();
} }
navigationResponse(): HTTPResponse | null { async navigationResponse(): Promise<HTTPResponse | null> {
// We may need to wait for ExtraInfo events before the request is complete.
return this._navigationRequest ? this._navigationRequest.response() : null; return this._navigationRequest ? this._navigationRequest.response() : null;
} }

View File

@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js';
import { EventEmitter } from './EventEmitter.js'; import { EventEmitter } from './EventEmitter.js';
import { Frame } from './FrameManager.js';
import { assert } from './assert.js'; import { assert } from './assert.js';
import { helper, debugError } from './helper.js'; import { helper, debugError } from './helper.js';
import { Protocol } from 'devtools-protocol'; import { Protocol } from 'devtools-protocol';
import { CDPSession } from './Connection.js';
import { FrameManager } from './FrameManager.js';
import { HTTPRequest } from './HTTPRequest.js'; import { HTTPRequest } from './HTTPRequest.js';
import { HTTPResponse } from './HTTPResponse.js'; import { HTTPResponse } from './HTTPResponse.js';
@ -62,6 +63,17 @@ export const NetworkManagerEmittedEvents = {
RequestFinished: Symbol('NetworkManager.RequestFinished'), RequestFinished: Symbol('NetworkManager.RequestFinished'),
} as const; } as const;
interface CDPSession extends EventEmitter {
send<T extends keyof ProtocolMapping.Commands>(
method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): Promise<ProtocolMapping.Commands[T]['returnType']>;
}
interface FrameManager {
frame(frameId: string): Frame | null;
}
/** /**
* @internal * @internal
*/ */
@ -110,6 +122,37 @@ export class NetworkManager extends EventEmitter {
>(); >();
_requestIdToRequest = new Map<string, HTTPRequest>(); _requestIdToRequest = new Map<string, HTTPRequest>();
/*
* The below maps are used to reconcile Network.responseReceivedExtraInfo
* events with their corresponding request. Each response and redirect
* response gets an ExtraInfo event, and we don't know which will come first.
* This means that we have to store a Response or an ExtraInfo for each
* response, and emit the event when we get both of them. In addition, to
* handle redirects, we have to make them Arrays to represent the chain of
* events.
*/
_requestIdToResponseReceivedExtraInfo = new Map<
string,
Protocol.Network.ResponseReceivedExtraInfoEvent[]
>();
_requestIdToQueuedRedirectInfoMap = new Map<
string,
Array<{
event: Protocol.Network.RequestWillBeSentEvent;
interceptionId?: string;
}>
>();
_requestIdToQueuedEvents = new Map<
string,
{
responseReceived: Protocol.Network.ResponseReceivedEvent;
promise: Promise<void>;
resolver: () => void;
loadingFinished?: Protocol.Network.LoadingFinishedEvent;
loadingFailed?: Protocol.Network.LoadingFailedEvent;
}
>();
_extraHTTPHeaders: Record<string, string> = {}; _extraHTTPHeaders: Record<string, string> = {};
_credentials?: Credentials = null; _credentials?: Credentials = null;
_attemptedAuthentications = new Set<string>(); _attemptedAuthentications = new Set<string>();
@ -152,6 +195,10 @@ export class NetworkManager extends EventEmitter {
this._onLoadingFinished.bind(this) this._onLoadingFinished.bind(this)
); );
this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this)); this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this));
this._client.on(
'Network.responseReceivedExtraInfo',
this._onResponseReceivedExtraInfo.bind(this)
);
} }
async initialize(): Promise<void> { async initialize(): Promise<void> {
@ -361,17 +408,61 @@ export class NetworkManager extends EventEmitter {
} }
} }
_requestIdToQueuedRedirectInfo(requestId: string): Array<{
event: Protocol.Network.RequestWillBeSentEvent;
interceptionId?: string;
}> {
if (!this._requestIdToQueuedRedirectInfoMap.has(requestId)) {
this._requestIdToQueuedRedirectInfoMap.set(requestId, []);
}
return this._requestIdToQueuedRedirectInfoMap.get(requestId);
}
_requestIdToResponseExtraInfo(
requestId: string
): Protocol.Network.ResponseReceivedExtraInfoEvent[] {
if (!this._requestIdToResponseReceivedExtraInfo.has(requestId)) {
this._requestIdToResponseReceivedExtraInfo.set(requestId, []);
}
return this._requestIdToResponseReceivedExtraInfo.get(requestId);
}
_onRequest( _onRequest(
event: Protocol.Network.RequestWillBeSentEvent, event: Protocol.Network.RequestWillBeSentEvent,
interceptionId?: string interceptionId?: string
): void { ): void {
let redirectChain = []; let redirectChain = [];
if (event.redirectResponse) { if (event.redirectResponse) {
// We want to emit a response and requestfinished for the
// redirectResponse, but we can't do so unless we have a
// responseExtraInfo ready to pair it up with. If we don't have any
// responseExtraInfos saved in our queue, they we have to wait until
// the next one to emit response and requestfinished, *and* we should
// also wait to emit this Request too because it should come after the
// response/requestfinished.
let redirectResponseExtraInfo = null;
if (event.redirectHasExtraInfo) {
redirectResponseExtraInfo = this._requestIdToResponseExtraInfo(
event.requestId
).shift();
if (!redirectResponseExtraInfo) {
this._requestIdToQueuedRedirectInfo(event.requestId).push({
event,
interceptionId,
});
return;
}
}
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(event.requestId);
// If we connect late to the target, we could have missed the // If we connect late to the target, we could have missed the
// requestWillBeSent event. // requestWillBeSent event.
if (request) { if (request) {
this._handleRequestRedirect(request, event.redirectResponse); this._handleRequestRedirect(
request,
event.redirectResponse,
redirectResponseExtraInfo
);
redirectChain = request._redirectChain; redirectChain = request._redirectChain;
} }
} }
@ -401,9 +492,15 @@ export class NetworkManager extends EventEmitter {
_handleRequestRedirect( _handleRequestRedirect(
request: HTTPRequest, request: HTTPRequest,
responsePayload: Protocol.Network.Response responsePayload: Protocol.Network.Response,
extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent
): void { ): void {
const response = new HTTPResponse(this._client, request, responsePayload); const response = new HTTPResponse(
this._client,
request,
responsePayload,
extraInfo
);
request._response = response; request._response = response;
request._redirectChain.push(request); request._redirectChain.push(request);
response._resolveBody( response._resolveBody(
@ -414,15 +511,93 @@ export class NetworkManager extends EventEmitter {
this.emit(NetworkManagerEmittedEvents.RequestFinished, request); this.emit(NetworkManagerEmittedEvents.RequestFinished, request);
} }
_onResponseReceived(event: Protocol.Network.ResponseReceivedEvent): void { _emitResponseEvent(
const request = this._requestIdToRequest.get(event.requestId); responseReceived: Protocol.Network.ResponseReceivedEvent,
extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent
): void {
const request = this._requestIdToRequest.get(responseReceived.requestId);
// FileUpload sends a response without a matching request. // FileUpload sends a response without a matching request.
if (!request) return; if (!request) return;
const response = new HTTPResponse(this._client, request, event.response);
const extraInfos = this._requestIdToResponseExtraInfo(
responseReceived.requestId
);
if (extraInfos.length) {
throw new Error(
'Unexpected extraInfo events for request ' + responseReceived.requestId
);
}
const response = new HTTPResponse(
this._client,
request,
responseReceived.response,
extraInfo
);
request._response = response; request._response = response;
this.emit(NetworkManagerEmittedEvents.Response, response); this.emit(NetworkManagerEmittedEvents.Response, response);
} }
_onResponseReceived(event: Protocol.Network.ResponseReceivedEvent): void {
const request = this._requestIdToRequest.get(event.requestId);
let extraInfo = null;
if (request && !request._fromMemoryCache && event.hasExtraInfo) {
extraInfo = this._requestIdToResponseExtraInfo(event.requestId).shift();
if (!extraInfo) {
// Wait until we get the corresponding ExtraInfo event.
let resolver = null;
const promise = new Promise<void>((resolve) => (resolver = resolve));
this._requestIdToQueuedEvents.set(event.requestId, {
responseReceived: event,
promise,
resolver,
});
return;
}
}
this._emitResponseEvent(event, extraInfo);
}
responseWaitingForExtraInfoPromise(requestId: string): Promise<void> {
const responseReceived = this._requestIdToQueuedEvents.get(requestId);
if (!responseReceived) return Promise.resolve();
return responseReceived.promise;
}
_onResponseReceivedExtraInfo(
event: Protocol.Network.ResponseReceivedExtraInfoEvent
): void {
// We may have skipped a redirect response/request pair due to waiting for
// this ExtraInfo event. If so, continue that work now that we have the
// request.
const redirectInfo = this._requestIdToQueuedRedirectInfo(
event.requestId
).shift();
if (redirectInfo) {
this._requestIdToResponseExtraInfo(event.requestId).push(event);
this._onRequest(redirectInfo.event, redirectInfo.interceptionId);
return;
}
// We may have skipped response and loading events because we didn't have
// this ExtraInfo event yet. If so, emit those events now.
const queuedEvents = this._requestIdToQueuedEvents.get(event.requestId);
if (queuedEvents) {
this._emitResponseEvent(queuedEvents.responseReceived, event);
if (queuedEvents.loadingFinished) {
this._emitLoadingFinished(queuedEvents.loadingFinished);
}
if (queuedEvents.loadingFailed) {
this._emitLoadingFailed(queuedEvents.loadingFailed);
}
queuedEvents.resolver();
return;
}
// Wait until we get another event that can use this ExtraInfo event.
this._requestIdToResponseExtraInfo(event.requestId).push(event);
}
_forgetRequest(request: HTTPRequest, events: boolean): void { _forgetRequest(request: HTTPRequest, events: boolean): void {
const requestId = request._requestId; const requestId = request._requestId;
const interceptionId = request._interceptionId; const interceptionId = request._interceptionId;
@ -433,10 +608,24 @@ export class NetworkManager extends EventEmitter {
if (events) { if (events) {
this._requestIdToRequestWillBeSentEvent.delete(requestId); this._requestIdToRequestWillBeSentEvent.delete(requestId);
this._requestIdToRequestPausedEvent.delete(requestId); this._requestIdToRequestPausedEvent.delete(requestId);
this._requestIdToQueuedEvents.delete(requestId);
this._requestIdToQueuedRedirectInfoMap.delete(requestId);
this._requestIdToResponseReceivedExtraInfo.delete(requestId);
} }
} }
_onLoadingFinished(event: Protocol.Network.LoadingFinishedEvent): void { _onLoadingFinished(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._requestIdToQueuedEvents.get(event.requestId);
if (queuedEvents) {
queuedEvents.loadingFinished = event;
} else {
this._emitLoadingFinished(event);
}
}
_emitLoadingFinished(event: Protocol.Network.LoadingFinishedEvent): void {
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(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
@ -450,6 +639,17 @@ export class NetworkManager extends EventEmitter {
} }
_onLoadingFailed(event: Protocol.Network.LoadingFailedEvent): void { _onLoadingFailed(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._requestIdToQueuedEvents.get(event.requestId);
if (queuedEvents) {
queuedEvents.loadingFailed = event;
} else {
this._emitLoadingFailed(event);
}
}
_emitLoadingFailed(event: Protocol.Network.LoadingFailedEvent): void {
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(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

View File

@ -20,6 +20,6 @@ type Revisions = Readonly<{
}>; }>;
export const PUPPETEER_REVISIONS: Revisions = { export const PUPPETEER_REVISIONS: Revisions = {
chromium: '901912', chromium: '938248',
firefox: 'latest', firefox: 'latest',
}; };

459
test/NetworkManager.spec.ts Normal file
View File

@ -0,0 +1,459 @@
/**
* Copyright 2020 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describeChromeOnly } from './mocha-utils'; // eslint-disable-line import/extensions
import { NetworkManager } from '../lib/cjs/puppeteer/common/NetworkManager.js';
import { EventEmitter } from '../lib/cjs/puppeteer/common/EventEmitter.js';
import { Frame } from '../lib/cjs/puppeteer/common/FrameManager.js';
describeChromeOnly('NetworkManager', () => {
it('should process extra info on multiple redirects', async () => {
class MockCDPSession extends EventEmitter {
send(): any {}
}
const mockCDPSession = new MockCDPSession();
new NetworkManager(mockCDPSession, true, {
frame(): Frame | null {
return null;
},
});
mockCDPSession.emit('Network.requestWillBeSent', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
loaderId: '7760711DEFCFA23132D98ABA6B4E175C',
documentURL: 'http://localhost:8907/redirect/1.html',
request: {
url: 'http://localhost:8907/redirect/1.html',
method: 'GET',
headers: {
'Upgrade-Insecure-Requests': '1',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/97.0.4691.0 Safari/537.36',
},
mixedContentType: 'none',
initialPriority: 'VeryHigh',
referrerPolicy: 'strict-origin-when-cross-origin',
isSameSite: true,
},
timestamp: 2111.55635,
wallTime: 1637315638.473634,
initiator: { type: 'other' },
redirectHasExtraInfo: false,
type: 'Document',
frameId: '099A5216AF03AAFEC988F214B024DF08',
hasUserGesture: false,
});
mockCDPSession.emit('Network.requestWillBeSentExtraInfo', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
associatedCookies: [],
headers: {
Host: 'localhost:8907',
Connection: 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/97.0.4691.0 Safari/537.36',
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document',
'Accept-Encoding': 'gzip, deflate, br',
},
connectTiming: { requestTime: 2111.557593 },
});
mockCDPSession.emit('Network.responseReceivedExtraInfo', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
blockedCookies: [],
headers: {
location: '/redirect/2.html',
Date: 'Fri, 19 Nov 2021 09:53:58 GMT',
Connection: 'keep-alive',
'Keep-Alive': 'timeout=5',
'Transfer-Encoding': 'chunked',
},
resourceIPAddressSpace: 'Local',
statusCode: 302,
headersText:
'HTTP/1.1 302 Found\r\nlocation: /redirect/2.html\r\nDate: Fri, 19 Nov 2021 09:53:58 GMT\r\nConnection: keep-alive\r\nKeep-Alive: timeout=5\r\nTransfer-Encoding: chunked\r\n\r\n',
});
mockCDPSession.emit('Network.requestWillBeSent', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
loaderId: '7760711DEFCFA23132D98ABA6B4E175C',
documentURL: 'http://localhost:8907/redirect/2.html',
request: {
url: 'http://localhost:8907/redirect/2.html',
method: 'GET',
headers: {
'Upgrade-Insecure-Requests': '1',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/97.0.4691.0 Safari/537.36',
},
mixedContentType: 'none',
initialPriority: 'VeryHigh',
referrerPolicy: 'strict-origin-when-cross-origin',
isSameSite: true,
},
timestamp: 2111.559124,
wallTime: 1637315638.47642,
initiator: { type: 'other' },
redirectHasExtraInfo: true,
redirectResponse: {
url: 'http://localhost:8907/redirect/1.html',
status: 302,
statusText: 'Found',
headers: {
location: '/redirect/2.html',
Date: 'Fri, 19 Nov 2021 09:53:58 GMT',
Connection: 'keep-alive',
'Keep-Alive': 'timeout=5',
'Transfer-Encoding': 'chunked',
},
mimeType: '',
connectionReused: false,
connectionId: 322,
remoteIPAddress: '[::1]',
remotePort: 8907,
fromDiskCache: false,
fromServiceWorker: false,
fromPrefetchCache: false,
encodedDataLength: 162,
timing: {
requestTime: 2111.557593,
proxyStart: -1,
proxyEnd: -1,
dnsStart: 0.241,
dnsEnd: 0.251,
connectStart: 0.251,
connectEnd: 0.47,
sslStart: -1,
sslEnd: -1,
workerStart: -1,
workerReady: -1,
workerFetchStart: -1,
workerRespondWithSettled: -1,
sendStart: 0.537,
sendEnd: 0.611,
pushStart: 0,
pushEnd: 0,
receiveHeadersEnd: 0.939,
},
responseTime: 1.637315638475744e12,
protocol: 'http/1.1',
securityState: 'secure',
},
type: 'Document',
frameId: '099A5216AF03AAFEC988F214B024DF08',
hasUserGesture: false,
});
mockCDPSession.emit('Network.requestWillBeSentExtraInfo', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
associatedCookies: [],
headers: {
Host: 'localhost:8907',
Connection: 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/97.0.4691.0 Safari/537.36',
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document',
'Accept-Encoding': 'gzip, deflate, br',
},
connectTiming: { requestTime: 2111.559346 },
});
mockCDPSession.emit('Network.requestWillBeSent', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
loaderId: '7760711DEFCFA23132D98ABA6B4E175C',
documentURL: 'http://localhost:8907/redirect/3.html',
request: {
url: 'http://localhost:8907/redirect/3.html',
method: 'GET',
headers: {
'Upgrade-Insecure-Requests': '1',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/97.0.4691.0 Safari/537.36',
},
mixedContentType: 'none',
initialPriority: 'VeryHigh',
referrerPolicy: 'strict-origin-when-cross-origin',
isSameSite: true,
},
timestamp: 2111.560249,
wallTime: 1637315638.477543,
initiator: { type: 'other' },
redirectHasExtraInfo: true,
redirectResponse: {
url: 'http://localhost:8907/redirect/2.html',
status: 302,
statusText: 'Found',
headers: {
location: '/redirect/3.html',
Date: 'Fri, 19 Nov 2021 09:53:58 GMT',
Connection: 'keep-alive',
'Keep-Alive': 'timeout=5',
'Transfer-Encoding': 'chunked',
},
mimeType: '',
connectionReused: true,
connectionId: 322,
remoteIPAddress: '[::1]',
remotePort: 8907,
fromDiskCache: false,
fromServiceWorker: false,
fromPrefetchCache: false,
encodedDataLength: 162,
timing: {
requestTime: 2111.559346,
proxyStart: -1,
proxyEnd: -1,
dnsStart: -1,
dnsEnd: -1,
connectStart: -1,
connectEnd: -1,
sslStart: -1,
sslEnd: -1,
workerStart: -1,
workerReady: -1,
workerFetchStart: -1,
workerRespondWithSettled: -1,
sendStart: 0.15,
sendEnd: 0.196,
pushStart: 0,
pushEnd: 0,
receiveHeadersEnd: 0.507,
},
responseTime: 1.637315638477063e12,
protocol: 'http/1.1',
securityState: 'secure',
},
type: 'Document',
frameId: '099A5216AF03AAFEC988F214B024DF08',
hasUserGesture: false,
});
mockCDPSession.emit('Network.responseReceivedExtraInfo', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
blockedCookies: [],
headers: {
location: '/redirect/3.html',
Date: 'Fri, 19 Nov 2021 09:53:58 GMT',
Connection: 'keep-alive',
'Keep-Alive': 'timeout=5',
'Transfer-Encoding': 'chunked',
},
resourceIPAddressSpace: 'Local',
statusCode: 302,
headersText:
'HTTP/1.1 302 Found\r\nlocation: /redirect/3.html\r\nDate: Fri, 19 Nov 2021 09:53:58 GMT\r\nConnection: keep-alive\r\nKeep-Alive: timeout=5\r\nTransfer-Encoding: chunked\r\n\r\n',
});
mockCDPSession.emit('Network.requestWillBeSentExtraInfo', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
associatedCookies: [],
headers: {
Host: 'localhost:8907',
Connection: 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/97.0.4691.0 Safari/537.36',
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document',
'Accept-Encoding': 'gzip, deflate, br',
},
connectTiming: { requestTime: 2111.560482 },
});
mockCDPSession.emit('Network.requestWillBeSent', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
loaderId: '7760711DEFCFA23132D98ABA6B4E175C',
documentURL: 'http://localhost:8907/empty.html',
request: {
url: 'http://localhost:8907/empty.html',
method: 'GET',
headers: {
'Upgrade-Insecure-Requests': '1',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/97.0.4691.0 Safari/537.36',
},
mixedContentType: 'none',
initialPriority: 'VeryHigh',
referrerPolicy: 'strict-origin-when-cross-origin',
isSameSite: true,
},
timestamp: 2111.561542,
wallTime: 1637315638.478837,
initiator: { type: 'other' },
redirectHasExtraInfo: true,
redirectResponse: {
url: 'http://localhost:8907/redirect/3.html',
status: 302,
statusText: 'Found',
headers: {
location: 'http://localhost:8907/empty.html',
Date: 'Fri, 19 Nov 2021 09:53:58 GMT',
Connection: 'keep-alive',
'Keep-Alive': 'timeout=5',
'Transfer-Encoding': 'chunked',
},
mimeType: '',
connectionReused: true,
connectionId: 322,
remoteIPAddress: '[::1]',
remotePort: 8907,
fromDiskCache: false,
fromServiceWorker: false,
fromPrefetchCache: false,
encodedDataLength: 178,
timing: {
requestTime: 2111.560482,
proxyStart: -1,
proxyEnd: -1,
dnsStart: -1,
dnsEnd: -1,
connectStart: -1,
connectEnd: -1,
sslStart: -1,
sslEnd: -1,
workerStart: -1,
workerReady: -1,
workerFetchStart: -1,
workerRespondWithSettled: -1,
sendStart: 0.149,
sendEnd: 0.198,
pushStart: 0,
pushEnd: 0,
receiveHeadersEnd: 0.478,
},
responseTime: 1.637315638478184e12,
protocol: 'http/1.1',
securityState: 'secure',
},
type: 'Document',
frameId: '099A5216AF03AAFEC988F214B024DF08',
hasUserGesture: false,
});
mockCDPSession.emit('Network.responseReceivedExtraInfo', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
blockedCookies: [],
headers: {
location: 'http://localhost:8907/empty.html',
Date: 'Fri, 19 Nov 2021 09:53:58 GMT',
Connection: 'keep-alive',
'Keep-Alive': 'timeout=5',
'Transfer-Encoding': 'chunked',
},
resourceIPAddressSpace: 'Local',
statusCode: 302,
headersText:
'HTTP/1.1 302 Found\r\nlocation: http://localhost:8907/empty.html\r\nDate: Fri, 19 Nov 2021 09:53:58 GMT\r\nConnection: keep-alive\r\nKeep-Alive: timeout=5\r\nTransfer-Encoding: chunked\r\n\r\n',
});
mockCDPSession.emit('Network.requestWillBeSentExtraInfo', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
associatedCookies: [],
headers: {
Host: 'localhost:8907',
Connection: 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/97.0.4691.0 Safari/537.36',
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document',
'Accept-Encoding': 'gzip, deflate, br',
},
connectTiming: { requestTime: 2111.561759 },
});
mockCDPSession.emit('Network.responseReceivedExtraInfo', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
blockedCookies: [],
headers: {
'Cache-Control': 'no-cache, no-store',
'Content-Type': 'text/html; charset=utf-8',
Date: 'Fri, 19 Nov 2021 09:53:58 GMT',
Connection: 'keep-alive',
'Keep-Alive': 'timeout=5',
'Content-Length': '0',
},
resourceIPAddressSpace: 'Local',
statusCode: 200,
headersText:
'HTTP/1.1 200 OK\r\nCache-Control: no-cache, no-store\r\nContent-Type: text/html; charset=utf-8\r\nDate: Fri, 19 Nov 2021 09:53:58 GMT\r\nConnection: keep-alive\r\nKeep-Alive: timeout=5\r\nContent-Length: 0\r\n\r\n',
});
mockCDPSession.emit('Network.responseReceived', {
requestId: '7760711DEFCFA23132D98ABA6B4E175C',
loaderId: '7760711DEFCFA23132D98ABA6B4E175C',
timestamp: 2111.563565,
type: 'Document',
response: {
url: 'http://localhost:8907/empty.html',
status: 200,
statusText: 'OK',
headers: {
'Cache-Control': 'no-cache, no-store',
'Content-Type': 'text/html; charset=utf-8',
Date: 'Fri, 19 Nov 2021 09:53:58 GMT',
Connection: 'keep-alive',
'Keep-Alive': 'timeout=5',
'Content-Length': '0',
},
mimeType: 'text/html',
connectionReused: true,
connectionId: 322,
remoteIPAddress: '[::1]',
remotePort: 8907,
fromDiskCache: false,
fromServiceWorker: false,
fromPrefetchCache: false,
encodedDataLength: 197,
timing: {
requestTime: 2111.561759,
proxyStart: -1,
proxyEnd: -1,
dnsStart: -1,
dnsEnd: -1,
connectStart: -1,
connectEnd: -1,
sslStart: -1,
sslEnd: -1,
workerStart: -1,
workerReady: -1,
workerFetchStart: -1,
workerRespondWithSettled: -1,
sendStart: 0.148,
sendEnd: 0.19,
pushStart: 0,
pushEnd: 0,
receiveHeadersEnd: 0.925,
},
responseTime: 1.637315638479928e12,
protocol: 'http/1.1',
securityState: 'secure',
},
hasExtraInfo: true,
frameId: '099A5216AF03AAFEC988F214B024DF08',
});
});
});

View File

@ -168,7 +168,8 @@ describeFailsFirefox('Accessibility', function () {
'<div tabIndex=-1 aria-roledescription="foo">Hi</div>' '<div tabIndex=-1 aria-roledescription="foo">Hi</div>'
); );
const snapshot = await page.accessibility.snapshot(); const snapshot = await page.accessibility.snapshot();
expect(snapshot.children[0].roledescription).toEqual('foo'); // See https://chromium-review.googlesource.com/c/chromium/src/+/3088862
expect(snapshot.children[0].roledescription).toEqual(undefined);
}); });
it('orientation', async () => { it('orientation', async () => {
const { page } = getTestState(); const { page } = getTestState();

View File

@ -21,6 +21,7 @@ import {
setupTestBrowserHooks, setupTestBrowserHooks,
setupTestPageAndContextHooks, setupTestPageAndContextHooks,
itFailsFirefox, itFailsFirefox,
shortWaitForArrayToHaveAtLeastNElements,
} from './mocha-utils'; // eslint-disable-line import/extensions } from './mocha-utils'; // eslint-disable-line import/extensions
describe('JSHandle', function () { describe('JSHandle', function () {
@ -390,6 +391,7 @@ describe('JSHandle', function () {
y: 15, y: 15,
}, },
}); });
await shortWaitForArrayToHaveAtLeastNElements(clicks, 2);
expect(clicks).toEqual([ expect(clicks).toEqual([
[45 + 60, 45 + 30], // margin + middle point offset [45 + 60, 45 + 30], // margin + middle point offset
[30 + 10, 30 + 15], // margin + offset [30 + 10, 30 + 15], // margin + offset

View File

@ -319,3 +319,17 @@ export const expectCookieEquals = (
expect(cookies).toEqual(expectedCookies); expect(cookies).toEqual(expectedCookies);
}; };
export const shortWaitForArrayToHaveAtLeastNElements = async (
data: unknown[],
minLength: number,
attempts = 3,
timeout = 50
): Promise<void> => {
for (let i = 0; i < attempts; i++) {
if (data.length >= minLength) {
break;
}
await new Promise((resolve) => setTimeout(resolve, timeout));
}
};

View File

@ -686,4 +686,77 @@ describe('network', function () {
expect(responses.get('one-style.html').fromCache()).toBe(false); expect(responses.get('one-style.html').fromCache()).toBe(false);
}); });
}); });
describeFailsFirefox('raw network headers', async () => {
it('Same-origin set-cookie navigation', async () => {
const { page, server } = getTestState();
const setCookieString = 'foo=bar';
server.setRoute('/empty.html', (req, res) => {
res.setHeader('set-cookie', setCookieString);
res.end('hello world');
});
const response = await page.goto(server.EMPTY_PAGE);
expect(response.headers()['set-cookie']).toBe(setCookieString);
});
it('Same-origin set-cookie subresource', async () => {
const { page, server } = getTestState();
await page.goto(server.EMPTY_PAGE);
const setCookieString = 'foo=bar';
server.setRoute('/foo', (req, res) => {
res.setHeader('set-cookie', setCookieString);
res.end('hello world');
});
const responsePromise = new Promise<HTTPResponse>((resolve) =>
page.on('response', (response) => resolve(response))
);
page.evaluate(() => {
const xhr = new XMLHttpRequest();
xhr.open('GET', '/foo');
xhr.send();
});
const subresourceResponse = await responsePromise;
expect(subresourceResponse.headers()['set-cookie']).toBe(setCookieString);
});
it('Cross-origin set-cookie', async () => {
const { httpsServer, puppeteer, defaultBrowserOptions } = getTestState();
const browser = await puppeteer.launch({
...defaultBrowserOptions,
ignoreHTTPSErrors: true,
});
const page = await browser.newPage();
try {
await page.goto(httpsServer.PREFIX + '/empty.html');
const setCookieString = 'hello=world';
httpsServer.setRoute('/setcookie.html', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('set-cookie', setCookieString);
res.end();
});
await page.goto(httpsServer.PREFIX + '/setcookie.html');
const response = await new Promise<HTTPResponse>((resolve) => {
page.on('response', resolve);
const url = httpsServer.CROSS_PROCESS_PREFIX + '/setcookie.html';
page.evaluate<(src: string) => void>((src) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', src);
xhr.send();
}, url);
});
expect(response.headers()['set-cookie']).toBe(setCookieString);
} finally {
await page.close();
await browser.close();
}
});
});
}); });

View File

@ -17,6 +17,7 @@
const versionsPerRelease = new Map([ const versionsPerRelease = new Map([
// This is a mapping from Chromium version => Puppeteer version. // This is a mapping from Chromium version => Puppeteer version.
// In Chromium roll patches, use 'NEXT' for the Puppeteer version. // In Chromium roll patches, use 'NEXT' for the Puppeteer version.
['97.0.4692.0', 'NEXT'],
['93.0.4577.0', 'v10.2.0'], ['93.0.4577.0', 'v10.2.0'],
['92.0.4512.0', 'v10.0.0'], ['92.0.4512.0', 'v10.0.0'],
['91.0.4469.0', 'v9.0.0'], ['91.0.4469.0', 'v9.0.0'],