text: "use strict";
  const path = require("path");
  const whatwgURL = require("whatwg-url");
  const querystring = require("querystring");
  const domSymbolTree = require("./living/helpers/internal-constants").domSymbolTree;
  const SYMBOL_TREE_POSITION = require("symbol-tree").TreePosition;
  const parseURLToResultingURLRecord = require("./living/helpers/document-base-url").parseURLToResultingURLRecord;
  
  exports.toFileUrl = function (fileName) {
    // Beyond just the `path.resolve`, this is mostly for the benefit of Windows,
    // where we need to convert "\" to "/" and add an extra "/" prefix before the
    // drive letter.
    let pathname = path.resolve(process.cwd(), fileName).replace(/\\/g, "/");
    if (pathname[0] !== "/") {
      pathname = "/" + pathname;
    }
  
    // path might contain spaces, so convert those to %20
    return "file://" + encodeURI(pathname);
  };
  
  /**
   * Define a getter on an object
   *
   * This method replaces any existing getter but leaves setters in place.
   *
   * - `object` {Object} the object to define the getter on
   * - `property` {String} the name of the getter
   * - `getterFn` {Function} the getter
   */
  exports.defineGetter = function defineGetter(object, property, getterFn) {
    const descriptor = Object.getOwnPropertyDescriptor(object, property) || {
      configurable: true,
      enumerable: true
    };
  
    descriptor.get = getterFn;
  
    Object.defineProperty(object, property, descriptor);
  };
  
  /**
   * Define a set of properties on an object, by copying the property descriptors
   * from the original object.
   *
   * - `object` {Object} the target object
   * - `properties` {Object} the source from which to copy property descriptors
   */
  exports.define = function define(object, properties) {
    for (const name of Object.getOwnPropertyNames(properties)) {
      const propDesc = Object.getOwnPropertyDescriptor(properties, name);
      Object.defineProperty(object, name, propDesc);
    }
  };
  
  /**
   * Define a list of constants on a constructor and its .prototype
   *
   * - `Constructor` {Function} the constructor to define the constants on
   * - `propertyMap` {Object}  key/value map of properties to define
   */
  exports.addConstants = function addConstants(Constructor, propertyMap) {
    for (const property in propertyMap) {
      const value = propertyMap[property];
      addConstant(Constructor, property, value);
      addConstant(Constructor.prototype, property, value);
    }
  };
  
  function addConstant(object, property, value) {
    Object.defineProperty(object, property, {
      configurable: false,
      enumerable: true,
      writable: false,
      value
    });
  }
  
  let memoizeQueryTypeCounter = 0;
  
  /**
   * Returns a version of a method that memoizes specific types of calls on the object
   *
   * - `fn` {Function} the method to be memozied
   */
  exports.memoizeQuery = function memoizeQuery(fn) {
    // Only memoize query functions with arity <= 2
    if (fn.length > 2) {
      return fn;
    }
  
    const type = memoizeQueryTypeCounter++;
  
    return function () {
      if (!this._memoizedQueries) {
        return fn.apply(this, arguments);
      }
  
      if (!this._memoizedQueries[type]) {
        this._memoizedQueries[type] = Object.create(null);
      }
  
      let key;
      if (arguments.length === 1 && typeof arguments[0] === "string") {
        key = arguments[0];
      } else if (arguments.length === 2 && typeof arguments[0] === "string" && typeof arguments[1] === "string") {
        key = arguments[0] + "::" + arguments[1];
      } else {
        return fn.apply(this, arguments);
      }
  
      if (!(key in this._memoizedQueries[type])) {
        this._memoizedQueries[type][key] = fn.apply(this, arguments);
      }
      return this._memoizedQueries[type][key];
    };
  };
  
  exports.reflectURLAttribute = (elementImpl, contentAttributeName) => {
    const attributeValue = elementImpl.getAttribute(contentAttributeName);
    if (attributeValue === null || attributeValue === "") {
      return "";
    }
  
    const urlRecord = parseURLToResultingURLRecord(attributeValue, elementImpl._ownerDocument);
    if (urlRecord === null) {
      return attributeValue;
    }
    return whatwgURL.serializeURL(urlRecord);
  };
  
  function isValidAbsoluteURL(str) {
    return whatwgURL.parseURL(str) !== null;
  }
  
  exports.isValidTargetOrigin = function (str) {
    return str === "*" || str === "/" || isValidAbsoluteURL(str);
  };
  
  exports.simultaneousIterators = function* (first, second) {
    for (;;) {
      const firstResult = first.next();
      const secondResult = second.next();
  
      if (firstResult.done && secondResult.done) {
        return;
      }
  
      yield [
        firstResult.done ? null : firstResult.value,
        secondResult.done ? null : secondResult.value
      ];
    }
  };
  
  exports.treeOrderSorter = function (a, b) {
    const compare = domSymbolTree.compareTreePosition(a, b);
  
    if (compare & SYMBOL_TREE_POSITION.PRECEDING) { // b is preceding a
      return 1;
    }
  
    if (compare & SYMBOL_TREE_POSITION.FOLLOWING) {
      return -1;
    }
  
    // disconnected or equal:
    return 0;
  };
  
  exports.lengthFromProperties = function (arrayLike) {
    let max = -1;
    const keys = Object.keys(arrayLike);
    const highestKeyIndex = keys.length - 1;
  
    // Abuses a v8 implementation detail for a very fast case
    // (if this implementation detail changes, this method will still
    //  return correct results)
    /* eslint-disable eqeqeq */
    if (highestKeyIndex == keys[highestKeyIndex]) { // not ===
      /* eslint-enable eqeqeq */
      return keys.length;
    }
  
    for (let i = highestKeyIndex; i >= 0; --i) {
      const asNumber = Number(keys[i]);
  
      if (!Number.isNaN(asNumber) && asNumber > max) {
        max = asNumber;
      }
    }
    return max + 1;
  };
  
  const base64Regexp = /^(?:[A-Z0-9+/]{4})*(?:[A-Z0-9+/]{2}==|[A-Z0-9+/]{3}=|[A-Z0-9+/]{4})$/i;
  
  exports.parseDataUrl = function parseDataUrl(url) {
    const urlParts = url.match(/^data:(.+?)(?:;(base64))?,(.*)$/);
    let buffer;
    if (urlParts[2] === "base64") {
      if (urlParts[3] && !base64Regexp.test(urlParts[3])) {
        throw new Error("Not a base64 string");
      }
      buffer = new Buffer(urlParts[3], "base64");
    } else {
      buffer = new Buffer(querystring.unescape(urlParts[3]));
    }
    return { buffer, type: urlParts[1] };
  };
  
  /* eslint-disable global-require */
  
  exports.Canvas = null;
  ["canvas", "canvas-prebuilt"].some(moduleName => {
    try {
      exports.Canvas = require(moduleName);
      if (typeof exports.Canvas !== "function") {
        // In browserify, the require will succeed but return an empty object
        exports.Canvas = null;
      }
    } catch (e) {
      exports.Canvas = null;
    }
    return exports.Canvas !== null;
  });
  