chore: use HeadersArray instead of Headers object on the server side (#3512)
This simplifies implementation and avoids multiple conversions. Also adding some tests around lowercase and wrong types.
This commit is contained in:
parent
77cab8bed3
commit
aeadf50165
|
|
@ -39,7 +39,7 @@ export interface BrowserContext extends EventEmitter {
|
|||
grantPermissions(permissions: string[], options?: { origin?: string }): Promise<void>;
|
||||
clearPermissions(): Promise<void>;
|
||||
setGeolocation(geolocation?: types.Geolocation): Promise<void>;
|
||||
setExtraHTTPHeaders(headers: types.Headers): Promise<void>;
|
||||
setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void>;
|
||||
setOffline(offline: boolean): Promise<void>;
|
||||
setHTTPCredentials(httpCredentials?: types.Credentials): Promise<void>;
|
||||
addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void>;
|
||||
|
|
@ -103,7 +103,7 @@ export abstract class BrowserContextBase extends EventEmitter implements Browser
|
|||
abstract _doClearPermissions(): Promise<void>;
|
||||
abstract setGeolocation(geolocation?: types.Geolocation): Promise<void>;
|
||||
abstract _doSetHTTPCredentials(httpCredentials?: types.Credentials): Promise<void>;
|
||||
abstract setExtraHTTPHeaders(headers: types.Headers): Promise<void>;
|
||||
abstract setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void>;
|
||||
abstract setOffline(offline: boolean): Promise<void>;
|
||||
abstract _doAddInitScript(expression: string): Promise<void>;
|
||||
abstract _doExposeBinding(binding: PageBinding): Promise<void>;
|
||||
|
|
@ -189,9 +189,11 @@ export abstract class BrowserContextBase extends EventEmitter implements Browser
|
|||
const { username, password } = proxy;
|
||||
if (username) {
|
||||
this._options.httpCredentials = { username, password: password! };
|
||||
this._options.extraHTTPHeaders = this._options.extraHTTPHeaders || {};
|
||||
const token = Buffer.from(`${username}:${password}`).toString('base64');
|
||||
this._options.extraHTTPHeaders['Proxy-Authorization'] = `Basic ${token}`;
|
||||
this._options.extraHTTPHeaders = network.mergeHeaders([
|
||||
this._options.extraHTTPHeaders,
|
||||
network.singleHeader('Proxy-Authorization', `Basic ${token}`),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -236,8 +238,6 @@ export function validateBrowserContextOptions(options: types.BrowserContextOptio
|
|||
if (!options.viewport && !options.noDefaultViewport)
|
||||
options.viewport = { width: 1280, height: 720 };
|
||||
verifyGeolocation(options.geolocation);
|
||||
if (options.extraHTTPHeaders)
|
||||
options.extraHTTPHeaders = network.verifyHeaders(options.extraHTTPHeaders);
|
||||
}
|
||||
|
||||
export function verifyGeolocation(geolocation?: types.Geolocation) {
|
||||
|
|
|
|||
|
|
@ -387,8 +387,8 @@ export class CRBrowserContext extends BrowserContextBase {
|
|||
await (page._delegate as CRPage).updateGeolocation();
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: types.Headers): Promise<void> {
|
||||
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
|
||||
async setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void> {
|
||||
this._options.extraHTTPHeaders = headers;
|
||||
for (const page of this.pages())
|
||||
await (page._delegate as CRPage).updateExtraHTTPHeaders();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import * as network from '../network';
|
|||
import * as frames from '../frames';
|
||||
import * as types from '../types';
|
||||
import { CRPage } from './crPage';
|
||||
import { headersObjectToArray } from '../converters';
|
||||
|
||||
export class CRNetworkManager {
|
||||
private _client: CRSession;
|
||||
|
|
@ -239,7 +240,7 @@ export class CRNetworkManager {
|
|||
const response = await this._client.send('Network.getResponseBody', { requestId: request._requestId });
|
||||
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||
};
|
||||
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), getResponseBody);
|
||||
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers), getResponseBody);
|
||||
}
|
||||
|
||||
_handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response) {
|
||||
|
|
@ -350,7 +351,7 @@ class InterceptableRequest implements network.RouteDelegate {
|
|||
if (postDataEntries && postDataEntries.length && postDataEntries[0].bytes)
|
||||
postDataBuffer = Buffer.from(postDataEntries[0].bytes, 'base64');
|
||||
|
||||
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, url, type, method, postDataBuffer, headersObject(headers));
|
||||
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, url, type, method, postDataBuffer, headersObjectToArray(headers));
|
||||
}
|
||||
|
||||
async continue(overrides: types.NormalizedContinueOverrides) {
|
||||
|
|
@ -406,11 +407,3 @@ const errorReasons: { [reason: string]: Protocol.Network.ErrorReason } = {
|
|||
'timedout': 'TimedOut',
|
||||
'failed': 'Failed',
|
||||
};
|
||||
|
||||
function headersObject(headers: Protocol.Network.Headers): types.Headers {
|
||||
const result: types.Headers = {};
|
||||
for (const key of Object.keys(headers))
|
||||
result[key.toLowerCase()] = headers[key];
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import * as types from '../types';
|
|||
import { ConsoleMessage } from '../console';
|
||||
import * as sourceMap from '../utils/sourceMap';
|
||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||
import { headersArrayToObject } from '../converters';
|
||||
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
|
|
@ -729,7 +730,7 @@ class FrameSession {
|
|||
this._crPage._browserContext._options.extraHTTPHeaders,
|
||||
this._page._state.extraHTTPHeaders
|
||||
]);
|
||||
await this._client.send('Network.setExtraHTTPHeaders', { headers });
|
||||
await this._client.send('Network.setExtraHTTPHeaders', { headers: headersArrayToObject(headers, false /* lowerCase */) });
|
||||
}
|
||||
|
||||
async _updateGeolocation(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import * as mime from 'mime';
|
|||
import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
import * as types from './types';
|
||||
import { helper, assert } from './helper';
|
||||
|
||||
export async function normalizeFilePayloads(files: string | types.FilePayload | string[] | types.FilePayload[]): Promise<types.FilePayload[]> {
|
||||
let ff: string[] | types.FilePayload[];
|
||||
|
|
@ -43,65 +42,18 @@ export async function normalizeFilePayloads(files: string | types.FilePayload |
|
|||
return filePayloads;
|
||||
}
|
||||
|
||||
export async function normalizeFulfillParameters(params: types.FulfillResponse & { path?: string }): Promise<types.NormalizedFulfillResponse> {
|
||||
let body = '';
|
||||
let isBase64 = false;
|
||||
let length = 0;
|
||||
if (params.path) {
|
||||
const buffer = await util.promisify(fs.readFile)(params.path);
|
||||
body = buffer.toString('base64');
|
||||
isBase64 = true;
|
||||
length = buffer.length;
|
||||
} else if (helper.isString(params.body)) {
|
||||
body = params.body;
|
||||
isBase64 = false;
|
||||
length = Buffer.byteLength(body);
|
||||
} else if (params.body) {
|
||||
body = params.body.toString('base64');
|
||||
isBase64 = true;
|
||||
length = params.body.length;
|
||||
}
|
||||
const headers: types.Headers = {};
|
||||
for (const header of Object.keys(params.headers || {}))
|
||||
headers[header.toLowerCase()] = String(params.headers![header]);
|
||||
if (params.contentType)
|
||||
headers['content-type'] = String(params.contentType);
|
||||
else if (params.path)
|
||||
headers['content-type'] = mime.getType(params.path) || 'application/octet-stream';
|
||||
if (length && !('content-length' in headers))
|
||||
headers['content-length'] = String(length);
|
||||
|
||||
return {
|
||||
status: params.status || 200,
|
||||
headers: headersObjectToArray(headers),
|
||||
body,
|
||||
isBase64
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeContinueOverrides(overrides: types.ContinueOverrides): types.NormalizedContinueOverrides {
|
||||
return {
|
||||
method: overrides.method,
|
||||
headers: overrides.headers ? headersObjectToArray(overrides.headers) : undefined,
|
||||
postData: helper.isString(overrides.postData) ? Buffer.from(overrides.postData, 'utf8') : overrides.postData,
|
||||
};
|
||||
}
|
||||
|
||||
export function headersObjectToArray(headers: types.Headers): types.HeadersArray {
|
||||
export function headersObjectToArray(headers: { [key: string]: string }): types.HeadersArray {
|
||||
const result: types.HeadersArray = [];
|
||||
for (const name in headers) {
|
||||
if (!Object.is(headers[name], undefined)) {
|
||||
const value = headers[name];
|
||||
assert(helper.isString(value), `Expected value of header "${name}" to be String, but "${typeof value}" is found.`);
|
||||
result.push({ name, value });
|
||||
}
|
||||
if (!Object.is(headers[name], undefined))
|
||||
result.push({ name, value: headers[name] });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function headersArrayToObject(headers: types.HeadersArray): types.Headers {
|
||||
const result: types.Headers = {};
|
||||
export function headersArrayToObject(headers: types.HeadersArray, lowerCase: boolean): { [key: string]: string } {
|
||||
const result: { [key: string]: string } = {};
|
||||
for (const { name, value } of headers)
|
||||
result[name] = value;
|
||||
result[lowerCase ? name.toLowerCase() : name] = value;
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import { Page, PageBinding } from '../page';
|
|||
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||
import * as types from '../types';
|
||||
import { ConnectionEvents, FFConnection } from './ffConnection';
|
||||
import { headersArray } from './ffNetworkManager';
|
||||
import { FFPage } from './ffPage';
|
||||
import { Protocol } from './protocol';
|
||||
|
||||
|
|
@ -211,7 +210,7 @@ export class FFBrowserContext extends BrowserContextBase {
|
|||
if (this._options.permissions)
|
||||
promises.push(this.grantPermissions(this._options.permissions));
|
||||
if (this._options.extraHTTPHeaders || this._options.locale)
|
||||
promises.push(this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || {}));
|
||||
promises.push(this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || []));
|
||||
if (this._options.httpCredentials)
|
||||
promises.push(this.setHTTPCredentials(this._options.httpCredentials));
|
||||
if (this._options.geolocation)
|
||||
|
|
@ -294,12 +293,12 @@ export class FFBrowserContext extends BrowserContextBase {
|
|||
await this._browser._connection.send('Browser.setGeolocationOverride', { browserContextId: this._browserContextId || undefined, geolocation: geolocation || null });
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: types.Headers): Promise<void> {
|
||||
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
|
||||
const allHeaders = { ...this._options.extraHTTPHeaders };
|
||||
async setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void> {
|
||||
this._options.extraHTTPHeaders = headers;
|
||||
let allHeaders = this._options.extraHTTPHeaders;
|
||||
if (this._options.locale)
|
||||
allHeaders['Accept-Language'] = this._options.locale;
|
||||
await this._browser._connection.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId || undefined, headers: headersArray(allHeaders) });
|
||||
allHeaders = network.mergeHeaders([allHeaders, network.singleHeader('Accept-Language', this._options.locale)]);
|
||||
await this._browser._connection.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId || undefined, headers: allHeaders });
|
||||
}
|
||||
|
||||
async setOffline(offline: boolean): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -75,10 +75,7 @@ export class FFNetworkManager {
|
|||
throw new Error(`Response body for ${request.request.method()} ${request.request.url()} was evicted!`);
|
||||
return Buffer.from(response.base64body, 'base64');
|
||||
};
|
||||
const headers: types.Headers = {};
|
||||
for (const {name, value} of event.headers)
|
||||
headers[name.toLowerCase()] = value;
|
||||
const response = new network.Response(request.request, event.status, event.statusText, headers, getResponseBody);
|
||||
const response = new network.Response(request.request, event.status, event.statusText, event.headers, getResponseBody);
|
||||
this._page._frameManager.requestReceivedResponse(response);
|
||||
}
|
||||
|
||||
|
|
@ -150,14 +147,11 @@ class InterceptableRequest implements network.RouteDelegate {
|
|||
this._id = payload.requestId;
|
||||
this._session = session;
|
||||
|
||||
const headers: types.Headers = {};
|
||||
for (const {name, value} of payload.headers)
|
||||
headers[name.toLowerCase()] = value;
|
||||
let postDataBuffer = null;
|
||||
if (payload.postData)
|
||||
postDataBuffer = Buffer.from(payload.postData, 'base64');
|
||||
this.request = new network.Request(payload.isIntercepted ? this : null, frame, redirectedFrom ? redirectedFrom.request : null, payload.navigationId,
|
||||
payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, postDataBuffer, headers);
|
||||
payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, postDataBuffer, payload.headers);
|
||||
}
|
||||
|
||||
async continue(overrides: types.NormalizedContinueOverrides) {
|
||||
|
|
@ -188,12 +182,3 @@ class InterceptableRequest implements network.RouteDelegate {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function headersArray(headers: types.Headers): Protocol.Network.HTTPHeader[] {
|
||||
const result: Protocol.Network.HTTPHeader[] = [];
|
||||
for (const name in headers) {
|
||||
if (!Object.is(headers[name], undefined))
|
||||
result.push({name, value: headers[name] + ''});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import { FFBrowserContext } from './ffBrowser';
|
|||
import { FFSession, FFSessionEvents } from './ffConnection';
|
||||
import { FFExecutionContext } from './ffExecutionContext';
|
||||
import { RawKeyboardImpl, RawMouseImpl } from './ffInput';
|
||||
import { FFNetworkManager, headersArray } from './ffNetworkManager';
|
||||
import { FFNetworkManager } from './ffNetworkManager';
|
||||
import { Protocol } from './protocol';
|
||||
import { selectors } from '../selectors';
|
||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||
|
|
@ -272,7 +272,7 @@ export class FFPage implements PageDelegate {
|
|||
}
|
||||
|
||||
async updateExtraHTTPHeaders(): Promise<void> {
|
||||
await this._session.send('Network.setExtraHTTPHeaders', { headers: headersArray(this._page._state.extraHTTPHeaders || {}) });
|
||||
await this._session.send('Network.setExtraHTTPHeaders', { headers: this._page._state.extraHTTPHeaders || [] });
|
||||
}
|
||||
|
||||
async setViewportSize(viewportSize: types.Size): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -428,8 +428,9 @@ export class Frame {
|
|||
return runNavigationTask(this, options, async progress => {
|
||||
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||
progress.log(`navigating to "${url}", waiting until "${waitUntil}"`);
|
||||
const headers = (this._page._state.extraHTTPHeaders || {});
|
||||
let referer = headers['referer'] || headers['Referer'];
|
||||
const headers = this._page._state.extraHTTPHeaders || [];
|
||||
const refererHeader = headers.find(h => h.name === 'referer' || h.name === 'Referer');
|
||||
let referer = refererHeader ? refererHeader.value : undefined;
|
||||
if (options.referer !== undefined) {
|
||||
if (referer !== undefined && referer !== options.referer)
|
||||
throw new Error('"referer" is already specified as extra HTTP header');
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@
|
|||
|
||||
import * as frames from './frames';
|
||||
import * as types from './types';
|
||||
import { assert, helper } from './helper';
|
||||
import { normalizeFulfillParameters, normalizeContinueOverrides } from './converters';
|
||||
import { assert } from './helper';
|
||||
|
||||
export function filterCookies(cookies: types.NetworkCookie[], urls: string[]): types.NetworkCookie[] {
|
||||
const parsedURLs = urls.map(s => new URL(s));
|
||||
|
|
@ -78,13 +77,13 @@ export class Request {
|
|||
private _resourceType: string;
|
||||
private _method: string;
|
||||
private _postData: Buffer | null;
|
||||
private _headers: types.Headers;
|
||||
private _headers: types.HeadersArray;
|
||||
private _frame: frames.Frame;
|
||||
private _waitForResponsePromise: Promise<Response | null>;
|
||||
private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {};
|
||||
|
||||
constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined,
|
||||
url: string, resourceType: string, method: string, postData: Buffer | null, headers: types.Headers) {
|
||||
url: string, resourceType: string, method: string, postData: Buffer | null, headers: types.HeadersArray) {
|
||||
assert(!url.startsWith('data:'), 'Data urls should not fire requests');
|
||||
assert(!(routeDelegate && redirectedFrom), 'Should not be able to intercept redirects');
|
||||
this._routeDelegate = routeDelegate;
|
||||
|
|
@ -123,8 +122,8 @@ export class Request {
|
|||
return this._postData;
|
||||
}
|
||||
|
||||
headers(): {[key: string]: string} {
|
||||
return { ...this._headers };
|
||||
headers(): types.HeadersArray {
|
||||
return this._headers;
|
||||
}
|
||||
|
||||
response(): Promise<Response | null> {
|
||||
|
|
@ -191,15 +190,15 @@ export class Route {
|
|||
await this._delegate.abort(errorCode);
|
||||
}
|
||||
|
||||
async fulfill(response: types.FulfillResponse & { path?: string }) {
|
||||
async fulfill(response: types.NormalizedFulfillResponse) {
|
||||
assert(!this._handled, 'Route is already handled!');
|
||||
this._handled = true;
|
||||
await this._delegate.fulfill(await normalizeFulfillParameters(response));
|
||||
await this._delegate.fulfill(response);
|
||||
}
|
||||
|
||||
async continue(overrides: types.ContinueOverrides = {}) {
|
||||
async continue(overrides: types.NormalizedContinueOverrides = {}) {
|
||||
assert(!this._handled, 'Route is already handled!');
|
||||
await this._delegate.continue(normalizeContinueOverrides(overrides));
|
||||
await this._delegate.continue(overrides);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -215,10 +214,10 @@ export class Response {
|
|||
private _status: number;
|
||||
private _statusText: string;
|
||||
private _url: string;
|
||||
private _headers: types.Headers;
|
||||
private _headers: types.HeadersArray;
|
||||
private _getResponseBodyCallback: GetResponseBodyCallback;
|
||||
|
||||
constructor(request: Request, status: number, statusText: string, headers: types.Headers, getResponseBodyCallback: GetResponseBodyCallback) {
|
||||
constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray, getResponseBodyCallback: GetResponseBodyCallback) {
|
||||
this._request = request;
|
||||
this._status = status;
|
||||
this._statusText = statusText;
|
||||
|
|
@ -247,8 +246,8 @@ export class Response {
|
|||
return this._statusText;
|
||||
}
|
||||
|
||||
headers(): types.Headers {
|
||||
return { ...this._headers };
|
||||
headers(): types.HeadersArray {
|
||||
return this._headers;
|
||||
}
|
||||
|
||||
finished(): Promise<Error | null> {
|
||||
|
|
@ -348,30 +347,24 @@ export const STATUS_TEXTS: { [status: string]: string } = {
|
|||
'511': 'Network Authentication Required',
|
||||
};
|
||||
|
||||
export function verifyHeaders(headers: types.Headers): types.Headers {
|
||||
const result: types.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 singleHeader(name: string, value: string): types.HeadersArray {
|
||||
return [{ name, value }];
|
||||
}
|
||||
|
||||
export function mergeHeaders(headers: (types.Headers | undefined | null)[]): types.Headers {
|
||||
export function mergeHeaders(headers: (types.HeadersArray | undefined | null)[]): types.HeadersArray {
|
||||
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]);
|
||||
for (const { name, value } of h) {
|
||||
const lower = name.toLowerCase();
|
||||
lowerCaseToOriginalCase.set(lower, name);
|
||||
lowerCaseToValue.set(lower, value);
|
||||
}
|
||||
}
|
||||
const result: types.Headers = {};
|
||||
const result: types.HeadersArray = [];
|
||||
for (const [lower, value] of lowerCaseToValue)
|
||||
result[lowerCaseToOriginalCase.get(lower)!] = value;
|
||||
result.push({ name: lowerCaseToOriginalCase.get(lower)!, value });
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ type PageState = {
|
|||
viewportSize: types.Size | null;
|
||||
mediaType: types.MediaType | null;
|
||||
colorScheme: types.ColorScheme | null;
|
||||
extraHTTPHeaders: types.Headers | null;
|
||||
extraHTTPHeaders: types.HeadersArray | null;
|
||||
};
|
||||
|
||||
export class Page extends EventEmitter {
|
||||
|
|
@ -214,8 +214,8 @@ export class Page extends EventEmitter {
|
|||
await this._delegate.exposeBinding(binding);
|
||||
}
|
||||
|
||||
setExtraHTTPHeaders(headers: types.Headers) {
|
||||
this._state.extraHTTPHeaders = network.verifyHeaders(headers);
|
||||
setExtraHTTPHeaders(headers: types.HeadersArray) {
|
||||
this._state.extraHTTPHeaders = headers;
|
||||
return this._delegate.updateExtraHTTPHeaders();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { Events } from './events';
|
|||
import { BrowserType } from './browserType';
|
||||
import { headersObjectToArray } from '../../converters';
|
||||
import { BrowserContextOptions } from './types';
|
||||
import { validateHeaders } from './network';
|
||||
|
||||
export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
|
||||
readonly _contexts = new Set<BrowserContext>();
|
||||
|
|
@ -47,8 +48,9 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
|
|||
|
||||
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
|
||||
const logger = options.logger;
|
||||
options = { ...options, logger: undefined };
|
||||
return this._wrapApiCall('browser.newContext', async () => {
|
||||
if (options.extraHTTPHeaders)
|
||||
validateHeaders(options.extraHTTPHeaders);
|
||||
const contextOptions: BrowserNewContextParams = {
|
||||
...options,
|
||||
viewport: options.viewport === null ? undefined : options.viewport,
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
|
|||
|
||||
async setExtraHTTPHeaders(headers: Headers): Promise<void> {
|
||||
return this._wrapApiCall('browserContext.setExtraHTTPHeaders', async () => {
|
||||
network.validateHeaders(headers);
|
||||
await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { Events } from './events';
|
|||
import { TimeoutSettings } from '../../timeoutSettings';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { envObjectToArray } from './clientHelper';
|
||||
import { validateHeaders } from './network';
|
||||
|
||||
export interface BrowserServerLauncher {
|
||||
launchServer(options?: LaunchServerOptions): Promise<BrowserServer>;
|
||||
|
|
@ -88,6 +89,8 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
|
|||
const logger = options.logger;
|
||||
options = { ...options, logger: undefined };
|
||||
return this._wrapApiCall('browserType.launchPersistentContext', async () => {
|
||||
if (options.extraHTTPHeaders)
|
||||
validateHeaders(options.extraHTTPHeaders);
|
||||
const persistentOptions: BrowserTypeLaunchPersistentContextParams = {
|
||||
...options,
|
||||
viewport: options.viewport === null ? undefined : options.viewport,
|
||||
|
|
|
|||
|
|
@ -18,8 +18,12 @@ import { URLSearchParams } from 'url';
|
|||
import { RequestChannel, ResponseChannel, RouteChannel, RequestInitializer, ResponseInitializer, RouteInitializer } from '../channels';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { Frame } from './frame';
|
||||
import { normalizeFulfillParameters, headersArrayToObject, normalizeContinueOverrides } from '../../converters';
|
||||
import { headersArrayToObject, headersObjectToArray } from '../../converters';
|
||||
import { Headers } from './types';
|
||||
import * as fs from 'fs';
|
||||
import * as mime from 'mime';
|
||||
import * as util from 'util';
|
||||
import { helper } from '../../helper';
|
||||
|
||||
export type NetworkCookie = {
|
||||
name: string,
|
||||
|
|
@ -44,19 +48,6 @@ export type SetNetworkCookieParam = {
|
|||
sameSite?: 'Strict' | 'Lax' | 'None'
|
||||
};
|
||||
|
||||
type FulfillResponse = {
|
||||
status?: number,
|
||||
headers?: Headers,
|
||||
contentType?: string,
|
||||
body?: string | Buffer,
|
||||
};
|
||||
|
||||
type ContinueOverrides = {
|
||||
method?: string,
|
||||
headers?: Headers,
|
||||
postData?: string | Buffer,
|
||||
};
|
||||
|
||||
export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
|
||||
private _redirectedFrom: Request | null = null;
|
||||
private _redirectedTo: Request | null = null;
|
||||
|
|
@ -77,7 +68,7 @@ export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
|
|||
this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
|
||||
if (this._redirectedFrom)
|
||||
this._redirectedFrom._redirectedTo = this;
|
||||
this._headers = headersArrayToObject(initializer.headers);
|
||||
this._headers = headersArrayToObject(initializer.headers, true /* lowerCase */);
|
||||
this._postData = initializer.postData ? Buffer.from(initializer.postData, 'base64') : null;
|
||||
}
|
||||
|
||||
|
|
@ -175,17 +166,49 @@ export class Route extends ChannelOwner<RouteChannel, RouteInitializer> {
|
|||
await this._channel.abort({ errorCode });
|
||||
}
|
||||
|
||||
async fulfill(response: FulfillResponse & { path?: string }) {
|
||||
const normalized = await normalizeFulfillParameters(response);
|
||||
await this._channel.fulfill(normalized);
|
||||
async fulfill(response: { status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string }) {
|
||||
let body = '';
|
||||
let isBase64 = false;
|
||||
let length = 0;
|
||||
if (response.path) {
|
||||
const buffer = await util.promisify(fs.readFile)(response.path);
|
||||
body = buffer.toString('base64');
|
||||
isBase64 = true;
|
||||
length = buffer.length;
|
||||
} else if (helper.isString(response.body)) {
|
||||
body = response.body;
|
||||
isBase64 = false;
|
||||
length = Buffer.byteLength(body);
|
||||
} else if (response.body) {
|
||||
body = response.body.toString('base64');
|
||||
isBase64 = true;
|
||||
length = response.body.length;
|
||||
}
|
||||
|
||||
const headers: Headers = {};
|
||||
for (const header of Object.keys(response.headers || {}))
|
||||
headers[header.toLowerCase()] = String(response.headers![header]);
|
||||
if (response.contentType)
|
||||
headers['content-type'] = String(response.contentType);
|
||||
else if (response.path)
|
||||
headers['content-type'] = mime.getType(response.path) || 'application/octet-stream';
|
||||
if (length && !('content-length' in headers))
|
||||
headers['content-length'] = String(length);
|
||||
|
||||
await this._channel.fulfill({
|
||||
status: response.status || 200,
|
||||
headers: headersObjectToArray(headers),
|
||||
body,
|
||||
isBase64
|
||||
});
|
||||
}
|
||||
|
||||
async continue(overrides: ContinueOverrides = {}) {
|
||||
const normalized = normalizeContinueOverrides(overrides);
|
||||
async continue(overrides: { method?: string, headers?: Headers, postData?: string | Buffer } = {}) {
|
||||
const postDataBuffer = helper.isString(overrides.postData) ? Buffer.from(overrides.postData, 'utf8') : overrides.postData;
|
||||
await this._channel.continue({
|
||||
method: normalized.method,
|
||||
headers: normalized.headers,
|
||||
postData: normalized.postData ? normalized.postData.toString('base64') : undefined
|
||||
method: overrides.method,
|
||||
headers: overrides.headers ? headersObjectToArray(overrides.headers) : undefined,
|
||||
postData: postDataBuffer ? postDataBuffer.toString('base64') : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -205,7 +228,7 @@ export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer>
|
|||
|
||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: ResponseInitializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
this._headers = headersArrayToObject(initializer.headers);
|
||||
this._headers = headersArrayToObject(initializer.headers, true /* lowerCase */);
|
||||
}
|
||||
|
||||
url(): string {
|
||||
|
|
@ -257,3 +280,11 @@ export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer>
|
|||
return Request.from(this._initializer.request).frame();
|
||||
}
|
||||
}
|
||||
|
||||
export function validateHeaders(headers: Headers) {
|
||||
for (const key of Object.keys(headers)) {
|
||||
const value = headers[key];
|
||||
if (!Object.is(value, undefined) && !helper.isString(value))
|
||||
throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import { Worker } from './worker';
|
|||
import { Frame, FunctionWithSource, verifyLoadState, WaitForNavigationOptions } from './frame';
|
||||
import { Keyboard, Mouse } from './input';
|
||||
import { assertMaxArguments, Func1, FuncOn, SmartHandle, serializeArgument, parseResult } from './jsHandle';
|
||||
import { Request, Response, Route, RouteHandler } from './network';
|
||||
import { Request, Response, Route, RouteHandler, validateHeaders } from './network';
|
||||
import { FileChooser } from './fileChooser';
|
||||
import { Buffer } from 'buffer';
|
||||
import { ChromiumCoverage } from './chromiumCoverage';
|
||||
|
|
@ -291,6 +291,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
|
|||
|
||||
async setExtraHTTPHeaders(headers: Headers) {
|
||||
return this._wrapApiCall('page.setExtraHTTPHeaders', async () => {
|
||||
validateHeaders(headers);
|
||||
await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
|
|||
import { CRBrowserContext } from '../../chromium/crBrowser';
|
||||
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
||||
import { Events as ChromiumEvents } from '../../chromium/events';
|
||||
import { headersArrayToObject } from '../../converters';
|
||||
|
||||
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, BrowserContextInitializer> implements BrowserContextChannel {
|
||||
private _context: BrowserContextBase;
|
||||
|
|
@ -96,7 +95,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, Browser
|
|||
}
|
||||
|
||||
async setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void> {
|
||||
await this._context.setExtraHTTPHeaders(headersArrayToObject(params.headers));
|
||||
await this._context.setExtraHTTPHeaders(params.headers);
|
||||
}
|
||||
|
||||
async setOffline(params: { offline: boolean }): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
|||
import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||
import { CRBrowser } from '../../chromium/crBrowser';
|
||||
import { PageDispatcher } from './pageDispatcher';
|
||||
import { headersArrayToObject } from '../../converters';
|
||||
|
||||
export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> implements BrowserChannel {
|
||||
constructor(scope: DispatcherScope, browser: BrowserBase, guid?: string) {
|
||||
|
|
@ -37,11 +36,7 @@ export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> i
|
|||
}
|
||||
|
||||
async newContext(params: BrowserNewContextParams): Promise<{ context: BrowserContextChannel }> {
|
||||
const options = {
|
||||
...params,
|
||||
extraHTTPHeaders: params.extraHTTPHeaders ? headersArrayToObject(params.extraHTTPHeaders) : undefined,
|
||||
};
|
||||
return { context: new BrowserContextDispatcher(this._scope, await this._object.newContext(options) as BrowserContextBase) };
|
||||
return { context: new BrowserContextDispatcher(this._scope, await this._object.newContext(params) as BrowserContextBase) };
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel, BrowserTypeI
|
|||
import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||
import { BrowserContextBase } from '../../browserContext';
|
||||
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||
import { headersArrayToObject } from '../../converters';
|
||||
|
||||
export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeInitializer> implements BrowserTypeChannel {
|
||||
constructor(scope: DispatcherScope, browserType: BrowserTypeBase) {
|
||||
|
|
@ -37,11 +36,7 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeIn
|
|||
}
|
||||
|
||||
async launchPersistentContext(params: BrowserTypeLaunchPersistentContextParams): Promise<{ context: BrowserContextChannel }> {
|
||||
const options = {
|
||||
...params,
|
||||
extraHTTPHeaders: params.extraHTTPHeaders ? headersArrayToObject(params.extraHTTPHeaders) : undefined,
|
||||
};
|
||||
const browserContext = await this._object.launchPersistentContext(params.userDataDir, options);
|
||||
const browserContext = await this._object.launchPersistentContext(params.userDataDir, params);
|
||||
return { context: new BrowserContextDispatcher(this._scope, browserContext as BrowserContextBase) };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import { Request, Response, Route } from '../../network';
|
|||
import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, RequestInitializer, RouteInitializer, Binary } from '../channels';
|
||||
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
|
||||
import { FrameDispatcher } from './frameDispatcher';
|
||||
import { headersObjectToArray, headersArrayToObject } from '../../converters';
|
||||
import * as types from '../../types';
|
||||
|
||||
export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> implements RequestChannel {
|
||||
|
|
@ -40,7 +39,7 @@ export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> i
|
|||
resourceType: request.resourceType(),
|
||||
method: request.method(),
|
||||
postData: postData === null ? undefined : postData.toString('base64'),
|
||||
headers: headersObjectToArray(request.headers()),
|
||||
headers: request.headers(),
|
||||
isNavigationRequest: request.isNavigationRequest(),
|
||||
redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()),
|
||||
});
|
||||
|
|
@ -60,7 +59,7 @@ export class ResponseDispatcher extends Dispatcher<Response, ResponseInitializer
|
|||
url: response.url(),
|
||||
status: response.status(),
|
||||
statusText: response.statusText(),
|
||||
headers: headersObjectToArray(response.headers()),
|
||||
headers: response.headers(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -85,17 +84,13 @@ export class RouteDispatcher extends Dispatcher<Route, RouteInitializer> impleme
|
|||
async continue(params: { method?: string, headers?: types.HeadersArray, postData?: string }): Promise<void> {
|
||||
await this._object.continue({
|
||||
method: params.method,
|
||||
headers: params.headers ? headersArrayToObject(params.headers) : undefined,
|
||||
headers: params.headers,
|
||||
postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
async fulfill(params: types.NormalizedFulfillResponse): Promise<void> {
|
||||
await this._object.fulfill({
|
||||
status: params.status,
|
||||
headers: params.headers ? headersArrayToObject(params.headers) : undefined,
|
||||
body: params.isBase64 ? Buffer.from(params.body, 'base64') : params.body,
|
||||
});
|
||||
await this._object.fulfill(params);
|
||||
}
|
||||
|
||||
async abort(params: { errorCode?: string }): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import * as types from '../../types';
|
|||
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, SerializedArgument, PagePdfParams, SerializedError, PageAccessibilitySnapshotResult, SerializedValue, PageEmulateMediaParams, AXNode } from '../channels';
|
||||
import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher';
|
||||
import { parseError, serializeError } from '../serializers';
|
||||
import { headersArrayToObject } from '../../converters';
|
||||
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
|
||||
import { DialogDispatcher } from './dialogDispatcher';
|
||||
import { DownloadDispatcher } from './downloadDispatcher';
|
||||
|
|
@ -92,7 +91,7 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
|
|||
}
|
||||
|
||||
async setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void> {
|
||||
await this._page.setExtraHTTPHeaders(headersArrayToObject(params.headers));
|
||||
await this._page.setExtraHTTPHeaders(params.headers);
|
||||
}
|
||||
|
||||
async reload(params: types.NavigateOptions): Promise<{ response?: ResponseChannel }> {
|
||||
|
|
|
|||
16
src/types.ts
16
src/types.ts
|
|
@ -208,20 +208,12 @@ export type MouseMultiClickOptions = PointerActionOptions & {
|
|||
|
||||
export type World = 'main' | 'utility';
|
||||
|
||||
export type Headers = { [key: string]: string };
|
||||
export type HeadersArray = { name: string, value: string }[];
|
||||
|
||||
export type GotoOptions = NavigateOptions & {
|
||||
referer?: string,
|
||||
};
|
||||
|
||||
export type FulfillResponse = {
|
||||
status?: number,
|
||||
headers?: Headers,
|
||||
contentType?: string,
|
||||
body?: string | Buffer,
|
||||
};
|
||||
|
||||
export type NormalizedFulfillResponse = {
|
||||
status: number,
|
||||
headers: HeadersArray,
|
||||
|
|
@ -229,12 +221,6 @@ export type NormalizedFulfillResponse = {
|
|||
isBase64: boolean,
|
||||
};
|
||||
|
||||
export type ContinueOverrides = {
|
||||
method?: string,
|
||||
headers?: Headers,
|
||||
postData?: string | Buffer,
|
||||
};
|
||||
|
||||
export type NormalizedContinueOverrides = {
|
||||
method?: string,
|
||||
headers?: HeadersArray,
|
||||
|
|
@ -275,7 +261,7 @@ export type BrowserContextOptions = {
|
|||
timezoneId?: string,
|
||||
geolocation?: Geolocation,
|
||||
permissions?: string[],
|
||||
extraHTTPHeaders?: Headers,
|
||||
extraHTTPHeaders?: HeadersArray,
|
||||
offline?: boolean,
|
||||
httpCredentials?: Credentials,
|
||||
deviceScaleFactor?: number,
|
||||
|
|
|
|||
|
|
@ -293,8 +293,8 @@ export class WKBrowserContext extends BrowserContextBase {
|
|||
await this._browser._browserSession.send('Playwright.setGeolocationOverride', { browserContextId: this._browserContextId, geolocation: payload });
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: types.Headers): Promise<void> {
|
||||
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
|
||||
async setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void> {
|
||||
this._options.extraHTTPHeaders = headers;
|
||||
for (const page of this.pages())
|
||||
await (page._delegate as WKPage).updateExtraHTTPHeaders();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import * as network from '../network';
|
|||
import * as types from '../types';
|
||||
import { Protocol } from './protocol';
|
||||
import { WKSession } from './wkConnection';
|
||||
import { headersArrayToObject } from '../converters';
|
||||
import { headersArrayToObject, headersObjectToArray } from '../converters';
|
||||
|
||||
const errorReasons: { [reason: string]: Protocol.Network.ResourceErrorType } = {
|
||||
'aborted': 'Cancellation',
|
||||
|
|
@ -57,7 +57,7 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
|||
if (event.request.postData)
|
||||
postDataBuffer = Buffer.from(event.request.postData, 'binary');
|
||||
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, event.request.url,
|
||||
resourceType, event.request.method, postDataBuffer, headersObject(event.request.headers));
|
||||
resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers));
|
||||
this._interceptedPromise = new Promise(f => this._interceptedCallback = f);
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
|||
// In certain cases, protocol will return error if the request was already canceled
|
||||
// or the page was closed. We should tolerate these errors.
|
||||
let mimeType = response.isBase64 ? 'application/octet-stream' : 'text/plain';
|
||||
const headers = headersArrayToObject(response.headers);
|
||||
const headers = headersArrayToObject(response.headers, false /* lowerCase */);
|
||||
const contentType = headers['content-type'];
|
||||
if (contentType)
|
||||
mimeType = contentType.split(';')[0].trim();
|
||||
|
|
@ -98,7 +98,7 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
|||
await this._session.sendMayFail('Network.interceptWithRequest', {
|
||||
requestId: this._requestId,
|
||||
method: overrides.method,
|
||||
headers: overrides.headers ? headersArrayToObject(overrides.headers) : undefined,
|
||||
headers: overrides.headers ? headersArrayToObject(overrides.headers, false /* lowerCase */) : undefined,
|
||||
postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined
|
||||
});
|
||||
}
|
||||
|
|
@ -108,13 +108,6 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
|||
const response = await this._session.send('Network.getResponseBody', { requestId: this._requestId });
|
||||
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||
};
|
||||
return new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), getResponseBody);
|
||||
return new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers), getResponseBody);
|
||||
}
|
||||
}
|
||||
|
||||
function headersObject(headers: Protocol.Network.Headers): types.Headers {
|
||||
const result: types.Headers = {};
|
||||
for (const key of Object.keys(headers))
|
||||
result[key.toLowerCase()] = headers[key];
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import { selectors } from '../selectors';
|
|||
import * as jpeg from 'jpeg-js';
|
||||
import * as png from 'pngjs';
|
||||
import { JSHandle } from '../javascript';
|
||||
import { headersArrayToObject } from '../converters';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
|
||||
|
|
@ -175,7 +176,7 @@ export class WKPage implements PageDelegate {
|
|||
}));
|
||||
}
|
||||
promises.push(this.updateEmulateMedia());
|
||||
promises.push(session.send('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() }));
|
||||
promises.push(session.send('Network.setExtraHTTPHeaders', { headers: headersArrayToObject(this._calculateExtraHTTPHeaders(), false /* lowerCase */) }));
|
||||
if (contextOptions.offline)
|
||||
promises.push(session.send('Network.setEmulateOfflineState', { offline: true }));
|
||||
promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: !!contextOptions.hasTouch }));
|
||||
|
|
@ -551,17 +552,16 @@ export class WKPage implements PageDelegate {
|
|||
}
|
||||
|
||||
async updateExtraHTTPHeaders(): Promise<void> {
|
||||
await this._updateState('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() });
|
||||
await this._updateState('Network.setExtraHTTPHeaders', { headers: headersArrayToObject(this._calculateExtraHTTPHeaders(), false /* lowerCase */) });
|
||||
}
|
||||
|
||||
_calculateExtraHTTPHeaders(): types.Headers {
|
||||
_calculateExtraHTTPHeaders(): types.HeadersArray {
|
||||
const locale = this._browserContext._options.locale;
|
||||
const headers = network.mergeHeaders([
|
||||
this._browserContext._options.extraHTTPHeaders,
|
||||
this._page._state.extraHTTPHeaders
|
||||
this._page._state.extraHTTPHeaders,
|
||||
locale ? network.singleHeader('Accept-Language', locale) : undefined,
|
||||
]);
|
||||
const locale = this._browserContext._options.locale;
|
||||
if (locale)
|
||||
headers['Accept-Language'] = locale;
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@ import path from 'path';
|
|||
it('should work', async({page, server}) => {
|
||||
server.setRoute('/empty.html', (req, res) => {
|
||||
res.setHeader('foo', 'bar');
|
||||
res.setHeader('BaZ', 'bAz');
|
||||
res.end();
|
||||
});
|
||||
const response = await page.goto(server.EMPTY_PAGE);
|
||||
expect(response.headers()['foo']).toBe('bar');
|
||||
expect(response.headers()['baz']).toBe('bAz');
|
||||
expect(response.headers()['BaZ']).toBe(undefined);
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,13 +18,15 @@ import './base.fixture';
|
|||
|
||||
it('should work', async({page, server}) => {
|
||||
await page.setExtraHTTPHeaders({
|
||||
foo: 'bar'
|
||||
foo: 'bar',
|
||||
baz: undefined,
|
||||
});
|
||||
const [request] = await Promise.all([
|
||||
server.waitForRequest('/empty.html'),
|
||||
page.goto(server.EMPTY_PAGE),
|
||||
]);
|
||||
expect(request.headers['foo']).toBe('bar');
|
||||
expect(request.headers['baz']).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should work with redirects', async({page, server}) => {
|
||||
|
|
@ -70,12 +72,11 @@ it('should override extra headers from browser context', async({browser, server}
|
|||
expect(request.headers['bar']).toBe('foO');
|
||||
});
|
||||
|
||||
it('should throw for non-string header values', async({page, server}) => {
|
||||
let error = null;
|
||||
try {
|
||||
await page.setExtraHTTPHeaders({ 'foo': 1 as any });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.message).toContain('Expected value of header "foo" to be String, but "number" is found.');
|
||||
it('should throw for non-string header values', async({browser, page}) => {
|
||||
const error1 = await page.setExtraHTTPHeaders({ 'foo': 1 as any }).catch(e => e);
|
||||
expect(error1.message).toContain('Expected value of header "foo" to be String, but "number" is found.');
|
||||
const error2 = await page.context().setExtraHTTPHeaders({ 'foo': true as any }).catch(e => e);
|
||||
expect(error2.message).toContain('Expected value of header "foo" to be String, but "boolean" is found.');
|
||||
const error3 = await browser.newContext({ extraHTTPHeaders: { 'foo': null as any } }).catch(e => e);
|
||||
expect(error3.message).toContain('Expected value of header "foo" to be String, but "object" is found.');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue