Skip to content

Commit 937d719

Browse files
feat: implement network.setExtraHeaders (#3629)
1 parent c66d125 commit 937d719

File tree

14 files changed

+581
-12
lines changed

14 files changed

+581
-12
lines changed

.github/workflows/e2e.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106
CHROMEDRIVER: ${{ matrix.kind == 'cd' }}
107107
# TODO: Fix tests and don't rerun them.
108108
# https://github.com/GoogleChromeLabs/chromium-bidi/issues/3412
109-
RERUNS_TIMES: ${{ matrix.kind == 'node' && '4' || '0' }}
109+
RERUNS_TIMES: ${{ matrix.kind == 'node' && '4' || '2' }}
110110
- name: Run E2E tests
111111
if: matrix.os != 'ubuntu-latest' || matrix.head != 'headful'
112112
timeout-minutes: 20
@@ -120,7 +120,7 @@ jobs:
120120
CHROMEDRIVER: ${{ matrix.kind == 'cd' }}
121121
# TODO: Fix tests and don't rerun them.
122122
# https://github.com/GoogleChromeLabs/chromium-bidi/issues/3412
123-
RERUNS_TIMES: ${{ matrix.kind == 'node' && '4' || '0' }}
123+
RERUNS_TIMES: ${{ matrix.kind == 'node' && '4' || '2' }}
124124
- name: Upload artifacts
125125
if: ${{ !cancelled() }}
126126
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2

src/bidiMapper/BidiServer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import type {CdpClient} from '../cdp/CdpClient';
18+
import type {CdpClient} from '../cdp/CdpClient.js';
1919
import type {CdpConnection} from '../cdp/CdpConnection.js';
2020
import type {Browser, ChromiumBidi} from '../protocol/protocol.js';
2121
import {EventEmitter} from '../utils/EventEmitter.js';

src/bidiMapper/CommandProcessor.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import type {CdpClient} from '../cdp/CdpClient';
18+
import type {CdpClient} from '../cdp/CdpClient.js';
1919
import type {CdpConnection} from '../cdp/CdpConnection.js';
2020
import {
2121
Exception,
@@ -136,6 +136,7 @@ export class CommandProcessor extends EventEmitter<CommandProcessorEventsMap> {
136136
browsingContextStorage,
137137
networkStorage,
138138
userContextStorage,
139+
contextConfigStorage,
139140
);
140141
this.#permissionsProcessor = new PermissionsProcessor(browserCdpClient);
141142
this.#scriptProcessor = new ScriptProcessor(
@@ -409,9 +410,8 @@ export class CommandProcessor extends EventEmitter<CommandProcessorEventsMap> {
409410
this.#parser.parseSetCacheBehaviorParams(command.params),
410411
);
411412
case 'network.setExtraHeaders':
412-
this.#parser.parseSetExtraHeadersParams(command.params);
413-
throw new UnknownErrorException(
414-
`Method ${command.method} is not implemented.`,
413+
return await this.#networkProcessor.setExtraHeaders(
414+
this.#parser.parseSetExtraHeadersParams(command.params),
415415
);
416416
// keep-sorted end
417417

src/bidiMapper/modules/browser/ContextConfig.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
* limitations under the License.
1616
*/
1717

18+
import type {Protocol} from 'devtools-protocol';
19+
1820
import type {
1921
BrowsingContext,
2022
Emulation,
@@ -29,13 +31,16 @@ export class ContextConfig {
2931
acceptInsecureCerts?: boolean;
3032
viewport?: BrowsingContext.Viewport | null;
3133
devicePixelRatio?: number | null;
34+
// Extra headers are kept in CDP format.
35+
extraHeaders?: Protocol.Network.Headers;
3236
geolocation?:
3337
| Emulation.GeolocationCoordinates
3438
| Emulation.GeolocationPositionError
3539
| null;
3640
locale?: string | null;
3741
prerenderingDisabled?: boolean;
3842
screenOrientation?: Emulation.ScreenOrientation | null;
43+
// Timezone is kept in CDP format with GMT prefix for offset values.
3944
timezone?: string | null;
4045
userPromptHandler?: Session.UserPromptHandler;
4146
}

src/bidiMapper/modules/cdp/CdpTarget.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,10 @@ export class CdpTarget {
676676
promises.push(this.setTimezoneOverride(config.timezone));
677677
}
678678

679+
if (config.extraHeaders !== undefined) {
680+
promises.push(this.setExtraHeaders(config.extraHeaders));
681+
}
682+
679683
if (config.acceptInsecureCerts !== undefined) {
680684
promises.push(
681685
this.cdpClient.sendCommand('Security.setIgnoreCertificateErrors', {
@@ -858,4 +862,10 @@ export class CdpTarget {
858862
});
859863
}
860864
}
865+
866+
async setExtraHeaders(headers: Protocol.Network.Headers): Promise<void> {
867+
await this.cdpClient.sendCommand('Network.setExtraHTTPHeaders', {
868+
headers,
869+
});
870+
}
861871
}

src/bidiMapper/modules/context/BrowsingContextProcessor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
import {CdpErrorConstants} from '../../../utils/cdpErrorConstants.js';
2929
import type {ContextConfig} from '../browser/ContextConfig.js';
3030
import type {ContextConfigStorage} from '../browser/ContextConfigStorage.js';
31-
import type {UserContextStorage} from '../browser/UserContextStorage';
31+
import type {UserContextStorage} from '../browser/UserContextStorage.js';
3232
import type {EventManager} from '../session/EventManager.js';
3333

3434
import type {BrowsingContextImpl} from './BrowsingContextImpl.js';

src/bidiMapper/modules/network/NetworkProcessor.spec.ts

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
*/
1717
import {expect} from 'chai';
1818

19-
import {NetworkProcessor} from './NetworkProcessor.js';
19+
import {UnsupportedOperationException} from '../../../protocol/ErrorResponse.js';
20+
import type {Network} from '../../../protocol/protocol.js';
21+
22+
import {NetworkProcessor, parseBiDiHeaders} from './NetworkProcessor.js';
2023

2124
describe('NetworkProcessor', () => {
2225
describe('parse url string', () => {
@@ -209,4 +212,98 @@ describe('NetworkProcessor', () => {
209212
expect(NetworkProcessor.isMethodValid('')).to.be.false;
210213
});
211214
});
215+
describe('parseBiDiHeaders', () => {
216+
const SOME_HEADER_NAME = 'SOME HEADER NAME';
217+
const SOME_HEADER_VALUE = 'SOME HEADER VALUE';
218+
const ANOTHER_HEADER_NAME = 'ANOTHER HEADER NAME';
219+
const ANOTHER_HEADER_VALUE = 'ANOTHER HEADER VALUE';
220+
221+
it('should return an empty object for an empty array', () => {
222+
const result = parseBiDiHeaders([]);
223+
expect(result).to.deep.equal({});
224+
});
225+
226+
it('should parse a single header', () => {
227+
const headers: Network.Header[] = [
228+
{
229+
name: SOME_HEADER_NAME,
230+
value: {type: 'string', value: SOME_HEADER_VALUE},
231+
},
232+
];
233+
const result = parseBiDiHeaders(headers);
234+
expect(result).to.deep.equal({
235+
[SOME_HEADER_NAME]: SOME_HEADER_VALUE,
236+
});
237+
});
238+
239+
it('should parse multiple unique headers', () => {
240+
const headers: Network.Header[] = [
241+
{
242+
name: SOME_HEADER_NAME,
243+
value: {type: 'string', value: SOME_HEADER_VALUE},
244+
},
245+
{
246+
name: ANOTHER_HEADER_NAME,
247+
value: {type: 'string', value: ANOTHER_HEADER_VALUE},
248+
},
249+
];
250+
const result = parseBiDiHeaders(headers);
251+
expect(result).to.deep.equal({
252+
[SOME_HEADER_NAME]: SOME_HEADER_VALUE,
253+
[ANOTHER_HEADER_NAME]: ANOTHER_HEADER_VALUE,
254+
});
255+
});
256+
257+
it('should combine multiple headers with the same name', () => {
258+
const headers: Network.Header[] = [
259+
{
260+
name: SOME_HEADER_NAME,
261+
value: {type: 'string', value: 'SOME VALUE'},
262+
},
263+
{
264+
name: SOME_HEADER_NAME,
265+
value: {type: 'string', value: 'ANOTHER VALUE'},
266+
},
267+
{
268+
name: SOME_HEADER_NAME,
269+
value: {type: 'string', value: 'YET ANOTHER VALUE'},
270+
},
271+
];
272+
const result = parseBiDiHeaders(headers);
273+
expect(result).to.deep.equal({
274+
[SOME_HEADER_NAME]: 'SOME VALUE, ANOTHER VALUE, YET ANOTHER VALUE',
275+
});
276+
});
277+
278+
it('should throw for non-string header values', () => {
279+
const headers: Network.Header[] = [
280+
{
281+
name: SOME_HEADER_NAME,
282+
value: {type: 'base64', value: '...'},
283+
},
284+
];
285+
expect(() => parseBiDiHeaders(headers)).to.throw(
286+
UnsupportedOperationException,
287+
'Only string headers values are supported',
288+
);
289+
});
290+
291+
it('should be case-sensitive for header names', () => {
292+
const headers: Network.Header[] = [
293+
{
294+
name: 'SOME HEADER NAME',
295+
value: {type: 'string', value: 'SOME VALUE'},
296+
},
297+
{
298+
name: 'some header name',
299+
value: {type: 'string', value: 'some value'},
300+
},
301+
];
302+
const result = parseBiDiHeaders(headers);
303+
expect(result).to.deep.equal({
304+
['SOME HEADER NAME']: 'SOME VALUE',
305+
'some header name': 'some value',
306+
});
307+
});
308+
});
212309
});

src/bidiMapper/modules/network/NetworkProcessor.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@
1515
* limitations under the License.
1616
*/
1717

18+
import type {Protocol} from 'devtools-protocol';
19+
1820
import {
1921
Network,
2022
type EmptyResult,
2123
NoSuchRequestException,
2224
InvalidArgumentException,
25+
UnsupportedOperationException,
2326
} from '../../../protocol/protocol.js';
27+
import type {ContextConfigStorage} from '../browser/ContextConfigStorage.js';
2428
import type {UserContextStorage} from '../browser/UserContextStorage.js';
29+
import type {CdpTarget} from '../cdp/CdpTarget.js';
2530
import type {BrowsingContextStorage} from '../context/BrowsingContextStorage.js';
2631

2732
import type {NetworkRequest} from './NetworkRequest.js';
@@ -33,15 +38,18 @@ export class NetworkProcessor {
3338
readonly #browsingContextStorage: BrowsingContextStorage;
3439
readonly #networkStorage: NetworkStorage;
3540
readonly #userContextStorage: UserContextStorage;
41+
readonly #contextConfigStorage: ContextConfigStorage;
3642

3743
constructor(
3844
browsingContextStorage: BrowsingContextStorage,
3945
networkStorage: NetworkStorage,
4046
userContextStorage: UserContextStorage,
47+
contextConfigStorage: ContextConfigStorage,
4148
) {
4249
this.#userContextStorage = userContextStorage;
4350
this.#browsingContextStorage = browsingContextStorage;
4451
this.#networkStorage = networkStorage;
52+
this.#contextConfigStorage = contextConfigStorage;
4553
}
4654

4755
async addIntercept(
@@ -534,6 +542,68 @@ export class NetworkProcessor {
534542
this.#networkStorage.disownData(params);
535543
return {};
536544
}
545+
546+
async setExtraHeaders(
547+
params: Network.SetExtraHeadersParameters,
548+
): Promise<EmptyResult> {
549+
if (params.userContexts !== undefined && params.contexts !== undefined) {
550+
throw new InvalidArgumentException(
551+
'contexts and userContexts are mutually exclusive',
552+
);
553+
}
554+
555+
const cdpExtraHeaders = parseBiDiHeaders(params.headers);
556+
const affectedCdpTargets = new Set<CdpTarget>();
557+
558+
if (params.userContexts === undefined && params.contexts === undefined) {
559+
this.#contextConfigStorage.updateGlobalConfig({
560+
extraHeaders: cdpExtraHeaders,
561+
});
562+
this.#browsingContextStorage
563+
.getAllContexts()
564+
.forEach((c) => affectedCdpTargets.add(c.cdpTarget));
565+
}
566+
567+
if (params.userContexts !== undefined) {
568+
// Assert the user contexts exist.
569+
await this.#userContextStorage.verifyUserContextIdList(
570+
params.userContexts,
571+
);
572+
params.userContexts.forEach((userContext) => {
573+
this.#contextConfigStorage.updateUserContextConfig(userContext, {
574+
extraHeaders: cdpExtraHeaders,
575+
});
576+
this.#browsingContextStorage
577+
.getAllContexts()
578+
.filter((c) => c.userContext === userContext)
579+
.forEach((c) => affectedCdpTargets.add(c.cdpTarget));
580+
});
581+
}
582+
if (params.contexts !== undefined) {
583+
this.#browsingContextStorage.verifyTopLevelContextsList(params.contexts);
584+
params.contexts.forEach((browsingContextId) => {
585+
this.#contextConfigStorage.updateBrowsingContextConfig(
586+
browsingContextId,
587+
{extraHeaders: cdpExtraHeaders},
588+
);
589+
590+
affectedCdpTargets.add(
591+
this.#browsingContextStorage.getContext(browsingContextId).cdpTarget,
592+
);
593+
this.#browsingContextStorage
594+
.getContext(browsingContextId)
595+
.allChildren.forEach((c) => affectedCdpTargets.add(c.cdpTarget));
596+
});
597+
}
598+
599+
await Promise.all(
600+
Array.from(affectedCdpTargets).map((cdpTarget) =>
601+
cdpTarget.setExtraHeaders(cdpExtraHeaders),
602+
),
603+
);
604+
605+
return {};
606+
}
537607
}
538608

