UNPKG

chrome-devtools-frontend

Version:
823 lines (785 loc) • 31.3 kB
// Copyright 2015 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* eslint-disable @devtools/no-imperative-dom-api */ import * as Common from '../../core/common/common.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as Platform from '../../core/platform/platform.js'; import * as SDK from '../../core/sdk/sdk.js'; import type * as Protocol from '../../generated/protocol.js'; import * as SettingsUI from '../../ui/legacy/components/settings_ui/settings_ui.js'; import * as UI from '../../ui/legacy/legacy.js'; import {html, render} from '../../ui/lit/lit.js'; import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; import * as MobileThrottling from '../mobile_throttling/mobile_throttling.js'; import * as EmulationComponents from '../settings/emulation/components/components.js'; import networkConfigViewStyles from './networkConfigView.css.js'; const UIStrings = { /** * @description Option in network conditions view of the Network panel shown in user agent dropdown. */ custom: 'Custom…', /** * @description Placeholder text for custom user agent input in network conditions view of the Network panel. */ enterACustomUserAgent: 'Enter a custom user agent', /** * @description Error message in network conditions view of the Network panel when custom user agent is empty. */ customUserAgentFieldIsRequired: 'Custom user agent field is required', /** * @description Section header for caching settings in network conditions view of the Network panel. */ caching: 'Caching', /** * @description Checkbox label to disable cache in network conditions view of the Network panel. */ disableCache: 'Disable cache', /** * @description Section header for network throttling settings in network conditions view of the Network panel. */ networkThrottling: 'Network', /** * @description Section header for user agent settings in network conditions view of the Network panel. */ userAgent: 'User agent', /** * @description Checkbox label in network conditions view of the Network panel to use browser default user agent. */ selectAutomatically: 'Use browser default', /** * @description Status message in network conditions view of the Network panel after updating user agent client hints. */ clientHintsStatusText: 'User agent updated', /** * @description Accessible announcement when network conditions view is shown. */ networkConditionsPanelShown: 'Network conditions shown', } as const; const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkConfigView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); let networkConfigViewInstance: NetworkConfigView; export class NetworkConfigView extends UI.Widget.VBox { readonly #cacheDisabledSetting = Common.Settings.Settings.instance().resolve(SDK.SDKSettings.cacheDisabledSettingDescriptor); readonly #customUserAgentSetting = Common.Settings.Settings.instance().createSetting('custom-user-agent', ''); readonly #customUserAgentMetadataSetting = Common.Settings.Settings.instance().createSetting<Protocol.Emulation.UserAgentMetadata|null>( 'custom-user-agent-metadata', null); #autoCheckbox?: UI.UIUtils.CheckboxLabel; #customUserAgentSelectBox?: HTMLElement; #customSelectAndInput?: {select: HTMLSelectElement, input: HTMLInputElement, error: HTMLElement}; #clientHints?: EmulationComponents.UserAgentClientHintsForm.UserAgentClientHintsForm; #userAgentUpdateButtonStatusText?: HTMLElement; constructor() { super({ jslog: `${VisualLogging.panel('network-conditions').track({resize: true})}`, useShadowDom: true, }); this.registerRequiredCSS(networkConfigViewStyles); this.contentElement.classList.add('network-config'); this.createCacheSection(); this.contentElement.createChild('div', 'panel-section-separator'); this.createNetworkThrottlingSection(); this.contentElement.createChild('div', 'panel-section-separator'); this.createUserAgentSection(); } static instance(opts: { forceNew: boolean|null, } = {forceNew: null}): NetworkConfigView { const {forceNew} = opts; if (!networkConfigViewInstance || forceNew) { networkConfigViewInstance = new NetworkConfigView(); } return networkConfigViewInstance; } private createUserAgentSelectAndInput(title: string): { select: HTMLSelectElement, input: HTMLInputElement, error: HTMLElement, } { const userAgentSelectElement = document.createElement('select'); userAgentSelectElement.setAttribute( 'jslog', `${VisualLogging.dropDown().track({change: true}).context(this.#customUserAgentSetting.name)}`); UI.ARIAUtils.setLabel(userAgentSelectElement, title); const customOverride = {title: i18nString(UIStrings.custom), value: 'custom'}; const {patchUserAgentWithChromeVersion} = SDK.NetworkManager.MultitargetNetworkManager; const {toKebabCase} = Platform.StringUtilities; // clang-format off // eslint-disable-next-line @devtools/no-lit-render-outside-of-view render(html` <option value=${customOverride.value} jslog=${VisualLogging.item('custom').track({click: true})}> ${customOverride.title} </option> ${userAgentGroups.map(group => html` <optgroup label=${group.title}> ${group.values.map(val => html` <option value=${patchUserAgentWithChromeVersion(val.value)} jslog=${VisualLogging.item(toKebabCase(val.title)).track({click: true})}> ${val.title} </option> `)} </optgroup> `)} `, userAgentSelectElement); // clang-format on userAgentSelectElement.selectedIndex = 0; const otherUserAgentElement = UI.UIUtils.createInput('', 'text'); otherUserAgentElement.setAttribute( 'jslog', `${VisualLogging.textField().track({change: true}).context(this.#customUserAgentSetting.name)}`); otherUserAgentElement.value = this.#customUserAgentSetting.get(); UI.Tooltip.Tooltip.install(otherUserAgentElement, this.#customUserAgentSetting.get()); otherUserAgentElement.placeholder = i18nString(UIStrings.enterACustomUserAgent); otherUserAgentElement.required = true; UI.ARIAUtils.setLabel(otherUserAgentElement, otherUserAgentElement.placeholder); const errorElement = document.createElement('div'); errorElement.classList.add('network-config-input-validation-error'); UI.ARIAUtils.markAsAlert(errorElement); if (!otherUserAgentElement.value) { errorElement.textContent = i18nString(UIStrings.customUserAgentFieldIsRequired); } this.#settingChanged(userAgentSelectElement); userAgentSelectElement.addEventListener('change', () => this.#onUserAgentSelect(userAgentSelectElement.value), false); otherUserAgentElement.addEventListener('input', () => this.#onCustomUserAgentInput(otherUserAgentElement.value), false); return {select: userAgentSelectElement, input: otherUserAgentElement, error: errorElement}; } private createSection(title: string, className?: string): HTMLElement { const section = this.contentElement.createChild('section', 'network-config-group'); if (className) { section.classList.add(className); } section.createChild('div', 'network-config-title').textContent = title; return section.createChild('div', 'network-config-fields'); } private createCacheSection(): void { const section = this.createSection(i18nString(UIStrings.caching), 'network-config-disable-cache'); section.appendChild( SettingsUI.SettingsUI.createSettingCheckbox(i18nString(UIStrings.disableCache), this.#cacheDisabledSetting)); } private createNetworkThrottlingSection(): void { const title = i18nString(UIStrings.networkThrottling); const section = this.createSection(title, 'network-config-throttling'); MobileThrottling.NetworkThrottlingSelector.NetworkThrottlingSelect.createForGlobalConditions(section, title); const saveDataSelect = MobileThrottling.ThrottlingManager.throttlingManager().createSaveDataOverrideSelector('chrome-select'); section.appendChild(saveDataSelect); } private createUserAgentSection(): void { const title = i18nString(UIStrings.userAgent); const section = this.createSection(title, 'network-config-ua'); this.#autoCheckbox = UI.UIUtils.CheckboxLabel.create(i18nString(UIStrings.selectAutomatically), true, undefined, this.#customUserAgentSetting.name); section.appendChild(this.#autoCheckbox); this.#customUserAgentSetting.addChangeListener(() => { if (this.#autoCheckbox?.checked) { return; } const customUA = this.#customUserAgentSetting.get(); const userAgentMetadata = getUserAgentMetadata(customUA); SDK.NetworkManager.MultitargetNetworkManager.instance().setCustomUserAgentOverride(customUA, userAgentMetadata); }); this.#customUserAgentSelectBox = section.createChild('div', 'network-config-ua-custom'); this.#autoCheckbox.addEventListener('change', () => this.#onAutoCheckboxChange(this.#autoCheckbox?.checked ?? false)); this.#customSelectAndInput = this.createUserAgentSelectAndInput(title); this.#customUserAgentSelectBox.appendChild(this.#customSelectAndInput.select); this.#customUserAgentSelectBox.appendChild(this.#customSelectAndInput.input); this.#customUserAgentSelectBox.appendChild(this.#customSelectAndInput.error); this.#clientHints = new EmulationComponents.UserAgentClientHintsForm.UserAgentClientHintsForm(); const userAgentMetaDataSetting = this.#customUserAgentMetadataSetting.get(); const initialUserAgentMetaData = getUserAgentMetadata(this.#customSelectAndInput.select.value); this.#clientHints.value = { showMobileCheckbox: true, showSubmitButton: true, metaData: userAgentMetaDataSetting || initialUserAgentMetaData || undefined, }; this.#customUserAgentSelectBox.appendChild(this.#clientHints); this.#clientHints.addEventListener('clienthintschange', () => this.#onClientHintsChange()); this.#clientHints.addEventListener('clienthintssubmit', event => this.#onClientHintsSubmit((event as CustomEvent).detail.value as Protocol.Emulation.UserAgentMetadata)); this.#userAgentUpdateButtonStatusText = section.createChild('span', 'status-text'); this.#userAgentUpdateButtonStatusText.textContent = ''; this.#onAutoCheckboxChange(this.#autoCheckbox.checked); } #onAutoCheckboxChange(checked: boolean): void { const useCustomUA = !checked; if (this.#customUserAgentSelectBox && this.#customSelectAndInput && this.#clientHints) { this.#customUserAgentSelectBox.classList.toggle('checked', useCustomUA); this.#customSelectAndInput.select.disabled = !useCustomUA; this.#customSelectAndInput.input.disabled = !useCustomUA; this.#customSelectAndInput.error.hidden = !useCustomUA; this.#clientHints.disabled = !useCustomUA; } const customUA = useCustomUA ? this.#customUserAgentSetting.get() : ''; const userAgentMetadata = useCustomUA ? getUserAgentMetadata(customUA) : null; SDK.NetworkManager.MultitargetNetworkManager.instance().setCustomUserAgentOverride(customUA, userAgentMetadata); } #onUserAgentSelect(value: string): void { if (!this.#customSelectAndInput || !this.#clientHints || !this.#userAgentUpdateButtonStatusText) { return; } const customOverride = 'custom'; if (value !== customOverride) { this.#customUserAgentSetting.set(value); this.#customSelectAndInput.input.value = value; UI.Tooltip.Tooltip.install(this.#customSelectAndInput.input, value); const userAgentMetadata = getUserAgentMetadata(value); this.#customUserAgentMetadataSetting.set(userAgentMetadata); SDK.NetworkManager.MultitargetNetworkManager.instance().setCustomUserAgentOverride(value, userAgentMetadata); this.#clientHints.value = { metaData: userAgentMetadata || undefined, showMobileCheckbox: true, showSubmitButton: true, }; } else { this.#customUserAgentMetadataSetting.set(null); this.#clientHints.value = { showMobileCheckbox: true, showSubmitButton: true, }; this.#customSelectAndInput.input.select(); } this.#customSelectAndInput.error.textContent = ''; this.#userAgentUpdateButtonStatusText.textContent = ''; } #onCustomUserAgentInput(value: string): void { if (!this.#customSelectAndInput) { return; } if (this.#customUserAgentSetting.get() !== value) { if (!value) { this.#customSelectAndInput.error.textContent = i18nString(UIStrings.customUserAgentFieldIsRequired); } else { this.#customSelectAndInput.error.textContent = ''; } this.#customUserAgentSetting.set(value); UI.Tooltip.Tooltip.install(this.#customSelectAndInput.input, value); this.#settingChanged(this.#customSelectAndInput.select); } } #settingChanged(selectElement?: HTMLSelectElement): void { const select = selectElement ?? this.#customSelectAndInput?.select; if (!select) { return; } const value = this.#customUserAgentSetting.get(); const options = select.options; let selectionRestored = false; for (let i = 0; i < options.length; ++i) { if (options[i].value === value) { select.selectedIndex = i; selectionRestored = true; break; } } if (!selectionRestored) { select.selectedIndex = 0; } } #onClientHintsChange(): void { if (!this.#customSelectAndInput || !this.#userAgentUpdateButtonStatusText) { return; } this.#customSelectAndInput.select.value = 'custom'; this.#userAgentUpdateButtonStatusText.textContent = ''; } #onClientHintsSubmit(metaData: Protocol.Emulation.UserAgentMetadata): void { if (!this.#userAgentUpdateButtonStatusText) { return; } const customUA = this.#customUserAgentSetting.get(); this.#customUserAgentMetadataSetting.set(metaData); SDK.NetworkManager.MultitargetNetworkManager.instance().setCustomUserAgentOverride(customUA, metaData); this.#userAgentUpdateButtonStatusText.textContent = i18nString(UIStrings.clientHintsStatusText); } override wasShown(): void { super.wasShown(); UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.networkConditionsPanelShown)); } } function getUserAgentMetadata(userAgent: string): Protocol.Emulation.UserAgentMetadata|null { for (const userAgentDescriptor of userAgentGroups) { for (const userAgentVersion of userAgentDescriptor.values) { if (userAgent === SDK.NetworkManager.MultitargetNetworkManager.patchUserAgentWithChromeVersion(userAgentVersion.value)) { if (!userAgentVersion.metadata) { return null; } SDK.NetworkManager.MultitargetNetworkManager.patchUserAgentMetadataWithChromeVersion(userAgentVersion.metadata); return userAgentVersion.metadata; } } } return null; } interface UserAgentGroup { title: string; values: Array<{ title: string, value: string, metadata: Protocol.Emulation.UserAgentMetadata|null, }>; } export const userAgentGroups: UserAgentGroup[] = [ { title: 'Android', values: [ { title: 'Android (4.0.2) Browser \u2014 Galaxy Nexus', value: 'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Google Chrome', version: '%s'}, ], fullVersion: '%s', platform: 'Android', platformVersion: '4.0.2', architecture: '', model: 'Galaxy Nexus', mobile: true, }, }, { title: 'Android (2.3) Browser \u2014 Nexus S', value: 'Mozilla/5.0 (Linux; U; Android 2.3.6; en-us; Nexus S Build/GRK39F) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Google Chrome', version: '%s'}, ], fullVersion: '%s', platform: 'Android', platformVersion: '2.3.6', architecture: '', model: 'Nexus S', mobile: true, }, }, ], }, { title: 'BlackBerry', values: [ { title: 'BlackBerry \u2014 BB10', value: 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.1+ (KHTML, like Gecko) Version/10.0.0.1337 Mobile Safari/537.1+', metadata: null, }, { title: 'BlackBerry \u2014 PlayBook 2.1', value: 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML, like Gecko) Version/7.2.1.0 Safari/536.2+', metadata: null, }, { title: 'BlackBerry \u2014 9900', value: 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en-US) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.0.0.187 Mobile Safari/534.11+', metadata: null, }, ], }, { title: 'Chrome', values: [ { title: 'Chrome \u2014 Android Mobile', value: 'Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Google Chrome', version: '%s'}, ], fullVersion: '%s', platform: 'Android', platformVersion: '16', architecture: '', model: 'Pixel 10', mobile: true, }, }, { title: 'Chrome \u2014 Android Mobile (high-end)', value: 'Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Google Chrome', version: '%s'}, ], fullVersion: '%s', platform: 'Android', platformVersion: '16', architecture: '', model: 'Pixel 10 Pro XL', mobile: true, }, }, { title: 'Chrome \u2014 Android Tablet', value: 'Mozilla/5.0 (Linux; Android 16; Pixel Tablet) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Google Chrome', version: '%s'}, ], fullVersion: '%s', platform: 'Android', platformVersion: '16', architecture: '', model: 'Pixel Tablet', mobile: true, }, }, { title: 'Chrome \u2014 iPhone', value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 26_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/%s Mobile/15E148 Safari/604.1', metadata: null, }, { title: 'Chrome \u2014 iPad', value: 'Mozilla/5.0 (iPad; CPU OS 26_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/%s Mobile/15E148 Safari/604.1', metadata: null, }, { title: 'Chrome \u2014 Chrome OS', value: 'Mozilla/5.0 (X11; CrOS x86_64 10066.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Google Chrome', version: '%s'}, ], fullVersion: '%s', platform: 'Chrome OS', platformVersion: '10066.0.0', architecture: 'x86', model: '', mobile: false, }, }, { title: 'Chrome \u2014 Mac', value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Google Chrome', version: '%s'}, ], fullVersion: '%s', platform: 'macOS', platformVersion: '10_14_6', architecture: 'x86', model: '', mobile: false, }, }, { title: 'Chrome \u2014 Windows', value: 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Google Chrome', version: '%s'}, ], fullVersion: '%s', platform: 'Windows', platformVersion: '10.0', architecture: 'x86', model: '', mobile: false, }, }, ], }, { title: 'Firefox', values: [ { title: 'Firefox \u2014 Android Mobile', value: 'Mozilla/5.0 (Android 4.4; Mobile; rv:70.0) Gecko/70.0 Firefox/70.0', metadata: null, }, { title: 'Firefox \u2014 Android Tablet', value: 'Mozilla/5.0 (Android 4.4; Tablet; rv:70.0) Gecko/70.0 Firefox/70.0', metadata: null, }, { title: 'Firefox \u2014 iPhone', value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4', metadata: null, }, { title: 'Firefox \u2014 iPad', value: 'Mozilla/5.0 (iPad; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4', metadata: null, }, { title: 'Firefox \u2014 Mac', value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0', metadata: null, }, { title: 'Firefox \u2014 Windows', value: 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0', metadata: null, }, ], }, { title: 'Googlebot', values: [ { title: 'Googlebot', value: 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', metadata: null, }, { title: 'Googlebot Desktop', value: 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/%s Safari/537.36', metadata: null, }, { title: 'Googlebot Smartphone', value: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', metadata: null, }, ], }, { title: 'Internet Explorer', values: [ { title: 'Internet Explorer 11', value: 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko', metadata: null, }, { title: 'Internet Explorer 10', value: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)', metadata: null, }, { title: 'Internet Explorer 9', value: 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)', metadata: null, }, { title: 'Internet Explorer 8', value: 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)', metadata: null, }, {title: 'Internet Explorer 7', value: 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)', metadata: null}, ], }, { title: 'Microsoft Edge', values: [ { title: 'Microsoft Edge (Chromium) \u2014 Windows', value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 Edg/%s', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Microsoft Edge', version: '%s'}, ], fullVersion: '%s', platform: 'Windows', platformVersion: '10.0', architecture: 'x86', model: '', mobile: false, }, }, { title: 'Microsoft Edge (Chromium) \u2014 Mac', value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Chrome/%s Safari/604.1 Edg/%s', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Microsoft Edge', version: '%s'}, ], fullVersion: '%s', platform: 'macOS', platformVersion: '10_14_6', architecture: 'x86', model: '', mobile: false, }, }, { title: 'Microsoft Edge \u2014 iPhone', value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 EdgiOS/44.5.0.10 Mobile/15E148 Safari/604.1', metadata: null, }, { title: 'Microsoft Edge \u2014 iPad', value: 'Mozilla/5.0 (iPad; CPU OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 EdgiOS/44.5.2 Mobile/15E148 Safari/605.1.15', metadata: null, }, { title: 'Microsoft Edge \u2014 Android Mobile', value: 'Mozilla/5.0 (Linux; Android 8.1.0; Pixel Build/OPM4.171019.021.D1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36 EdgA/42.0.0.2057', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Microsoft Edge', version: '%s'}, ], fullVersion: '%s', platform: 'Android', platformVersion: '8.1.0', architecture: '', model: 'Pixel', mobile: true, }, }, { title: 'Microsoft Edge \u2014 Android Tablet', value: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 EdgA/42.0.0.2057', metadata: { brands: [ {brand: 'Not A;Brand', version: '99'}, {brand: 'Chromium', version: '%s'}, {brand: 'Microsoft Edge', version: '%s'}, ], fullVersion: '%s', platform: 'Android', platformVersion: '6.0.1', architecture: '', model: 'Nexus 7', mobile: true, }, }, { title: 'Microsoft Edge (EdgeHTML) \u2014 Windows', value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 Edge/18.19042', metadata: null, }, { title: 'Microsoft Edge (EdgeHTML) \u2014 XBox', value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 Edge/18.19041', metadata: null, }, ], }, { title: 'Opera', values: [ { title: 'Opera \u2014 Mac', value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 OPR/65.0.3467.48', metadata: null, }, { title: 'Opera \u2014 Windows', value: 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 OPR/65.0.3467.48', metadata: null, }, { title: 'Opera (Presto) \u2014 Mac', value: 'Opera/9.80 (Macintosh; Intel Mac OS X 10.9.1) Presto/2.12.388 Version/12.16', metadata: null, }, { title: 'Opera (Presto) \u2014 Windows', value: 'Opera/9.80 (Windows NT 6.1) Presto/2.12.388 Version/12.16', metadata: null, }, { title: 'Opera Mobile \u2014 Android Mobile', value: 'Opera/12.02 (Android 4.1; Linux; Opera Mobi/ADR-1111101157; U; en-US) Presto/2.9.201 Version/12.02', metadata: null, }, { title: 'Opera Mini \u2014 iOS', value: 'Opera/9.80 (iPhone; Opera Mini/8.0.0/34.2336; U; en) Presto/2.8.119 Version/11.10', metadata: null, }, ], }, { title: 'Safari', values: [ { title: 'Safari \u2014 iPad iOS 13.2', value: 'Mozilla/5.0 (iPad; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1', metadata: null, }, { title: 'Safari \u2014 iPhone iOS 13.2', value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1', metadata: null, }, { title: 'Safari \u2014 Mac', value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15', metadata: null, }, ], }, { title: 'UC Browser', values: [ { title: 'UC Browser \u2014 Android Mobile', value: 'Mozilla/5.0 (Linux; U; Android 8.1.0; en-US; Nexus 6P Build/OPM7.181205.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/%s UCBrowser/12.11.1.1197 Mobile Safari/537.36', metadata: null, }, { title: 'UC Browser \u2014 iOS', value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X; zh-CN) AppleWebKit/537.51.1 (KHTML, like Gecko) Mobile/16B92 UCBrowser/12.1.7.1109 Mobile AliApp(TUnionSDK/0.1.20.3)', metadata: null, }, { title: 'UC Browser \u2014 Windows Phone', value: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920) UCBrowser/10.1.0.563 Mobile', metadata: null, }, ], }, ];