"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 setter on an object * * This method replaces any existing setter but leaves getters in place. * * - `object` {Object} the object to define the setter on * - `property` {String} the name of the setter * - `setterFn` {Function} the setter */ exports.defineSetter = function defineSetter(object, property, setterFn) { const descriptor = Object.getOwnPropertyDescriptor(object, property) || { configurable: true, enumerable: true }; descriptor.set = setterFn; Object.defineProperty(object, property, descriptor); }; /** * 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 === "failure") { return attributeValue; } return whatwgURL.serializeURL(urlRecord); }; function isValidAbsoluteURL(str) { return whatwgURL.parseURL(str) !== "failure"; } 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; try { exports.Canvas = require("canvas"); if (typeof exports.Canvas !== "function") { // In browserify, the require will succeed but return an empty object exports.Canvas = null; } } catch (e) { exports.Canvas = null; }