539609
/**
@@ -558,3 +628,27 @@ function unescapeURLPattern(pattern: string) {
558628
}
559629
return result;
560630
}
631+
632+
// Export for testing.
633+
export function parseBiDiHeaders(
634+
headers: Network.Header[],
635+
): Protocol.Network.Headers {
636+
const parsedHeaders: Protocol.Network.Headers = {};
637+
for (const bidiHeader of headers) {
638+
if (bidiHeader.value.type === 'string') {
639+
if (parsedHeaders[bidiHeader.name] === undefined) {
640+
parsedHeaders[bidiHeader.name] = bidiHeader.value.value;
641+
} else {
642+
// Combine headers with the same name, meaning concatenate them with 0x2C 0x20
643+
// separator: https://fetch.spec.whatwg.org/#concept-header-list-combine.
644+
parsedHeaders[bidiHeader.name] =
645+
`${parsedHeaders[bidiHeader.name]}, ${bidiHeader.value.value}`;
646+
}
647+
} else {
648+
throw new UnsupportedOperationException(
649+
'Only string headers values are supported',
650+
);
651+
}
652+
}
653+
return parsedHeaders;
654+
}

src/bidiMapper/modules/network/NetworkStorage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {type LoggerFn, LogType} from '../../../utils/log.js';
2828
import {uuidv4} from '../../../utils/uuid.js';
2929
import type {CdpClient} from '../../BidiMapper.js';
3030
import type {CdpTarget} from '../cdp/CdpTarget.js';
31-
import type {BrowsingContextStorage} from '../context/BrowsingContextStorage';
31+
import type {BrowsingContextStorage} from '../context/BrowsingContextStorage.js';
3232
import type {EventManager} from '../session/EventManager.js';
3333

3434
import {NetworkRequest} from './NetworkRequest.js';

src/cdp/CdpClient.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import sinon from 'sinon';
2222

2323
import {StubTransport} from '../utils/transportStub.spec.js';
2424

25-
import type {CdpClient} from './CdpClient';
25+
import type {CdpClient} from './CdpClient.js';
2626
import {MapperCdpConnection} from './CdpConnection.js';
2727

2828
chai.use(chaiAsPromised);

0 commit comments

Comments
 (0)