src/utils/env.js

// namespaces
var dwv = dwv || {};
/** @namespace */
dwv.env = dwv.env || {};

/**
 * Local function to ask Modernizr if a property is supported.
 *
 * @param {string} property The property to test.
 * @returns {boolean} True if the env supports the input feature.
 */
dwv.env.askModernizr = function (property) {
  if (typeof dwv.Modernizr === 'undefined') {
    dwv.ModernizrInit(window, document);
  }
  var props = property.split('.');
  var prop = dwv.Modernizr;
  for (var i = 0; i < props.length; ++i) {
    prop = prop[props[i]];
  }
  return prop;
};

/**
 * Browser check for the FileAPI.
 * Assume support for Safari5.
 *
 * @returns {boolean} True if the env supports the feature.
 */
dwv.env.hasFileApi = function () {
  // regular test does not work on Safari 5
  var isSafari5 = (navigator.appVersion.indexOf('Safari') !== -1) &&
        (navigator.appVersion.indexOf('Chrome') === -1) &&
        ((navigator.appVersion.indexOf('5.0.') !== -1) ||
          (navigator.appVersion.indexOf('5.1.') !== -1));
  if (isSafari5) {
    dwv.logger.warn('Assuming FileAPI support for Safari5...');
    return true;
  }
  // regular test
  return dwv.env.askModernizr('filereader');
};

/**
 * Browser check for the XMLHttpRequest.
 *
 * @returns {boolean} True if the env supports the feature.
 */
dwv.env.hasXmlHttpRequest = function () {
  return dwv.env.askModernizr('xhrresponsetype') &&
        dwv.env.askModernizr('xhrresponsetypearraybuffer') &&
        dwv.env.askModernizr('xhrresponsetypetext') &&
        'XMLHttpRequest' in window && 'withCredentials' in new XMLHttpRequest();
};

/**
 * Browser check for typed array.
 *
 * @returns {boolean} True if the env supports the feature.
 */
dwv.env.hasTypedArray = function () {
  return dwv.env.askModernizr('dataview') &&
    dwv.env.askModernizr('typedarrays');
};

/**
 * Browser check for input with type='color'.
 * Missing in IE and Safari.
 *
 * @returns {boolean} True if the env supports the feature.
 */
dwv.env.hasInputColor = function () {
  return dwv.env.askModernizr('inputtypes.color');
};

/**
 * Browser check for input with type='files' and webkitdirectory flag.
 * Missing in IE and Safari.
 *
 * @returns {boolean} True if the env supports the feature.
 */
dwv.env.hasInputDirectory = function () {
  return dwv.env.askModernizr('fileinputdirectory');
};


//only check at startup (since we propose a replacement)
dwv.env._hasTypedArraySlice =
  (typeof Uint8Array.prototype.slice !== 'undefined');

/**
 * Browser check for typed array slice method.
 * Missing in Internet Explorer 11.
 *
 * @returns {boolean} True if the env supports the feature.
 */
dwv.env.hasTypedArraySlice = function () {
  return dwv.env._hasTypedArraySlice;
};

// only check at startup (since we propose a replacement)
dwv.env._hasFloat64Array = ('Float64Array' in window);

/**
 * Browser check for Float64Array array.
 * Missing in PhantomJS 1.9.20 (on Travis).
 *
 * @returns {boolean} True if the env supports the feature.
 */
dwv.env.hasFloat64Array = function () {
  return dwv.env._hasFloat64Array;
};

//only check at startup (since we propose a replacement)
dwv.env._hasClampedArray = ('Uint8ClampedArray' in window);

/**
 * Browser check for clamped array.
 * Missing in:
 * - Safari 5.1.7 for Windows
 * - PhantomJS 1.9.20 (on Travis).
 *
 * @returns {boolean} True if the env supports the feature.
 */
dwv.env.hasClampedArray = function () {
  return dwv.env._hasClampedArray;
};

/**
 * Browser checks to see if it can run dwv. Throws an error if not.
 * Silently replaces basic functions.
 */
