Newer
Older
Kusanagi-System / FeatureDetection.js
import Check from "./Check.js";
import defaultValue from "./defaultValue.js";
import defined from "./defined.js";
import DeveloperError from "./DeveloperError.js";
import Fullscreen from "./Fullscreen.js";

let theNavigator;
if (typeof navigator !== "undefined") {
  theNavigator = navigator;
} else {
  theNavigator = {};
}

function extractVersion(versionString) {
  const parts = versionString.split(".");
  for (let i = 0, len = parts.length; i < len; ++i) {
    parts[i] = parseInt(parts[i], 10);
  }
  return parts;
}

let isChromeResult;
let chromeVersionResult;
function isChrome() {
  if (!defined(isChromeResult)) {
    isChromeResult = false;
    // Edge contains Chrome in the user agent too
    if (!isEdge()) {
      const fields = / Chrome\/([\.0-9]+)/.exec(theNavigator.userAgent);
      if (fields !== null) {
        isChromeResult = true;
        chromeVersionResult = extractVersion(fields[1]);
      }
    }
  }

  return isChromeResult;
}

function chromeVersion() {
  return isChrome() && chromeVersionResult;
}

let isSafariResult;
let safariVersionResult;
function isSafari() {
  if (!defined(isSafariResult)) {
    isSafariResult = false;

    // Chrome and Edge contain Safari in the user agent too
    if (
      !isChrome() &&
      !isEdge() &&
      / Safari\/[\.0-9]+/.test(theNavigator.userAgent)
    ) {
      const fields = / Version\/([\.0-9]+)/.exec(theNavigator.userAgent);
      if (fields !== null) {
        isSafariResult = true;
        safariVersionResult = extractVersion(fields[1]);
      }
    }
  }

  return isSafariResult;
}

function safariVersion() {
  return isSafari() && safariVersionResult;
}

let isWebkitResult;
let webkitVersionResult;
function isWebkit() {
  if (!defined(isWebkitResult)) {
    isWebkitResult = false;

    const fields = / AppleWebKit\/([\.0-9]+)(\+?)/.exec(theNavigator.userAgent);
    if (fields !== null) {
      isWebkitResult = true;
      webkitVersionResult = extractVersion(fields[1]);
      webkitVersionResult.isNightly = !!fields[2];
    }
  }

  return isWebkitResult;
}

function webkitVersion() {
  return isWebkit() && webkitVersionResult;
}

let isInternetExplorerResult;
let internetExplorerVersionResult;
function isInternetExplorer() {
  if (!defined(isInternetExplorerResult)) {
    isInternetExplorerResult = false;

    let fields;
    if (theNavigator.appName === "Microsoft Internet Explorer") {
      fields = /MSIE ([0-9]{1,}[\.0-9]{0,})/.exec(theNavigator.userAgent);
      if (fields !== null) {
        isInternetExplorerResult = true;
        internetExplorerVersionResult = extractVersion(fields[1]);
      }
    } else if (theNavigator.appName === "Netscape") {
      fields = /Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(
        theNavigator.userAgent
      );
      if (fields !== null) {
        isInternetExplorerResult = true;
        internetExplorerVersionResult = extractVersion(fields[1]);
      }
    }
  }
  return isInternetExplorerResult;
}

function internetExplorerVersion() {
  return isInternetExplorer() && internetExplorerVersionResult;
}

let isEdgeResult;
let edgeVersionResult;
function isEdge() {
  if (!defined(isEdgeResult)) {
    isEdgeResult = false;
    const fields = / Edg\/([\.0-9]+)/.exec(theNavigator.userAgent);
    if (fields !== null) {
      isEdgeResult = true;
      edgeVersionResult = extractVersion(fields[1]);
    }
  }
  return isEdgeResult;
}

function edgeVersion() {
  return isEdge() && edgeVersionResult;
}

let isFirefoxResult;
let firefoxVersionResult;
function isFirefox() {
  if (!defined(isFirefoxResult)) {
    isFirefoxResult = false;

    const fields = /Firefox\/([\.0-9]+)/.exec(theNavigator.userAgent);
    if (fields !== null) {
      isFirefoxResult = true;
      firefoxVersionResult = extractVersion(fields[1]);
    }
  }
  return isFirefoxResult;
}

let isWindowsResult;
function isWindows() {
  if (!defined(isWindowsResult)) {
    isWindowsResult = /Windows/i.test(theNavigator.appVersion);
  }
  return isWindowsResult;
}

let isIPadOrIOSResult;
function isIPadOrIOS() {
  if (!defined(isIPadOrIOSResult)) {
    isIPadOrIOSResult =
      navigator.platform === "iPhone" ||
      navigator.platform === "iPod" ||
      navigator.platform === "iPad";
  }

  return isIPadOrIOSResult;
}

function firefoxVersion() {
  return isFirefox() && firefoxVersionResult;
}

let hasPointerEvents;
function supportsPointerEvents() {
  if (!defined(hasPointerEvents)) {
    //While navigator.pointerEnabled is deprecated in the W3C specification
    //we still need to use it if it exists in order to support browsers
    //that rely on it, such as the Windows WebBrowser control which defines
    //PointerEvent but sets navigator.pointerEnabled to false.

    //Firefox disabled because of https://github.com/CesiumGS/cesium/issues/6372
    hasPointerEvents =
      !isFirefox() &&
      typeof PointerEvent !== "undefined" &&
      (!defined(theNavigator.pointerEnabled) || theNavigator.pointerEnabled);
  }
  return hasPointerEvents;
}

let imageRenderingValueResult;
let supportsImageRenderingPixelatedResult;
function supportsImageRenderingPixelated() {
  if (!defined(supportsImageRenderingPixelatedResult)) {
    const canvas = document.createElement("canvas");
    canvas.setAttribute(
      "style",
      "image-rendering: -moz-crisp-edges;" + "image-rendering: pixelated;"
    );
    //canvas.style.imageRendering will be undefined, null or an empty string on unsupported browsers.
    const tmp = canvas.style.imageRendering;
    supportsImageRenderingPixelatedResult = defined(tmp) && tmp !== "";
    if (supportsImageRenderingPixelatedResult) {
      imageRenderingValueResult = tmp;
    }
  }
  return supportsImageRenderingPixelatedResult;
}

function imageRenderingValue() {
  return supportsImageRenderingPixelated()
    ? imageRenderingValueResult
    : undefined;
}

function supportsWebP() {
  //>>includeStart('debug', pragmas.debug);
  if (!supportsWebP.initialized) {
    throw new DeveloperError(
      "You must call FeatureDetection.supportsWebP.initialize and wait for the promise to resolve before calling FeatureDetection.supportsWebP"
    );
  }
  //>>includeEnd('debug');
  return supportsWebP._result;
}
supportsWebP._promise = undefined;
supportsWebP._result = undefined;
supportsWebP.initialize = function () {
  // From https://developers.google.com/speed/webp/faq#how_can_i_detect_browser_support_for_webp
  if (defined(supportsWebP._promise)) {
    return supportsWebP._promise;
  }

  supportsWebP._promise = new Promise((resolve) => {
    const image = new Image();
    image.onload = function () {
      supportsWebP._result = image.width > 0 && image.height > 0;
      resolve(supportsWebP._result);
    };

    image.onerror = function () {
      supportsWebP._result = false;
      resolve(supportsWebP._result);
    };
    image.src =
      "data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA";
  });

  return supportsWebP._promise;
};
Object.defineProperties(supportsWebP, {
  initialized: {
    get: function () {
      return defined(supportsWebP._result);
    },
  },
});

const typedArrayTypes = [];
if (typeof ArrayBuffer !== "undefined") {
  typedArrayTypes.push(
    Int8Array,
    Uint8Array,
    Int16Array,
    Uint16Array,
    Int32Array,
    Uint32Array,
    Float32Array,
    Float64Array
  );

  if (typeof Uint8ClampedArray !== "undefined") {
    typedArrayTypes.push(Uint8ClampedArray);
  }

  if (typeof Uint8ClampedArray !== "undefined") {
    typedArrayTypes.push(Uint8ClampedArray);
  }

  if (typeof BigInt64Array !== "undefined") {
    // eslint-disable-next-line no-undef
    typedArrayTypes.push(BigInt64Array);
  }

  if (typeof BigUint64Array !== "undefined") {
    // eslint-disable-next-line no-undef
    typedArrayTypes.push(BigUint64Array);
  }
}

/**
 * A set of functions to detect whether the current browser supports
 * various features.
 *
 * @namespace FeatureDetection
 */
