Skip to content

Commit

Permalink
feat(popups): introduce BrowserContext.setDefaultHTTPHeaders (#1116)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgozman authored Feb 26, 2020
1 parent 4f69930 commit 672f3f9
Show file tree
Hide file tree
Showing 14 changed files with 134 additions and 30 deletions.
12 changes: 12 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ Indicates that the browser is connected.
- `accuracy` <[number]> Optional non-negative accuracy value.
- `locale` <?[string]> Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules.
- `permissions` <[Object]> A map from origin keys to permissions values. See [browserContext.setPermissions](#browsercontextsetpermissionsorigin-permissions) for more details.
- `extraHTTPHeaders` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings.
- returns: <[Promise]<[BrowserContext]>>

Creates a new browser context. It won't share cookies/cache with other browser contexts.
Expand Down Expand Up @@ -232,6 +233,7 @@ Creates a new browser context. It won't share cookies/cache with other browser c
- `accuracy` <[number]> Optional non-negative accuracy value.
- `locale` <?[string]> Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules.
- `permissions` <[Object]> A map from origin keys to permissions values. See [browserContext.setPermissions](#browsercontextsetpermissionsorigin-permissions) for more details.
- `extraHTTPHeaders` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings.
- returns: <[Promise]<[Page]>>

Creates a new page in a new browser context. Closing this page will close the context as well.
Expand Down Expand Up @@ -271,6 +273,7 @@ await context.close();
- [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
- [browserContext.setDefaultNavigationTimeout(timeout)](#browsercontextsetdefaultnavigationtimeouttimeout)
- [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout)
- [browserContext.setExtraHTTPHeaders(headers)](#browsercontextsetextrahttpheadersheaders)
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
<!-- GEN:stop -->
Expand Down Expand Up @@ -374,6 +377,14 @@ This setting will change the default maximum time for all the methods accepting

> **NOTE** [`page.setDefaultNavigationTimeout`](#pagesetdefaultnavigationtimeouttimeout), [`page.setDefaultTimeout`](#pagesetdefaulttimeouttimeout) and [`browserContext.setDefaultNavigationTimeout`](#browsercontextsetdefaultnavigationtimeouttimeout) take priority over [`browserContext.setDefaultTimeout`](#browserContextsetdefaulttimeouttimeout).
#### browserContext.setExtraHTTPHeaders(headers)
- `headers` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings.
- returns: <[Promise]>

The extra HTTP headers will be sent with every request initiated by any page in the context. These headers are merged with page-specific extra HTTP headers set with [page.setExtraHTTPHeaders()](#pagesetextrahttpheadersheaders). If page overrides a particular header, page-specific header value will be used instead of the browser context header value.

> **NOTE** `browserContext.setExtraHTTPHeaders` does not guarantee the order of headers in the outgoing requests.
#### browserContext.setGeolocation(geolocation)
- `geolocation` <[Object]>
- `latitude` <[number]> Latitude between -90 and 90.
Expand Down Expand Up @@ -3582,6 +3593,7 @@ const backgroundPage = await backroundPageTarget.page();
- [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
- [browserContext.setDefaultNavigationTimeout(timeout)](#browsercontextsetdefaultnavigationtimeouttimeout)
- [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout)
- [browserContext.setExtraHTTPHeaders(headers)](#browsercontextsetextrahttpheadersheaders)
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
<!-- GEN:stop -->
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"main": "index.js",
"playwright": {
"chromium_revision": "744254",
"firefox_revision": "1029",
"firefox_revision": "1031",
"webkit_revision": "1155"
},
"scripts": {
Expand Down
6 changes: 5 additions & 1 deletion src/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export type BrowserContextOptions = {
locale?: string,
timezoneId?: string,
geolocation?: types.Geolocation,
permissions?: { [key: string]: string[] };
permissions?: { [key: string]: string[] },
extraHTTPHeaders?: network.Headers,
};

export interface BrowserContext {
Expand All @@ -44,6 +45,7 @@ export interface BrowserContext {
setPermissions(origin: string, permissions: string[]): Promise<void>;
clearPermissions(): Promise<void>;
setGeolocation(geolocation: types.Geolocation | null): Promise<void>;
setExtraHTTPHeaders(headers: network.Headers): Promise<void>;
close(): Promise<void>;

_existingPages(): Page[];
Expand All @@ -67,6 +69,8 @@ export function validateBrowserContextOptions(options: BrowserContextOptions): B
result.viewport = { ...result.viewport };
if (result.geolocation)
result.geolocation = verifyGeolocation(result.geolocation);
if (result.extraHTTPHeaders)
result.extraHTTPHeaders = network.verifyHeaders(result.extraHTTPHeaders);
return result;
}

Expand Down
6 changes: 6 additions & 0 deletions src/chromium/crBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,12 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
await (page._delegate as CRPage)._client.send('Emulation.setGeolocationOverride', geolocation || {});
}

async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
for (const page of this._existingPages())
await (page._delegate as CRPage).updateExtraHTTPHeaders();
}

async close() {
if (this._closed)
return;
Expand Down
7 changes: 6 additions & 1 deletion src/chromium/crPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class CRPage implements PageDelegate {
promises.push(emulateTimezone(this._client, options.timezoneId));
if (options.geolocation)
promises.push(this._client.send('Emulation.setGeolocationOverride', options.geolocation));
promises.push(this.updateExtraHTTPHeaders());
await Promise.all(promises);
}

Expand Down Expand Up @@ -316,7 +317,11 @@ export class CRPage implements PageDelegate {
this._page._onFileChooserOpened(handle);
}

async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
async updateExtraHTTPHeaders(): Promise<void> {
const headers = network.mergeHeaders([
this._page.context()._options.extraHTTPHeaders,
this._page._state.extraHTTPHeaders
]);
await this._client.send('Network.setExtraHTTPHeaders', { headers });
}

Expand Down
8 changes: 8 additions & 0 deletions src/firefox/ffBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import * as platform from '../platform';
import { Protocol } from './protocol';
import { ConnectionTransport, SlowMoTransport } from '../transport';
import { TimeoutSettings } from '../timeoutSettings';
import { headersArray } from './ffNetworkManager';

export class FFBrowser extends platform.EventEmitter implements Browser {
_connection: FFConnection;
Expand Down Expand Up @@ -274,6 +275,8 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
await Promise.all(entries.map(entry => this.setPermissions(entry[0], entry[1])));
if (this._options.geolocation)
await this.setGeolocation(this._options.geolocation);
if (this._options.extraHTTPHeaders)
await this.setExtraHTTPHeaders(this._options.extraHTTPHeaders);
}

_existingPages(): Page[] {
Expand Down Expand Up @@ -349,6 +352,11 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
throw new Error('Geolocation emulation is not supported in Firefox');
}

async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
await this._browser._connection.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId || undefined, headers: headersArray(this._options.extraHTTPHeaders) });
}

async close() {
if (this._closed)
return;
Expand Down
2 changes: 1 addition & 1 deletion src/firefox/ffNetworkManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ class InterceptableRequest implements network.RequestDelegate {
}
}

function headersArray(headers: network.Headers): Protocol.Network.HTTPHeader[] {
export function headersArray(headers: network.Headers): Protocol.Network.HTTPHeader[] {
const result: Protocol.Network.HTTPHeader[] = [];
for (const name in headers) {
if (!Object.is(headers[name], undefined))
Expand Down
10 changes: 3 additions & 7 deletions src/firefox/ffPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ import * as dom from '../dom';
import { FFSession } from './ffConnection';
import { FFExecutionContext } from './ffExecutionContext';
import { Page, PageDelegate, Worker } from '../page';
import { FFNetworkManager } from './ffNetworkManager';
import { FFNetworkManager, headersArray } from './ffNetworkManager';
import { Events } from '../events';
import * as dialog from '../dialog';
import { Protocol } from './protocol';
import { RawMouseImpl, RawKeyboardImpl } from './ffInput';
import { BrowserContext } from '../browserContext';
import { getAccessibilityTree } from './ffAccessibility';
import * as network from '../network';
import * as types from '../types';
import * as platform from '../platform';
import { kScreenshotDuringNavigationError } from '../screenshotter';
Expand Down Expand Up @@ -251,11 +250,8 @@ export class FFPage implements PageDelegate {
return { newDocumentId: response.navigationId || undefined };
}

async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
const array = [];
for (const [name, value] of Object.entries(headers))
array.push({ name, value });
await this._session.send('Network.setExtraHTTPHeaders', { headers: array });
async updateExtraHTTPHeaders(): Promise<void> {
await this._session.send('Network.setExtraHTTPHeaders', { headers: headersArray(this._page._state.extraHTTPHeaders || {}) });
}

async setViewportSize(viewportSize: types.Size): Promise<void> {
Expand Down
30 changes: 29 additions & 1 deletion src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import * as frames from './frames';
import { assert } from './helper';
import { assert, helper } from './helper';
import * as platform from './platform';

export type NetworkCookie = {
Expand Down Expand Up @@ -373,3 +373,31 @@ export const STATUS_TEXTS: { [status: string]: string } = {
'510': 'Not Extended',
'511': 'Network Authentication Required',
};

export function verifyHeaders(headers: Headers): Headers {
const result: Headers = {};
for (const key of Object.keys(headers)) {
const value = headers[key];
assert(helper.isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
result[key] = value;
}
return result;
}

export function mergeHeaders(headers: (Headers | undefined | null)[]): Headers {
const lowerCaseToValue = new Map<string, string>();
const lowerCaseToOriginalCase = new Map<string, string>();
for (const h of headers) {
if (!h)
continue;
for (const key of Object.keys(h)) {
const lower = key.toLowerCase();
lowerCaseToOriginalCase.set(lower, key);
lowerCaseToValue.set(lower, h[key]);
}
}
const result: Headers = {};
for (const [lower, value] of lowerCaseToValue)
result[lowerCaseToOriginalCase.get(lower)!] = value;
return result;
}
11 changes: 3 additions & 8 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export interface PageDelegate {

navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult>;

setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void>;
updateExtraHTTPHeaders(): Promise<void>;
setViewportSize(viewportSize: types.Size): Promise<void>;
setEmulateMedia(mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise<void>;
setCacheEnabled(enabled: boolean): Promise<void>;
Expand Down Expand Up @@ -268,13 +268,8 @@ export class Page extends platform.EventEmitter {
}

setExtraHTTPHeaders(headers: network.Headers) {
this._state.extraHTTPHeaders = {};
for (const key of Object.keys(headers)) {
const value = headers[key];
assert(helper.isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
this._state.extraHTTPHeaders[key] = value;
}
return this._delegate.setExtraHTTPHeaders(headers);
this._state.extraHTTPHeaders = network.verifyHeaders(headers);
return this._delegate.updateExtraHTTPHeaders();
}

async _onBindingCalled(payload: string, context: js.ExecutionContext) {
Expand Down
7 changes: 7 additions & 0 deletions src/webkit/wkBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { WKConnection, WKSession, kPageProxyMessageReceived, PageProxyMessageRec
import { WKPageProxy } from './wkPageProxy';
import * as platform from '../platform';
import { TimeoutSettings } from '../timeoutSettings';
import { WKPage } from './wkPage';

const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15';

Expand Down Expand Up @@ -269,6 +270,12 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo
await this._browser._browserSession.send('Browser.setGeolocationOverride', { browserContextId: this._browserContextId, geolocation: payload });
}

async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
for (const page of this._existingPages())
await (page._delegate as WKPage).updateExtraHTTPHeaders();
}

async close() {
if (this._closed)
return;
Expand Down
22 changes: 12 additions & 10 deletions src/webkit/wkPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,7 @@ export class WKPage implements PageDelegate {
}
if (contextOptions.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
if (this._page._state.extraHTTPHeaders || contextOptions.locale) {
const headers = this._page._state.extraHTTPHeaders || {};
if (contextOptions.locale)
headers['Accept-Language'] = contextOptions.locale;
promises.push(session.send('Network.setExtraHTTPHeaders', { headers }));
}
promises.push(session.send('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() }));
if (this._page._state.hasTouch)
promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: true }));
if (contextOptions.timezoneId) {
Expand Down Expand Up @@ -378,12 +373,19 @@ export class WKPage implements PageDelegate {
await Promise.all(promises);
}

async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
const copy = { ...headers };
async updateExtraHTTPHeaders(): Promise<void> {
await this._updateState('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() });
}

_calculateExtraHTTPHeaders(): network.Headers {
const headers = network.mergeHeaders([
this._page.context()._options.extraHTTPHeaders,
this._page._state.extraHTTPHeaders
]);
const locale = this._page.context()._options.locale;
if (locale)
copy['Accept-Language'] = locale;
await this._updateState('Network.setExtraHTTPHeaders', { headers: copy });
headers['Accept-Language'] = locale;
return headers;
}

async setEmulateMedia(mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise<void> {
Expand Down
29 changes: 29 additions & 0 deletions test/network.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,35 @@ module.exports.describe = function({testRunner, expect, MAC, WIN, FFOX, CHROMIUM
]);
expect(request.headers['foo']).toBe('bar');
});
it('should work with extra headers from browser context', async({browser, server}) => {
const context = await browser.newContext();
await context.setExtraHTTPHeaders({
'foo': 'bar',
});
const page = await context.newPage();
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
await context.close();
expect(request.headers['foo']).toBe('bar');
});
it('should override extra headers from browser context', async({browser, server}) => {
const context = await browser.newContext({
extraHTTPHeaders: { 'fOo': 'bAr', 'baR': 'foO' },
});
const page = await context.newPage();
await page.setExtraHTTPHeaders({
'Foo': 'Bar'
});
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
await context.close();
expect(request.headers['foo']).toBe('Bar');
expect(request.headers['bar']).toBe('foO');
});
it('should throw for non-string header values', async({page, server}) => {
let error = null;
try {
Expand Down
12 changes: 12 additions & 0 deletions test/popup.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
expect(userAgent).toBe('hey');
expect(request.headers['user-agent']).toBe('hey');
});
it.skip(CHROMIUM)('should inherit extra headers from browser context', async function({browser, server}) {
const context = await browser.newContext({
extraHTTPHeaders: { 'foo': 'bar' },
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const requestPromise = server.waitForRequest('/dummy.html');
await page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/dummy.html');
const request = await requestPromise;
await context.close();
expect(request.headers['foo']).toBe('bar');
});
it.skip(CHROMIUM)('should inherit touch support from browser context', async function({browser, server}) {
const context = await browser.newContext({
viewport: { width: 400, height: 500, isMobile: true }
Expand Down

0 comments on commit 672f3f9

Please sign in to comment.