/* * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ var emDash = "\u2014"; var enDash = "\u2013"; var figureDash = "\u2012"; var ellipsis = "\u2026"; var zeroWidthSpace = "\u200b"; var multiplicationSign = "\u00d7"; function xor(a, b) { if (a) return b ? false : a; return b || false; } Object.defineProperty(Object, "shallowCopy", { value(object) { // Make a new object and copy all the key/values. The values are not copied. var copy = {}; var keys = Object.keys(object); for (var i = 0; i < keys.length; ++i) copy[keys[i]] = object[keys[i]]; return copy; } }); Object.defineProperty(Object, "shallowEqual", { value(a, b) { // Checks if two objects have the same top-level properties. if (!(a instanceof Object) || !(b instanceof Object)) return false; if (a === b) return true; if (Array.shallowEqual(a, b)) return true; if (a.constructor !== b.constructor) return false; let aKeys = Object.keys(a); let bKeys = Object.keys(b); if (aKeys.length !== bKeys.length) return false; for (let aKey of aKeys) { if (!(aKey in b)) return false; let aValue = a[aKey]; let bValue = b[aKey]; if (aValue !== bValue && !Array.shallowEqual(aValue, bValue)) return false; } return true; } }); Object.defineProperty(Object, "filter", { value(object, callback) { let filtered = {}; for (let key in object) { if (callback(key, object[key])) filtered[key] = object[key]; } return filtered; } }); Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey", { value(key) { if (this.hasOwnProperty(key)) return this[key]; var lowerCaseKey = key.toLowerCase(); for (var currentKey in this) { if (currentKey.toLowerCase() === lowerCaseKey) return this[currentKey]; } return undefined; } }); Object.defineProperty(Map, "fromObject", { value(object) { let map = new Map; for (let key in object) map.set(key, object[key]); return map; } }); Object.defineProperty(Map.prototype, "take", { value(key) { let deletedValue = this.get(key); this.delete(key); return deletedValue; } }); Object.defineProperty(Map.prototype, "getOrInitialize", { value(key, initialValue) { console.assert(initialValue !== undefined, "getOrInitialize should not be used with undefined."); let value = this.get(key); if (value) return value; this.set(key, initialValue); return initialValue; } }); Object.defineProperty(Set.prototype, "find", { value(predicate) { for (let item of this) { if (predicate(item, this)) return item; } return undefined; }, }); Object.defineProperty(Set.prototype, "addAll", { value(iterable) { for (let item of iterable) this.add(item); }, }); Object.defineProperty(Set.prototype, "take", { value(key) { if (this.has(key)) { this.delete(key); return key; } return undefined; } }); Object.defineProperty(Set.prototype, "equals", { value(other) { return this.size === other.size && this.isSubsetOf(other); } }); Object.defineProperty(Set.prototype, "difference", { value(other) { if (other === this) return new Set; let result = new Set; for (let item of this) { if (!other.has(item)) result.add(item); } return result; } }); Object.defineProperty(Set.prototype, "firstValue", { get() { return this.values().next().value; } }); Object.defineProperty(Set.prototype, "lastValue", { get() { return Array.from(this.values()).lastValue; } }); Object.defineProperty(Set.prototype, "intersects", { value(other) { if (!this.size || !other.size) return false; for (let item of this) { if (other.has(item)) return true; } return false; } }); Object.defineProperty(Set.prototype, "isSubsetOf", { value(other) { for (let item of this) { if (!other.has(item)) return false; } return true; } }); Object.defineProperty(Node.prototype, "traverseNextNode", { value(stayWithin) { var node = this.firstChild; if (node) return node; if (stayWithin && this === stayWithin) return null; node = this.nextSibling; if (node) return node; node = this; while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin)) node = node.parentNode; if (!node) return null; return node.nextSibling; } }); Object.defineProperty(Node.prototype, "traversePreviousNode", { value(stayWithin) { if (stayWithin && this === stayWithin) return null; var node = this.previousSibling; while (node && node.lastChild) node = node.lastChild; if (node) return node; return this.parentNode; } }); Object.defineProperty(Node.prototype, "rangeOfWord", { value(offset, stopCharacters, stayWithinNode, direction) { var startNode; var startOffset = 0; var endNode; var endOffset = 0; if (!stayWithinNode) stayWithinNode = this; if (!direction || direction === "backward" || direction === "both") { var node = this; while (node) { if (node === stayWithinNode) { if (!startNode) startNode = stayWithinNode; break; } if (node.nodeType === Node.TEXT_NODE) { let start = node === this ? (offset - 1) : (node.nodeValue.length - 1); for (var i = start; i >= 0; --i) { if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { startNode = node; startOffset = i + 1; break; } } } if (startNode) break; node = node.traversePreviousNode(stayWithinNode); } if (!startNode) { startNode = stayWithinNode; startOffset = 0; } } else { startNode = this; startOffset = offset; } if (!direction || direction === "forward" || direction === "both") { node = this; while (node) { if (node === stayWithinNode) { if (!endNode) endNode = stayWithinNode; break; } if (node.nodeType === Node.TEXT_NODE) { let start = node === this ? offset : 0; for (var i = start; i < node.nodeValue.length; ++i) { if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { endNode = node; endOffset = i; break; } } } if (endNode) break; node = node.traverseNextNode(stayWithinNode); } if (!endNode) { endNode = stayWithinNode; endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length; } } else { endNode = this; endOffset = offset; } var result = this.ownerDocument.createRange(); result.setStart(startNode, startOffset); result.setEnd(endNode, endOffset); return result; } }); Object.defineProperty(Element.prototype, "realOffsetWidth", { get() { return this.getBoundingClientRect().width; } }); Object.defineProperty(Element.prototype, "realOffsetHeight", { get() { return this.getBoundingClientRect().height; } }); Object.defineProperty(Element.prototype, "totalOffsetLeft", { get() { return this.getBoundingClientRect().left; } }); Object.defineProperty(Element.prototype, "totalOffsetRight", { get() { return this.getBoundingClientRect().right; } }); Object.defineProperty(Element.prototype, "totalOffsetTop", { get() { return this.getBoundingClientRect().top; } }); Object.defineProperty(Element.prototype, "totalOffsetBottom", { get() { return this.getBoundingClientRect().bottom; } }); Object.defineProperty(Element.prototype, "removeChildren", { value() { // This has been tested to be the fastest removal method. if (this.firstChild) this.textContent = ""; } }); Object.defineProperty(Element.prototype, "isInsertionCaretInside", { value() { var selection = window.getSelection(); if (!selection.rangeCount || !selection.isCollapsed) return false; var selectionRange = selection.getRangeAt(0); return selectionRange.startContainer === this || this.contains(selectionRange.startContainer); } }); Object.defineProperty(Element.prototype, "createChild", { value(elementName, className) { var element = this.ownerDocument.createElement(elementName); if (className) element.className = className; this.appendChild(element); return element; } }); Object.defineProperty(Element.prototype, "isScrolledToBottom", { value() { // This code works only for 0-width border return this.scrollTop + this.clientHeight === this.scrollHeight; } }); Object.defineProperty(Element.prototype, "recalculateStyles", { value() { this.ownerDocument.defaultView.getComputedStyle(this); } }); Object.defineProperty(DocumentFragment.prototype, "createChild", { value: Element.prototype.createChild }); (function() { const fontSymbol = Symbol("font"); Object.defineProperty(HTMLInputElement.prototype, "autosize", { value(extra = 0) { extra += 6; // UserAgent styles add 1px padding and 2px border. if (this.type === "number") extra += 13; // Number input inner spin button width. extra += 2; // Add extra pixels for the cursor. WI.ImageUtilities.scratchCanvasContext2D((context) => { this[fontSymbol] ||= window.getComputedStyle(this).font; context.font = this[fontSymbol]; let textMetrics = context.measureText(this.value || this.placeholder); this.style.setProperty("width", (textMetrics.width + extra) + "px"); }); }, }); })(); Object.defineProperty(Event.prototype, "stop", { value() { this.stopImmediatePropagation(); this.preventDefault(); } }); Object.defineProperty(KeyboardEvent.prototype, "commandOrControlKey", { get() { return WI.Platform.name === "mac" ? this.metaKey : this.ctrlKey; } }); Object.defineProperty(MouseEvent.prototype, "commandOrControlKey", { get() { return WI.Platform.name === "mac" ? this.metaKey : this.ctrlKey; } }); Object.defineProperty(Array, "isTypedArray", { value(array) { if (!array) return false; let constructor = array.constructor; return constructor === Int8Array || constructor === Int16Array || constructor === Int32Array || constructor === Uint8Array || constructor === Uint8ClampedArray || constructor === Uint16Array || constructor === Uint32Array || constructor === Float32Array || constructor === Float64Array; } }); Object.defineProperty(Array, "shallowEqual", { value(a, b) { function isArrayLike(x) { return Array.isArray(x) || Array.isTypedArray(x); } if (!isArrayLike(a) || !isArrayLike(b)) return false; if (a === b) return true; let length = a.length; if (length !== b.length) return false; for (let i = 0; i < length; ++i) { if (a[i] === b[i]) continue; if (!Object.shallowEqual(a[i], b[i])) return false; } return true; } }); Object.defineProperty(Array, "diffArrays", { value(initialArray, currentArray, onEach, comparator) { "use strict"; function defaultComparator(initial, current) { return initial === current; } comparator = comparator || defaultComparator; // Find the shortest prefix of matching items in both arrays. // // initialArray = ["a", "b", "b", "c"] // currentArray = ["c", "b", "b", "a"] // findShortestEdit() // [1, 1] // function findShortestEdit() { let deletionCount = initialArray.length; let additionCount = currentArray.length; let editCount = deletionCount + additionCount; for (let i = 0; i < initialArray.length; ++i) { if (i > editCount) { // Break since any possible edits at this point are going to be longer than the one already found. break; } for (let j = 0; j < currentArray.length; ++j) { let newEditCount = i + j; if (newEditCount > editCount) { // Break since any possible edits at this point are going to be longer than the one already found. break; } if (comparator(initialArray[i], currentArray[j])) { // A candidate for the shortest edit found. if (newEditCount < editCount) { editCount = newEditCount; deletionCount = i; additionCount = j; } break; } } } return [deletionCount, additionCount]; } function commonPrefixLength(listA, listB) { let shorterListLength = Math.min(listA.length, listB.length); let i = 0; while (i < shorterListLength) { if (!comparator(listA[i], listB[i])) break; ++i; } return i; } function fireOnEach(count, diffAction, array) { for (let i = 0; i < count; ++i) onEach(array[i], diffAction); } while (initialArray.length || currentArray.length) { // Remove common prefix. let prefixLength = commonPrefixLength(initialArray, currentArray); if (prefixLength) { fireOnEach(prefixLength, 0, currentArray); initialArray = initialArray.slice(prefixLength); currentArray = currentArray.slice(prefixLength); } if (!initialArray.length && !currentArray.length) break; let [deletionCount, additionCount] = findShortestEdit(); fireOnEach(deletionCount, -1, initialArray); fireOnEach(additionCount, 1, currentArray); initialArray = initialArray.slice(deletionCount); currentArray = currentArray.slice(additionCount); } } }); Object.defineProperty(Array.prototype, "lastValue", { get() { if (!this.length) return undefined; return this[this.length - 1]; } }); Object.defineProperty(Array.prototype, "adjacencies", { value: function*() { for (let i = 1; i < this.length; ++i) yield [this[i - 1], this[i]]; } }); Object.defineProperty(Array.prototype, "remove", { value(value) { for (let i = 0; i < this.length; ++i) { if (this[i] === value) { this.splice(i, 1); return true; } } return false; } }); Object.defineProperty(Array.prototype, "removeAll", { value(value) { for (let i = this.length - 1; i >= 0; --i) { if (this[i] === value) this.splice(i, 1); } } }); Object.defineProperty(Array.prototype, "toggleIncludes", { value(value, force) { let exists = this.includes(value); if (exists === !!force) return; if (exists) this.remove(value); else this.push(value); } }); Object.defineProperty(Array.prototype, "insertAtIndex", { value(value, index) { this.splice(index, 0, value); } }); Object.defineProperty(Array.prototype, "pushAll", { value(iterable) { for (let item of iterable) this.push(item); }, }); Object.defineProperty(Array.prototype, "partition", { value(callback) { let positive = []; let negative = []; for (let i = 0; i < this.length; ++i) { let value = this[i]; if (callback(value)) positive.push(value); else negative.push(value); } return [positive, negative]; } }); Object.defineProperty(String.prototype, "isLowerCase", { value() { return String(this) === this.toLowerCase(); } }); Object.defineProperty(String.prototype, "isUpperCase", { value() { return String(this) === this.toUpperCase(); } }); Object.defineProperty(String.prototype, "isJSON", { value(predicate) { try { let json = JSON.parse(this); return !predicate || predicate(json); } catch { } return false; } }); Object.defineProperty(String.prototype, "truncateStart", { value(maxLength) { "use strict"; if (this.length <= maxLength) return this; return ellipsis + this.substr(this.length - maxLength + 1); } }); Object.defineProperty(String.prototype, "truncateMiddle", { value(maxLength) { "use strict"; if (this.length <= maxLength) return this; var leftHalf = maxLength >> 1; var rightHalf = maxLength - leftHalf - 1; return this.substr(0, leftHalf) + ellipsis + this.substr(this.length - rightHalf, rightHalf); } }); Object.defineProperty(String.prototype, "truncateEnd", { value(maxLength) { "use strict"; if (this.length <= maxLength) return this; return this.substr(0, maxLength - 1) + ellipsis; } }); Object.defineProperty(String.prototype, "truncate", { value(maxLength) { "use strict"; if (this.length <= maxLength) return this; let clipped = this.slice(0, maxLength); let indexOfLastWhitespace = clipped.search(/\s\S*$/); if (indexOfLastWhitespace > Math.floor(maxLength / 2)) clipped = clipped.slice(0, indexOfLastWhitespace - 1); return clipped + ellipsis; } }); Object.defineProperty(String.prototype, "collapseWhitespace", { value() { return this.replace(/[\s\xA0]+/g, " "); } }); Object.defineProperty(String.prototype, "removeWhitespace", { value() { return this.replace(/[\s\xA0]+/g, ""); } }); Object.defineProperty(String.prototype, "escapeCharacters", { value(charactersToEscape) { if (!charactersToEscape) return this.valueOf(); let charactersToEscapeSet = new Set(charactersToEscape); let foundCharacter = false; for (let c of this) { if (!charactersToEscapeSet.has(c)) continue; foundCharacter = true; break; } if (!foundCharacter) return this.valueOf(); let result = ""; for (let c of this) { if (charactersToEscapeSet.has(c)) result += "\\"; result += c; } return result.valueOf(); } }); Object.defineProperty(String.prototype, "escapeForRegExp", { value() { return this.escapeCharacters("^[]{}()\\.$*+?|"); } }); Object.defineProperty(String.prototype, "capitalize", { value() { return this.charAt(0).toUpperCase() + this.slice(1); } }); Object.defineProperty(String.prototype, "extendedLocaleCompare", { value(other) { return this.localeCompare(other, undefined, {numeric: true}); } }); Object.defineProperty(String, "tokenizeFormatString", { value(format) { var tokens = []; var substitutionIndex = 0; function addStringToken(str) { tokens.push({type: "string", value: str}); } function addSpecifierToken(specifier, precision, substitutionIndex) { tokens.push({type: "specifier", specifier, precision, substitutionIndex}); } var index = 0; for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { addStringToken(format.substring(index, precentIndex)); index = precentIndex + 1; if (format[index] === "%") { addStringToken("%"); ++index; continue; } if (!isNaN(format[index])) { // The first character is a number, it might be a substitution index. var number = parseInt(format.substring(index), 10); while (!isNaN(format[index])) ++index; // If the number is greater than zero and ends with a "$", // then this is a substitution index. if (number > 0 && format[index] === "$") { substitutionIndex = (number - 1); ++index; } } const defaultPrecision = 6; let precision = defaultPrecision; if (format[index] === ".") { // This is a precision specifier. If no digit follows the ".", // then use the default precision of six digits (ISO C99 specification). ++index; precision = parseInt(format.substring(index), 10); if (isNaN(precision)) precision = defaultPrecision; while (!isNaN(format[index])) ++index; } addSpecifierToken(format[index], precision, substitutionIndex); ++substitutionIndex; ++index; } addStringToken(format.substring(index)); return tokens; } }); Object.defineProperty(String.prototype, "lineCount", { get() { "use strict"; let lineCount = 1; let index = 0; while (true) { index = this.indexOf("\n", index); if (index === -1) return lineCount; index += "\n".length; lineCount++; } } }); Object.defineProperty(String.prototype, "lastLine", { get() { "use strict"; let index = this.lastIndexOf("\n"); if (index === -1) return this; return this.slice(index + "\n".length); } }); Object.defineProperty(String.prototype, "hash", { get() { // Matches the wtf/Hasher.h (SuperFastHash) algorithm. // Arbitrary start value to avoid mapping all 0's to all 0's. const stringHashingStartValue = 0x9e3779b9; var result = stringHashingStartValue; var pendingCharacter = null; for (var i = 0; i < this.length; ++i) { var currentCharacter = this[i].charCodeAt(0); if (pendingCharacter === null) { pendingCharacter = currentCharacter; continue; } result += pendingCharacter; result = (result << 16) ^ ((currentCharacter << 11) ^ result); result += result >> 11; pendingCharacter = null; } // Handle the last character in odd length strings. if (pendingCharacter !== null) { result += pendingCharacter; result ^= result << 11; result += result >> 17; } // Force "avalanching" of final 31 bits. result ^= result << 3; result += result >> 5; result ^= result << 2; result += result >> 15; result ^= result << 10; // Prevent 0 and negative results. return (0xffffffff + result + 1).toString(36); } }); Object.defineProperty(String, "standardFormatters", { value: { d: function(substitution) { return parseInt(substitution).toLocaleString(); }, f: function(substitution, token) { let value = parseFloat(substitution); if (isNaN(value)) return NaN; let options = { minimumFractionDigits: token.precision, maximumFractionDigits: token.precision, useGrouping: false }; return value.toLocaleString(undefined, options); }, s: function(substitution) { return substitution; } } }); Object.defineProperty(String, "format", { value(format, substitutions, formatters, initialValue, append) { if (!format || !substitutions || !substitutions.length) return {formattedResult: append(initialValue, format), unusedSubstitutions: substitutions}; function prettyFunctionName() { return "String.format(\"" + format + "\", \"" + Array.from(substitutions).join("\", \"") + "\")"; } function warn(msg) { console.warn(prettyFunctionName() + ": " + msg); } function error(msg) { console.error(prettyFunctionName() + ": " + msg); } var result = initialValue; var tokens = String.tokenizeFormatString(format); var usedSubstitutionIndexes = {}; for (var i = 0; i < tokens.length; ++i) { var token = tokens[i]; if (token.type === "string") { result = append(result, token.value); continue; } if (token.type !== "specifier") { error("Unknown token type \"" + token.type + "\" found."); continue; } if (token.substitutionIndex >= substitutions.length) { // If there are not enough substitutions for the current substitutionIndex // just output the format specifier literally and move on. error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped."); result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier); continue; } usedSubstitutionIndexes[token.substitutionIndex] = true; if (!(token.specifier in formatters)) { // Encountered an unsupported format character, treat as a string. warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string."); result = append(result, substitutions[token.substitutionIndex]); continue; } result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token)); } var unusedSubstitutions = []; for (var i = 0; i < substitutions.length; ++i) { if (i in usedSubstitutionIndexes) continue; unusedSubstitutions.push(substitutions[i]); } return {formattedResult: result, unusedSubstitutions}; } }); Object.defineProperty(String.prototype, "format", { value() { return String.format(this, arguments, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult; } }); Object.defineProperty(String.prototype, "insertWordBreakCharacters", { value() { // Add zero width spaces after characters that are good to break after. // Otherwise a string with no spaces will not break and overflow its container. // This is mainly used on URL strings, so the characters are tailored for URLs. return this.replace(/([\/;:\)\]\}&?])/g, "$1\u200b"); } }); Object.defineProperty(String.prototype, "removeWordBreakCharacters", { value() { // Undoes what insertWordBreakCharacters did. return this.replace(/\u200b/g, ""); } }); Object.defineProperty(String.prototype, "levenshteinDistance", { value(s) { var m = this.length; var n = s.length; var d = new Array(m + 1); for (var i = 0; i <= m; ++i) { d[i] = new Array(n + 1); d[i][0] = i; } for (var j = 0; j <= n; ++j) d[0][j] = j; for (var j = 1; j <= n; ++j) { for (var i = 1; i <= m; ++i) { if (this[i - 1] === s[j - 1]) d[i][j] = d[i - 1][j - 1]; else { var deletion = d[i - 1][j] + 1; var insertion = d[i][j - 1] + 1; var substitution = d[i - 1][j - 1] + 1; d[i][j] = Math.min(deletion, insertion, substitution); } } } return d[m][n]; } }); Object.defineProperty(String.prototype, "toCamelCase", { value() { return this.toLowerCase().replace(/[^\w]+(\w)/g, (match, group) => group.toUpperCase()); } }); Object.defineProperty(String.prototype, "hasMatchingEscapedQuotes", { value() { return /^\"(?:[^\"\\]|\\.)*\"$/.test(this) || /^\'(?:[^\'\\]|\\.)*\'$/.test(this); } }); Object.defineProperty(Math, "roundTo", { value(num, step) { return Math.round(num / step) * step; } }); // https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web#Multiplying_a_matrix_and_a_point Object.defineProperty(Math, "multiplyMatrixByVector", { value(matrix, vector) { let height = matrix.length; let width = matrix[0].length; console.assert(width === vector.length); let result = Array(width).fill(0); for (let i = 0; i < width; ++i) { for (let rowIndex = 0; rowIndex < height; ++rowIndex) result[i] += vector[rowIndex] * matrix[i][rowIndex]; } return result; } }); Object.defineProperty(Number, "constrain", { value(num, min, max) { if (isNaN(num) || max < min) return min; if (num < min) num = min; else if (num > max) num = max; return num; } }); Object.defineProperty(Number, "percentageString", { value(fraction, precision = 1) { return fraction.toLocaleString(undefined, {minimumFractionDigits: precision, style: "percent"}); } }); Object.defineProperty(Number, "secondsToMillisecondsString", { value(seconds, higherResolution) { let ms = seconds * 1000; if (higherResolution) return WI.UIString("%.2fms").format(ms); return WI.UIString("%.1fms").format(ms); } }); Object.defineProperty(Number, "secondsToString", { value(seconds, higherResolution) { const epsilon = 0.0001; let ms = seconds * 1000; if (ms < epsilon) return WI.UIString("%.0fms").format(0); if (Math.abs(ms) < (10 + epsilon)) { if (higherResolution) return WI.UIString("%.3fms").format(ms); return WI.UIString("%.2fms").format(ms); } if (Math.abs(ms) < (100 + epsilon)) { if (higherResolution) return WI.UIString("%.2fms").format(ms); return WI.UIString("%.1fms").format(ms); } if (Math.abs(ms) < (1000 + epsilon)) { if (higherResolution) return WI.UIString("%.1fms").format(ms); return WI.UIString("%.0fms").format(ms); } // Do not go over seconds when in high resolution mode. if (higherResolution || Math.abs(seconds) < 60) return WI.UIString("%.2fs").format(seconds); let minutes = seconds / 60; if (Math.abs(minutes) < 60) return WI.UIString("%.1fmin").format(minutes); let hours = minutes / 60; if (Math.abs(hours) < 24) return WI.UIString("%.1fhrs").format(hours); let days = hours / 24; return WI.UIString("%.1f days").format(days); } }); Object.defineProperty(Number, "bytesToString", { value(bytes, higherResolution, bytesThreshold) { higherResolution ??= true; bytesThreshold ??= 1000; if (Math.abs(bytes) < bytesThreshold) return WI.UIString("%.0f B").format(bytes); let kilobytes = bytes / 1000; if (Math.abs(kilobytes) < 1000) { if (higherResolution || Math.abs(kilobytes) < 10) return WI.UIString("%.2f KB").format(kilobytes); return WI.UIString("%.1f KB").format(kilobytes); } let megabytes = kilobytes / 1000; if (Math.abs(megabytes) < 1000) { if (higherResolution || Math.abs(megabytes) < 10) return WI.UIString("%.2f MB").format(megabytes); return WI.UIString("%.1f MB").format(megabytes); } let gigabytes = megabytes / 1000; if (higherResolution || Math.abs(gigabytes) < 10) return WI.UIString("%.2f GB").format(gigabytes); return WI.UIString("%.1f GB").format(gigabytes); } }); Object.defineProperty(Number, "abbreviate", { value(num) { if (num < 1000) return num.toLocaleString(); if (num < 1_000_000) return WI.UIString("%.1fK").format(Math.round(num / 100) / 10); if (num < 1_000_000_000) return WI.UIString("%.1fM").format(Math.round(num / 100_000) / 10); return WI.UIString("%.1fB").format(Math.round(num / 100_000_000) / 10); } }); Object.defineProperty(Number, "zeroPad", { value(num, length) { let string = num.toLocaleString(); return string.padStart(length, "0"); }, }); Object.defineProperty(Number, "countDigits", { value(num) { if (num === 0) return 1; num = Math.abs(num); return Math.floor(Math.log(num) * Math.LOG10E) + 1; } }); Object.defineProperty(Number.prototype, "maxDecimals", { value(decimals) { let power = 10 ** decimals; return Math.round(this * power) / power; } }); Object.defineProperty(Uint32Array, "isLittleEndian", { value() { if ("_isLittleEndian" in this) return this._isLittleEndian; var buffer = new ArrayBuffer(4); var longData = new Uint32Array(buffer); var data = new Uint8Array(buffer); longData[0] = 0x0a0b0c0d; this._isLittleEndian = data[0] === 0x0d && data[1] === 0x0c && data[2] === 0x0b && data[3] === 0x0a; return this._isLittleEndian; } }); function isEmptyObject(object) { for (var property in object) return false; return true; } function isEnterKey(event) { // Check if this is an IME event. return event.keyCode !== 229 && event.keyIdentifier === "Enter"; } function resolveDotsInPath(path) { if (!path) return path; if (path.indexOf("./") === -1) return path; console.assert(path.charAt(0) === "/"); var result = []; var components = path.split("/"); for (var i = 0; i < components.length; ++i) { var component = components[i]; // Skip over "./". if (component === ".") continue; // Rewind one component for "../". if (component === "..") { if (result.length === 1) continue; result.pop(); continue; } result.push(component); } return result.join("/"); } function parseMIMEType(fullMimeType) { if (!fullMimeType) return {type: fullMimeType, boundary: null, encoding: null}; var typeParts = fullMimeType.split(/\s*;\s*/); console.assert(typeParts.length >= 1); var type = typeParts[0]; var boundary = null; var encoding = null; for (var i = 1; i < typeParts.length; ++i) { var subparts = typeParts[i].split(/\s*=\s*/); if (subparts.length !== 2) continue; if (subparts[0].toLowerCase() === "boundary") boundary = subparts[1]; else if (subparts[0].toLowerCase() === "charset") encoding = subparts[1].replace("^\"|\"$", ""); // Trim quotes. } return {type, boundary: boundary || null, encoding: encoding || null}; } function simpleGlobStringToRegExp(globString, regExpFlags) { // Only supports "*" globs. if (!globString) return null; // Escape everything from String.prototype.escapeForRegExp except "*". var regexString = globString.escapeCharacters("^[]{}()\\.$+?|"); // Unescape all doubly escaped backslashes in front of escaped asterisks. // So "\\*" will become "\*" again, undoing escapeCharacters escaping of "\". // This makes "\*" match a literal "*" instead of using the "*" for globbing. regexString = regexString.replace(/\\\\\*/g, "\\*"); // The following regex doesn't match an asterisk that has a backslash in front. // It also catches consecutive asterisks so they collapse down when replaced. var unescapedAsteriskRegex = /(^|[^\\])\*+/g; if (unescapedAsteriskRegex.test(globString)) { // Replace all unescaped asterisks with ".*". regexString = regexString.replace(unescapedAsteriskRegex, "$1.*"); // Match edge boundaries when there is an asterisk to better meet the expectations // of the user. When someone types "*.js" they don't expect "foo.json" to match. They // would only expect that if they type "*.js*". We use \b (instead of ^ and $) to allow // matches inside paths or URLs, so "ba*.js" will match "foo/bar.js" but not "boo/bbar.js". // When there isn't an asterisk the regexString is just a substring search. regexString = "\\b" + regexString + "\\b"; } return new RegExp(regexString, regExpFlags); } Object.defineProperty(Array.prototype, "lowerBound", { // Return index of the leftmost element that is equal or greater // than the specimen object. If there's no such element (i.e. all // elements are smaller than the specimen) returns array.length. // The function works for sorted array. value(object, comparator) { function defaultComparator(a, b) { return a - b; } comparator = comparator || defaultComparator; var l = 0; var r = this.length; while (l < r) { var m = (l + r) >> 1; if (comparator(object, this[m]) > 0) l = m + 1; else r = m; } return r; } }); Object.defineProperty(Array.prototype, "upperBound", { // Return index of the leftmost element that is greater // than the specimen object. If there's no such element (i.e. all // elements are smaller than the specimen) returns array.length. // The function works for sorted array. value(object, comparator) { function defaultComparator(a, b) { return a - b; } comparator = comparator || defaultComparator; var l = 0; var r = this.length; while (l < r) { var m = (l + r) >> 1; if (comparator(object, this[m]) >= 0) l = m + 1; else r = m; } return r; } }); Object.defineProperty(Array.prototype, "binaryIndexOf", { value(value, comparator) { function defaultComparator(a, b) { return a - b; } comparator = comparator || defaultComparator; var index = this.lowerBound(value, comparator); return index < this.length && comparator(value, this[index]) === 0 ? index : -1; } }); Object.defineProperty(Array.prototype, "groupBy", { value(groupFunction, minGroupSize = 1) { let result = []; let startIndex = null; let flush = (endIndex) => { if (startIndex === null) return; let group = this.slice(startIndex, endIndex + 1); let adjacentCount = (endIndex + 1) - startIndex; if (adjacentCount >= minGroupSize) result.push(group); else result.pushAll(group); } this.forEach((item, i) => { if (groupFunction(item)) { startIndex ??= i; if (i === this.length - 1) flush(this.length - 1); } else { flush(i - 1); result.push(item); startIndex = null; } }); return result; } }); Object.defineProperty(Promise, "chain", { async value(callbacks, initialValue) { let results = []; for (let i = 0; i < callbacks.length; ++i) results.push(await callbacks[i](results.lastValue || initialValue || null, i)); return results; } }); Object.defineProperty(Promise, "delay", { value(delay) { return new Promise((resolve) => setTimeout(resolve, delay || 0)); } }); function appendWebInspectorSourceURL(string) { if (string.includes("//# sourceURL")) return string; return "\n//# sourceURL=__WebInspectorInternal__\n" + string; } function appendWebInspectorConsoleEvaluationSourceURL(string) { if (string.includes("//# sourceURL")) return string; return "\n//# sourceURL=__WebInspectorConsoleEvaluation__\n" + string; } function isWebInspectorBootstrapScript(url) { return url === WI.NetworkManager.bootstrapScriptURL; } function isWebInspectorInternalScript(url) { return url === "__WebInspectorInternal__"; } function isWebInspectorConsoleEvaluationScript(url) { return url === "__WebInspectorConsoleEvaluation__"; } function isWebKitInjectedScript(url) { return url && url.startsWith("__InjectedScript_") && url.endsWith(".js"); } function isWebKitInternalScript(url) { if (isWebInspectorConsoleEvaluationScript(url)) return false; if (isWebKitInjectedScript(url)) return true; return url && url.startsWith("__Web") && url.endsWith("__"); } function isFunctionStringNativeCode(str) { return str.endsWith("{\n [native code]\n}"); } function whitespaceRatio(content, start, end) { let whitespaceScore = 0; let size = end - start; for (let i = start; i < end; i++) { let char = content[i]; if (char === " ") whitespaceScore++; else if (char === "\t") whitespaceScore += 4; else if (char === "\n") whitespaceScore += 8; } let ratio = whitespaceScore / size; return ratio; } function isTextLikelyMinified(content) { const autoFormatMaxCharactersToCheck = 2500; const autoFormatWhitespaceRatio = 0.2; if (content.length <= autoFormatMaxCharactersToCheck) { let ratio = whitespaceRatio(content, 0, content.length); return ratio < autoFormatWhitespaceRatio; } let startRatio = whitespaceRatio(content, 0, autoFormatMaxCharactersToCheck); if (startRatio < autoFormatWhitespaceRatio) return true; let endRatio = whitespaceRatio(content, content.length - autoFormatMaxCharactersToCheck, content.length); if (endRatio < autoFormatWhitespaceRatio) return true; return false; } function doubleQuotedString(str) { return JSON.stringify(str); } function insertionIndexForObjectInListSortedByFunction(object, list, comparator, insertionIndexAfter) { if (insertionIndexAfter) { return list.upperBound(object, comparator); } else { return list.lowerBound(object, comparator); } } function insertObjectIntoSortedArray(object, array, comparator) { array.splice(insertionIndexForObjectInListSortedByFunction(object, array, comparator), 0, object); } WI.setReentrantCheck = function(object, key) { key = "__checkReentrant_" + key; object[key] = (object[key] || 0) + 1; return object[key] === 1; }; WI.clearReentrantCheck = function(object, key) { key = "__checkReentrant_" + key; object[key] = (object[key] || 0) - 1; return object[key] === 0; };