Path: blob/trunk/third_party/closure/goog/labs/useragent/util.js
4143 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Utilities used by goog.labs.userAgent tools. These functions8* should not be used outside of goog.labs.userAgent.*.9*10*/1112goog.module('goog.labs.userAgent.util');13goog.module.declareLegacyNamespace();1415const {caseInsensitiveContains, contains} = goog.require('goog.string.internal');16const {useClientHints} = goog.require('goog.labs.userAgent');1718/**19* @const {boolean} If true, use navigator.userAgentData without check.20* TODO(user): FEATURESET_YEAR >= 2024 if it supports mobile and all the21* brands we need. See https://caniuse.com/mdn-api_navigator_useragentdata.22*/23const ASSUME_CLIENT_HINTS_SUPPORT = false;2425/**26* Gets the native userAgent string from navigator if it exists.27* If navigator or navigator.userAgent string is missing, returns an empty28* string.29* @return {string}30*/31function getNativeUserAgentString() {32const navigator = getNavigator();33if (navigator) {34const userAgent = navigator.userAgent;35if (userAgent) {36return userAgent;37}38}39return '';40}4142/**43* Gets the native userAgentData object from navigator if it exists.44* If navigator.userAgentData object is missing returns null.45* @return {?NavigatorUAData}46*/47function getNativeUserAgentData() {48const navigator = getNavigator();49// TODO(user): Use navigator?.userAgent ?? null once it's supported.50if (navigator) {51return navigator.userAgentData || null;52}53return null;54}5556/**57* Getter for the native navigator.58* @return {!Navigator}59*/60function getNavigator() {61return goog.global.navigator;62}6364/**65* A possible override for applications which wish to not check66* navigator.userAgent but use a specified value for detection instead.67* @type {?string}68*/69let userAgentInternal = null;7071/**72* A possible override for applications which wish to not check73* navigator.userAgentData but use a specified value for detection instead.74* @type {?NavigatorUAData}75*/76let userAgentDataInternal = getNativeUserAgentData();7778/**79* Override the user agent string with the given value.80* This should only be used for testing within the goog.labs.userAgent81* namespace.82* Pass `null` to use the native browser object instead.83* @param {?string=} userAgent The userAgent override.84* @return {void}85*/86function setUserAgent(userAgent = undefined) {87userAgentInternal =88typeof userAgent === 'string' ? userAgent : getNativeUserAgentString();89}9091/** @return {string} The user agent string. */92function getUserAgent() {93return userAgentInternal == null ? getNativeUserAgentString() :94userAgentInternal;95}9697/**98* Override the user agent data object with the given value.99* This should only be used for testing within the goog.labs.userAgent100* namespace.101* Pass `null` to specify the absence of userAgentData. Note that this behavior102* is different from setUserAgent.103* @param {?NavigatorUAData} userAgentData The userAgentData override.104*/105function setUserAgentData(userAgentData) {106userAgentDataInternal = userAgentData;107}108109/**110* If the user agent data object was overridden using setUserAgentData,111* reset it so that it uses the native browser object instead, if it exists.112*/113function resetUserAgentData() {114userAgentDataInternal = getNativeUserAgentData();115}116117/** @return {?NavigatorUAData} Navigator.userAgentData if exist */118function getUserAgentData() {119return userAgentDataInternal;120}121122/**123* Checks if any string in userAgentData.brands matches str.124* Returns false if userAgentData is not supported.125* @param {string} str126* @return {boolean} Whether any brand string from userAgentData contains the127* given string.128*/129function matchUserAgentDataBrand(str) {130if (!useClientHints()) return false;131const data = getUserAgentData();132if (!data) return false;133return data.brands.some(({brand}) => brand && contains(brand, str));134}135136/**137* @param {string} str138* @return {boolean} Whether the user agent contains the given string.139*/140function matchUserAgent(str) {141const userAgent = getUserAgent();142return contains(userAgent, str);143}144145/**146* @param {string} str147* @return {boolean} Whether the user agent contains the given string, ignoring148* case.149*/150function matchUserAgentIgnoreCase(str) {151const userAgent = getUserAgent();152return caseInsensitiveContains(userAgent, str);153}154155/**156* Parses the user agent into tuples for each section.157* @param {string} userAgent158* @return {!Array<!Array<string>>} Tuples of key, version, and the contents of159* the parenthetical.160*/161function extractVersionTuples(userAgent) {162// Matches each section of a user agent string.163// Example UA:164// Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us)165// AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405166// This has three version tuples: Mozilla, AppleWebKit, and Mobile.167168const versionRegExp = new RegExp(169// Key. Note that a key may have a space.170// (i.e. 'Mobile Safari' in 'Mobile Safari/5.0')171'([A-Z][\\w ]+)' +172173'/' + // slash174'([^\\s]+)' + // version (i.e. '5.0b')175'\\s*' + // whitespace176'(?:\\((.*?)\\))?', // parenthetical info. parentheses not matched.177'g');178179const data = [];180let match;181182// Iterate and collect the version tuples. Each iteration will be the183// next regex match.184while (match = versionRegExp.exec(userAgent)) {185data.push([186match[1], // key187match[2], // value188// || undefined as this is not undefined in IE7 and IE8189match[3] || undefined // info190]);191}192193return data;194}195196exports = {197ASSUME_CLIENT_HINTS_SUPPORT,198extractVersionTuples,199getNativeUserAgentString,200getUserAgent,201getUserAgentData,202matchUserAgent,203matchUserAgentDataBrand,204matchUserAgentIgnoreCase,205resetUserAgentData,206setUserAgent,207setUserAgentData,208};209210211