From 2c80473f352504f670329dd6a97a9aa4ad0b3cc9 Mon Sep 17 00:00:00 2001 From: Thiago Perrotta Date: Tue, 13 Jun 2023 16:01:28 +0200 Subject: [PATCH] prototype network request interception Bug: #643, #644 Upstream: https://github.com/w3c/webdriver-bidi/issues/66 --- .../domains/network/networkRequest.ts | 22 +++--- src/protocol/protocol.ts | 75 +++++++++++++++++-- src/protocol/types.ts | 29 +++++++ tests/test_browsing_context.py | 2 + tests/test_network.py | 5 ++ 5 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 src/protocol/types.ts diff --git a/src/bidiMapper/domains/network/networkRequest.ts b/src/bidiMapper/domains/network/networkRequest.ts index be6ba66bb4..28ab9c11c3 100644 --- a/src/bidiMapper/domains/network/networkRequest.ts +++ b/src/bidiMapper/domains/network/networkRequest.ts @@ -158,6 +158,7 @@ export class NetworkRequest { #getBaseEventParams(): Network.BaseParameters { return { + isBlocked: false, // TODO: Implement. context: this.#requestWillBeSentEvent?.frameId ?? null, navigation: this.#requestWillBeSentEvent?.loaderId ?? null, // TODO: implement. @@ -183,12 +184,9 @@ export class NetworkRequest { url: this.#requestWillBeSentEvent?.request.url ?? NetworkRequest.#unknown, method: this.#requestWillBeSentEvent?.request.method ?? NetworkRequest.#unknown, - headers: Object.keys( - this.#requestWillBeSentEvent?.request.headers ?? [] - ).map((key) => ({ - name: key, - value: this.#requestWillBeSentEvent?.request.headers[key], - })), + headers: NetworkRequest.#getHeaders( + this.#requestWillBeSentEvent?.request.headers + ), cookies, // TODO: implement. headersSize: -1, @@ -322,10 +320,14 @@ export class NetworkRequest { ); } - static #getHeaders(headers: Protocol.Network.Headers): Network.Header[] { - return Object.keys(headers).map((key) => ({ - name: key, - value: headers[key], + static #getHeaders(headers?: Protocol.Network.Headers): Network.Header[] { + if (!headers) { + return []; + } + + return Object.entries(headers).map(([name, value]) => ({ + name, + value, })); } diff --git a/src/protocol/protocol.ts b/src/protocol/protocol.ts index 3fe131aba0..bfd869b875 100644 --- a/src/protocol/protocol.ts +++ b/src/protocol/protocol.ts @@ -24,6 +24,8 @@ import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; +import type {Range} from './types.js'; + interface EventResponse { method: MethodType; params: ParamsType; @@ -56,6 +58,7 @@ export namespace Message { export type CommandResponse = ResultData & Pick; export type EmptyCommand = never; + export type EmptyMessage = Record; export type EmptyParams = Record; export type EmptyResult = {result: Record}; @@ -97,6 +100,7 @@ export namespace Message { NoSuchElement = 'no such element', NoSuchFrame = 'no such frame', NoSuchHandle = 'no such handle', + NoSuchIntercept = 'no such intercept', NoSuchNode = 'no such node', NoSuchScript = 'no such script', SessionNotCreated = 'session not created', @@ -177,6 +181,12 @@ export namespace Message { } } + export class NoSuchInterceptException extends ErrorResponse { + constructor(message: string, stacktrace?: string) { + super(ErrorCode.NoSuchIntercept, message, stacktrace); + } + } + export class NoSuchScriptException extends ErrorResponse { constructor(message: string, stacktrace?: string) { super(ErrorCode.NoSuchScript, message, stacktrace); @@ -1051,7 +1061,7 @@ export namespace Log { } export namespace Network { - export type Command = Message.EmptyCommand; + export type Command = AddInterceptCommand; export type Result = Message.EmptyResult; @@ -1081,16 +1091,22 @@ export namespace Network { FetchErrorParams >; + export type StringHeaderValue = { + value: string; + }; + + export type BinaryHeaderValue = { + binaryValue: Range<0, 255>[]; + }; + export type Header = { name: string; - value?: string; - binaryValue?: number[]; - }; + } & (StringHeaderValue | BinaryHeaderValue); export type Cookie = { name: string; value?: string; - binaryValue?: number[]; + binaryValue?: Range<0, 255>[]; domain: string; path: string; expires?: number; @@ -1129,12 +1145,16 @@ export namespace Network { timings: FetchTimingInfo; }; + export type Intercept = string; + export type BaseParameters = { context: CommonDataTypes.BrowsingContext | null; + isBlocked: boolean; navigation: BrowsingContext.Navigation | null; redirectCount: number; request: RequestData; timestamp: number; + intercepts?: Intercept[]; }; export type Initiator = { @@ -1161,6 +1181,7 @@ export namespace Network { headersSize: number | null; bodySize: number | null; content: ResponseContent; + authChallenge?: AuthChallenge; }; export type BeforeRequestSentParams = BaseParameters & { @@ -1179,6 +1200,50 @@ export namespace Network { errorText: string; }; + export type AuthChallenge = { + scheme: string; + realm: string; + }; + + export type AuthCredentials = { + type: 'password'; // XXX: 'credentials'? + username: string; + password: string; + }; + + export type Body = StringBody | Base64Body; + + export type StringBody = { + type: 'string'; + value: string; + }; + + export type Base64Body = { + type: 'base64'; + value: string; + }; + + export type AddInterceptCommand = { + method: 'network.addIntercept'; + params: AddInterceptParameters; + }; + + export type AddInterceptParameters = { + phases: InterceptPhase[]; + urlPatterns?: string[]; + }; + + export type InterceptPhase = + | 'beforeRequestSent' + | 'responseStarted' + | 'authRequired'; + + export type AddInterceptResult = { + result: { + intercept: Intercept; + }; + }; + export const AllEvents = 'network'; export enum EventNames { diff --git a/src/protocol/types.ts b/src/protocol/types.ts new file mode 100644 index 0000000000..edfc5318be --- /dev/null +++ b/src/protocol/types.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2023 Google LLC. + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @see: https://stackoverflow.com/a/70307091 */ +export type Enumerate< + N extends number, + Acc extends number[] = [] +> = Acc['length'] extends N + ? Acc[number] + : Enumerate; + +export type Range = Exclude< + Enumerate, + Enumerate +>; diff --git a/tests/test_browsing_context.py b/tests/test_browsing_context.py index d09e7bcdd7..2669b28298 100644 --- a/tests/test_browsing_context.py +++ b/tests/test_browsing_context.py @@ -894,6 +894,7 @@ async def test_browsingContext_ignoreCache(websocket, context_id, ignoreCache): assert response == { "method": "network.beforeRequestSent", "params": { + "isBlocked": False, "context": context_id, "initiator": ANY_DICT, "navigation": ANY_STR, @@ -907,6 +908,7 @@ async def test_browsingContext_ignoreCache(websocket, context_id, ignoreCache): assert response == { "method": "network.responseCompleted", "params": { + "isBlocked": False, "context": context_id, "navigation": ANY_STR, "redirectCount": 0, diff --git a/tests/test_network.py b/tests/test_network.py index 62fd80f771..01d8a9e706 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -39,6 +39,7 @@ async def test_network_before_request_sent_event_emitted( assert resp == { "method": "network.beforeRequestSent", "params": { + "isBlocked": False, "context": context_id, "navigation": ANY_STR, "redirectCount": 0, @@ -159,6 +160,7 @@ async def test_network_before_request_sent_event_with_cookies_emitted( assert resp == { "method": "network.beforeRequestSent", "params": { + "isBlocked": False, "context": context_id, "navigation": ANY_STR, "redirectCount": 0, @@ -210,6 +212,7 @@ async def test_network_network_response_completed_event_emitted( assert resp == { "method": "network.responseCompleted", "params": { + "isBlocked": False, "context": context_id, "navigation": ANY_STR, "redirectCount": 0, @@ -264,6 +267,7 @@ async def test_network_bad_ssl(websocket, context_id): assert resp == { "method": "network.fetchError", "params": { + "isBlocked": False, "context": context_id, "navigation": ANY_STR, "redirectCount": 0, @@ -301,6 +305,7 @@ async def test_network_before_request_sent_event_with_data_url_emitted( assert resp == { "method": "network.beforeRequestSent", "params": { + "isBlocked": False, "context": context_id, "navigation": ANY_STR, "redirectCount": 0,