// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import {assert} from 'chai'; import {beforeEach, describe, it} from 'mocha'; import sinon from 'sinon'; import * as Protocol from '../../generated/protocol.js'; import * as Bindings from '../../models/bindings/bindings.js'; import * as Persistence from '../../models/persistence/persistence.js'; import * as Workspace from '../../models/workspace/workspace.js'; import {setupLocaleHooks} from '../../testing/LocaleHelpers.js'; import {MockCDPConnection} from '../../testing/MockCDPConnection.js'; import {createWorkspaceProject} from '../../testing/OverridesHelpers.js'; import {getMainFrame, navigate} from '../../testing/ResourceTreeHelpers.js'; import {setupRuntimeHooks} from '../../testing/RuntimeHelpers.js'; import {setupSettingsHooks} from '../../testing/SettingsHelpers.js'; import {TestUniverse} from '../../testing/TestUniverse.js'; import * as Common from '../common/common.js'; import * as Platform from '../platform/platform.js'; import * as Root from '../root/root.js'; import * as TextUtils from '../text_utils/text_utils.js'; import * as SDK from './sdk.js'; const {urlString} = Platform.DevToolsPath; const LONG_URL_PART = 'LoremIpsumDolorSitAmetConsecteturAdipiscingElitPhasellusVitaeOrciInAugueCondimentumTinciduntUtEgetDolorQuisqueEfficiturUltricesTinciduntVivamusVelitPurusCommodoQuisErosSitAmetTemporMalesuadaNislNullamTtempusVulputateAugueEgetScelerisqueLacusVestibulumNon/index.html'; describe('NetworkManager', () => { setupLocaleHooks(); setupRuntimeHooks(); describe('request post data', () => { async function createPostRequestWithHeaders( headers: Protocol.Network.Headers, universe: TestUniverse, connection: MockCDPConnection): Promise { const networkManager = new SDK.NetworkManager.NetworkManager(universe.createTarget({connection})); const requestStartedPromise = networkManager.once(SDK.NetworkManager.Events.RequestStarted); networkManager.dispatcher.requestWillBeSent({ requestId: 'mock-request-id' as Protocol.Network.RequestId, loaderId: 'mock-loader-id' as Protocol.Network.LoaderId, documentURL: 'https://example.test/', request: { url: 'https://example.test/', method: 'POST', headers, hasPostData: true, postData: 'garbled-inline-post-data', }, timestamp: 1, wallTime: 1, initiator: {type: Protocol.Network.InitiatorType.Other}, } as Protocol.Network.RequestWillBeSentEvent); const {request} = await requestStartedPromise; return request; } it('decodes gzip-compressed request form data', async () => { const universe = new TestUniverse(); const connection = new MockCDPConnection(); const expectedPostData = 'a=1&b=hello+world'; const compressedPostData = await Common.Gzip.compress(expectedPostData); const encodedPostData = btoa(String.fromCharCode(...new Uint8Array(compressedPostData))); connection.setSuccessHandler('Network.getRequestPostData', () => ({ postData: encodedPostData, base64Encoded: true, })); const request = await createPostRequestWithHeaders( { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'Content-Encoding': 'gzip', }, universe, connection); const requestFormData = await request.requestFormData(); assert.strictEqual(requestFormData, expectedPostData); }); it('returns decompressed base64 ContentData via requestPostDataContentData for gzip bodies', async () => { const universe = new TestUniverse(); const connection = new MockCDPConnection(); // eslint-disable-next-line @devtools/no-instance-of-migrated-singletons SDK.NetworkManager.MultitargetNetworkManager.instance({forceNew: true, targetManager: universe.targetManager}); const originalText = 'a'; const compressedPostData = await Common.Gzip.compress(originalText); const encodedPostData = btoa(String.fromCharCode(...new Uint8Array(compressedPostData))); connection.setHandler('Network.getRequestPostData', () => ({ result: { postData: encodedPostData, base64Encoded: true, }, })); const request = await createPostRequestWithHeaders({ 'Content-Type': 'application/octet-stream', 'Content-Encoding': 'gzip', }, universe, connection); const contentData = await SDK.NetworkManager.NetworkManager.requestPostDataContentData(request); assert.isFalse(TextUtils.ContentData.ContentData.isError(contentData)); if (!TextUtils.ContentData.ContentData.isError(contentData)) { assert.isTrue(contentData.createdFromBase64); // The base64 should contain the DECOMPRESSED bytes, not the gzip bytes. const decodedBytes = Common.Base64.decode(contentData.base64); const decodedText = new TextDecoder().decode(decodedBytes); assert.strictEqual(decodedText, originalText); } }); it('returns text ContentData via requestPostDataContentData for non-base64 bodies', async () => { const universe = new TestUniverse(); const connection = new MockCDPConnection(); // eslint-disable-next-line @devtools/no-instance-of-migrated-singletons SDK.NetworkManager.MultitargetNetworkManager.instance({forceNew: true, targetManager: universe.targetManager}); const plainPostData = 'a=1&b=2'; connection.setHandler('Network.getRequestPostData', () => ({ result: { postData: plainPostData, base64Encoded: false, }, })); const request = await createPostRequestWithHeaders({'Content-Type': 'application/x-www-form-urlencoded'}, universe, connection); const contentData = await SDK.NetworkManager.NetworkManager.requestPostDataContentData(request); assert.isFalse(TextUtils.ContentData.ContentData.isError(contentData)); if (!TextUtils.ContentData.ContentData.isError(contentData)) { assert.isFalse(contentData.createdFromBase64); } }); it('returns decompressed base64 ContentData via requestFormDataContentData on NetworkRequest', async () => { const universe = new TestUniverse(); const connection = new MockCDPConnection(); // eslint-disable-next-line @devtools/no-instance-of-migrated-singletons SDK.NetworkManager.MultitargetNetworkManager.instance({forceNew: true, targetManager: universe.targetManager}); const originalText = 'hello'; const compressedPostData = await Common.Gzip.compress(originalText); const encodedPostData = btoa(String.fromCharCode(...new Uint8Array(compressedPostData))); connection.setHandler('Network.getRequestPostData', () => ({ result: { postData: encodedPostData, base64Encoded: true, }, })); const request = await createPostRequestWithHeaders({ 'Content-Type': 'application/octet-stream', 'Content-Encoding': 'gzip', }, universe, connection); const contentData = await request.requestFormDataContentData(); assert.isFalse(TextUtils.ContentData.ContentData.isError(contentData)); if (!TextUtils.ContentData.ContentData.isError(contentData)) { assert.isTrue(contentData.createdFromBase64); const decodedBytes = Common.Base64.decode(contentData.base64); const decodedText = new TextDecoder().decode(decodedBytes); assert.strictEqual(decodedText, originalText); } }); }); describe('Direct TCP socket handling', () => { it('on CDP created event creates request ', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const startedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, event => { startedRequests.push(event.data.request); }); networkDispatcher.directTCPSocketCreated({ identifier: 'mockId' as Protocol.Network.RequestId, remoteAddr: 'example.com', remotePort: 1001, options: { noDelay: true, keepAliveDelay: 1002, sendBufferSize: 1003, receiveBufferSize: 1004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv4, }, timestamp: 1000, }); assert.lengthOf(startedRequests, 1); const req: SDK.NetworkRequest.NetworkRequest = startedRequests[0]; assert.strictEqual(req.requestId(), 'mockId' as Protocol.Network.RequestId); assert.strictEqual(req.remoteAddress(), 'example.com:1001'); assert.strictEqual(req.url(), urlString`example.com:1001`); assert.isTrue(req.hasNetworkData); assert.strictEqual(req.protocol, 'tcp'); assert.strictEqual(req.statusText, 'Opening'); assert.deepEqual(req.directSocketInfo, { type: SDK.NetworkRequest.DirectSocketType.TCP, status: SDK.NetworkRequest.DirectSocketStatus.OPENING, createOptions: { remoteAddr: 'example.com', remotePort: 1001, noDelay: true, keepAliveDelay: 1002, sendBufferSize: 1003, receiveBufferSize: 1004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv4, }, }); assert.strictEqual(req.resourceType(), Common.ResourceType.resourceTypes.DirectSocket); assert.strictEqual(req.issueTime(), 1000); assert.strictEqual(req.startTime, 1000); }); describe('on CDP opened event', () => { it('does nothing if no request exists', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const updatedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.directTCPSocketOpened({ identifier: 'mockId' as Protocol.Network.RequestId, remoteAddr: 'example.com', remotePort: 1001, timestamp: 1000, }); assert.lengthOf(updatedRequests, 0); }); it('does nothing if the request has no direct socket info', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const updatedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.webTransportCreated( {transportId: 'mockId' as Protocol.Network.RequestId, url: 'example.com', timestamp: 1000}); networkDispatcher.directTCPSocketOpened({ identifier: 'mockId' as Protocol.Network.RequestId, remoteAddr: 'example.com', remotePort: 1001, timestamp: 1000, }); assert.lengthOf(updatedRequests, 0); }); it('updates request successfully', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const updatedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.directTCPSocketCreated({ identifier: 'mockId' as Protocol.Network.RequestId, remoteAddr: 'example.com', remotePort: 1001, options: { noDelay: true, keepAliveDelay: 1002, sendBufferSize: 1003, receiveBufferSize: 1004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv4, }, timestamp: 1000, }); assert.lengthOf(updatedRequests, 0); // update the request and check all fields are filled as necessary networkDispatcher.directTCPSocketOpened({ identifier: 'mockId' as Protocol.Network.RequestId, remoteAddr: '192.81.29.1', remotePort: 1010, timestamp: 2000, localAddr: '127.0.0.1', localPort: 8000, }); assert.lengthOf(updatedRequests, 1); const req: SDK.NetworkRequest.NetworkRequest = updatedRequests[0]; assert.deepEqual(req.directSocketInfo, { type: SDK.NetworkRequest.DirectSocketType.TCP, status: SDK.NetworkRequest.DirectSocketStatus.OPEN, createOptions: { remoteAddr: 'example.com', remotePort: 1001, noDelay: true, keepAliveDelay: 1002, sendBufferSize: 1003, receiveBufferSize: 1004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv4, }, openInfo: { remoteAddr: '192.81.29.1', remotePort: 1010, localAddr: '127.0.0.1', localPort: 8000, }, }); }); }); describe('on CDP event aborted', () => { it('does nothing if no request exists', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.directTCPSocketAborted({ identifier: 'mockId' as Protocol.Network.RequestId, errorMessage: Protocol.Network.ErrorReason.Failed, timestamp: 1000, }); assert.lengthOf(finishedRequests, 0); }); it('does nothing if the request has no direct socket info', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.webTransportCreated( {transportId: 'mockId' as Protocol.Network.RequestId, url: 'example.com', timestamp: 1000}); networkDispatcher.directTCPSocketAborted({ identifier: 'mockId' as Protocol.Network.RequestId, errorMessage: Protocol.Network.ErrorReason.Failed, timestamp: 1000, }); assert.lengthOf(finishedRequests, 0); }); it('updates request successfully', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.directTCPSocketCreated({ identifier: 'mockId' as Protocol.Network.RequestId, remoteAddr: 'example.com', remotePort: 1001, options: { noDelay: true, keepAliveDelay: 1002, sendBufferSize: 1003, receiveBufferSize: 1004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv4, }, timestamp: 1000, }); assert.lengthOf(finishedRequests, 0); // update the request and check all fields are filled as necessary networkDispatcher.directTCPSocketAborted({ identifier: 'mockId' as Protocol.Network.RequestId, errorMessage: Protocol.Network.ErrorReason.Failed, timestamp: 1000, }); assert.lengthOf(finishedRequests, 1); const req: SDK.NetworkRequest.NetworkRequest = finishedRequests[0]; assert.strictEqual(req.statusText, 'Aborted'); assert.isTrue(req.failed); assert.isTrue(req.finished); assert.deepEqual(req.directSocketInfo, { type: SDK.NetworkRequest.DirectSocketType.TCP, status: SDK.NetworkRequest.DirectSocketStatus.ABORTED, errorMessage: Protocol.Network.ErrorReason.Failed, createOptions: { remoteAddr: 'example.com', remotePort: 1001, noDelay: true, keepAliveDelay: 1002, sendBufferSize: 1003, receiveBufferSize: 1004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv4, }, }); }); }); describe('on CDP event closed', () => { it('does nothing if no request exists', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.directTCPSocketClosed({ identifier: 'mockId' as Protocol.Network.RequestId, timestamp: 1000, }); assert.lengthOf(finishedRequests, 0); }); it('does nothing if the request has no direct socket info', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.webTransportCreated( {transportId: 'mockId' as Protocol.Network.RequestId, url: 'example.com', timestamp: 1000}); networkDispatcher.directTCPSocketClosed({ identifier: 'mockId' as Protocol.Network.RequestId, timestamp: 1000, }); assert.lengthOf(finishedRequests, 0); }); it('updates request successfully', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.directTCPSocketCreated({ identifier: 'mockId' as Protocol.Network.RequestId, remoteAddr: 'example.com', remotePort: 1001, options: { noDelay: true, keepAliveDelay: 1002, sendBufferSize: 1003, receiveBufferSize: 1004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv4, }, timestamp: 1000, }); assert.lengthOf(finishedRequests, 0); networkDispatcher.directTCPSocketClosed({ identifier: 'mockId' as Protocol.Network.RequestId, timestamp: 1000, }); assert.lengthOf(finishedRequests, 1); const req: SDK.NetworkRequest.NetworkRequest = finishedRequests[0]; assert.strictEqual(req.statusText, 'Closed'); assert.notExists(req.failed); assert.isTrue(req.finished); assert.deepEqual(req.directSocketInfo, { type: SDK.NetworkRequest.DirectSocketType.TCP, status: SDK.NetworkRequest.DirectSocketStatus.CLOSED, createOptions: { remoteAddr: 'example.com', remotePort: 1001, noDelay: true, keepAliveDelay: 1002, sendBufferSize: 1003, receiveBufferSize: 1004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv4, }, }); }); }); describe('on CDP chunk event', () => { let networkManager: SDK.NetworkManager.NetworkManager; let networkDispatcher: SDK.NetworkManager.NetworkDispatcher; let updatedRequests: SDK.NetworkRequest.NetworkRequest[]; let targetRequest: SDK.NetworkRequest.NetworkRequest|null; const mockIdentifier = 'mockParamChunkId' as Protocol.Network.RequestId; const baseTimestamp = 1000; beforeEach(() => { networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); updatedRequests = []; targetRequest = null; networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, event => { if (event.data.request.requestId() === mockIdentifier) { targetRequest = event.data.request; } }); networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.directTCPSocketCreated({ identifier: mockIdentifier, remoteAddr: 'example-param.com', remotePort: 1005, options: { noDelay: true, keepAliveDelay: 1002, sendBufferSize: 1003, receiveBufferSize: 1004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv4, }, timestamp: baseTimestamp, }); }); const testCases = [ { description: 'adds SENT chunk to request successfully', eventPayload: {data: 'c2VudCBkYXRh', timestamp: 3000}, expectedChunk: { type: SDK.NetworkRequest.DirectSocketChunkType.SEND, data: 'c2VudCBkYXRh', timestamp: 3000, }, }, { description: 'adds RECEIVED chunk to request successfully', eventPayload: {data: 'cmVjZWl2ZWQgZGF0YQ==', timestamp: 4000}, expectedChunk: { type: SDK.NetworkRequest.DirectSocketChunkType.RECEIVE, data: 'cmVjZWl2ZWQgZGF0YQ==', timestamp: 4000, }, }, ]; testCases.forEach(testCase => { it(testCase.description, () => { assert.exists(targetRequest, 'Target request should be created in beforeEach'); assert.lengthOf(updatedRequests, 0, 'No updates should occur during initial setup'); switch (testCase.expectedChunk.type) { case SDK.NetworkRequest.DirectSocketChunkType.SEND: networkDispatcher.directTCPSocketChunkSent({ identifier: mockIdentifier, data: testCase.eventPayload.data!, timestamp: testCase.eventPayload.timestamp, }); break; case SDK.NetworkRequest.DirectSocketChunkType.RECEIVE: networkDispatcher.directTCPSocketChunkReceived({ identifier: mockIdentifier, data: testCase.eventPayload.data!, timestamp: testCase.eventPayload.timestamp, }); break; } assert.lengthOf(updatedRequests, 1, 'Should trigger exactly one update'); const req = updatedRequests[0]; assert.strictEqual(req.requestId(), mockIdentifier); assert.strictEqual( req.responseReceivedTime, testCase.eventPayload.timestamp, 'responseReceivedTime should be updated'); const chunks = req.directSocketChunks(); assert.lengthOf(chunks, 1, 'Should have exactly one chunk added'); assert.deepEqual(chunks[0], testCase.expectedChunk, 'Chunk details should match expected'); }); }); }); }); describe('WebSocket handling', () => { it('updates request with handshake info on CDP events', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const updatedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.webSocketCreated({ requestId: 'mockWsId' as Protocol.Network.RequestId, url: 'ws://example.com/ws', initiator: {type: Protocol.Network.InitiatorType.Script}, }); assert.lengthOf(updatedRequests, 0); networkDispatcher.webSocketWillSendHandshakeRequest({ requestId: 'mockWsId' as Protocol.Network.RequestId, timestamp: 1000, wallTime: 1000, request: { headers: {Connection: 'Upgrade', Upgrade: 'websocket', 'Sec-WebSocket-Key': 'key', 'Sec-WebSocket-Version': '13'}, } as Protocol.Network.WebSocketRequest, }); assert.lengthOf(updatedRequests, 1); let req = updatedRequests[updatedRequests.length - 1]; assert.strictEqual(req.requestMethod, 'GET'); assert.deepEqual(req.requestHeaders(), [ {name: 'Connection', value: 'Upgrade'}, {name: 'Upgrade', value: 'websocket'}, {name: 'Sec-WebSocket-Key', value: 'key'}, {name: 'Sec-WebSocket-Version', value: '13'}, ]); networkDispatcher.webSocketHandshakeResponseReceived({ requestId: 'mockWsId' as Protocol.Network.RequestId, timestamp: 1001, response: { status: 101, statusText: 'Switching Protocols', headers: {Connection: 'Upgrade', Upgrade: 'websocket', 'Sec-WebSocket-Accept': 'accept'}, headersText: 'HTTP/1.1 101 Switching Protocols', } as Protocol.Network.WebSocketResponse, }); assert.lengthOf(updatedRequests, 2); req = updatedRequests[updatedRequests.length - 1]; assert.strictEqual(req.statusCode, 101); assert.strictEqual(req.statusText, 'Switching Protocols'); assert.deepEqual(req.responseHeaders, [ {name: 'Connection', value: 'Upgrade'}, {name: 'Upgrade', value: 'websocket'}, {name: 'Sec-WebSocket-Accept', value: 'accept'}, ]); assert.strictEqual(req.responseHeadersText, 'HTTP/1.1 101 Switching Protocols'); }); }); describe('Direct UDP socket handling', () => { describe('on CDP created event', () => { it('creates request for connected UDP', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const startedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, event => { startedRequests.push(event.data.request); }); // remoteAddress/remotePort and localAddress/localPort // pairs cannot be specified together. networkDispatcher.directUDPSocketCreated({ identifier: 'mockUdpId1' as Protocol.Network.RequestId, options: { remoteAddr: 'example.com', remotePort: 2001, sendBufferSize: 2003, receiveBufferSize: 2004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv6, }, timestamp: 2000, }); assert.lengthOf(startedRequests, 1); const req: SDK.NetworkRequest.NetworkRequest = startedRequests[0]; assert.strictEqual(req.requestId(), 'mockUdpId1' as Protocol.Network.RequestId); assert.strictEqual(req.remoteAddress(), 'example.com:2001'); assert.strictEqual(req.url(), urlString`example.com:2001`); assert.isTrue(req.hasNetworkData); assert.strictEqual(req.protocol, 'udp'); assert.strictEqual(req.statusText, 'Opening'); assert.deepEqual(req.directSocketInfo, { type: SDK.NetworkRequest.DirectSocketType.UDP_CONNECTED, status: SDK.NetworkRequest.DirectSocketStatus.OPENING, createOptions: { remoteAddr: 'example.com', remotePort: 2001, localAddr: undefined, localPort: undefined, sendBufferSize: 2003, receiveBufferSize: 2004, dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv6, multicastLoopback: undefined, multicastTimeToLive: undefined, multicastAllowAddressSharing: undefined, }, joinedMulticastGroups: new Set(), }); assert.strictEqual(req.resourceType(), Common.ResourceType.resourceTypes.DirectSocket); assert.strictEqual(req.issueTime(), 2000); assert.strictEqual(req.startTime, 2000); }); it('creates request for bound UDP', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const startedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, event => { startedRequests.push(event.data.request); }); networkDispatcher.directUDPSocketCreated({ identifier: 'mockUdpId2' as Protocol.Network.RequestId, options: { localAddr: '192.168.0.1', localPort: 2005, sendBufferSize: 2006, receiveBufferSize: 2007, multicastLoopback: undefined, multicastTimeToLive: undefined, multicastAllowAddressSharing: undefined, }, timestamp: 2100, }); assert.lengthOf(startedRequests, 1); const req: SDK.NetworkRequest.NetworkRequest = startedRequests[0]; assert.strictEqual(req.requestId(), 'mockUdpId2' as Protocol.Network.RequestId); // No remote address for bound UDP initially assert.strictEqual(req.remoteAddress(), ''); // URL uses localAddr if remoteAddr is not present assert.strictEqual(req.url(), urlString`192.168.0.1:2005`); assert.isTrue(req.hasNetworkData); assert.strictEqual(req.protocol, 'udp'); assert.strictEqual(req.statusText, 'Opening'); assert.deepEqual(req.directSocketInfo, { type: SDK.NetworkRequest.DirectSocketType.UDP_BOUND, status: SDK.NetworkRequest.DirectSocketStatus.OPENING, createOptions: { remoteAddr: undefined, remotePort: undefined, localAddr: '192.168.0.1', localPort: 2005, sendBufferSize: 2006, receiveBufferSize: 2007, dnsQueryType: undefined, multicastLoopback: undefined, multicastTimeToLive: undefined, multicastAllowAddressSharing: undefined, }, joinedMulticastGroups: new Set(), }); assert.strictEqual(req.resourceType(), Common.ResourceType.resourceTypes.DirectSocket); assert.strictEqual(req.issueTime(), 2100); assert.strictEqual(req.startTime, 2100); }); it('does nothing if no localAddr for bound UDP', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const startedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, event => { startedRequests.push(event.data.request); }); networkDispatcher.directUDPSocketCreated({ identifier: 'mockUdpId3' as Protocol.Network.RequestId, options: { // Skip request if remoteAddr and localAddr are both absent. }, timestamp: 2200, }); assert.lengthOf(startedRequests, 0); }); }); describe('on CDP opened event', () => { it('does nothing if no request exists', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const updatedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.directUDPSocketOpened({ identifier: 'mockUdpId' as Protocol.Network.RequestId, localAddr: '127.0.0.1', localPort: 2002, timestamp: 2000, }); assert.lengthOf(updatedRequests, 0); }); it('does nothing if the request has no direct socket info', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const updatedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.webTransportCreated( {transportId: 'mockUdpId' as Protocol.Network.RequestId, url: 'example.com', timestamp: 2000}); networkDispatcher.directUDPSocketOpened({ identifier: 'mockUdpId' as Protocol.Network.RequestId, localAddr: '127.0.0.1', localPort: 2002, timestamp: 2000, }); assert.lengthOf(updatedRequests, 0); }); it('updates request successfully for connected UDP', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const updatedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.directUDPSocketCreated({ identifier: 'mockUdpId' as Protocol.Network.RequestId, options: { remoteAddr: 'example.com', remotePort: 2001, }, timestamp: 2000, }); assert.lengthOf(updatedRequests, 0); networkDispatcher.directUDPSocketOpened({ identifier: 'mockUdpId' as Protocol.Network.RequestId, localAddr: '127.0.0.1', localPort: 8001, remoteAddr: '192.168.1.1', remotePort: 2010, timestamp: 2050, }); assert.lengthOf(updatedRequests, 1); const req: SDK.NetworkRequest.NetworkRequest = updatedRequests[0]; assert.strictEqual(req.remoteAddress(), '192.168.1.1:2010'); assert.strictEqual(req.url(), urlString`192.168.1.1:2010`); assert.strictEqual(req.statusText, 'Open'); assert.deepEqual(req.directSocketInfo?.status, SDK.NetworkRequest.DirectSocketStatus.OPEN); assert.deepEqual(req.directSocketInfo?.openInfo, { remoteAddr: '192.168.1.1', remotePort: 2010, localAddr: '127.0.0.1', localPort: 8001, }); assert.strictEqual(req.responseReceivedTime, 2050); }); it('updates request successfully for bound UDP', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const updatedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.directUDPSocketCreated({ identifier: 'mockUdpBoundId' as Protocol.Network.RequestId, options: { localAddr: '0.0.0.0', localPort: 7000, }, timestamp: 2100, }); assert.lengthOf(updatedRequests, 0); networkDispatcher.directUDPSocketOpened({ identifier: 'mockUdpBoundId' as Protocol.Network.RequestId, localAddr: '192.168.0.5', localPort: 7000, // No remoteAddr/remotePort for bound socket open event timestamp: 2150, }); assert.lengthOf(updatedRequests, 1); const req: SDK.NetworkRequest.NetworkRequest = updatedRequests[0]; assert.strictEqual(req.remoteAddress(), ''); // Still no remote address assert.strictEqual(req.url(), urlString`192.168.0.5:7000`); // URL updated with resolved local address assert.strictEqual(req.statusText, 'Open'); assert.deepEqual(req.directSocketInfo?.status, SDK.NetworkRequest.DirectSocketStatus.OPEN); assert.deepEqual(req.directSocketInfo?.openInfo, { remoteAddr: undefined, remotePort: undefined, localAddr: '192.168.0.5', localPort: 7000, }); assert.strictEqual(req.responseReceivedTime, 2150); }); }); describe('on CDP event aborted', () => { it('does nothing if no request exists', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.directUDPSocketAborted({ identifier: 'mockUdpId' as Protocol.Network.RequestId, errorMessage: Protocol.Network.ErrorReason.Failed, timestamp: 2000, }); assert.lengthOf(finishedRequests, 0); }); it('does nothing if the request has no direct socket info', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.webTransportCreated( {transportId: 'mockUdpId' as Protocol.Network.RequestId, url: 'example.com', timestamp: 2000}); networkDispatcher.directUDPSocketAborted({ identifier: 'mockUdpId' as Protocol.Network.RequestId, errorMessage: Protocol.Network.ErrorReason.Failed, timestamp: 2000, }); assert.lengthOf(finishedRequests, 0); }); it('updates request successfully', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.directUDPSocketCreated({ identifier: 'mockUdpId' as Protocol.Network.RequestId, options: { remoteAddr: 'example.com', remotePort: 2001, }, timestamp: 2000, }); assert.lengthOf(finishedRequests, 0); networkDispatcher.directUDPSocketAborted({ identifier: 'mockUdpId' as Protocol.Network.RequestId, errorMessage: Protocol.Network.ErrorReason.Failed, timestamp: 2050, }); assert.lengthOf(finishedRequests, 1); const req: SDK.NetworkRequest.NetworkRequest = finishedRequests[0]; assert.strictEqual(req.statusText, 'Aborted'); assert.isTrue(req.failed); assert.isTrue(req.finished); assert.strictEqual(req.endTime, 2050); assert.deepEqual(req.directSocketInfo?.status, SDK.NetworkRequest.DirectSocketStatus.ABORTED); assert.strictEqual(req.directSocketInfo?.errorMessage, Protocol.Network.ErrorReason.Failed); }); }); describe('on CDP event closed', () => { it('does nothing if no request exists', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.directUDPSocketClosed({ identifier: 'mockUdpId' as Protocol.Network.RequestId, timestamp: 2000, }); assert.lengthOf(finishedRequests, 0); }); it('does nothing if the request has no direct socket info', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.webTransportCreated( {transportId: 'mockUdpId' as Protocol.Network.RequestId, url: 'example.com', timestamp: 2000}); networkDispatcher.directUDPSocketClosed({ identifier: 'mockUdpId' as Protocol.Network.RequestId, timestamp: 2000, }); assert.lengthOf(finishedRequests, 0); }); it('updates request successfully', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const finishedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, event => { finishedRequests.push(event.data); }); networkDispatcher.directUDPSocketCreated({ identifier: 'mockUdpId' as Protocol.Network.RequestId, options: { remoteAddr: 'example.com', remotePort: 2001, }, timestamp: 2000, }); assert.lengthOf(finishedRequests, 0); networkDispatcher.directUDPSocketClosed({ identifier: 'mockUdpId' as Protocol.Network.RequestId, timestamp: 2050, }); assert.lengthOf(finishedRequests, 1); const req: SDK.NetworkRequest.NetworkRequest = finishedRequests[0]; assert.strictEqual(req.statusText, 'Closed'); assert.notExists(req.failed); assert.isTrue(req.finished); assert.strictEqual(req.endTime, 2050); assert.deepEqual(req.directSocketInfo?.status, SDK.NetworkRequest.DirectSocketStatus.CLOSED); }); }); describe('on CDP chunk event', () => { let networkManager: SDK.NetworkManager.NetworkManager; let networkDispatcher: SDK.NetworkManager.NetworkDispatcher; let updatedRequests: SDK.NetworkRequest.NetworkRequest[]; let targetRequest: SDK.NetworkRequest.NetworkRequest|null; const mockIdentifier = 'mockUdpChunkId' as Protocol.Network.RequestId; const baseTimestamp = 3000; beforeEach(() => { networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); updatedRequests = []; targetRequest = null; networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, event => { if (event.data.request.requestId() === mockIdentifier) { targetRequest = event.data.request; } }); networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.directUDPSocketCreated({ identifier: mockIdentifier, options: { remoteAddr: 'udp-example.com', remotePort: 3005, }, timestamp: baseTimestamp, }); }); const testCases = [ { description: 'adds SENT chunk to request successfully (connected)', eventPayload: { // No remoteAddr/Port for connected. message: {data: 'c2VudCBkYXRh=='}, timestamp: 3100, }, expectedChunk: { type: SDK.NetworkRequest.DirectSocketChunkType.SEND, data: 'c2VudCBkYXRh==', timestamp: 3100, remoteAddress: undefined, remotePort: undefined, }, }, { description: 'adds SENT chunk to request successfully (bound with address)', eventPayload: {message: {data: 'Ym91bmQgc2VudA==', remoteAddr: '10.0.0.1', remotePort: 4000}, timestamp: 3150}, expectedChunk: { type: SDK.NetworkRequest.DirectSocketChunkType.SEND, data: 'Ym91bmQgc2VudA==', timestamp: 3150, remoteAddress: '10.0.0.1', remotePort: 4000, }, }, { description: 'adds SENT chunk to request successfully (bound with address, no port)', eventPayload: {message: {data: 'Ym91bmQgc2VudCBwbA==', remoteAddr: '10.0.0.2'}, timestamp: 3160}, expectedChunk: { type: SDK.NetworkRequest.DirectSocketChunkType.SEND, data: 'Ym91bmQgc2VudCBwbA==', timestamp: 3160, remoteAddress: '10.0.0.2', remotePort: undefined, }, }, { description: 'adds RECEIVED chunk to request successfully (connected)', eventPayload: {message: {data: 'cmVjZWl2ZWQgZGF0YQ=='}, timestamp: 3200}, expectedChunk: { type: SDK.NetworkRequest.DirectSocketChunkType.RECEIVE, data: 'cmVjZWl2ZWQgZGF0YQ==', timestamp: 3200, remoteAddress: undefined, remotePort: undefined, }, }, { description: 'adds RECEIVED chunk to request successfully (bound with address)', eventPayload: {message: {data: 'Ym91bmQgcmVjZWl2ZWQ=', remoteAddr: '10.0.0.3', remotePort: 4001}, timestamp: 3250}, expectedChunk: { type: SDK.NetworkRequest.DirectSocketChunkType.RECEIVE, data: 'Ym91bmQgcmVjZWl2ZWQ=', timestamp: 3250, remoteAddress: '10.0.0.3', remotePort: 4001, }, }, ]; testCases.forEach(testCase => { it(testCase.description, () => { assert.exists(targetRequest, 'Target request should be created in beforeEach'); updatedRequests.length = 0; switch (testCase.expectedChunk.type) { case SDK.NetworkRequest.DirectSocketChunkType.SEND: networkDispatcher.directUDPSocketChunkSent({ identifier: mockIdentifier, message: (testCase.eventPayload as Protocol.Network.DirectUDPSocketChunkSentEvent).message, timestamp: testCase.eventPayload.timestamp, }); break; case SDK.NetworkRequest.DirectSocketChunkType.RECEIVE: networkDispatcher.directUDPSocketChunkReceived({ identifier: mockIdentifier, message: (testCase.eventPayload as Protocol.Network.DirectUDPSocketChunkReceivedEvent).message, timestamp: testCase.eventPayload.timestamp, }); break; } assert.lengthOf(updatedRequests, 1, 'Should trigger exactly one update'); const req = updatedRequests[0]; assert.strictEqual(req.requestId(), mockIdentifier); assert.strictEqual( req.responseReceivedTime, testCase.eventPayload.timestamp, 'responseReceivedTime should be updated'); const chunks = req.directSocketChunks(); assert.lengthOf(chunks, 1, 'Should have exactly one chunk added'); assert.deepEqual(chunks[0], testCase.expectedChunk, 'Chunk details should match expected'); }); }); }); describe('on Join Multicast Group events', () => { let networkManager: SDK.NetworkManager.NetworkManager; let networkDispatcher: SDK.NetworkManager.NetworkDispatcher; let updatedRequests: SDK.NetworkRequest.NetworkRequest[]; let request: SDK.NetworkRequest.NetworkRequest|null; const mockIdentifier = 'mockUdpMulticastId' as Protocol.Network.RequestId; beforeEach(() => { networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); updatedRequests = []; request = null; networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, event => { if (event.data.request.requestId() === mockIdentifier) { request = event.data.request; // Reset state for the new request instance for each test if (request?.directSocketInfo) { request.directSocketInfo.joinedMulticastGroups = new Set(); } } }); networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.directUDPSocketCreated({ identifier: mockIdentifier, options: {localAddr: '0.0.0.0', localPort: 8080}, timestamp: 3000, }); }); it('directUDPSocketJoinedMulticastGroup adds the first group', () => { assert.strictEqual(request!.directSocketInfo?.joinedMulticastGroups?.size, 0); networkDispatcher.directUDPSocketJoinedMulticastGroup({ identifier: mockIdentifier, IPAddress: '237.132.100.17', }); assert.deepEqual(request!.directSocketInfo?.joinedMulticastGroups, new Set(['237.132.100.17'])); assert.lengthOf(updatedRequests, 1); assert.strictEqual(updatedRequests[0], request); }); it('directUDPSocketJoinedMulticastGroup adds subsequent groups', () => { request!.directSocketInfo!.joinedMulticastGroups = new Set(['237.132.100.17']); networkDispatcher.directUDPSocketJoinedMulticastGroup({ identifier: mockIdentifier, IPAddress: '237.132.100.18', }); assert.deepEqual( request!.directSocketInfo?.joinedMulticastGroups, new Set(['237.132.100.17', '237.132.100.18'])); assert.lengthOf(updatedRequests, 1); assert.strictEqual(updatedRequests[0], request); }); it('directUDPSocketJoinedMulticastGroup does not add a duplicate group', () => { request!.directSocketInfo!.joinedMulticastGroups = new Set(['237.132.100.17']); networkDispatcher.directUDPSocketJoinedMulticastGroup({ identifier: mockIdentifier, IPAddress: '237.132.100.17', }); assert.deepEqual(request!.directSocketInfo?.joinedMulticastGroups, new Set(['237.132.100.17'])); assert.lengthOf(updatedRequests, 0); }); it('directUDPSocketJoinedMulticastGroup keeps memberships for different sources of a group apart', () => { const sourceA = {identifier: mockIdentifier, IPAddress: '232.1.1.1', sourceAddress: '192.0.2.100'}; const sourceB = {identifier: mockIdentifier, IPAddress: '232.1.1.1', sourceAddress: '192.0.2.101'}; networkDispatcher.directUDPSocketJoinedMulticastGroup(sourceA); networkDispatcher.directUDPSocketJoinedMulticastGroup(sourceB); assert.deepEqual(request!.directSocketInfo?.joinedMulticastGroups, new Set(['232.1.1.1@192.0.2.100', '232.1.1.1@192.0.2.101'])); assert.lengthOf(updatedRequests, 2); }); }); describe('on Leave Multicast Group events', () => { let networkManager: SDK.NetworkManager.NetworkManager; let networkDispatcher: SDK.NetworkManager.NetworkDispatcher; let updatedRequests: SDK.NetworkRequest.NetworkRequest[]; let request: SDK.NetworkRequest.NetworkRequest|null; const mockIdentifier = 'mockUdpMulticastId' as Protocol.Network.RequestId; beforeEach(() => { networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); updatedRequests = []; request = null; networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, event => { if (event.data.request.requestId() === mockIdentifier) { request = event.data.request; // Reset state for the new request instance for each test if (request?.directSocketInfo) { request.directSocketInfo.joinedMulticastGroups = new Set(); } } }); networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); networkDispatcher.directUDPSocketCreated({ identifier: mockIdentifier, options: {localAddr: '0.0.0.0', localPort: 8080}, timestamp: 3000, }); }); it('directUDPSocketLeftMulticastGroup removes an existing group', () => { request!.directSocketInfo!.joinedMulticastGroups = new Set(['237.132.100.17', '237.132.100.18']); updatedRequests.length = 0; networkDispatcher.directUDPSocketLeftMulticastGroup({ identifier: mockIdentifier, IPAddress: '237.132.100.17', }); assert.deepEqual(request!.directSocketInfo?.joinedMulticastGroups, new Set(['237.132.100.18'])); assert.lengthOf(updatedRequests, 1); }); it('directUDPSocketLeftMulticastGroup does not remove a non-existing group', () => { request!.directSocketInfo!.joinedMulticastGroups = new Set(['237.132.100.17']); updatedRequests.length = 0; networkDispatcher.directUDPSocketLeftMulticastGroup({ identifier: mockIdentifier, IPAddress: '237.132.100.18', }); assert.deepEqual(request!.directSocketInfo?.joinedMulticastGroups, new Set(['237.132.100.17'])); assert.lengthOf(updatedRequests, 0); }); it('directUDPSocketLeftMulticastGroup handles empty array', () => { request!.directSocketInfo!.joinedMulticastGroups = new Set(); updatedRequests.length = 0; networkDispatcher.directUDPSocketLeftMulticastGroup({ identifier: mockIdentifier, IPAddress: '237.132.100.17', }); assert.deepEqual(request!.directSocketInfo?.joinedMulticastGroups, new Set()); assert.lengthOf(updatedRequests, 0); }); it('directUDPSocketLeftMulticastGroup removes only the membership for its source', () => { request!.directSocketInfo!.joinedMulticastGroups = new Set(['232.1.1.1@192.0.2.100', '232.1.1.1@192.0.2.101']); updatedRequests.length = 0; const sourceA = {identifier: mockIdentifier, IPAddress: '232.1.1.1', sourceAddress: '192.0.2.100'}; networkDispatcher.directUDPSocketLeftMulticastGroup(sourceA); assert.deepEqual(request!.directSocketInfo?.joinedMulticastGroups, new Set(['232.1.1.1@192.0.2.101'])); assert.lengthOf(updatedRequests, 1); }); }); }); describe('WebSocket handling', () => { it('handles WebSocket frame error event', () => { const networkManager = new SDK.NetworkManager.NetworkManager(new TestUniverse().createTarget({})); const networkDispatcher = new SDK.NetworkManager.NetworkDispatcher(networkManager); const updatedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { updatedRequests.push(event.data); }); const mockRequestId = 'mockRequestId' as Protocol.Network.RequestId; const mockUrl = urlString`ws://example.com`; networkDispatcher.webSocketCreated({ requestId: mockRequestId, url: mockUrl, initiator: {type: Protocol.Network.InitiatorType.Other}, }); networkDispatcher.webSocketFrameError({ requestId: mockRequestId, timestamp: 1000, errorMessage: 'Error during WebSocket handshake: Unexpected response code: 404', }); assert.lengthOf(updatedRequests, 1); const req = updatedRequests[0]; assert.strictEqual(req.requestId(), mockRequestId); assert.strictEqual(req.resourceType(), Common.ResourceType.resourceTypes.WebSocket); const frames = req.frames(); assert.lengthOf(frames, 1); assert.strictEqual(frames[0].type, SDK.NetworkRequest.WebSocketFrameType.Error); assert.strictEqual(frames[0].text, 'Error during WebSocket handshake: Unexpected response code: 404'); }); }); describe('canResendRequest', () => { setupLocaleHooks(); setupSettingsHooks(); setupRuntimeHooks(); function createRequestWithType( universe: TestUniverse, connection: MockCDPConnection, resourceType: Protocol.Network.ResourceType): Promise { const networkManager = new SDK.NetworkManager.NetworkManager(universe.createTarget({connection})); const requestStartedPromise = networkManager.once(SDK.NetworkManager.Events.RequestStarted); networkManager.dispatcher.requestWillBeSent({ requestId: 'req-' + resourceType as Protocol.Network.RequestId, loaderId: 'loader-1' as Protocol.Network.LoaderId, documentURL: 'https://example.test/', request: {url: 'https://example.test/resource', method: 'GET', headers: {}}, timestamp: 1, wallTime: 1, initiator: {type: Protocol.Network.InitiatorType.Other}, type: resourceType, } as Protocol.Network.RequestWillBeSentEvent); return requestStartedPromise.then(({request}) => request); } it('returns false when request has no backendRequestId', () => { const request = SDK.NetworkRequest.NetworkRequest.create('' as Protocol.Network.RequestId, urlString`https://example.test/`, urlString`https://example.test/`, null, null, null); assert.isFalse(SDK.NetworkManager.NetworkManager.canResendRequest(request, true)); }); it('returns false when request is a redirect', async () => { const universe = new TestUniverse(); const connection = new MockCDPConnection(); const networkManager = new SDK.NetworkManager.NetworkManager(universe.createTarget({connection})); const requestRedirectedPromise = networkManager.once(SDK.NetworkManager.Events.RequestRedirected); networkManager.dispatcher.requestWillBeSent({ requestId: 'redirect-req' as Protocol.Network.RequestId, loaderId: 'loader-1' as Protocol.Network.LoaderId, documentURL: 'https://example.test/', request: {url: 'https://example.test/a', method: 'GET', headers: {}}, timestamp: 1, wallTime: 1, initiator: {type: Protocol.Network.InitiatorType.Other}, type: Protocol.Network.ResourceType.Fetch, } as Protocol.Network.RequestWillBeSentEvent); // Second requestWillBeSent with redirectResponse makes the original a redirect. networkManager.dispatcher.requestWillBeSent({ requestId: 'redirect-req' as Protocol.Network.RequestId, loaderId: 'loader-1' as Protocol.Network.LoaderId, documentURL: 'https://example.test/', request: {url: 'https://example.test/b', method: 'GET', headers: {}}, timestamp: 2, wallTime: 2, initiator: {type: Protocol.Network.InitiatorType.Other}, type: Protocol.Network.ResourceType.Fetch, redirectHasExtraInfo: false, redirectResponse: { url: 'https://example.test/a', status: 302, statusText: 'Found', headers: {}, mimeType: 'text/html', charset: '', connectionReused: false, connectionId: 0, encodedDataLength: 0, securityState: Protocol.Security.SecurityState.Secure, }, } as Protocol.Network.RequestWillBeSentEvent); const newRequest = await requestRedirectedPromise; const redirectedRequest = newRequest.redirectSource()!; assert.isTrue(redirectedRequest.isRedirect()); assert.isFalse(SDK.NetworkManager.NetworkManager.canResendRequest(redirectedRequest, true)); }); it('fullFidelity parameter distinguishes partial-fidelity types', async () => { const universe = new TestUniverse(); const connection = new MockCDPConnection(); const request = await createRequestWithType(universe, connection, Protocol.Network.ResourceType.Document); assert.isFalse(SDK.NetworkManager.NetworkManager.canResendRequest(request, true)); assert.isTrue(SDK.NetworkManager.NetworkManager.canResendRequest(request, false)); }); }); describe('resendRequest', () => { setupLocaleHooks(); setupSettingsHooks(); setupRuntimeHooks(); it('uses invoke_replayXHR for XHR requests', async () => { const universe = new TestUniverse(); const connection = new MockCDPConnection(); const networkManager = new SDK.NetworkManager.NetworkManager(universe.createTarget({connection})); const requestStartedPromise = networkManager.once(SDK.NetworkManager.Events.RequestStarted); networkManager.dispatcher.requestWillBeSent({ requestId: 'xhr-req' as Protocol.Network.RequestId, loaderId: 'loader-1' as Protocol.Network.LoaderId, documentURL: 'https://example.test/', request: {url: 'https://example.test/api', method: 'POST', headers: {}}, timestamp: 1, wallTime: 1, initiator: {type: Protocol.Network.InitiatorType.Other}, type: Protocol.Network.ResourceType.XHR, } as Protocol.Network.RequestWillBeSentEvent); const {request} = await requestStartedPromise; let replayedRequestId: string|undefined; connection.setSuccessHandler('Network.replayXHR', params => { replayedRequestId = params.requestId; return {}; }); await SDK.NetworkManager.NetworkManager.resendRequest(request); assert.strictEqual(replayedRequestId, 'xhr-req'); }); it('uses Runtime.evaluate with fetch, filtering forbidden headers', async () => { const universe = new TestUniverse(); const connection = new MockCDPConnection(); const networkManager = new SDK.NetworkManager.NetworkManager(universe.createTarget({connection})); const requestStartedPromise = networkManager.once(SDK.NetworkManager.Events.RequestStarted); networkManager.dispatcher.requestWillBeSent({ requestId: 'fetch-req' as Protocol.Network.RequestId, loaderId: 'loader-1' as Protocol.Network.LoaderId, documentURL: 'https://example.test/', request: { url: 'https://example.test/data.json', method: 'GET', headers: { accept: 'application/json', ':authority': 'example.test', ':method': 'GET', host: 'example.test', cookie: 'session=abc', }, initialPriority: Protocol.Network.ResourcePriority.Medium, referrerPolicy: Protocol.Network.RequestReferrerPolicy.Origin, }, timestamp: 1, wallTime: 1, initiator: {type: Protocol.Network.InitiatorType.Other}, type: Protocol.Network.ResourceType.Fetch, redirectHasExtraInfo: false, } as Protocol.Network.RequestWillBeSentEvent); const {request} = await requestStartedPromise; let evaluatedExpression: string|undefined; connection.setSuccessHandler('Runtime.evaluate', params => { evaluatedExpression = params.expression; return {result: {type: Protocol.Runtime.RemoteObjectType.Object}}; }); // Provide a default execution context for the runtime model. const runtimeModel = networkManager.target().model(SDK.RuntimeModel.RuntimeModel); assert.exists(runtimeModel); runtimeModel!.executionContextCreated({ id: 1 as Protocol.Runtime.ExecutionContextId, origin: 'https://example.test', name: 'top', uniqueId: 'ctx-1', }); await SDK.NetworkManager.NetworkManager.resendRequest(request); assert.isDefined(evaluatedExpression); assert.include(evaluatedExpression!, 'fetch('); assert.include(evaluatedExpression!, 'https://example.test/data.json'); assert.include(evaluatedExpression!, '"credentials":"include"'); // Pseudo-headers and forbidden headers are excluded. assert.notInclude(evaluatedExpression!, ':authority'); assert.notInclude(evaluatedExpression!, ':method'); assert.notInclude(evaluatedExpression!, '"host"'); assert.notInclude(evaluatedExpression!, '"cookie"'); // Allowed headers are included. assert.include(evaluatedExpression!, '"accept"'); }); }); }); describe('MultitargetNetworkManager', () => { let universe: TestUniverse; beforeEach(() => { universe = new TestUniverse(); }); describe('User agent', () => { it('is patched with Chrome version', () => { const cases = [ 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0', 'GoogleChrome/%s Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 Edg/%s', ]; const version = Root.Runtime.getChromeVersion(); let expected0 = cases[0]; const expected1 = cases[1]; let expected2 = cases[2]; let expected3 = cases[3]; if (version.length > 0) { const appVer = version.split('.', 1)[0] + '.0.100.0'; expected0 = cases[0].replace('%s', version); expected2 = cases[2].replace('%s', version); expected3 = cases[3].replace('%s', version).replace('%s', appVer); } assert.strictEqual(SDK.NetworkManager.MultitargetNetworkManager.patchUserAgentWithChromeVersion(cases[0]), expected0); assert.strictEqual(SDK.NetworkManager.MultitargetNetworkManager.patchUserAgentWithChromeVersion(cases[1]), expected1); assert.strictEqual(SDK.NetworkManager.MultitargetNetworkManager.patchUserAgentWithChromeVersion(cases[2]), expected2); assert.strictEqual(SDK.NetworkManager.MultitargetNetworkManager.patchUserAgentWithChromeVersion(cases[3]), expected3); }); it('can be overridden manually', () => { const target = universe.createTarget(); const networkAgent = target.networkAgent(); const setUserAgentOverrideSpy = sinon.spy(networkAgent, 'invoke_setUserAgentOverride'); universe.multitargetNetworkManager.setCustomUserAgentOverride('foobar with %s inside'); sinon.assert.calledOnce(setUserAgentOverrideSpy); assert.deepEqual(setUserAgentOverrideSpy.firstCall.args[0], { userAgent: 'foobar with %s inside', userAgentMetadata: undefined, }); }); }); describe('Trust Token done event', () => { it('is not lost when arriving before the corresponding requestWillBeSent event', () => { const target = universe.createTarget({}); const networkManager = target.model(SDK.NetworkManager.NetworkManager)!; const startedRequests: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, event => { startedRequests.push(event.data.request); }); const networkDispatcher = networkManager.dispatcher; // 2) Fire a trust token event, followed by a requestWillBeSent event. const mockEvent = {requestId: 'mockId'} as Protocol.Network.TrustTokenOperationDoneEvent; networkDispatcher.trustTokenOperationDone(mockEvent); networkDispatcher.requestWillBeSent( {requestId: 'mockId', request: {url: 'example.com'}} as Protocol.Network.RequestWillBeSentEvent); // 3) Check that the resulting NetworkRequest has the Trust Token Event data associated with it. assert.lengthOf(startedRequests, 1); assert.strictEqual(startedRequests[0].trustTokenOperationDoneEvent(), mockEvent); }); }); it('handles worker requests originating from the frame target', async () => { const target = universe.createTarget({}); const workerTarget = universe.createTarget({type: SDK.Target.Type.Worker}); const multiTargetNetworkManager = universe.multitargetNetworkManager; const initialNetworkManager = target.model(SDK.NetworkManager.NetworkManager)!; assert.strictEqual(multiTargetNetworkManager.inflightMainResourceRequests.size, 0); const requestId = 'mockId'; const requestPromise = initialNetworkManager.once(SDK.NetworkManager.Events.RequestStarted); initialNetworkManager.dispatcher.requestWillBeSent( {requestId, loaderId: '', request: {url: 'example.com'}} as Protocol.Network.RequestWillBeSentEvent); const {request} = await requestPromise; assert.strictEqual(SDK.NetworkManager.NetworkManager.forRequest(request), initialNetworkManager); assert.isOk(multiTargetNetworkManager.inflightMainResourceRequests.has(requestId)); const workerNetworkManager = workerTarget.model(SDK.NetworkManager.NetworkManager)!; workerNetworkManager.dispatcher.loadingFinished({requestId} as Protocol.Network.LoadingFinishedEvent); assert.strictEqual(SDK.NetworkManager.NetworkManager.forRequest(request), workerNetworkManager); assert.isNotOk(multiTargetNetworkManager.inflightMainResourceRequests.has(requestId)); }); it('uses main frame to get certificate', () => { const targetManager = universe.targetManager; const tabTarget = universe.createTarget({type: SDK.Target.Type.TAB}); const mainFrameTarget = universe.createTarget({parentTarget: tabTarget}); const prerenderTarget = universe.createTarget({parentTarget: tabTarget, subtype: 'prerender'}); const subframeTarget = universe.createTarget({parentTarget: mainFrameTarget, subtype: ''}); const unexpectedCalls = [tabTarget, prerenderTarget, subframeTarget].map(t => sinon.spy(t.networkAgent(), 'invoke_getCertificate')); const expectedCall = sinon.spy(mainFrameTarget.networkAgent(), 'invoke_getCertificate'); void (new SDK.NetworkManager.MultitargetNetworkManager(targetManager)).getCertificate('https://example.com'); for (const unexpectedCall of unexpectedCalls) { sinon.assert.notCalled(unexpectedCall); } assert.isTrue(expectedCall.calledOnceWith({origin: 'https://example.com'})); }); it('blocking settings are consistent after change', async () => { const multitargetNetworkManager = universe.multitargetNetworkManager; let eventCounter = 0; multitargetNetworkManager.addEventListener( SDK.NetworkManager.MultitargetNetworkManager.Events.BLOCKED_PATTERNS_CHANGED, () => eventCounter++); const blockingEnabledSetting = universe.settings.resolve(SDK.SDKSettings.requestBlockingEnabledSettingDescriptor); // Change blocking setting via Common.Settings.Settings. assert.isFalse(multitargetNetworkManager.isBlocking()); assert.isFalse(multitargetNetworkManager.requestConditions.conditionsEnabled); blockingEnabledSetting.set(true); assert.strictEqual(eventCounter, 1); assert.isFalse(multitargetNetworkManager.isBlocking()); assert.isTrue(multitargetNetworkManager.requestConditions.conditionsEnabled); multitargetNetworkManager.requestConditions.add( SDK.NetworkManager.RequestCondition.createFromSetting({url: 'example.com', enabled: true}, universe.settings)); assert.strictEqual(eventCounter, 2); assert.isTrue(multitargetNetworkManager.isBlocking()); assert.isTrue(multitargetNetworkManager.requestConditions.conditionsEnabled); multitargetNetworkManager.requestConditions.clear(); assert.strictEqual(eventCounter, 3); assert.isFalse(multitargetNetworkManager.isBlocking()); assert.isTrue(multitargetNetworkManager.requestConditions.conditionsEnabled); blockingEnabledSetting.set(false); assert.strictEqual(eventCounter, 4); assert.isFalse(multitargetNetworkManager.isBlocking()); assert.isFalse(multitargetNetworkManager.requestConditions.conditionsEnabled); // Change blocking setting via MultitargetNetworkManager. assert.isFalse(multitargetNetworkManager.isBlocking()); assert.isFalse(multitargetNetworkManager.requestConditions.conditionsEnabled); multitargetNetworkManager.requestConditions.conditionsEnabled = (true); assert.strictEqual(eventCounter, 5); assert.isFalse(multitargetNetworkManager.isBlocking()); assert.isTrue(multitargetNetworkManager.requestConditions.conditionsEnabled); multitargetNetworkManager.requestConditions.add( SDK.NetworkManager.RequestCondition.createFromSetting({url: 'example.com', enabled: true}, universe.settings)); assert.strictEqual(eventCounter, 6); assert.isTrue(multitargetNetworkManager.isBlocking()); assert.isTrue(multitargetNetworkManager.requestConditions.conditionsEnabled); multitargetNetworkManager.requestConditions.clear(); assert.strictEqual(eventCounter, 7); assert.isFalse(multitargetNetworkManager.isBlocking()); assert.isTrue(multitargetNetworkManager.requestConditions.conditionsEnabled); multitargetNetworkManager.requestConditions.conditionsEnabled = (false); assert.strictEqual(eventCounter, 8); assert.isFalse(multitargetNetworkManager.isBlocking()); assert.isFalse(multitargetNetworkManager.requestConditions.conditionsEnabled); }); it('blocking settings allow deleting an item in the middle of the list', () => { const conditions = universe.multitargetNetworkManager.requestConditions; const condition1 = SDK.NetworkManager.RequestCondition.createFromSetting({url: 'url1', enabled: true}, universe.settings); const condition2 = SDK.NetworkManager.RequestCondition.createFromSetting({url: 'url2', enabled: true}, universe.settings); const condition3 = SDK.NetworkManager.RequestCondition.createFromSetting({url: 'url3', enabled: true}, universe.settings); conditions.add(condition1, condition2, condition3); assert.deepEqual(conditions.conditions.toArray(), [condition1, condition2, condition3]); conditions.delete(condition2); assert.deepEqual(conditions.conditions.toArray(), [condition1, condition3]); }); it('applies global conditions if request conditions are disabled', () => { const connection = new MockCDPConnection(); universe.createTarget({connection}); const manager = universe.multitargetNetworkManager; const rules: Protocol.Network.EmulateNetworkConditionsByRuleRequest[] = []; connection.setSuccessHandler('Network.emulateNetworkConditionsByRule', request => { rules.push(request); return {ruleIds: []}; }); manager.setNetworkConditions(SDK.NetworkManager.Slow4GConditions); assert.lengthOf(rules, 1); sinon.assert.match(rules[0], sinon.match({ offline: false, matchedNetworkConditions: [sinon.match({ downloadThroughput: SDK.NetworkManager.Slow4GConditions.download, latency: SDK.NetworkManager.Slow4GConditions.latency, uploadThroughput: SDK.NetworkManager.Slow4GConditions.upload, })], })); }); it('calls the request conditions model for global throttling if individual request throttling is enabled', () => { const manager = universe.multitargetNetworkManager; manager.setNetworkConditions(SDK.NetworkManager.Slow4GConditions); const targetManager = new SDK.NetworkManager.NetworkManager(universe.createTarget({})); const emulateNetworkConditions = sinon.stub(targetManager.target().networkAgent(), 'invoke_emulateNetworkConditions'); const stub = sinon.stub(manager.requestConditions, 'applyConditions'); manager.modelAdded(targetManager); sinon.assert.calledOnce(stub); assert.deepEqual(stub.args[0], [false, SDK.NetworkManager.Slow4GConditions, targetManager.target().networkAgent()]); manager.setNetworkConditions(SDK.NetworkManager.Slow3GConditions); sinon.assert.calledTwice(stub); assert.deepEqual(stub.args[1], [false, SDK.NetworkManager.Slow3GConditions, targetManager.target().networkAgent()]); sinon.assert.notCalled(emulateNetworkConditions); }); it('can reorder the conditions', () => { const requestConditions = new SDK.NetworkManager.RequestConditions(universe.settings); const condition1 = SDK.NetworkManager.RequestCondition.createFromSetting({url: 'url1', enabled: true}, universe.settings); const condition2 = SDK.NetworkManager.RequestCondition.createFromSetting({url: 'url2', enabled: true}, universe.settings); const condition3 = SDK.NetworkManager.RequestCondition.createFromSetting({url: 'url3', enabled: true}, universe.settings); requestConditions.add(condition1, condition2, condition3); const changedEventStub = sinon.stub<[]>(); requestConditions.addEventListener( SDK.NetworkManager.RequestConditions.Events.REQUEST_CONDITIONS_CHANGED, changedEventStub); // Can't move the first condition up and the last condition down requestConditions.increasePriority(condition1); requestConditions.decreasePriority(condition3); sinon.assert.notCalled(changedEventStub); requestConditions.increasePriority(condition2); sinon.assert.calledOnce(changedEventStub); assert.deepEqual(requestConditions.conditions.toArray(), [condition2, condition1, condition3]); requestConditions.decreasePriority(condition1); sinon.assert.calledTwice(changedEventStub); assert.deepEqual(requestConditions.conditions.toArray(), [condition2, condition3, condition1]); }); }); describe('RequestURLPattern', () => { it('successfully upgrades url block patterns from wildcards', () => { const testPattern = (pattern: string, expectation: Object|undefined): void => { const urlPattern = SDK.NetworkManager.RequestURLPattern.upgradeFromWildcard(pattern); const keys: Array = [ 'protocol', 'username', 'password', 'hostname', 'port', 'pathname', 'search', 'hash', ]; const relevantProperties = urlPattern?.pattern && Object.assign( {}, ...keys.map( key => key in urlPattern.pattern && urlPattern.pattern[key] !== '*' ? {[key]: urlPattern.pattern[key]} : {})); assert.deepEqual(relevantProperties, expectation, pattern); }; testPattern( 'http://example.com/foo/bar', {port: '', protocol: 'http', hostname: 'example.com', pathname: '/foo/bar'}, ); testPattern( 'http://example.com', {port: '', protocol: 'http', hostname: 'example.com'}, ); testPattern( 'http://example.com/', {port: '', protocol: 'http', hostname: 'example.com', pathname: '/'}, ); testPattern( '*://example.com', {port: '', hostname: 'example.com'}, ); testPattern( 'example.com', {port: '', hostname: 'example.com*'}, ); testPattern( 'example.com/foo/bar', {port: '', hostname: 'example.com', pathname: '/foo/bar*'}, ); testPattern( 'http://*.com/foo', {port: '', protocol: 'http', hostname: '*.com', pathname: '/foo'}, ); testPattern( 'http://localhost:*/', {protocol: 'http', hostname: 'localhost', pathname: '/'}, ); testPattern( 'http://localhost:1234', {port: '1234', protocol: 'http', hostname: 'localhost'}, ); testPattern( 'localhost:1234', {port: '1234', hostname: 'localhost'}, ); testPattern( 'http://example.com*', {port: '', protocol: 'http', hostname: 'example.com*'}, ); testPattern( 'e*m', {port: '', hostname: 'e*m*'}, ); testPattern('ht tp://*', undefined); testPattern('http://*/(:id)', undefined); }); it('correctly reports pattern constructor string validity', () => { assert.strictEqual( SDK.NetworkManager.RequestURLPattern.isValidPattern('ht tp://*'), SDK.NetworkManager.RequestURLPatternValidity.FAILED_TO_PARSE); assert.strictEqual( SDK.NetworkManager.RequestURLPattern.isValidPattern('http://*/(:id)'), SDK.NetworkManager.RequestURLPatternValidity.HAS_REGEXP_GROUPS); assert.strictEqual( SDK.NetworkManager.RequestURLPattern.isValidPattern('http://*/*'), SDK.NetworkManager.RequestURLPatternValidity.VALID); }); }); describe('RequestConditions', () => { setupLocaleHooks(); setupRuntimeHooks(); let universe: TestUniverse; beforeEach(() => { universe = new TestUniverse(); }); it('loads settings with url pattern', () => { universe.settings.createSetting('network-blocked-patterns', []).set([ { enabled: true, urlPattern: '*://example.com' as SDK.NetworkManager.URLPatternConstructorString, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.NO_THROTTLING, }, ]); const conditions = new SDK.NetworkManager.RequestConditions(universe.settings); const condition = conditions.conditions.next().value as SDK.NetworkManager.RequestCondition; assert.exists(condition); assert.isUndefined(condition.wildcardURL); assert.strictEqual(condition.constructorString, '*://example.com'); assert.exists(condition.originalOrUpgradedURLPattern); }); it('loads settings with url', () => { universe.settings.createSetting('network-blocked-patterns', []).set([ {enabled: true, url: 'foo'}, ]); const conditions = new SDK.NetworkManager.RequestConditions(universe.settings); const condition = conditions.conditions.next().value as SDK.NetworkManager.RequestCondition; assert.exists(condition); assert.strictEqual(condition.wildcardURL, 'foo'); assert.strictEqual(condition.constructorString, '*://foo*'); }); it('stores settings correctly', () => { const setting = universe.settings.createSetting('network-blocked-patterns', []); const conditions = new SDK.NetworkManager.RequestConditions(universe.settings); const patternCondition = SDK.NetworkManager.RequestCondition.createFromSetting({ enabled: true, urlPattern: '*://example.com' as SDK.NetworkManager.URLPatternConstructorString, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.NO_THROTTLING, }, universe.settings); assert.strictEqual(patternCondition.conditions, SDK.NetworkManager.NoThrottlingConditions); conditions.add(patternCondition); const wildcardCondition = SDK.NetworkManager.RequestCondition.createFromSetting({ enabled: true, url: 'foo', }, universe.settings); conditions.add(wildcardCondition); assert.strictEqual(wildcardCondition.conditions, SDK.NetworkManager.BlockingConditions); assert.deepEqual(setting.get()[0], { enabled: true, urlPattern: '*://example.com' as SDK.NetworkManager.URLPatternConstructorString, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.NO_THROTTLING, }); assert.deepEqual(setting.get()[1], {enabled: true, url: 'foo'}); }); it('upgrades url to url pattern', () => { const setting = universe.settings.createSetting('network-blocked-patterns', []); const conditions = new SDK.NetworkManager.RequestConditions(universe.settings); const condition = SDK.NetworkManager.RequestCondition.createFromSetting({ enabled: true, url: 'foo', }, universe.settings); conditions.add(condition); condition.conditions = SDK.NetworkManager.NoThrottlingConditions; assert.deepEqual(setting.get()[0], { enabled: true, urlPattern: '*://foo*' as SDK.NetworkManager.URLPatternConstructorString, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.NO_THROTTLING, }); }); describe('applyConditions', () => { function stubAgent() { const universe = new TestUniverse(); const target = universe.createTarget({url: urlString`http://example.com`}); const agent = target.networkAgent(); const setBlockedURLs = sinon.stub(agent, 'invoke_setBlockedURLs'); const emulateNetworkConditions = sinon.stub(agent, 'invoke_emulateNetworkConditions'); const emulateNetworkConditionsByRule = sinon.stub(agent, 'invoke_emulateNetworkConditionsByRule'); emulateNetworkConditionsByRule.resolves({ruleIds: [], getError: () => undefined}); return {universe, agent, setBlockedURLs, emulateNetworkConditions, emulateNetworkConditionsByRule}; } it('applies blocking, global, and local throttling if individual request throttling is enabled', () => { const {universe, agent, setBlockedURLs, emulateNetworkConditions, emulateNetworkConditionsByRule} = stubAgent(); // Stub the background applyConditions on the multitarget manager's requestConditions // to avoid settings listener side effects during this test. sinon.stub(universe.multitargetNetworkManager.requestConditions, 'applyConditions'); const conditions = new SDK.NetworkManager.RequestConditions(universe.settings); conditions.conditionsEnabled = true; conditions.add( SDK.NetworkManager.RequestCondition.createFromSetting({url: 'foo', enabled: true}, universe.settings)); conditions.add( SDK.NetworkManager.RequestCondition.createFromSetting({url: 'bar', enabled: false}, universe.settings)); conditions.add(SDK.NetworkManager.RequestCondition.createFromSetting({ urlPattern: '*://nothrottle:*' as SDK.NetworkManager.URLPatternConstructorString, enabled: true, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.NO_THROTTLING, }, universe.settings)); conditions.add(SDK.NetworkManager.RequestCondition.createFromSetting({ urlPattern: '*://block:*' as SDK.NetworkManager.URLPatternConstructorString, enabled: true, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.BLOCKING, }, universe.settings)); conditions.add(SDK.NetworkManager.RequestCondition.createFromSetting({ urlPattern: '*://throttle:*' as SDK.NetworkManager.URLPatternConstructorString, enabled: true, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.SPEED_3G, }, universe.settings)); conditions.add(SDK.NetworkManager.RequestCondition.createFromSetting({ urlPattern: '*://disabled_nothrottle:*' as SDK.NetworkManager.URLPatternConstructorString, enabled: false, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.NO_THROTTLING, }, universe.settings)); conditions.add(SDK.NetworkManager.RequestCondition.createFromSetting({ urlPattern: '*://disabled_block:*' as SDK.NetworkManager.URLPatternConstructorString, enabled: false, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.BLOCKING, }, universe.settings)); conditions.add(SDK.NetworkManager.RequestCondition.createFromSetting({ urlPattern: '*://disabled_throttle:*' as SDK.NetworkManager.URLPatternConstructorString, enabled: false, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.SPEED_3G, }, universe.settings)); conditions.applyConditions(false, null, agent); sinon.assert.notCalled(emulateNetworkConditions); sinon.assert.calledOnce(emulateNetworkConditionsByRule); assert.deepEqual(emulateNetworkConditionsByRule.args[0][0], { offline: false, emulateOfflineServiceWorker: false, matchedNetworkConditions: [{ urlPattern: '*://throttle:*', latency: 2000, downloadThroughput: 50000, uploadThroughput: 50000, packetLoss: undefined, packetQueueLength: undefined, packetReordering: undefined, connectionType: Protocol.Network.ConnectionType.Cellular3g, offline: false, }], }); sinon.assert.calledOnceWithExactly(setBlockedURLs, { urlPatterns: [ {urlPattern: '*://foo*', block: true}, {urlPattern: '*://block:*', block: true}, {urlPattern: '*://throttle:*', block: false}, ], }); setBlockedURLs.resetHistory(); emulateNetworkConditions.resetHistory(); emulateNetworkConditionsByRule.resetHistory(); conditions.applyConditions(true, SDK.NetworkManager.Slow4GConditions, agent); sinon.assert.notCalled(emulateNetworkConditions); sinon.assert.calledOnce(emulateNetworkConditionsByRule); assert.deepEqual(emulateNetworkConditionsByRule.args[0][0], { offline: true, emulateOfflineServiceWorker: true, matchedNetworkConditions: [ { urlPattern: '*://throttle:*', latency: 2000, downloadThroughput: 50000, uploadThroughput: 50000, packetLoss: undefined, packetQueueLength: undefined, packetReordering: undefined, connectionType: Protocol.Network.ConnectionType.Cellular3g, offline: true, }, { urlPattern: '', latency: 562.5, downloadThroughput: 180000, uploadThroughput: 84375, packetLoss: undefined, packetQueueLength: undefined, packetReordering: undefined, connectionType: Protocol.Network.ConnectionType.Cellular4g, offline: true, }, ], }); sinon.assert.calledOnceWithExactly(setBlockedURLs, { urlPatterns: [ {urlPattern: '*://foo*', block: true}, {urlPattern: '*://block:*', block: true}, {urlPattern: '*://throttle:*', block: false}, ], }); }); it('disables throttling and blocking when the effect gets disabled globally', () => { const {universe, setBlockedURLs, emulateNetworkConditions, emulateNetworkConditionsByRule} = stubAgent(); const conditions = universe.multitargetNetworkManager.requestConditions; conditions.conditionsEnabled = true; conditions.add( SDK.NetworkManager.RequestCondition.createFromSetting({url: 'foo', enabled: true}, universe.settings)); conditions.add(SDK.NetworkManager.RequestCondition.createFromSetting({ urlPattern: '*://throttle:*' as SDK.NetworkManager.URLPatternConstructorString, enabled: true, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.SPEED_3G, }, universe.settings)); emulateNetworkConditions.resetHistory(); emulateNetworkConditionsByRule.resetHistory(); setBlockedURLs.resetHistory(); conditions.conditionsEnabled = false; sinon.assert.notCalled(emulateNetworkConditions); sinon.assert.calledOnceWithExactly( emulateNetworkConditionsByRule, {offline: false, emulateOfflineServiceWorker: false, matchedNetworkConditions: []}); sinon.assert.calledOnceWithExactly(setBlockedURLs, {urlPatterns: []}); emulateNetworkConditions.resetHistory(); emulateNetworkConditionsByRule.resetHistory(); setBlockedURLs.resetHistory(); conditions.conditionsEnabled = true; sinon.assert.notCalled(emulateNetworkConditions); sinon.assert.calledOnceWithExactly(emulateNetworkConditionsByRule, { offline: false, emulateOfflineServiceWorker: false, matchedNetworkConditions: [{ urlPattern: '*://throttle:*', latency: 2000, downloadThroughput: 50000, uploadThroughput: 50000, packetLoss: undefined, packetQueueLength: undefined, packetReordering: undefined, connectionType: Protocol.Network.ConnectionType.Cellular3g, offline: false, }], }); sinon.assert.calledOnceWithExactly(setBlockedURLs, { urlPatterns: [ {urlPattern: '*://foo*', block: true}, {urlPattern: '*://throttle:*', block: false}, ], }); }); it('correctly maps ruleIds to conditions', async () => { const {universe, emulateNetworkConditionsByRule} = stubAgent(); const multitargetNetworkManager = universe.multitargetNetworkManager; const requestConditions = multitargetNetworkManager.requestConditions; const ruleId = 'mock-rule-id-1'; const throttlingCondition = SDK.NetworkManager.RequestCondition.createFromSetting({ urlPattern: '*://example.com:*' as SDK.NetworkManager.URLPatternConstructorString, enabled: true, conditions: SDK.NetworkManager.PredefinedThrottlingConditionKey.SPEED_3G, }, universe.settings); requestConditions.add(throttlingCondition); emulateNetworkConditionsByRule.resolves({ruleIds: [ruleId], getError: () => undefined}); requestConditions.conditionsEnabled = true; await requestConditions.conditionsAppliedForTest(); const conditions = requestConditions.conditionsForId(ruleId); assert.strictEqual(conditions?.conditions, SDK.NetworkManager.Slow3GConditions); assert.strictEqual(conditions?.urlPattern, '*://example.com:*'); const request = SDK.NetworkRequest.NetworkRequest.create( 'mockRequestId' as Protocol.Network.RequestId, urlString`http://example.com`, urlString`http://example.com`, null, null, null); request.addExtraRequestInfo({ blockedRequestCookies: [], includedRequestCookies: [], requestHeaders: [], connectTiming: {requestTime: 0}, appliedNetworkConditionsId: ruleId, }); const appliedConditions = multitargetNetworkManager.appliedRequestConditions(request); assert.strictEqual(appliedConditions?.conditions, SDK.NetworkManager.Slow3GConditions); assert.strictEqual(appliedConditions?.urlPattern, '*://example.com:*'); }); }); }); describe('NetworkDispatcher', () => { const requestWillBeSentEvent = {requestId: 'mockId', request: {url: 'example.com'}} as Protocol.Network.RequestWillBeSentEvent; const responseReceivedEvent = { requestId: 'mockId', loaderId: 'mockLoaderId', frameId: 'mockFrameId', timestamp: 581734.083213, type: Protocol.Network.ResourceType.Document, response: { url: 'example.com', status: 200, statusText: '', mimeType: 'text/html', connectionReused: true, connectionId: 12345, encodedDataLength: 100, securityState: 'secure', } as Protocol.Network.Response, hasExtraInfo: true, } as Protocol.Network.ResponseReceivedEvent; const loadingFinishedEvent = {requestId: 'mockId', timestamp: 42, encodedDataLength: 42} as Protocol.Network.LoadingFinishedEvent; describe('request', () => { let universe: TestUniverse; let networkDispatcher: SDK.NetworkManager.NetworkDispatcher; beforeEach(() => { universe = new TestUniverse(); const target = universe.createTarget({}); const networkManager = target.model(SDK.NetworkManager.NetworkManager)!; networkDispatcher = networkManager.dispatcher; }); it('is preserved after loadingFinished', () => { networkDispatcher.requestWillBeSent(requestWillBeSentEvent); networkDispatcher.loadingFinished(loadingFinishedEvent); assert.exists(networkDispatcher.requestForId('mockId')); }); it('captures the cache-disabled setting when the request is sent', () => { const cacheDisabledSetting = universe.settings.resolve(SDK.SDKSettings.cacheDisabledSettingDescriptor); cacheDisabledSetting.set(true); networkDispatcher.requestWillBeSent({ ...requestWillBeSentEvent, requestId: 'cacheDisabledRequest' as Protocol.Network.RequestId, }); const cacheDisabledRequest = networkDispatcher.requestForId('cacheDisabledRequest'); assert.exists(cacheDisabledRequest); assert.isTrue(cacheDisabledRequest.cacheDisabled()); cacheDisabledSetting.set(false); assert.isTrue(cacheDisabledRequest.cacheDisabled()); networkDispatcher.requestWillBeSent({ ...requestWillBeSentEvent, requestId: 'cacheEnabledRequest' as Protocol.Network.RequestId, }); const cacheEnabledRequest = networkDispatcher.requestForId('cacheEnabledRequest'); assert.exists(cacheEnabledRequest); assert.isFalse(cacheEnabledRequest.cacheDisabled()); }); it('clears finished requests on clearRequests()', () => { networkDispatcher.requestWillBeSent(requestWillBeSentEvent); networkDispatcher.loadingFinished(loadingFinishedEvent); const unfinishedRequestWillBeSentEvent = {requestId: 'unfinishedRequestId', request: {url: 'example.com'}} as Protocol.Network.RequestWillBeSentEvent; networkDispatcher.requestWillBeSent(unfinishedRequestWillBeSentEvent); networkDispatcher.clearRequests(); assert.notExists(networkDispatcher.requestForId('mockId')); assert.exists(networkDispatcher.requestForId('unfinishedRequestId')); }); it('preserves extra info for unfinished clearRequests()', () => { const requestWillBeSentExtraInfoEvent = { requestId: 'mockId', associatedCookies: [], headers: {'Header-From-Extra-Info': 'foo'}, connectTiming: {requestTime: 0}, } as unknown as Protocol.Network.RequestWillBeSentExtraInfoEvent; networkDispatcher.requestWillBeSentExtraInfo(requestWillBeSentExtraInfoEvent); networkDispatcher.clearRequests(); networkDispatcher.requestWillBeSent(requestWillBeSentEvent); networkDispatcher.responseReceived(responseReceivedEvent); assert.exists(networkDispatcher.requestForId('mockId')); assert.deepEqual( networkDispatcher.requestForId('mockId')?.requestHeaders(), [{name: 'Header-From-Extra-Info', value: 'foo'}]); }); it('handles redirect chains with mixed presence of raw headers', () => { const hop1RequestWillBeSent = {requestId: 'mockId', request: {url: 'http://example.com'}} as Protocol.Network.RequestWillBeSentEvent; const hop2RequestWillBeSent = { requestId: 'mockId', request: {url: 'https://example.com'}, redirectHasExtraInfo: false, redirectResponse: { url: 'http://example.com', status: 307, statusText: 'Temporary redirect', headers: { Location: 'https://example.com', }, } as unknown as Protocol.Network.Response, } as Protocol.Network.RequestWillBeSentEvent; const responseExtraInfo = { requestId: 'mockId' as Protocol.Network.RequestId, blockedCookies: [], headers: {}, resourceIPAddressSpace: Protocol.Network.IPAddressSpace.Public, statusCode: 200, headersText: 'HTTP/1.1 200 OK\r\n', } as Protocol.Network.ResponseReceivedExtraInfoEvent; networkDispatcher.requestWillBeSent(hop1RequestWillBeSent); networkDispatcher.requestWillBeSent(hop2RequestWillBeSent); networkDispatcher.responseReceived(responseReceivedEvent); networkDispatcher.responseReceivedExtraInfo(responseExtraInfo); const originalResqest = networkDispatcher.requestForURL(urlString`http://example.com`); assert.exists(originalResqest); assert.strictEqual(originalResqest.statusCode, 307); assert.strictEqual(originalResqest.statusText, 'Temporary redirect'); const redirectedRequest = networkDispatcher.requestForURL(urlString`https://example.com`); assert.exists(redirectedRequest); assert.strictEqual(redirectedRequest.statusCode, 200); assert.strictEqual(redirectedRequest.statusText, 'OK'); }); it('raw headers are processed when response fails', () => { networkDispatcher.requestWillBeSent( {requestId: 'mockId', request: {url: 'http://example.com'}} as Protocol.Network.RequestWillBeSentEvent); const responseExtraInfo = { requestId: 'mockId' as Protocol.Network.RequestId, blockedCookies: [], headers: {}, resourceIPAddressSpace: Protocol.Network.IPAddressSpace.Public, statusCode: 200, headersText: 'HTTP/1.1 200 OK\r\n', } as Protocol.Network.ResponseReceivedExtraInfoEvent; networkDispatcher.responseReceivedExtraInfo(responseExtraInfo); const request = networkDispatcher.requestForId('mockId'); assert.exists(request); assert.strictEqual(request.statusCode, 0); networkDispatcher.loadingFailed({ requestId: 'mockId' as Protocol.Network.RequestId, timestamp: 2345, type: Protocol.Network.ResourceType.Document, errorText: 'net::ERR_FAILED', canceled: false, corsErrorStatus: {corsError: Protocol.Network.CorsError.MissingAllowOriginHeader, failedParameter: ''}, }); assert.strictEqual(request.statusCode, 200); assert.strictEqual(request.statusText, 'OK'); }); it('response headers are overwritten by request interception', () => { const responseReceivedExtraInfoEvent = { requestId: 'mockId' as Protocol.Network.RequestId, blockedCookies: [], headers: { 'test-header': 'first', } as Protocol.Network.Headers, resourceIPAddressSpace: Protocol.Network.IPAddressSpace.Public, statusCode: 200, } as Protocol.Network.ResponseReceivedExtraInfoEvent; const mockResponseReceivedEventWithHeaders = (headers: Protocol.Network.Headers) => { const event = structuredClone(responseReceivedEvent) as Protocol.Network.ResponseReceivedEvent; event.response.headers = headers; return event; }; networkDispatcher.requestWillBeSent(requestWillBeSentEvent); networkDispatcher.responseReceivedExtraInfo(responseReceivedExtraInfoEvent); // ResponseReceived does not overwrite response headers. networkDispatcher.responseReceived(mockResponseReceivedEventWithHeaders({'test-header': 'second'})); assert.deepEqual( networkDispatcher.requestForId('mockId')?.responseHeaders, [{name: 'test-header', value: 'first'}]); // ResponseReceived does overwrite response headers if request is marked as intercepted. universe.multitargetNetworkManager.dispatchEventToListeners( SDK.NetworkManager.MultitargetNetworkManager.Events.REQUEST_INTERCEPTED, 'mockId'); networkDispatcher.responseReceived(mockResponseReceivedEventWithHeaders({'test-header': 'third'})); assert.deepEqual( networkDispatcher.requestForId('mockId')?.responseHeaders, [{name: 'test-header', value: 'third'}]); }); it('has populated \'originalHeaders\' after receiving \'responseReceivedExtraInfo\'', () => { const responseReceivedExtraInfoEvent = { requestId: 'mockId' as Protocol.Network.RequestId, blockedCookies: [], headers: { 'test-header': 'first', 'set-cookie': 'foo=bar\ncolor=green', } as Protocol.Network.Headers, resourceIPAddressSpace: Protocol.Network.IPAddressSpace.Public, statusCode: 200, } as Protocol.Network.ResponseReceivedExtraInfoEvent; networkDispatcher.requestWillBeSent(requestWillBeSentEvent); networkDispatcher.responseReceivedExtraInfo(responseReceivedExtraInfoEvent); networkDispatcher.responseReceived(responseReceivedEvent); assert.deepEqual(networkDispatcher.requestForId('mockId')?.responseHeaders, [ {name: 'test-header', value: 'first'}, {name: 'set-cookie', value: 'foo=bar'}, {name: 'set-cookie', value: 'color=green'}, ]); }); it('Correctly set early hints properties on receivedResponse event', () => { const responseReceivedEvent = { requestId: 'mockId', loaderId: 'mockLoaderId', frameId: 'mockFrameId', timestamp: 581734.083213, type: Protocol.Network.ResourceType.Document, response: { url: 'example.com', status: 200, statusText: '', headers: { 'test-header': 'first', } as Protocol.Network.Headers, mimeType: 'text/html', connectionReused: true, connectionId: 12345, encodedDataLength: 100, securityState: 'secure', fromEarlyHints: true, } as Protocol.Network.Response, } as Protocol.Network.ResponseReceivedEvent; networkDispatcher.requestWillBeSent(requestWillBeSentEvent); networkDispatcher.responseReceived(responseReceivedEvent); assert.isTrue(networkDispatcher.requestForId('mockId')?.fromEarlyHints()); }); it('has populated early hints headers after receiving \'repsonseReceivedEarlyHints\'', () => { const earlyHintsEvent = { requestId: 'mockId' as Protocol.Network.RequestId, headers: { link: '; as=style;', } as Protocol.Network.Headers, }; networkDispatcher.requestWillBeSent(requestWillBeSentEvent); networkDispatcher.loadingFinished(loadingFinishedEvent); networkDispatcher.responseReceivedEarlyHints(earlyHintsEvent); assert.deepEqual(networkDispatcher.requestForId('mockId')?.earlyHintsHeaders, [ {name: 'link', value: '; as=style;'}, ]); }); }); describe('webSocket', () => { let universe: TestUniverse; let networkManager: SDK.NetworkManager.NetworkManager; let networkDispatcher: SDK.NetworkManager.NetworkDispatcher; beforeEach(() => { universe = new TestUniverse(); const target = universe.createTarget({}); networkManager = target.model(SDK.NetworkManager.NetworkManager)!; networkDispatcher = networkManager.dispatcher; }); it('dispatches RequestUpdated and updates frames when WebSocketFrames are sent and received', () => { const requestId = 'mock-ws-id' as Protocol.Network.RequestId; networkDispatcher.webSocketCreated({ requestId, url: 'ws://localhost:8880/echo', initiator: {type: Protocol.Network.InitiatorType.Script}, }); const request = networkManager.requestForId(requestId); assert.exists(request); assert.strictEqual(request.resourceType(), Common.ResourceType.resourceTypes.WebSocket); const requestUpdatedEvents: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { requestUpdatedEvents.push(event.data); }); networkDispatcher.webSocketFrameSent({ requestId, timestamp: 1, response: {opcode: 1, mask: true, payloadData: 'test'}, }); assert.lengthOf(requestUpdatedEvents, 1); assert.strictEqual(requestUpdatedEvents[0], request); assert.deepEqual(request.frames(), [ { type: SDK.NetworkRequest.WebSocketFrameType.Send, text: 'test', time: request.pseudoWallTime(1), opCode: 1, mask: true, }, ]); networkDispatcher.webSocketFrameSent({ requestId, timestamp: 2, response: {opcode: 1, mask: true, payloadData: 'exit'}, }); assert.lengthOf(requestUpdatedEvents, 2); assert.deepEqual(request.frames(), [ { type: SDK.NetworkRequest.WebSocketFrameType.Send, text: 'test', time: request.pseudoWallTime(1), opCode: 1, mask: true, }, { type: SDK.NetworkRequest.WebSocketFrameType.Send, text: 'exit', time: request.pseudoWallTime(2), opCode: 1, mask: true, }, ]); networkDispatcher.webSocketFrameReceived({ requestId, timestamp: 3, response: {opcode: 1, mask: false, payloadData: 'test'}, }); assert.lengthOf(requestUpdatedEvents, 3); assert.deepEqual(request.frames(), [ { type: SDK.NetworkRequest.WebSocketFrameType.Send, text: 'test', time: request.pseudoWallTime(1), opCode: 1, mask: true, }, { type: SDK.NetworkRequest.WebSocketFrameType.Send, text: 'exit', time: request.pseudoWallTime(2), opCode: 1, mask: true, }, { type: SDK.NetworkRequest.WebSocketFrameType.Receive, text: 'test', time: request.pseudoWallTime(3), opCode: 1, mask: false, }, ]); networkDispatcher.webSocketFrameReceived({ requestId, timestamp: 4, response: {opcode: 1, mask: false, payloadData: 'exit'}, }); assert.lengthOf(requestUpdatedEvents, 4); assert.deepEqual(request.frames(), [ { type: SDK.NetworkRequest.WebSocketFrameType.Send, text: 'test', time: request.pseudoWallTime(1), opCode: 1, mask: true, }, { type: SDK.NetworkRequest.WebSocketFrameType.Send, text: 'exit', time: request.pseudoWallTime(2), opCode: 1, mask: true, }, { type: SDK.NetworkRequest.WebSocketFrameType.Receive, text: 'test', time: request.pseudoWallTime(3), opCode: 1, mask: false, }, { type: SDK.NetworkRequest.WebSocketFrameType.Receive, text: 'exit', time: request.pseudoWallTime(4), opCode: 1, mask: false, }, ]); }); it('dispatches RequestUpdated when webSocketFrameError occurs', () => { const requestId = 'mock-ws-id' as Protocol.Network.RequestId; networkDispatcher.webSocketCreated({ requestId, url: 'ws://localhost:8880/echo', initiator: {type: Protocol.Network.InitiatorType.Script}, }); const request = networkManager.requestForId(requestId); assert.exists(request); const requestUpdatedEvents: SDK.NetworkRequest.NetworkRequest[] = []; networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => { requestUpdatedEvents.push(event.data); }); networkDispatcher.webSocketFrameError({ requestId, timestamp: 1, errorMessage: 'connection failed', }); assert.lengthOf(requestUpdatedEvents, 1); assert.deepEqual(request.frames(), [ { type: SDK.NetworkRequest.WebSocketFrameType.Error, text: 'connection failed', time: request.pseudoWallTime(1), opCode: -1, mask: false, }, ]); }); }); }); interface OverriddenResponse { requestId: Protocol.Fetch.RequestId; responseCode: number; body: string; responseHeaders: Protocol.Fetch.HeaderEntry[]; } describe('InterceptedRequest', () => { setupLocaleHooks(); setupRuntimeHooks(); let target: SDK.Target.Target; let fulfillRequestSpy: sinon.SinonSpy; async function checkRequestOverride( target: SDK.Target.Target, request: Protocol.Network.Request, requestId: Protocol.Fetch.RequestId, responseStatusCode: number, responseHeaders: Protocol.Fetch.HeaderEntry[], responseBody: string, expectedOverriddenResponse: OverriddenResponse, expectedSetCookieHeaders: Protocol.Fetch.HeaderEntry[] = []): Promise { const multitargetNetworkManager = universe.multitargetNetworkManager; const fetchAgent = target.fetchAgent(); const fulfilledRequest = new Promise(resolve => { multitargetNetworkManager.addEventListener( SDK.NetworkManager.MultitargetNetworkManager.Events.REQUEST_FULFILLED, resolve); }); const networkRequest = SDK.NetworkRequest.NetworkRequest.create( requestId as unknown as Protocol.Network.RequestId, urlString`${request.url}`, urlString`${request.url}`, null, null, null); networkRequest.originalResponseHeaders = responseHeaders; // The response headers passed to 'interceptedRequest' do not contain any // 'set-cookie' headers, because they originate from CDP's 'Fetch.requestPaused' // which receives its header information via mojo which in turn filters out // 'set-cookie' headers. const filteredResponseHeaders = responseHeaders.filter(header => header.name !== 'set-cookie'); const interceptedRequest = new SDK.NetworkManager.InterceptedRequest( multitargetNetworkManager, fetchAgent, request, Protocol.Network.ResourceType.Document, requestId, networkRequest, responseStatusCode, filteredResponseHeaders); interceptedRequest.responseBody = async () => { return new TextUtils.ContentData.ContentData(responseBody, false, 'text/html'); }; sinon.assert.notCalled(fulfillRequestSpy); await multitargetNetworkManager.requestIntercepted(interceptedRequest); await fulfilledRequest; sinon.assert.calledOnceWithExactly(fulfillRequestSpy, expectedOverriddenResponse); assert.deepEqual(networkRequest.setCookieHeaders, expectedSetCookieHeaders); fulfillRequestSpy.resetHistory(); return networkRequest; } async function checkSetCookieOverride( url: string, headersFromServer: Protocol.Fetch.HeaderEntry[], expectedOverriddenHeaders: Protocol.Fetch.HeaderEntry[], expectedPersistedSetCookieHeaders: Protocol.Fetch.HeaderEntry[]): Promise { const responseCode = 200; const requestId = 'request_id_for_cookies' as Protocol.Fetch.RequestId; const responseBody = 'interceptedRequest content'; const networkRequest = { method: 'GET', url, } as Protocol.Network.Request; await checkRequestOverride( target, networkRequest, requestId, responseCode, headersFromServer, responseBody, { requestId, responseCode, body: btoa(responseBody), responseHeaders: expectedOverriddenHeaders, }, expectedPersistedSetCookieHeaders); } let universe: TestUniverse; let networkPersistenceManager: Persistence.NetworkPersistenceManager.NetworkPersistenceManager; beforeEach(async () => { universe = new TestUniverse(); const {targetManager, workspace, settings} = universe; sinon.stub(Workspace.Workspace.WorkspaceImpl, 'instance').returns(workspace); sinon.stub(SDK.TargetManager.TargetManager, 'instance').returns(targetManager); sinon.stub(Common.Settings.Settings, 'instance').returns(settings); sinon.stub(SDK.NetworkManager.MultitargetNetworkManager, 'instance').returns(universe.multitargetNetworkManager); settings.resolve(Persistence.NetworkPersistenceManager.persistenceNetworkOverridesEnabledSettingDescriptor) .set(true); target = universe.createTarget({}); networkPersistenceManager = await createWorkspaceProject(urlString`file:///path/to/overrides`, [ { name: '.headers', path: 'www.example.com/', content: `[ { "applyTo": "index.html", "headers": [{ "name": "index-only", "value": "only added to index.html" }] }, { "applyTo": "*.css", "headers": [{ "name": "css-only", "value": "only added to css files" }] }, { "applyTo": "path/to/*.js", "headers": [{ "name": "another-header", "value": "only added to specific path" }] }, { "applyTo": "withCookie.html", "headers": [{ "name": "set-cookie", "value": "userId=12345" }] }, { "applyTo": "withCookie2.html", "headers": [ { "name": "set-cookie", "value": "userName=DevTools" }, { "name": "set-cookie", "value": "themeColour=dark" } ] }, { "applyTo": "withCookie3.html", "headers": [ { "name": "set-cookie", "value": "userName=DevTools" }, { "name": "set-cookie", "value": "malformed_override" } ] }, { "applyTo": "cookies/*", "headers": [ { "name": "set-cookie", "value": "unique=value" }, { "name": "set-cookie", "value": "override-me=first" } ] }, { "applyTo": "cookies/mergeCookies.html", "headers": [ { "name": "set-cookie", "value": "override-me=second" }, { "name": "set-cookie", "value": "foo=bar" } ] } ]`, }, { name: '.headers', path: '', content: `[ { "applyTo": "*", "headers": [{ "name": "age", "value": "overridden" }] } ]`, }, {name: 'helloWorld.html', path: 'www.example.com/', content: 'Hello World!'}, {name: 'utf16.html', path: 'www.example.com/', content: 'Overwritten with non-UTF16 (TODO: fix this!)'}, {name: 'something.html', path: 'file:/usr/local/foo/content/', content: 'Override for something'}, { name: '.headers', path: 'file:/usr/local/example/', content: `[ { "applyTo": "*", "headers": [{ "name": "test-file-urls", "value": "file url value" }] } ]`, }, {name: 'index.html', path: 'file:/usr/local/example/', content: 'Overridden file content'}, { name: '.headers', path: 'www.longurl.com/longurls/', content: `[ { "applyTo": "index.html-${ Platform.StringUtilities.hashCode('www.longurl.com/' + LONG_URL_PART).toString(16)}.html", "headers": [{ "name": "long-url-header", "value": "long url header value" }] } ]`, }, { name: `index.html-${Platform.StringUtilities.hashCode('www.longurl.com/' + LONG_URL_PART).toString(16)}.html`, path: 'www.longurl.com/longurls/', content: 'Overridden long URL file content', }, { name: '.headers', path: 'file:/longurls/', content: `[ { "applyTo": "index.html-${ Platform.StringUtilities .hashCode( Persistence.NetworkPersistenceManager.NetworkPersistenceManager.encodeEncodedPathToLocalPathParts( 'file:' as Platform.DevToolsPath.EncodedPathString)[0] + '/' + LONG_URL_PART) .toString(16)}.html", "headers": [{ "name": "long-file-url-header", "value": "long file url header value" }] } ]`, }, ]); sinon.stub(target.fetchAgent(), 'invoke_enable'); fulfillRequestSpy = sinon.spy(target.fetchAgent(), 'invoke_fulfillRequest'); await networkPersistenceManager.updateInterceptionPatternsForTests(); }); it('can override headers-only for a status 200 request', async () => { const responseCode = 200; const requestId = 'request_id_1' as Protocol.Fetch.RequestId; const responseBody = 'interceptedRequest content'; await checkRequestOverride( target, { method: 'GET', url: 'https://www.example.com/styles.css', } as Protocol.Network.Request, requestId, responseCode, [{name: 'content-type', value: 'text/html; charset=utf-8'}], responseBody, { requestId, responseCode, body: btoa(responseBody), responseHeaders: [ {name: 'css-only', value: 'only added to css files'}, {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); }); it('does not intercept OPTIONS requests', async () => { const requestId = 'request_id_1' as Protocol.Fetch.RequestId; const request = { method: 'OPTIONS', url: 'https://www.example.com/styles.css', } as Protocol.Network.Request; const fetchAgent = target.fetchAgent(); const continueRequestSpy = sinon.spy(fetchAgent, 'invoke_continueRequest'); const networkRequest = SDK.NetworkRequest.NetworkRequest.create( requestId as unknown as Protocol.Network.RequestId, urlString`${request.url}`, urlString`${request.url}`, null, null, null); const interceptedRequest = new SDK.NetworkManager.InterceptedRequest(universe.multitargetNetworkManager, fetchAgent, request, Protocol.Network.ResourceType.Document, requestId, networkRequest); interceptedRequest.responseBody = async () => { return new TextUtils.ContentData.ContentData('interceptedRequest content', false, 'text/html'); }; sinon.assert.notCalled(continueRequestSpy); await universe.multitargetNetworkManager.requestIntercepted(interceptedRequest); sinon.assert.notCalled(fulfillRequestSpy); sinon.assert.calledOnce(continueRequestSpy); }); it('can override headers and content for a status 200 request', async () => { const responseCode = 200; const requestId = 'request_id_2' as Protocol.Fetch.RequestId; const responseBody = 'interceptedRequest content'; const networkRequest = await checkRequestOverride( target, { method: 'GET', url: 'https://www.example.com/helloWorld.html', } as Protocol.Network.Request, requestId, responseCode, [{name: 'content-type', value: 'text/html; charset=utf-8'}], responseBody, { requestId, responseCode, body: btoa('Hello World!'), responseHeaders: [ {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); const contentData = await networkRequest.requestContentData(); assert.isFalse(TextUtils.ContentData.ContentData.isError(contentData)); assert.strictEqual((contentData as TextUtils.ContentData.ContentData).text, 'Hello World!'); }); it('provides overridden content data without querying backend with preserve-log off', async () => { universe.settings.resolve(SDK.SDKSettings.preserveNetworkLogSettingDescriptor).set(false); const responseCode = 200; const requestId = 'request_id_override_preserve_log_off' as Protocol.Fetch.RequestId; const serverResponseBody = 'original server content that should not be shown'; const networkRequest = await checkRequestOverride( target, { method: 'GET', url: 'https://www.example.com/helloWorld.html', } as Protocol.Network.Request, requestId, responseCode, [{name: 'content-type', value: 'text/html; charset=utf-8'}], serverResponseBody, { requestId, responseCode, body: btoa('Hello World!'), responseHeaders: [ {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); const requestContentDataSpy = sinon.spy(SDK.NetworkManager.NetworkManager, 'requestContentData'); const contentData = await networkRequest.requestContentData(); assert.isFalse(TextUtils.ContentData.ContentData.isError(contentData)); assert.strictEqual((contentData as TextUtils.ContentData.ContentData).text, 'Hello World!'); sinon.assert.notCalled(requestContentDataSpy); }); it('preserves overridden content data across cross-origin navigation with preserve-log on', async () => { universe.settings.resolve(SDK.SDKSettings.preserveNetworkLogSettingDescriptor).set(true); const responseCode = 200; const requestId = 'request_id_override_cross_origin_nav' as Protocol.Fetch.RequestId; const serverResponseBody = 'original server content that should not be shown'; const networkRequest = await checkRequestOverride( target, { method: 'GET', url: 'https://www.example.com/helloWorld.html', } as Protocol.Network.Request, requestId, responseCode, [{name: 'content-type', value: 'text/html; charset=utf-8'}], serverResponseBody, { requestId, responseCode, body: btoa('Hello World!'), responseHeaders: [ {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); // Simulate cross-origin navigation to a new page navigate(getMainFrame(target), { url: urlString`https://www.different-origin.com/index.html`, securityOrigin: 'https://www.different-origin.com', }); const requestContentDataSpy = sinon.spy(SDK.NetworkManager.NetworkManager, 'requestContentData'); const contentData = await networkRequest.requestContentData(); assert.isFalse(TextUtils.ContentData.ContentData.isError(contentData)); assert.strictEqual((contentData as TextUtils.ContentData.ContentData).text, 'Hello World!'); sinon.assert.notCalled(requestContentDataSpy); }); it('populates content data on networkRequest when continueRequestWithContent is called with overridden body', async () => { const fetchAgent = target.fetchAgent(); const requestId = 'request_id_continue_with_content' as Protocol.Fetch.RequestId; const request = { method: 'GET', url: 'https://www.example.com/custom.json', } as Protocol.Network.Request; const networkRequest = SDK.NetworkRequest.NetworkRequest.create( requestId as unknown as Protocol.Network.RequestId, urlString`${request.url}`, urlString`${request.url}`, null, null, null); const interceptedRequest = new SDK.NetworkManager.InterceptedRequest( universe.multitargetNetworkManager, fetchAgent, request, Protocol.Network.ResourceType.XHR, requestId, networkRequest, 200, [{name: 'content-type', value: 'application/json'}]); const overriddenJson = '{"overridden": true}'; const blob = new Blob([overriddenJson], {type: 'application/json'}); await interceptedRequest.continueRequestWithContent(blob, /* encoded= */ false, [], /* isBodyOverridden= */ true); const contentData = await networkRequest.requestContentData(); assert.isFalse(TextUtils.ContentData.ContentData.isError(contentData)); assert.strictEqual((contentData as TextUtils.ContentData.ContentData).text, overriddenJson); }); describe('NetworkPersistenceManager', () => { it('decodes the intercepted response body with the right charset', async () => { const requestId = 'request_id_utf_16' as Protocol.Fetch.RequestId; const request = { method: 'GET', url: 'https://www.example.com/utf16.html', } as Protocol.Network.Request; const fetchAgent = target.fetchAgent(); sinon.spy(fetchAgent, 'invoke_continueRequest'); const networkRequest = SDK.NetworkRequest.NetworkRequest.create( requestId as unknown as Protocol.Network.RequestId, urlString`${request.url}`, urlString`${request.url}`, null, null, null); networkRequest.originalResponseHeaders = [{name: 'content-type', value: 'text/html; charset-utf-16'}]; // Create a quick'n dirty network UISourceCode for the request manually. We need to establish a binding to the // overridden file system UISourceCode. const networkProject = new Bindings.ContentProviderBasedProject.ContentProviderBasedProject( universe.workspace, 'testing-network', Workspace.Workspace.projectTypes.Network, 'Override network project', false); const uiSourceCode = networkProject.createUISourceCode( urlString`https://www.example.com/utf16.html`, Common.ResourceType.resourceTypes.Document); networkProject.addUISourceCodeWithProvider( uiSourceCode, TextUtils.StaticContentProvider.StaticContentProvider.fromString( uiSourceCode.url(), Common.ResourceType.resourceTypes.Document, ''), null, 'text/html'); const interceptedRequest = new SDK.NetworkManager.InterceptedRequest( universe.multitargetNetworkManager, fetchAgent, request, Protocol.Network.ResourceType.Document, requestId, networkRequest, 200, [{name: 'content-type', value: 'text/html; charset-utf-16'}]); interceptedRequest.responseBody = async () => { // Very simple HTML doc base64 encoded. return new TextUtils.ContentData.ContentData( '//48ACEARABPAEMAVABZAFAARQAgAGgAdABtAGwAPgAKADwAcAA+AEkA8QB0AOsAcgBuAOIAdABpAPQAbgDgAGwAaQB6AOYAdABpAPgAbgADJjTYBt88AC8AcAA+AAoA', true, 'text/html', 'utf-16'); }; await universe.multitargetNetworkManager.requestIntercepted(interceptedRequest); const content = await networkPersistenceManager.originalContentForUISourceCode(uiSourceCode); assert.strictEqual(content, '\n

Iñtërnâtiônàlizætiøn☃𝌆

\n'); }); }); it('can override headers-only for a status 300 (redirect) request', async () => { const responseCode = 300; const requestId = 'request_id_3' as Protocol.Fetch.RequestId; const responseBody = 'interceptedRequest content'; await checkRequestOverride( target, { method: 'GET', url: 'https://www.example.com/path/to/foo.js', } as Protocol.Network.Request, requestId, responseCode, [{name: 'content-type', value: 'text/html; charset=utf-8'}], responseBody, { requestId, responseCode, body: '', responseHeaders: [ {name: 'another-header', value: 'only added to specific path'}, {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); }); it('can override headers and content for a status 300 (redirect) request', async () => { const responseCode = 300; const requestId = 'request_id_4' as Protocol.Fetch.RequestId; const responseBody = 'interceptedRequest content'; await checkRequestOverride( target, { method: 'GET', url: 'https://www.example.com/helloWorld.html', } as Protocol.Network.Request, requestId, responseCode, [{name: 'content-type', value: 'text/html; charset=utf-8'}], responseBody, { requestId, responseCode: 200, body: btoa('Hello World!'), responseHeaders: [ {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); }); it('can override headers-only for a status 404 (not found) request', async () => { const responseCode = 404; const requestId = 'request_id_5' as Protocol.Fetch.RequestId; const responseBody = 'interceptedRequest content'; await checkRequestOverride( target, { method: 'GET', url: 'https://www.example.com/doesNotExist.html', } as Protocol.Network.Request, requestId, responseCode, [{name: 'content-type', value: 'text/html; charset=utf-8'}], responseBody, { requestId, responseCode, body: btoa(responseBody), responseHeaders: [ {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); }); it('can override headers and content for a status 404 (not found) request', async () => { const responseCode = 404; const requestId = 'request_id_6' as Protocol.Fetch.RequestId; const responseBody = 'interceptedRequest content'; await checkRequestOverride( target, { method: 'GET', url: 'https://www.example.com/helloWorld.html', } as Protocol.Network.Request, requestId, responseCode, [{name: 'content-type', value: 'text/html; charset=utf-8'}], responseBody, { requestId, responseCode: 200, body: btoa('Hello World!'), responseHeaders: [ {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); }); it('can override headers and content for a request with a \'file:/\'-URL', async () => { const responseCode = 200; const requestId = 'request_id_8' as Protocol.Fetch.RequestId; const responseBody = 'interceptedRequest content'; await checkRequestOverride( target, { method: 'GET', url: 'file:///usr/local/example/index.html', } as Protocol.Network.Request, requestId, responseCode, [ {name: 'content-type', value: 'text/html; charset=utf-8'}, {name: 'age', value: 'original'}, ], responseBody, { requestId, responseCode, body: btoa('Overridden file content'), responseHeaders: [ {name: 'test-file-urls', value: 'file url value'}, {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); }); it('can apply global header overrides to a request with a \'file:/\'-URL', async () => { const responseCode = 200; const requestId = 'request_id_9' as Protocol.Fetch.RequestId; const responseBody = 'content of something/index.html'; await checkRequestOverride( target, { method: 'GET', url: 'file:///usr/local/whatever/index.html', } as Protocol.Network.Request, requestId, responseCode, [ {name: 'content-type', value: 'text/html; charset=utf-8'}, {name: 'age', value: 'original'}, ], responseBody, { requestId, responseCode, body: btoa(responseBody), responseHeaders: [ {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); }); it('can override headers and content for a request with a very long URL', async () => { const responseCode = 200; const requestId = 'request_id_10' as Protocol.Fetch.RequestId; const responseBody = 'interceptedRequest content'; await checkRequestOverride( target, { method: 'GET', url: `https://www.longurl.com/${LONG_URL_PART}`, } as Protocol.Network.Request, requestId, responseCode, [ {name: 'content-type', value: 'text/html; charset=utf-8'}, {name: 'age', value: 'original'}, ], responseBody, { requestId, responseCode, body: btoa('Overridden long URL file content'), responseHeaders: [ {name: 'long-url-header', value: 'long url header value'}, {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); }); it('can override headers for a request with a very long \'file:/\'-URL', async () => { const responseCode = 200; const requestId = 'request_id_11' as Protocol.Fetch.RequestId; const responseBody = 'interceptedRequest content'; await checkRequestOverride( target, { method: 'GET', url: 'file:///' + LONG_URL_PART, } as Protocol.Network.Request, requestId, responseCode, [ {name: 'content-type', value: 'text/html; charset=utf-8'}, {name: 'age', value: 'original'}, ], responseBody, { requestId, responseCode, body: btoa(responseBody), responseHeaders: [ {name: 'long-file-url-header', value: 'long file url header value'}, {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ], }); }); it('can override \'set-cookie\' headers', async () => { const headersFromServer = [{name: 'content-type', value: 'text/html; charset=utf-8'}]; const expectedOverriddenHeaders = [ {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, {name: 'set-cookie', value: 'userId=12345'}, ]; const expectedPersistedSetCookieHeaders = [{name: 'set-cookie', value: 'userId=12345'}]; await checkSetCookieOverride( 'https://www.example.com/withCookie.html', headersFromServer, expectedOverriddenHeaders, expectedPersistedSetCookieHeaders); }); it('marks both requests as overridden when there are 2 requests with the same URL', async () => { const responseCode = 200; const requestId1 = 'request_id_1' as Protocol.Fetch.RequestId; const requestId2 = 'request_id_2' as Protocol.Fetch.RequestId; const body = 'interceptedRequest content'; const request = { method: 'GET', url: 'https://www.example.com/styles.css', } as Protocol.Network.Request; const originalResponseHeaders = [{name: 'content-type', value: 'text/html; charset=utf-8'}]; const responseHeaders = [ {name: 'css-only', value: 'only added to css files'}, {name: 'age', value: 'overridden'}, {name: 'content-type', value: 'text/html; charset=utf-8'}, ]; const {dispatcher} = target.model(SDK.NetworkManager.NetworkManager)!; dispatcher.requestWillBeSent({requestId: requestId1 as string, request} as Protocol.Network.RequestWillBeSentEvent); dispatcher.requestWillBeSent({requestId: requestId2 as string, request} as Protocol.Network.RequestWillBeSentEvent); await checkRequestOverride(target, request, requestId1, responseCode, originalResponseHeaders, body, { requestId: requestId1, responseCode, body: btoa(body), responseHeaders, }); await checkRequestOverride(target, request, requestId2, responseCode, originalResponseHeaders, body, { requestId: requestId2, responseCode, body: btoa(body), responseHeaders, }); assert.isTrue(dispatcher.requestForId(requestId1)?.wasIntercepted()); assert.isTrue(dispatcher.requestForId(requestId2)?.wasIntercepted()); }); it('stores \'set-cookie\' headers on the request', async () => { const headersFromServer = [{name: 'set-cookie', value: 'foo=bar'}]; const expectedOverriddenHeaders = [ {name: 'age', value: 'overridden'}, ]; const expectedPersistedSetCookieHeaders = [{name: 'set-cookie', value: 'foo=bar'}]; await checkSetCookieOverride( 'https://www.example.com/noCookie.html', headersFromServer, expectedOverriddenHeaders, expectedPersistedSetCookieHeaders); }); it('can override \'set-cookie\' headers when there server also sends \'set-cookie\' headers', async () => { const headersFromServer = [{name: 'set-cookie', value: 'foo=bar'}]; const expectedOverriddenHeaders = [ {name: 'age', value: 'overridden'}, {name: 'set-cookie', value: 'userId=12345'}, ]; const expectedPersistedSetCookieHeaders = [{name: 'set-cookie', value: 'foo=bar'}, {name: 'set-cookie', value: 'userId=12345'}]; await checkSetCookieOverride( 'https://www.example.com/withCookie.html', headersFromServer, expectedOverriddenHeaders, expectedPersistedSetCookieHeaders); }); it('can overwrite a cookie value from server with a cookie value from overrides', async () => { const headersFromServer = [{name: 'set-cookie', value: 'userId=999'}]; const expectedOverriddenHeaders = [ {name: 'age', value: 'overridden'}, {name: 'set-cookie', value: 'userId=12345'}, ]; const expectedPersistedSetCookieHeaders = [{name: 'set-cookie', value: 'userId=12345'}]; await checkSetCookieOverride( 'https://www.example.com/withCookie.html', headersFromServer, expectedOverriddenHeaders, expectedPersistedSetCookieHeaders); }); it('correctly merges cookies from server and from overrides', async () => { const headersFromServer = [ {name: 'set-cookie', value: 'foo=bar'}, {name: 'set-cookie', value: 'userName=server'}, ]; const expectedOverriddenHeaders = [ {name: 'age', value: 'overridden'}, {name: 'set-cookie', value: 'userName=DevTools'}, {name: 'set-cookie', value: 'themeColour=dark'}, ]; const expectedPersistedSetCookieHeaders = [ {name: 'set-cookie', value: 'foo=bar'}, {name: 'set-cookie', value: 'userName=DevTools'}, {name: 'set-cookie', value: 'themeColour=dark'}, ]; await checkSetCookieOverride( 'https://www.example.com/withCookie2.html', headersFromServer, expectedOverriddenHeaders, expectedPersistedSetCookieHeaders); }); it('correctly merges malformed cookies from server and from overrides', async () => { const headersFromServer = [ {name: 'set-cookie', value: 'malformed_original'}, {name: 'set-cookie', value: 'userName=server'}, ]; const expectedOverriddenHeaders = [ {name: 'age', value: 'overridden'}, {name: 'set-cookie', value: 'userName=DevTools'}, {name: 'set-cookie', value: 'malformed_override'}, ]; const expectedPersistedSetCookieHeaders = [ {name: 'set-cookie', value: 'malformed_original'}, {name: 'set-cookie', value: 'userName=DevTools'}, {name: 'set-cookie', value: 'malformed_override'}, ]; await checkSetCookieOverride( 'https://www.example.com/withCookie3.html', headersFromServer, expectedOverriddenHeaders, expectedPersistedSetCookieHeaders); }); it('correctly merges \'set-cookie\' headers from server with multiple defined overrides', async () => { const headersFromServer = [ {name: 'set-cookie', value: 'userName=server'}, {name: 'set-cookie', value: 'override-me=zero'}, ]; const expectedOverriddenHeaders = [ {name: 'age', value: 'overridden'}, {name: 'set-cookie', value: 'unique=value'}, {name: 'set-cookie', value: 'override-me=second'}, {name: 'set-cookie', value: 'foo=bar'}, ]; const expectedPersistedSetCookieHeaders = [ {name: 'set-cookie', value: 'userName=server'}, {name: 'set-cookie', value: 'override-me=second'}, {name: 'set-cookie', value: 'unique=value'}, {name: 'set-cookie', value: 'foo=bar'}, ]; await checkSetCookieOverride( 'https://www.example.com/cookies/mergeCookies.html', headersFromServer, expectedOverriddenHeaders, expectedPersistedSetCookieHeaders); }); it('correctly merges \'set-cookie\' headers with duplicates', () => { const original = [ {name: 'set-cookie', value: 'foo=original'}, {name: 'set-cookie', value: 'bar=original'}, {name: 'set-cookie', value: 'baz=original'}, {name: 'set-cookie', value: 'duplicate=duplicate'}, {name: 'set-cookie', value: 'duplicate=duplicate'}, {name: 'set-cookie', value: 'duplicate2=duplicate2'}, {name: 'set-cookie', value: 'duplicate2=duplicate2'}, {name: 'set-cookie', value: 'duplicate3=duplicate3'}, {name: 'set-cookie', value: 'duplicate3=duplicate3'}, {name: 'set-cookie', value: 'malformed'}, {name: 'set-cookie', value: 'both'}, {name: 'set-cookie', value: 'double'}, {name: 'set-cookie', value: 'double'}, {name: 'set-cookie', value: 'original_duplicate'}, {name: 'set-cookie', value: 'original_duplicate'}, {name: 'set-cookie', value: 'override_duplicate'}, ]; const overrides = [ {name: 'set-cookie', value: 'bar=overridden'}, {name: 'set-cookie', value: 'baz=overridden1'}, {name: 'set-cookie', value: 'baz=overridden2'}, {name: 'set-cookie', value: 'duplicate2=overridden'}, {name: 'set-cookie', value: 'duplicate3=overridden'}, {name: 'set-cookie', value: 'duplicate3=overridden'}, {name: 'set-cookie', value: 'malformed_override'}, {name: 'set-cookie', value: 'both'}, {name: 'set-cookie', value: 'original_duplicate'}, {name: 'set-cookie', value: 'override_duplicate'}, {name: 'set-cookie', value: 'override_duplicate'}, ]; const expected = [ {name: 'set-cookie', value: 'foo=original'}, {name: 'set-cookie', value: 'bar=overridden'}, {name: 'set-cookie', value: 'baz=overridden1'}, {name: 'set-cookie', value: 'baz=overridden2'}, {name: 'set-cookie', value: 'duplicate=duplicate'}, {name: 'set-cookie', value: 'duplicate=duplicate'}, {name: 'set-cookie', value: 'duplicate2=overridden'}, {name: 'set-cookie', value: 'duplicate3=overridden'}, {name: 'set-cookie', value: 'duplicate3=overridden'}, {name: 'set-cookie', value: 'malformed'}, {name: 'set-cookie', value: 'both'}, {name: 'set-cookie', value: 'double'}, {name: 'set-cookie', value: 'double'}, {name: 'set-cookie', value: 'original_duplicate'}, {name: 'set-cookie', value: 'override_duplicate'}, {name: 'set-cookie', value: 'override_duplicate'}, {name: 'set-cookie', value: 'malformed_override'}, ]; assert.deepEqual(SDK.NetworkManager.InterceptedRequest.mergeSetCookieHeaders(original, overrides), expected); }); describe('getRecommendedNetworkPreset', () => { it('should recommend "Slow 3G" for high RTT', () => { // RTT >= 276ms should be Slow 3G. const rtt = 500; const preset = SDK.NetworkManager.getRecommendedNetworkPreset(rtt); assert.isNotNull(preset); assert.strictEqual(preset?.key, SDK.NetworkManager.Slow3GConditions.key); }); it('should recommend "Slow 4G" for medium RTT', () => { // RTT between 106ms and 275ms should be Slow 4G. const rtt = 200; const preset = SDK.NetworkManager.getRecommendedNetworkPreset(rtt); assert.isNotNull(preset); assert.strictEqual(preset?.key, SDK.NetworkManager.Slow4GConditions.key); }); it('should recommend "Fast 4G" for low RTT', () => { // RTT between 60ms and 105ms should be Fast 4G. const rtt = 100; const preset = SDK.NetworkManager.getRecommendedNetworkPreset(rtt); assert.isNotNull(preset); assert.strictEqual(preset?.key, SDK.NetworkManager.Fast4GConditions.key); }); it('should return null for very low RTT, suggesting no throttling', () => { // RTT below 60ms should suggest no throttling by returning null. const rtt = 20; const preset = SDK.NetworkManager.getRecommendedNetworkPreset(rtt); assert.isNull(preset); }); it('should handle boundary conditions correctly', () => { // Crossover between Slow 3G and Slow 4G const slow3GPreset = SDK.NetworkManager.getRecommendedNetworkPreset(276); assert.isNotNull(slow3GPreset); assert.strictEqual(slow3GPreset.key, SDK.NetworkManager.Slow3GConditions.key, 'RTT 276 should be Slow 3G'); const slow4GAt3GBoundary = SDK.NetworkManager.getRecommendedNetworkPreset(275); assert.isNotNull(slow4GAt3GBoundary); assert.strictEqual(slow4GAt3GBoundary.key, SDK.NetworkManager.Slow4GConditions.key, 'RTT 275 should be Slow 4G'); // Crossover between Slow 4G and Fast 4G const slow4GAtFast4GBoundary = SDK.NetworkManager.getRecommendedNetworkPreset(106); assert.isNotNull(slow4GAtFast4GBoundary); assert.strictEqual( slow4GAtFast4GBoundary.key, SDK.NetworkManager.Slow4GConditions.key, 'RTT 106 should be Slow 4G'); const fast4GAtSlow4GBoundary = SDK.NetworkManager.getRecommendedNetworkPreset(105); assert.isNotNull(fast4GAtSlow4GBoundary); assert.strictEqual( fast4GAtSlow4GBoundary.key, SDK.NetworkManager.Fast4GConditions.key, 'RTT 105 should be Fast 4G'); // Boundary for Fast 4G and No Throttling const fast4GAtNoThrottlingBoundary = SDK.NetworkManager.getRecommendedNetworkPreset(60); assert.isNotNull(fast4GAtNoThrottlingBoundary); assert.strictEqual( fast4GAtNoThrottlingBoundary.key, SDK.NetworkManager.Fast4GConditions.key, 'RTT 60 should be Fast 4G'); const noThrottlingPreset = SDK.NetworkManager.getRecommendedNetworkPreset(59); assert.isNull(noThrottlingPreset, 'RTT 59 should be null'); }); }); });