Source: dom/util.js

'use strict';

const util = {
  /**
   * Make your URL absolute.
   * @memberof util
   * @param {String} url The URL to convert to absolute.
   * @returns {String} the absolute URL including host/protocol.
   */
  getAbsoluteURL: function (url) {
    const a = window.document.createElement('a');
    a.href = url;
    return a.href;
  },

  /**
   * Get the hostname from a URL
   * @memberof util
   * @param {String} url The URL.
   * @returns {String} the hostname from the URL.
   */
  getHostname: function (url) {
    const a = window.document.createElement('a');
    a.href = url;
    return a.hostname;
  },

  /**
   * Checks if an element exist in an array.
   *
   * @memberof util
   * @param {String} element the element.
   * @param {Array} array the array.
   * @returns {Boolean} true if the element exist.
   */
  exists: function (element, array) {
    return array.some(function (e) {
      return e === element;
    });
  },

  /**
   * Returns an array filter function for finding tags with an attribute specified value, matching case insensitive.
   * @param attributeName the name of the attribute to look for (html attribute values are always case insensitive).
   * @param attributeValue the value to match against, ignoring case.
   * @returns {Function} function that can be passed to Array#filter
   */
  caseInsensitiveAttributeValueFilter: function (
    attributeName,
    attributeValue
  ) {
    return function (item) {
      const attribute = item.getAttribute(attributeName) || '';
      if (attribute.toLowerCase() === attributeValue.toLowerCase()) {
        return item;
      }
      return undefined;
    };
  },

  /**
   * Is the connection used for the main document using HTTP/2?
   * Works in Chrome, Firefox, Edge and other browsers that supports Resource Timing
   * API v2 (not Safari yet).
   * @memberof util
   * @returns {Boolean} true if the connection is HTTP/2
   */
  isHTTP2: function () {
    const type = util.getConnectionType().toLowerCase();
    return type === 'h2' || type.startsWith('spdy');
  },

  /**
   * Is the connection used for the main document using HTTP/3?
   * @memberof util
   * @returns {Boolean} true if the connection is HTTP/3
   */
  isHTTP3: function () {
    const type = util.getConnectionType().toLowerCase();
    return type.startsWith('h3');
  },

  /**
   * Get the connection type used for the main document. Works in Chrome, Firefox,
   * Edge and  + browsers that support Resource Timing
   * API v2.
   * @memberof util
   * @returns {String} http/1 or h2 for http 1 and 2 respectively. 'unknown' if browser lacks api to determine it.
   */
  getConnectionType: function () {
    // it's easy in Chrome
    if (
      window.performance.getEntriesByType('navigation') &&
      window.performance.getEntriesByType('navigation')[0] &&
      window.performance.getEntriesByType('navigation')[0].nextHopProtocol
    ) {
      return window.performance.getEntriesByType('navigation')[0]
        .nextHopProtocol;
    } else if (
      window.performance &&
      window.performance.getEntriesByType &&
      window.performance.getEntriesByType('resource')
    ) {
      // if you support resource timing v2
      // it's kind of easy too
      const resources = window.performance.getEntriesByType('resource');
      // now we "only" need to know if it is v2
      if (resources.length > 1 && resources[0].nextHopProtocol) {
        // if it's the same domain, say it's OK
        const host = document.domain;

        for (let i = 0, len = resources.length; i < len; i++) {
          if (host === util.getHostname(resources[i].name)) {
            return resources[i].nextHopProtocol;
          }
        }
      }
    }
    return 'unknown';
  },

  /**
   * Get JavaScript requests that are loaded synchronously. All URLs are absolute.
   * @memberof util
   * @param {Object} parentElement the parent element that has all the scripts.
   * @returns {Array} an array with the URL to each JavaScript file that is loaded synchronously.
   */
  getSynchJSFiles: function (parentElement) {
    const scripts = Array.prototype.slice.call(
      parentElement.getElementsByTagName('script')
    );

    return scripts
      .filter(function (s) {
        return !s.async && s.src && !s.defer;
      })
      .map(function (s) {
        return util.getAbsoluteURL(s.src);
      });
  },

  /**
   * Get JavaScript requests that are loaded asynchronously. All URLs are absolute.
   * @memberof util
   * @param {Object} parentElement the parent element that has all the scripts.
   * @returns {Array} an array with the URL to each JavaScript file that are loaded asynchronously.
   */
  getAsynchJSFiles: function (parentElement) {
    const scripts = Array.prototype.slice.call(
      parentElement.getElementsByTagName('script')
    );

    return scripts
      .filter(function (s) {
        return s.async && s.src;
      })
      .map(function (s) {
        return util.getAbsoluteURL(s.src);
      });
  },

  /**
   * Get Resource Hints hrefs by type
   * @memberof util
   * @param {String} type the name of the Resources hint: dns-prefetch, preconnect, prefetch, prerender
   * @returns {Array} an array of matching hrefs
   */
  getResourceHintsHrefs: function (type) {
    const links = Array.prototype.slice.call(
      window.document.head.getElementsByTagName('link')
    );

    return links
      .filter(function (link) {
        return link.rel === type;
      })
      .map(function (link) {
        return link.href;
      });
  },

  /**
   * Get CSS requests. All URLs are absolute.
   * @memberof util
   * @param {Object} parentElement the parent element that has all the scripts.
   * @returns {Array} an array with the URL to each CSS file that is loaded synchronously.
   */
  getCSSFiles: function (parentElement) {
    const links = Array.prototype.slice.call(
      parentElement.getElementsByTagName('link')
    );

    return links
      .filter(function (link) {
        // make sure we skip data:
        return link.rel === 'stylesheet' && !link.href.startsWith('data:');
      })
      .map(function (link) {
        return util.getAbsoluteURL(link.href);
      });
  },

  plural: function (number, text) {
    if (number > 1) {
      text += 's';
    }
    return `${number} ${text}`;
  },

  /**
   * Get the size of an asset. Will try to use the Resource Timing V2. If that's
   * not available or the asset size is unknown we report 0.
   **/
  getTransferSize: function (url) {
    const entries = window.performance.getEntriesByName(url, 'resource');
    if (entries.length === 1 && typeof entries[0].transferSize === 'number') {
      return entries[0].transferSize;
    } else {
      return 0;
    }
  },
  ms(ms) {
    if (ms < 1000) {
      return ms + ' ms';
    } else {
      return Number(ms / 1000).toFixed(3) + ' s';
    }
  }
};