text: // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS-IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  /**
   * @fileOverview Rendering engine detection.
   * @see <a href="http://www.useragentstring.com/">User agent strings</a>
   * For information on the browser brand (such as Safari versus Chrome), see
   * goog.userAgent.product.
   * @see ../demos/useragent.html
   */
  
  goog.provide('goog.userAgent');
  
  goog.require('goog.labs.userAgent.browser');
  goog.require('goog.labs.userAgent.engine');
  goog.require('goog.labs.userAgent.platform');
  goog.require('goog.labs.userAgent.util');
  goog.require('goog.reflect');
  goog.require('goog.string');
  
  
  /**
   * @define {boolean} Whether we know at compile-time that the browser is IE.
   */
  goog.userAgent.ASSUME_IE = goog.define('goog.userAgent.ASSUME_IE', false);
  
  
  /**
   * @define {boolean} Whether we know at compile-time that the browser is EDGE,
   * referring to EdgeHTML based Edge.
   */
  goog.userAgent.ASSUME_EDGE = goog.define('goog.userAgent.ASSUME_EDGE', false);
  
  
  /**
   * @define {boolean} Whether we know at compile-time that the browser is GECKO.
   */
  goog.userAgent.ASSUME_GECKO = goog.define('goog.userAgent.ASSUME_GECKO', false);
  
  
  /**
   * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
   */
  goog.userAgent.ASSUME_WEBKIT =
      goog.define('goog.userAgent.ASSUME_WEBKIT', false);
  
  
  /**
   * @define {boolean} Whether we know at compile-time that the browser is a
   *     mobile device running WebKit e.g. iPhone or Android.
   */
  goog.userAgent.ASSUME_MOBILE_WEBKIT =
      goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);
  
  
  /**
   * @define {boolean} Whether we know at compile-time that the browser is OPERA,
   * referring to Presto-based Opera.
   */
  goog.userAgent.ASSUME_OPERA = goog.define('goog.userAgent.ASSUME_OPERA', false);
  
  
  /**
   * @define {boolean} Whether the
   *     `goog.userAgent.isVersionOrHigher`
   *     function will return true for any version.
   */
  goog.userAgent.ASSUME_ANY_VERSION =
      goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
  
  
  /**
   * Whether we know the browser engine at compile-time.
   * @type {boolean}
   * @private
   */
  goog.userAgent.BROWSER_KNOWN_ = goog.userAgent.ASSUME_IE ||
      goog.userAgent.ASSUME_EDGE || goog.userAgent.ASSUME_GECKO ||
      goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.ASSUME_WEBKIT ||
      goog.userAgent.ASSUME_OPERA;
  
  
  /**
   * Returns the userAgent string for the current browser.
   *
   * @return {string} The userAgent string.
   */
  goog.userAgent.getUserAgentString = function() {
    return goog.labs.userAgent.util.getUserAgent();
  };
  
  
  /**
   * @return {?Navigator} The native navigator object.
   */
  goog.userAgent.getNavigatorTyped = function() {
    // Need a local navigator reference instead of using the global one,
    // to avoid the rare case where they reference different objects.
    // (in a WorkerPool, for example).
    return goog.global['navigator'] || null;
  };
  
  
  /**
   * TODO(nnaze): Change type to "Navigator" and update compilation targets.
   * @return {?Object} The native navigator object.
   */
  goog.userAgent.getNavigator = function() {
    return goog.userAgent.getNavigatorTyped();
  };
  
  
  /**
   * Whether the user agent is Presto-based Opera.
   * @type {boolean}
   */
  goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
      goog.userAgent.ASSUME_OPERA :
      goog.labs.userAgent.browser.isOpera();
  
  
  /**
   * Whether the user agent is Internet Explorer.
   * @type {boolean}
   */
  goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
      goog.userAgent.ASSUME_IE :
      goog.labs.userAgent.browser.isIE();
  
  
  /**
   * Whether the user agent is Microsoft Edge (EdgeHTML based).
   * @type {boolean}
   */
  goog.userAgent.EDGE = goog.userAgent.BROWSER_KNOWN_ ?
      goog.userAgent.ASSUME_EDGE :
      goog.labs.userAgent.engine.isEdge();
  
  
  /**
   * Whether the user agent is MS Internet Explorer or MS Edge (EdgeHTML based).
   * @type {boolean}
   */
  goog.userAgent.EDGE_OR_IE = goog.userAgent.EDGE || goog.userAgent.IE;
  
  
  /**
   * Whether the user agent is Gecko. Gecko is the rendering engine used by
   * Mozilla, Firefox, and others.
   * @type {boolean}
   */
  goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
      goog.userAgent.ASSUME_GECKO :
      goog.labs.userAgent.engine.isGecko();
  
  
  /**
   * Whether the user agent is WebKit. WebKit is the rendering engine that
   * Safari, Edge Chromium, Opera Chromium, Android and others use.
   * @type {boolean}
   */
  goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
      goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
      goog.labs.userAgent.engine.isWebKit();
  
  
  /**
   * Whether the user agent is running on a mobile device.
   *
   * This is a separate function so that the logic can be tested.
   *
   * TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().
   *
   * @return {boolean} Whether the user agent is running on a mobile device.
   * @private
   */
  goog.userAgent.isMobile_ = function() {
    return goog.userAgent.WEBKIT &&
        goog.labs.userAgent.util.matchUserAgent('Mobile');
  };
  
  
  /**
   * Whether the user agent is running on a mobile device.
   *
   * TODO(nnaze): Consider deprecating MOBILE when labs.userAgent
   *   is promoted as the gecko/webkit logic is likely inaccurate.
   *
   * @type {boolean}
   */
  goog.userAgent.MOBILE =
      goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.isMobile_();
  
  
  /**
   * Used while transitioning code to use WEBKIT instead.
   * @type {boolean}
   * @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
   * TODO(nicksantos): Delete this from goog.userAgent.
   */
  goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
  
  
  /**
   * @return {string} the platform (operating system) the user agent is running
   *     on. Default to empty string because navigator.platform may not be defined
   *     (on Rhino, for example).
   * @private
   */
  goog.userAgent.determinePlatform_ = function() {
    var navigator = goog.userAgent.getNavigatorTyped();
    return navigator && navigator.platform || '';
  };
  
  
  /**
   * The platform (operating system) the user agent is running on. Default to
   * empty string because navigator.platform may not be defined (on Rhino, for
   * example).
   * @type {string}
   */
  goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
  
  
  /**
   * @define {boolean} Whether the user agent is running on a Macintosh operating
   *     system.
   */
  goog.userAgent.ASSUME_MAC = goog.define('goog.userAgent.ASSUME_MAC', false);
  
  
  /**
   * @define {boolean} Whether the user agent is running on a Windows operating
   *     system.
   */
  goog.userAgent.ASSUME_WINDOWS =
      goog.define('goog.userAgent.ASSUME_WINDOWS', false);
  
  
  /**
   * @define {boolean} Whether the user agent is running on a Linux operating
   *     system.
   */
  goog.userAgent.ASSUME_LINUX = goog.define('goog.userAgent.ASSUME_LINUX', false);
  
  
  /**
   * @define {boolean} Whether the user agent is running on a X11 windowing
   *     system.
   */
  goog.userAgent.ASSUME_X11 = goog.define('goog.userAgent.ASSUME_X11', false);
  
  
  /**
   * @define {boolean} Whether the user agent is running on Android.
   */
  goog.userAgent.ASSUME_ANDROID =
      goog.define('goog.userAgent.ASSUME_ANDROID', false);
  
  
  /**
   * @define {boolean} Whether the user agent is running on an iPhone.
   */
  goog.userAgent.ASSUME_IPHONE =
      goog.define('goog.userAgent.ASSUME_IPHONE', false);
  
  
  /**
   * @define {boolean} Whether the user agent is running on an iPad.
   */
  goog.userAgent.ASSUME_IPAD = goog.define('goog.userAgent.ASSUME_IPAD', false);
  
  
  /**
   * @define {boolean} Whether the user agent is running on an iPod.
   */
  goog.userAgent.ASSUME_IPOD = goog.define('goog.userAgent.ASSUME_IPOD', false);
  
  
  /**
   * @define {boolean} Whether the user agent is running on KaiOS.
   */
  goog.userAgent.ASSUME_KAIOS = goog.define('goog.userAgent.ASSUME_KAIOS', false);
  
  /**
   * @define {boolean} Whether the user agent is running on Go2Phone.
   */
  goog.userAgent.ASSUME_GO2PHONE =
      goog.define('goog.userAgent.ASSUME_GO2PHONE', false);
  
  
  /**
   * @type {boolean}
   * @private
   */
  goog.userAgent.PLATFORM_KNOWN_ = goog.userAgent.ASSUME_MAC ||
      goog.userAgent.ASSUME_WINDOWS || goog.userAgent.ASSUME_LINUX ||
      goog.userAgent.ASSUME_X11 || goog.userAgent.ASSUME_ANDROID ||
      goog.userAgent.ASSUME_IPHONE || goog.userAgent.ASSUME_IPAD ||
      goog.userAgent.ASSUME_IPOD;
  
  
  /**
   * Whether the user agent is running on a Macintosh operating system.
   * @type {boolean}
   */
  goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
      goog.userAgent.ASSUME_MAC :
      goog.labs.userAgent.platform.isMacintosh();
  
  
  /**
   * Whether the user agent is running on a Windows operating system.
   * @type {boolean}
   */
  goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
      goog.userAgent.ASSUME_WINDOWS :
      goog.labs.userAgent.platform.isWindows();
  
  
  /**
   * Whether the user agent is Linux per the legacy behavior of
   * goog.userAgent.LINUX, which considered ChromeOS to also be
   * Linux.
   * @return {boolean}
   * @private
   */
  goog.userAgent.isLegacyLinux_ = function() {
    return goog.labs.userAgent.platform.isLinux() ||
        goog.labs.userAgent.platform.isChromeOS();
  };
  
  
  /**
   * Whether the user agent is running on a Linux operating system.
   *
   * Note that goog.userAgent.LINUX considers ChromeOS to be Linux,
   * while goog.labs.userAgent.platform considers ChromeOS and
   * Linux to be different OSes.
   *
   * @type {boolean}
   */
  goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
      goog.userAgent.ASSUME_LINUX :
      goog.userAgent.isLegacyLinux_();
  
  
  /**
   * @return {boolean} Whether the user agent is an X11 windowing system.
   * @private
   */
  goog.userAgent.isX11_ = function() {
    var navigator = goog.userAgent.getNavigatorTyped();
    return !!navigator &&
        goog.string.contains(navigator['appVersion'] || '', 'X11');
  };
  
  
  /**
   * Whether the user agent is running on a X11 windowing system.
   * @type {boolean}
   */
  goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
      goog.userAgent.ASSUME_X11 :
      goog.userAgent.isX11_();
  
  
  /**
   * Whether the user agent is running on Android.
   * @type {boolean}
   */
  goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
      goog.userAgent.ASSUME_ANDROID :
      goog.labs.userAgent.platform.isAndroid();
  
  
  /**
   * Whether the user agent is running on an iPhone.
   * @type {boolean}
   */
  goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
      goog.userAgent.ASSUME_IPHONE :
      goog.labs.userAgent.platform.isIphone();
  
  
  /**
   * Whether the user agent is running on an iPad.
   * @type {boolean}
   */
  goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
      goog.userAgent.ASSUME_IPAD :
      goog.labs.userAgent.platform.isIpad();
  
  
  /**
   * Whether the user agent is running on an iPod.
   * @type {boolean}
   */
  goog.userAgent.IPOD = goog.userAgent.PLATFORM_KNOWN_ ?
      goog.userAgent.ASSUME_IPOD :
      goog.labs.userAgent.platform.isIpod();
  
  
  /**
   * Whether the user agent is running on iOS.
   * @type {boolean}
   */
  goog.userAgent.IOS = goog.userAgent.PLATFORM_KNOWN_ ?
      (goog.userAgent.ASSUME_IPHONE || goog.userAgent.ASSUME_IPAD ||
       goog.userAgent.ASSUME_IPOD) :
      goog.labs.userAgent.platform.isIos();
  
  /**
   * Whether the user agent is running on KaiOS.
   * @type {boolean}
   */
  goog.userAgent.KAIOS = goog.userAgent.PLATFORM_KNOWN_ ?
      goog.userAgent.ASSUME_KAIOS :
      goog.labs.userAgent.platform.isKaiOS();
  
  /**
   * Whether the user agent is running on Go2Phone.
   * @type {boolean}
   */
  goog.userAgent.GO2PHONE = goog.userAgent.PLATFORM_KNOWN_ ?
      goog.userAgent.ASSUME_GO2PHONE :
      goog.labs.userAgent.platform.isGo2Phone();
  
  
  /**
   * @return {string} The string that describes the version number of the user
   *     agent.
   * @private
   */
  goog.userAgent.determineVersion_ = function() {
    // All browsers have different ways to detect the version and they all have
    // different naming schemes.
    // version is a string rather than a number because it may contain 'b', 'a',
    // and so on.
    var version = '';
    var arr = goog.userAgent.getVersionRegexResult_();
    if (arr) {
      version = arr ? arr[1] : '';
    }
  
    if (goog.userAgent.IE) {
      // IE9 can be in document mode 9 but be reporting an inconsistent user agent
      // version.  If it is identifying as a version lower than 9 we take the
      // documentMode as the version instead.  IE8 has similar behavior.
      // It is recommended to set the X-UA-Compatible header to ensure that IE9
      // uses documentMode 9.
      var docMode = goog.userAgent.getDocumentMode_();
      if (docMode != null && docMode > parseFloat(version)) {
        return String(docMode);
      }
    }
  
    return version;
  };
  
  
  /**
   * @return {?IArrayLike<string>|undefined} The version regex matches from
   *     parsing the user
   *     agent string. These regex statements must be executed inline so they can
   *     be compiled out by the closure compiler with the rest of the useragent
   *     detection logic when ASSUME_* is specified.
   * @private
   */
  goog.userAgent.getVersionRegexResult_ = function() {
    var userAgent = goog.userAgent.getUserAgentString();
    if (goog.userAgent.GECKO) {
      return /rv\:([^\);]+)(\)|;)/.exec(userAgent);
    }
    if (goog.userAgent.EDGE) {
      return /Edge\/([\d\.]+)/.exec(userAgent);
    }
    if (goog.userAgent.IE) {
      return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(userAgent);
    }
    if (goog.userAgent.WEBKIT) {
      // WebKit/125.4
      return /WebKit\/(\S+)/.exec(userAgent);
    }
    if (goog.userAgent.OPERA) {
      // If none of the above browsers were detected but the browser is Opera, the
      // only string that is of interest is 'Version/<number>'.
      return /(?:Version)[ \/]?(\S+)/.exec(userAgent);
    }
    return undefined;
  };
  
  
  /**
   * @return {number|undefined} Returns the document mode (for testing).
   * @private
   */
  goog.userAgent.getDocumentMode_ = function() {
    // NOTE(user): goog.userAgent may be used in context where there is no DOM.
    var doc = goog.global['document'];
    return doc ? doc['documentMode'] : undefined;
  };
  
  
  /**
   * The version of the user agent. This is a string because it might contain
   * 'b' (as in beta) as well as multiple dots.
   * @type {string}
   */
  goog.userAgent.VERSION = goog.userAgent.determineVersion_();
  
  
  /**
   * Compares two version numbers.
   *
   * @param {string} v1 Version of first item.
   * @param {string} v2 Version of second item.
   *
   * @return {number}  1 if first argument is higher
   *                   0 if arguments are equal
   *                  -1 if second argument is higher.
   * @deprecated Use goog.string.compareVersions.
   */
  goog.userAgent.compare = function(v1, v2) {
    return goog.string.compareVersions(v1, v2);
  };
  
  
  /**
   * Cache for {@link goog.userAgent.isVersionOrHigher}.
   * Calls to compareVersions are surprisingly expensive and, as a browser's
   * version number is unlikely to change during a session, we cache the results.
   * @const
   * @private
   */
  goog.userAgent.isVersionOrHigherCache_ = {};
  
  
  /**
   * Whether the user agent version is higher or the same as the given version.
   * NOTE: When checking the version numbers for Firefox and Safari, be sure to
   * use the engine's version, not the browser's version number.  For example,
   * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
   * Opera and Internet Explorer versions match the product release number.<br>
   * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
   *     Webkit</a>
   * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
   *
   * @param {string|number} version The version to check.
   * @return {boolean} Whether the user agent version is higher or the same as
   *     the given version.
   */
  goog.userAgent.isVersionOrHigher = function(version) {
    return goog.userAgent.ASSUME_ANY_VERSION ||
        goog.reflect.cache(
            goog.userAgent.isVersionOrHigherCache_, version, function() {
              return goog.string.compareVersions(
                         goog.userAgent.VERSION, version) >= 0;
            });
  };
  
  
  /**
   * Deprecated alias to `goog.userAgent.isVersionOrHigher`.
   * @param {string|number} version The version to check.
   * @return {boolean} Whether the user agent version is higher or the same as
   *     the given version.
   * @deprecated Use goog.userAgent.isVersionOrHigher().
   */
  goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
  
  
  /**
   * Whether the IE effective document mode is higher or the same as the given
   * document mode version.
   * NOTE: Only for IE, return false for another browser.
   *
   * @param {number} documentMode The document mode version to check.
   * @return {boolean} Whether the IE effective document mode is higher or the
   *     same as the given version.
   */
  goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
    return Number(goog.userAgent.DOCUMENT_MODE) >= documentMode;
  };
  
  
  /**
   * Deprecated alias to `goog.userAgent.isDocumentModeOrHigher`.
   * @param {number} version The version to check.
   * @return {boolean} Whether the IE effective document mode is higher or the
   *      same as the given version.
   * @deprecated Use goog.userAgent.isDocumentModeOrHigher().
   */
  goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
  
  
  /**
   * For IE version < 7, documentMode is undefined, so attempt to use the
   * CSS1Compat property to see if we are in standards mode. If we are in
   * standards mode, treat the browser version as the document mode. Otherwise,
   * IE is emulating version 5.
   *
   * NOTE(user): Support for IE < 7 is long gone, so this is now simplified.
   * It returns document.documentMode for IE and undefined for everything else.
   *
   * @type {number|undefined}
   * @const
   */
  goog.userAgent.DOCUMENT_MODE = (function() {
    var doc = goog.global['document'];
    if (!doc || !goog.userAgent.IE) {
      return undefined;
    }
    return goog.userAgent.getDocumentMode_();
  })();
  