Path: blob/trunk/third_party/closure/goog/labs/useragent/platform.js
4147 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Closure user agent platform detection.8* @see <a href="http://www.useragentstring.com/">User agent strings</a>9* For more information on browser brand, rendering engine, or device see the10* other sub-namespaces in goog.labs.userAgent (browser, engine, and device11* respectively).12*/1314goog.module('goog.labs.userAgent.platform');15goog.module.declareLegacyNamespace();1617const googString = goog.require('goog.string.internal');18const util = goog.require('goog.labs.userAgent.util');19const {AsyncValue, Version} = goog.require('goog.labs.userAgent.highEntropy.highEntropyValue');20const {platformVersion} = goog.require('goog.labs.userAgent.highEntropy.highEntropyData');21const {useClientHints} = goog.require('goog.labs.userAgent');2223/**24* @param {boolean=} ignoreClientHintsFlag Iff truthy, the `useClientHints`25* function will not be called when evaluating if User-Agent Client Hints26* Brand data can be used. For existing labs.userAgent API surfaces with27* widespread use, this should be a falsy value so that usage of the Client28* Hints APIs can be gated behind flags / experiment rollouts.29* @return {boolean} Whether to use navigator.userAgentData to determine30* the current platform.31* userAgentData.platform was enabled by default in Chrome 93:32* https://www.chromestatus.com/feature/573349872585932833* TODO(user): Skip this check with FEATURESET_YEAR once userAgentData is34* present in all major browsers (may not be until 2024).35* See https://caniuse.com/mdn-api_navigator_useragentdata.36*/37function useUserAgentDataPlatform(ignoreClientHintsFlag = false) {38if (util.ASSUME_CLIENT_HINTS_SUPPORT) return true;39// High-entropy API surfaces should not be gated behind the useClientHints40// check (as in production it is gated behind a define).41if (!ignoreClientHintsFlag && !useClientHints()) return false;42const userAgentData = util.getUserAgentData();43return !!userAgentData && !!userAgentData.platform;44}4546/**47* @return {boolean} Whether the platform is Android.48*/49function isAndroid() {50if (useUserAgentDataPlatform()) {51return util.getUserAgentData().platform === 'Android';52}53return util.matchUserAgent('Android');54}5556/**57* @return {boolean} Whether the platform is iPod.58* TODO(user): Combine iPod/iPhone detection since they may become59* indistinguishable if we begin relying on userAgentdata in iOS.60*/61function isIpod() {62// navigator.userAgentData is currently not supported on any iOS browser, so63// rely only on navigator.userAgent.64return util.matchUserAgent('iPod');65}6667/**68* @return {boolean} Whether the platform is iPhone.69*/70function isIphone() {71// navigator.userAgentData is currently not supported on any iOS browser, so72// rely only on navigator.userAgent.73return util.matchUserAgent('iPhone') && !util.matchUserAgent('iPod') &&74!util.matchUserAgent('iPad');75}7677/**78* Returns whether the platform is iPad.79* Note that iPadOS 13+ spoofs macOS Safari by default in its user agent, and in80* this scenario the platform will not be recognized as iPad. If you must have81* iPad-specific behavior, use82* {@link goog.labs.userAgent.extra.isSafariDesktopOnMobile}.83* @return {boolean} Whether the platform is iPad.84*/85function isIpad() {86// navigator.userAgentData is currently not supported on any iOS browser, so87// rely only on navigator.userAgent.88return util.matchUserAgent('iPad');89}9091/**92* Returns whether the platform is iOS.93* Note that iPadOS 13+ spoofs macOS Safari by default in its user agent, and in94* this scenario the platform will not be recognized as iOS. If you must have95* iPad-specific behavior, use96* {@link goog.labs.userAgent.extra.isSafariDesktopOnMobile}.97* @return {boolean} Whether the platform is iOS.98*/99function isIos() {100return isIphone() || isIpad() || isIpod();101}102103/**104* @return {boolean} Whether the platform is Mac.105*/106function isMacintosh() {107if (useUserAgentDataPlatform()) {108return util.getUserAgentData().platform === 'macOS';109}110return util.matchUserAgent('Macintosh');111}112113/**114* Note: ChromeOS is not considered to be Linux as it does not report itself115* as Linux in the user agent string.116* @return {boolean} Whether the platform is Linux.117*/118function isLinux() {119if (useUserAgentDataPlatform()) {120return util.getUserAgentData().platform === 'Linux';121}122return util.matchUserAgent('Linux');123}124125/**126* @return {boolean} Whether the platform is Windows.127*/128function isWindows() {129if (useUserAgentDataPlatform()) {130return util.getUserAgentData().platform === 'Windows';131}132return util.matchUserAgent('Windows');133}134135/**136* @return {boolean} Whether the platform is ChromeOS.137*/138function isChromeOS() {139if (useUserAgentDataPlatform()) {140return util.getUserAgentData().platform === 'Chrome OS';141}142return util.matchUserAgent('CrOS');143}144145/**146* @return {boolean} Whether the platform is Chromecast.147*/148function isChromecast() {149// TODO(user): Check against util.getUserAgentData().platform once the150// OS string for Chromecast is known.151return util.matchUserAgent('CrKey');152}153154/**155* @return {boolean} Whether the platform is KaiOS.156*/157function isKaiOS() {158// navigator.userAgentData is currently not supported on any KaiOS browser, so159// rely only on navigator.userAgent.160return util.matchUserAgentIgnoreCase('KaiOS');161}162163/**164* The version of the platform. We only determine the version for Windows,165* Mac, and Chrome OS. It doesn't make much sense on Linux. For Windows, we only166* look at the NT version. Non-NT-based versions (e.g. 95, 98, etc.) are given167* version 0.0.168*169* @return {string} The platform version or empty string if version cannot be170* determined.171*/172function getVersion() {173const userAgentString = util.getUserAgent();174let version = '', re;175if (isWindows()) {176re = /Windows (?:NT|Phone) ([0-9.]+)/;177const match = re.exec(userAgentString);178if (match) {179version = match[1];180} else {181version = '0.0';182}183} else if (isIos()) {184re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/;185const match = re.exec(userAgentString);186// Report the version as x.y.z and not x_y_z187version = match && match[1].replace(/_/g, '.');188} else if (isMacintosh()) {189re = /Mac OS X ([0-9_.]+)/;190const match = re.exec(userAgentString);191// Note: some old versions of Camino do not report an OSX version.192// Default to 10.193version = match ? match[1].replace(/_/g, '.') : '10';194} else if (isKaiOS()) {195re = /(?:KaiOS)\/(\S+)/i;196const match = re.exec(userAgentString);197version = match && match[1];198} else if (isAndroid()) {199re = /Android\s+([^\);]+)(\)|;)/;200const match = re.exec(userAgentString);201version = match && match[1];202} else if (isChromeOS()) {203re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/;204const match = re.exec(userAgentString);205version = match && match[1];206}207return version || '';208}209210/**211* @param {string|number} version The version to check.212* @return {boolean} Whether the browser version is higher or the same as the213* given version.214*/215function isVersionOrHigher(version) {216return googString.compareVersions(getVersion(), version) >= 0;217}218219/**220* Represents a high-entropy version string.221* @implements {AsyncValue<!Version>}222*/223class PlatformVersion {224constructor() {225/** @private {boolean} */226this.preUachHasLoaded_ = false;227}228229/**230* @return {!Version|undefined}231* @override232*/233getIfLoaded() {234if (useUserAgentDataPlatform(true)) {235const loadedPlatformVersion = platformVersion.getIfLoaded();236if (loadedPlatformVersion === undefined) {237// No platform data has been cached238return undefined;239}240return new Version(loadedPlatformVersion);241} else if (!this.preUachHasLoaded_) {242// Nobody ever called `load` on this class instance, so we should return243// nothing to match the semantics of the class when using the Client Hint244// APIs.245return undefined;246} else {247// `load` has been called, so we can return a Version derived from the248// useragent string.249return new Version(getVersion());250}251}252253/**254* @return {!Promise<!Version>}255* @override256*/257async load() {258if (useUserAgentDataPlatform(true)) {259return new Version(await platformVersion.load());260} else {261this.preUachHasLoaded_ = true;262return new Version(getVersion());263}264}265266/** @package */267resetForTesting() {268platformVersion.resetForTesting();269this.preUachHasLoaded_ = false;270}271}272273/**274* The platform version, a high-entropy value.275* @type {!PlatformVersion}276*/277const version = new PlatformVersion();278279exports = {280getVersion,281isAndroid,282isChromeOS,283isChromecast,284isIos,285isIpad,286isIphone,287isIpod,288isKaiOS,289isLinux,290isMacintosh,291isVersionOrHigher,292isWindows,293version,294};295296297