fix(chromium): emulate navigator.userAgentData along with UA (#29159)

Fixes #28989, fixes #29139.
This commit is contained in:
Dmitry Gozman 2024-01-25 07:34:11 -08:00 committed by GitHub
parent 129f5bfdbe
commit bc83d7084c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 86 additions and 1 deletions

View File

@ -1094,7 +1094,11 @@ class FrameSession {
async _updateUserAgent(): Promise<void> {
const options = this._crPage._browserContext._options;
await this._client.send('Emulation.setUserAgentOverride', { userAgent: options.userAgent || '', acceptLanguage: options.locale });
await this._client.send('Emulation.setUserAgentOverride', {
userAgent: options.userAgent || '',
acceptLanguage: options.locale,
userAgentMetadata: calculateUserAgentMetadata(options),
});
}
private async _setDefaultFontFamilies(session: CRSession) {
@ -1257,3 +1261,48 @@ async function emulateTimezone(session: CRSession, timezoneId: string) {
}
const contextDelegateSymbol = Symbol('delegate');
// Chromium reference: https://source.chromium.org/chromium/chromium/src/+/main:components/embedder_support/user_agent_utils.cc;l=434;drc=70a6711e08e9f9e0d8e4c48e9ba5cab62eb010c2
function calculateUserAgentMetadata(options: channels.BrowserNewContextParams) {
const ua = options.userAgent;
if (!ua)
return undefined;
const metadata: Protocol.Emulation.UserAgentMetadata = {
mobile: !!options.isMobile,
model: '',
architecture: 'x64',
platform: 'Windows',
platformVersion: '',
};
const androidMatch = ua.match(/Android (\d+(\.\d+)?(\.\d+)?)/);
const iPhoneMatch = ua.match(/iPhone OS (\d+(_\d+)?)/);
const iPadMatch = ua.match(/iPad; CPU OS (\d+(_\d+)?)/);
const macOSMatch = ua.match(/Mac OS X (\d+(_\d+)?(_\d+)?)/);
const windowsMatch = ua.match(/Windows\D+(\d+(\.\d+)?(\.\d+)?)/);
if (androidMatch) {
metadata.platform = 'Android';
metadata.platformVersion = androidMatch[1];
metadata.architecture = 'arm';
} else if (iPhoneMatch) {
metadata.platform = 'iOS';
metadata.platformVersion = iPhoneMatch[1];
metadata.architecture = 'arm';
} else if (iPadMatch) {
metadata.platform = 'iOS';
metadata.platformVersion = iPadMatch[1];
metadata.architecture = 'arm';
} else if (macOSMatch) {
metadata.platform = 'macOS';
metadata.platformVersion = macOSMatch[1];
if (!ua.includes('Intel'))
metadata.architecture = 'arm';
} else if (windowsMatch) {
metadata.platform = 'Windows';
metadata.platformVersion = windowsMatch[1];
} else if (ua.toLowerCase().includes('linux')) {
metadata.platform = 'Linux';
}
if (ua.includes('ARM'))
metadata.architecture = 'arm';
return metadata;
}

View File

@ -106,3 +106,39 @@ it('custom user agent for download', async ({ server, contextFactory, browserVer
const req = await serverRequest;
expect(req.headers['user-agent']).toBe('MyCustomUA');
});
it('should work for navigator.userAgentData and sec-ch-ua headers', async ({ playwright, browserName, browser, server }) => {
it.skip(browserName !== 'chromium', 'This API is Chromium-only');
{
const context = await browser.newContext();
const page = await context.newPage();
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
expect.soft(request.headers['sec-ch-ua']).toContain(`"Chromium"`);
expect.soft(request.headers['sec-ch-ua-mobile']).toBe(`?0`);
expect.soft(request.headers['sec-ch-ua-platform']).toBeTruthy();
expect.soft(await page.evaluate(() => (window.navigator as any).userAgentData.toJSON())).toEqual(
expect.objectContaining({ mobile: false })
);
await context.close();
}
{
const context = await browser.newContext(playwright.devices['Pixel 7']);
const page = await context.newPage();
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
expect.soft(request.headers['sec-ch-ua']).toContain(`"Chromium"`);
expect.soft(request.headers['sec-ch-ua-mobile']).toBe(`?1`);
expect.soft(request.headers['sec-ch-ua-platform']).toBe(`"Android"`);
expect.soft(await page.evaluate(() => (window.navigator as any).userAgentData.toJSON())).toEqual(
expect.objectContaining({ mobile: true, platform: 'Android' })
);
await context.close();
}
});