dwv.env.check = function () {

  // Required --------------

  var message = '';
  // Check for the File API support
  if (!dwv.env.hasFileApi()) {
    message = 'The File APIs are not supported in this browser. ';
    throw new Error(message);
  }
  // Check for XMLHttpRequest
  if (!dwv.env.hasXmlHttpRequest()) {
    message = 'The XMLHttpRequest is not supported in this browser. ';
    throw new Error(message);
  }
  // Check typed array
  if (!dwv.env.hasTypedArray()) {
    message = 'The Typed arrays are not supported in this browser. ';
    throw new Error(message);
  }

  // Replaced if not present ------------

  // Check typed array slice
  if (!dwv.env.hasTypedArraySlice()) {
    // silent fail with warning
    dwv.logger.warn(
      'The TypedArray.slice method is not supported in this browser.' +
      ' This may impair performance. ');
    // basic Uint16Array implementation
    Uint16Array.prototype.slice = function (begin, end) {
      var size = end - begin;
      var cloned = new Uint16Array(size);
      for (var i = 0; i < size; i++) {
        cloned[i] = this[begin + i];
      }
      return cloned;
    };
    // basic Int16Array implementation
    Int16Array.prototype.slice = function (begin, end) {
      var size = end - begin;
      var cloned = new Int16Array(size);
      for (var i = 0; i < size; i++) {
        cloned[i] = this[begin + i];
      }
      return cloned;
    };
    // basic Uint8Array implementation
    Uint8Array.prototype.slice = function (begin, end) {
      var size = end - begin;
      var cloned = new Uint8Array(size);
      for (var i = 0; i < size; i++) {
        cloned[i] = this[begin + i];
      }
      return cloned;
    };
    // basic Int8Array implementation
    Int8Array.prototype.slice = function (begin, end) {
      var size = end - begin;
      var cloned = new Int8Array(size);
      for (var i = 0; i < size; i++) {
        cloned[i] = this[begin + i];
      }
      return cloned;
    };
  }
  // check clamped array
  if (!dwv.env.hasClampedArray()) {
    // silent fail with warning
    dwv.logger.warn(
      'The Uint8ClampedArray is not supported in this browser.' +
      ' This may impair performance. ');
    // Use Uint8Array instead... Not good
    // TODO Find better replacement!
    window.Uint8ClampedArray = window.Uint8Array;
  }
  // check Float64 array
  if (!dwv.env.hasFloat64Array()) {
    // silent fail with warning
    dwv.logger.warn(
      'The Float64Array is not supported in this browser.' +
      ' This may impair performance. ');
    // Use Float32Array instead... Not good
    // TODO Find better replacement!
    window.Float64Array = window.Float32Array;
  }

  // array Find
  // https://tc39.github.io/ecma262/#sec-array.prototype.find
  if (!Array.prototype.find) {
    Object.defineProperty(Array.prototype, 'find', {
      value: function (predicate) {
        // 1. Let O be ? ToObject(this value).
        if (this === null) {
          throw new TypeError('"this" is null or not defined');
        }

        var o = Object(this);

        // 2. Let len be ? ToLength(? Get(O, "length")).
        var len = o.length >>> 0;

        // 3. If IsCallable(predicate) is false, throw a TypeError exception.
        if (typeof predicate !== 'function') {
          throw new TypeError('predicate must be a function');
        }

        // 4. If thisArg was supplied, let T be thisArg; else let T
        //    be undefined.
        var thisArg = arguments[1];

        // 5. Let k be 0.
        var k = 0;

        // 6. Repeat, while k < len
        while (k < len) {
          // a. Let Pk be ! ToString(k).
          // b. Let kValue be ? Get(O, Pk).
          // c. Let testResult be ToBoolean(? Call(predicate,
          //    T, « kValue, k, O »)).
          // d. If testResult is true, return kValue.
          var kValue = o[k];
          if (predicate.call(thisArg, kValue, k, o)) {
            return kValue;
          }
          // e. Increase k by 1.
          k++;
        }

        // 7. Return undefined.
        return undefined;
      },
      configurable: true,
      writable: true
    });
  }

  // check string startsWith
  if (!String.prototype.startsWith) {
    String.prototype.startsWith = function (search, pos) {
      return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
    };
  }
};