const FeatureDetection = {
  isChrome: isChrome,
  chromeVersion: chromeVersion,
  isSafari: isSafari,
  safariVersion: safariVersion,
  isWebkit: isWebkit,
  webkitVersion: webkitVersion,
  isInternetExplorer: isInternetExplorer,
  internetExplorerVersion: internetExplorerVersion,
  isEdge: isEdge,
  edgeVersion: edgeVersion,
  isFirefox: isFirefox,
  firefoxVersion: firefoxVersion,
  isWindows: isWindows,
  isIPadOrIOS: isIPadOrIOS,
  hardwareConcurrency: defaultValue(theNavigator.hardwareConcurrency, 3),
  supportsPointerEvents: supportsPointerEvents,
  supportsImageRenderingPixelated: supportsImageRenderingPixelated,
  supportsWebP: supportsWebP,
  imageRenderingValue: imageRenderingValue,
  typedArrayTypes: typedArrayTypes,
};

/**
 * Detects whether the current browser supports Basis Universal textures and the web assembly modules needed to transcode them.
 *
 * @param {Scene} scene
 * @returns {boolean} true if the browser supports web assembly modules and the scene supports Basis Universal textures, false if not.
 */
FeatureDetection.supportsBasis = function (scene) {
  return FeatureDetection.supportsWebAssembly() && scene.context.supportsBasis;
};

/**
 * Detects whether the current browser supports the full screen standard.
 *
 * @returns {boolean} true if the browser supports the full screen standard, false if not.
 *
 * @see Fullscreen
 * @see {@link http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html|W3C Fullscreen Living Specification}
 */
FeatureDetection.supportsFullscreen = function () {
  return Fullscreen.supportsFullscreen();
};

/**
 * Detects whether the current browser supports typed arrays.
 *
 * @returns {boolean} true if the browser supports typed arrays, false if not.
 *
 * @see {@link https://tc39.es/ecma262/#sec-typedarray-objects|Typed Array Specification}
 */
FeatureDetection.supportsTypedArrays = function () {
  return typeof ArrayBuffer !== "undefined";
};

/**
 * Detects whether the current browser supports BigInt64Array typed arrays.
 *
 * @returns {boolean} true if the browser supports BigInt64Array typed arrays, false if not.
 *
 * @see {@link https://tc39.es/ecma262/#sec-typedarray-objects|Typed Array Specification}
 */
FeatureDetection.supportsBigInt64Array = function () {
  return typeof BigInt64Array !== "undefined";
};

/**
 * Detects whether the current browser supports BigUint64Array typed arrays.
 *
 * @returns {boolean} true if the browser supports BigUint64Array typed arrays, false if not.
 *
 * @see {@link https://tc39.es/ecma262/#sec-typedarray-objects|Typed Array Specification}
 */
FeatureDetection.supportsBigUint64Array = function () {
  return typeof BigUint64Array !== "undefined";
};

/**
 * Detects whether the current browser supports BigInt.
 *
 * @returns {boolean} true if the browser supports BigInt, false if not.
 *
 * @see {@link https://tc39.es/ecma262/#sec-bigint-objects|BigInt Specification}
 */
FeatureDetection.supportsBigInt = function () {
  return typeof BigInt !== "undefined";
};

/**
 * Detects whether the current browser supports Web Workers.
 *
 * @returns {boolean} true if the browsers supports Web Workers, false if not.
 *
 * @see {@link http://www.w3.org/TR/workers/}
 */
FeatureDetection.supportsWebWorkers = function () {
  return typeof Worker !== "undefined";
};

/**
 * Detects whether the current browser supports Web Assembly.
 *
 * @returns {boolean} true if the browsers supports Web Assembly, false if not.
 *
 * @see {@link https://developer.mozilla.org/en-US/docs/WebAssembly}
 */
FeatureDetection.supportsWebAssembly = function () {
  return typeof WebAssembly !== "undefined";
};

/**
 * Detects whether the current browser supports a WebGL2 rendering context for the specified scene.
 *
 * @param {Scene} scene the Cesium scene specifying the rendering context
 * @returns {boolean} true if the browser supports a WebGL2 rendering context, false if not.
 *
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext|WebGL2RenderingContext}
 */
FeatureDetection.supportsWebgl2 = function (scene) {
  //>>includeStart('debug', pragmas.debug);
  Check.defined("scene", scene);
  //>>includeEnd('debug');

  return scene.context.webgl2;
};

/**
 * Detects whether the current browser supports ECMAScript modules in web workers.
 * @returns {boolean} true if the browser supports ECMAScript modules in web workers.
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Worker|Worker}
 */
FeatureDetection.supportsEsmWebWorkers = function () {
  return !isFirefox() || parseInt(firefoxVersionResult) >= 114;
};

export default FeatureDetection;