summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Base/Utilities.js
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Base/Utilities.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Base/Utilities.js1509
1 files changed, 1509 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Base/Utilities.js b/Source/WebInspectorUI/UserInterface/Base/Utilities.js
new file mode 100644
index 000000000..06c2f1cb5
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Base/Utilities.js
@@ -0,0 +1,1509 @@
+/*
+ * 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";
+
+Object.defineProperty(Object, "shallowCopy",
+{
+ value: function(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: function(a, b)
+ {
+ // Checks if two objects have the same top-level properties.
+
+ // Only objects can proceed.
+ if (!(a instanceof Object) || !(b instanceof Object))
+ return false;
+
+ // Check for strict equality in case they are the same object.
+ if (a === b)
+ return true;
+
+ // Use an optimized version of shallowEqual for arrays.
+ if (Array.isArray(a) && Array.isArray(b))
+ return Array.shallowEqual(a, b);
+
+ if (a.constructor !== b.constructor)
+ return false;
+
+ var aKeys = Object.keys(a);
+ var bKeys = Object.keys(b);
+
+ // Check that each object has the same number of keys.
+ if (aKeys.length !== bKeys.length)
+ return false;
+
+ // Check if all the keys and their values are equal.
+ for (var i = 0; i < aKeys.length; ++i) {
+ // Check that b has the same key as a.
+ if (!(aKeys[i] in b))
+ return false;
+
+ // Check that the values are strict equal since this is only
+ // a shallow check, not a recursive one.
+ if (a[aKeys[i]] !== b[aKeys[i]])
+ return false;
+ }
+
+ return true;
+ }
+});
+
+Object.defineProperty(Object, "shallowMerge",
+{
+ value(a, b)
+ {
+ let result = Object.shallowCopy(a);
+ let keys = Object.keys(b);
+ for (let i = 0; i < keys.length; ++i) {
+ console.assert(!result.hasOwnProperty(keys[i]) || result[keys[i]] === b[keys[i]], keys[i]);
+ result[keys[i]] = b[keys[i]];
+ }
+ return result;
+ }
+});
+
+Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey",
+{
+ value: function(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: function(object)
+ {
+ let map = new Map;
+ for (let key in object)
+ map.set(key, object[key]);
+ return map;
+ }
+});
+
+Object.defineProperty(Map.prototype, "take",
+{
+ value: function(key)
+ {
+ var deletedValue = this.get(key);
+ this.delete(key);
+ return deletedValue;
+ }
+});
+
+Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithClass",
+{
+ value: function(className)
+ {
+ for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
+ if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className))
+ return node;
+ return null;
+ }
+});
+
+Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeNameInArray",
+{
+ value: function(nameArray)
+ {
+ var lowerCaseNameArray = nameArray.map(function(name) { return name.toLowerCase(); });
+ for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) {
+ for (var i = 0; i < nameArray.length; ++i) {
+ if (node.nodeName.toLowerCase() === lowerCaseNameArray[i])
+ return node;
+ }
+ }
+
+ return null;
+ }
+});
+
+Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeName",
+{
+ value: function(nodeName)
+ {
+ return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
+ }
+});
+
+Object.defineProperty(Node.prototype, "isAncestor",
+{
+ value: function(node)
+ {
+ if (!node)
+ return false;
+
+ var currentNode = node.parentNode;
+ while (currentNode) {
+ if (this === currentNode)
+ return true;
+ currentNode = currentNode.parentNode;
+ }
+
+ return false;
+ }
+});
+
+Object.defineProperty(Node.prototype, "isDescendant",
+{
+ value: function(descendant)
+ {
+ return !!descendant && descendant.isAncestor(this);
+ }
+});
+
+
+Object.defineProperty(Node.prototype, "isSelfOrAncestor",
+{
+ value: function(node)
+ {
+ return !!node && (node === this || this.isAncestor(node));
+ }
+});
+
+
+Object.defineProperty(Node.prototype, "isSelfOrDescendant",
+{
+ value: function(node)
+ {
+ return !!node && (node === this || this.isDescendant(node));
+ }
+});
+
+Object.defineProperty(Node.prototype, "traverseNextNode",
+{
+ value: function(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: function(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: function(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) {
+ var 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) {
+ var 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: function()
+ {
+ return this.getBoundingClientRect().width;
+ }
+});
+
+Object.defineProperty(Element.prototype, "realOffsetHeight",
+{
+ get: function()
+ {
+ return this.getBoundingClientRect().height;
+ }
+});
+
+Object.defineProperty(Element.prototype, "totalOffsetLeft",
+{
+ get: function()
+ {
+ return this.getBoundingClientRect().left;
+ }
+});
+
+Object.defineProperty(Element.prototype, "totalOffsetTop",
+{
+ get: function()
+ {
+ return this.getBoundingClientRect().top;
+ }
+});
+
+Object.defineProperty(Element.prototype, "removeChildren",
+{
+ value: function()
+ {
+ // This has been tested to be the fastest removal method.
+ if (this.firstChild)
+ this.textContent = "";
+ }
+});
+
+Object.defineProperty(Element.prototype, "isInsertionCaretInside",
+{
+ value: function()
+ {
+ var selection = window.getSelection();
+ if (!selection.rangeCount || !selection.isCollapsed)
+ return false;
+ var selectionRange = selection.getRangeAt(0);
+ return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this);
+ }
+});
+
+Object.defineProperty(Element.prototype, "removeMatchingStyleClasses",
+{
+ value: function(classNameRegex)
+ {
+ var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
+ if (regex.test(this.className))
+ this.className = this.className.replace(regex, " ");
+ }
+});
+
+Object.defineProperty(Element.prototype, "createChild",
+{
+ value: function(elementName, className)
+ {
+ var element = this.ownerDocument.createElement(elementName);
+ if (className)
+ element.className = className;
+ this.appendChild(element);
+ return element;
+ }
+});
+
+Object.defineProperty(Element.prototype, "isScrolledToBottom",
+{
+ value: function()
+ {
+ // This code works only for 0-width border
+ return this.scrollTop + this.clientHeight === this.scrollHeight;
+ }
+});
+
+Object.defineProperty(Element.prototype, "recalculateStyles",
+{
+ value: function()
+ {
+ this.ownerDocument.defaultView.getComputedStyle(this);
+ }
+});
+
+Object.defineProperty(DocumentFragment.prototype, "createChild",
+{
+ value: Element.prototype.createChild
+});
+
+Object.defineProperty(Array, "shallowEqual",
+{
+ value: function(a, b)
+ {
+ if (!Array.isArray(a) || !Array.isArray(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.prototype, "lastValue",
+{
+ get: function()
+ {
+ if (!this.length)
+ return undefined;
+ return this[this.length - 1];
+ }
+});
+
+Object.defineProperty(Array.prototype, "remove",
+{
+ value: function(value, onlyFirst)
+ {
+ for (var i = this.length - 1; i >= 0; --i) {
+ if (this[i] === value) {
+ this.splice(i, 1);
+ if (onlyFirst)
+ return;
+ }
+ }
+ }
+});
+
+Object.defineProperty(Array.prototype, "toggleIncludes",
+{
+ value: function(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: function(value, index)
+ {
+ this.splice(index, 0, value);
+ }
+});
+
+Object.defineProperty(Array.prototype, "keySet",
+{
+ value: function()
+ {
+ let keys = Object.create(null);
+ for (var i = 0; i < this.length; ++i)
+ keys[this[i]] = true;
+ return keys;
+ }
+});
+
+Object.defineProperty(Array.prototype, "partition",
+{
+ value: function(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: function()
+ {
+ return String(this) === this.toLowerCase();
+ }
+});
+
+Object.defineProperty(String.prototype, "isUpperCase",
+{
+ value: function()
+ {
+ return String(this) === this.toUpperCase();
+ }
+});
+
+Object.defineProperty(String.prototype, "trimMiddle",
+{
+ value: function(maxLength)
+ {
+ 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, "trimEnd",
+{
+ value: function(maxLength)
+ {
+ if (this.length <= maxLength)
+ return this;
+ return this.substr(0, maxLength - 1) + ellipsis;
+ }
+});
+
+Object.defineProperty(String.prototype, "truncate",
+{
+ value: function(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: function()
+ {
+ return this.replace(/[\s\xA0]+/g, " ");
+ }
+});
+
+Object.defineProperty(String.prototype, "removeWhitespace",
+{
+ value: function()
+ {
+ return this.replace(/[\s\xA0]+/g, "");
+ }
+});
+
+Object.defineProperty(String.prototype, "escapeCharacters",
+{
+ value: function(chars)
+ {
+ var foundChar = false;
+ for (var i = 0; i < chars.length; ++i) {
+ if (this.indexOf(chars.charAt(i)) !== -1) {
+ foundChar = true;
+ break;
+ }
+ }
+
+ if (!foundChar)
+ return this;
+
+ var result = "";
+ for (var i = 0; i < this.length; ++i) {
+ if (chars.indexOf(this.charAt(i)) !== -1)
+ result += "\\";
+ result += this.charAt(i);
+ }
+
+ return result;
+ }
+});
+
+Object.defineProperty(String.prototype, "escapeForRegExp",
+{
+ value: function()
+ {
+ return this.escapeCharacters("^[]{}()\\.$*+?|");
+ }
+});
+
+Object.defineProperty(String.prototype, "capitalize",
+{
+ value: function()
+ {
+ return this.charAt(0).toUpperCase() + this.slice(1);
+ }
+});
+
+Object.defineProperty(String, "tokenizeFormatString",
+{
+ value: function(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, "hash",
+{
+ get: function()
+ {
+ // 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);
+ },
+
+ 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: function(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: function()
+ {
+ return String.format(this, arguments, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
+ }
+});
+
+Object.defineProperty(String.prototype, "insertWordBreakCharacters",
+{
+ value: function()
+ {
+ // 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: function()
+ {
+ // Undoes what insertWordBreakCharacters did.
+ return this.replace(/\u200b/g, "");
+ }
+});
+
+Object.defineProperty(String.prototype, "getMatchingIndexes",
+{
+ value: function(needle)
+ {
+ var indexesOfNeedle = [];
+ var index = this.indexOf(needle);
+
+ while (index >= 0) {
+ indexesOfNeedle.push(index);
+ index = this.indexOf(needle, index + 1);
+ }
+
+ return indexesOfNeedle;
+ }
+});
+
+Object.defineProperty(String.prototype, "levenshteinDistance",
+{
+ value: function(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: function()
+ {
+ return this.toLowerCase().replace(/[^\w]+(\w)/g, (match, group) => group.toUpperCase());
+ }
+});
+
+Object.defineProperty(String.prototype, "hasMatchingEscapedQuotes",
+{
+ value: function()
+ {
+ return /^\"(?:[^\"\\]|\\.)*\"$/.test(this) || /^\'(?:[^\'\\]|\\.)*\'$/.test(this);
+ }
+});
+
+Object.defineProperty(Math, "roundTo",
+{
+ value: function(num, step)
+ {
+ return Math.round(num / step) * step;
+ }
+});
+
+Object.defineProperty(Number, "constrain",
+{
+ value: function(num, min, max)
+ {
+ if (max < min)
+ return min;
+
+ if (num < min)
+ num = min;
+ else if (num > max)
+ num = max;
+ return num;
+ }
+});
+
+Object.defineProperty(Number, "percentageString",
+{
+ value: function(fraction, precision = 1)
+ {
+ console.assert(fraction >= 0 && fraction <= 1);
+ return fraction.toLocaleString(undefined, {minimumFractionDigits: precision, style: "percent"});
+ }
+});
+
+Object.defineProperty(Number, "secondsToMillisecondsString",
+{
+ value: function(seconds, higherResolution)
+ {
+ let ms = seconds * 1000;
+
+ if (higherResolution)
+ return WebInspector.UIString("%.2fms").format(ms);
+ return WebInspector.UIString("%.1fms").format(ms);
+ }
+});
+
+Object.defineProperty(Number, "secondsToString",
+{
+ value: function(seconds, higherResolution)
+ {
+ let ms = seconds * 1000;
+ if (!ms)
+ return WebInspector.UIString("%.0fms").format(0);
+
+ if (Math.abs(ms) < 10) {
+ if (higherResolution)
+ return WebInspector.UIString("%.3fms").format(ms);
+ return WebInspector.UIString("%.2fms").format(ms);
+ }
+
+ if (Math.abs(ms) < 100) {
+ if (higherResolution)
+ return WebInspector.UIString("%.2fms").format(ms);
+ return WebInspector.UIString("%.1fms").format(ms);
+ }
+
+ if (Math.abs(ms) < 1000) {
+ if (higherResolution)
+ return WebInspector.UIString("%.1fms").format(ms);
+ return WebInspector.UIString("%.0fms").format(ms);
+ }
+
+ // Do not go over seconds when in high resolution mode.
+ if (higherResolution || Math.abs(seconds) < 60)
+ return WebInspector.UIString("%.2fs").format(seconds);
+
+ let minutes = seconds / 60;
+ if (Math.abs(minutes) < 60)
+ return WebInspector.UIString("%.1fmin").format(minutes);
+
+ let hours = minutes / 60;
+ if (Math.abs(hours) < 24)
+ return WebInspector.UIString("%.1fhrs").format(hours);
+
+ let days = hours / 24;
+ return WebInspector.UIString("%.1f days").format(days);
+ }
+});
+
+Object.defineProperty(Number, "bytesToString",
+{
+ value: function(bytes, higherResolution)
+ {
+ if (higherResolution === undefined)
+ higherResolution = true;
+
+ if (Math.abs(bytes) < 1024)
+ return WebInspector.UIString("%.0f B").format(bytes);
+
+ let kilobytes = bytes / 1024;
+ if (Math.abs(kilobytes) < 1024) {
+ if (higherResolution || Math.abs(kilobytes) < 10)
+ return WebInspector.UIString("%.2f KB").format(kilobytes);
+ return WebInspector.UIString("%.1f KB").format(kilobytes);
+ }
+
+ let megabytes = kilobytes / 1024;
+ if (higherResolution || Math.abs(megabytes) < 10)
+ return WebInspector.UIString("%.2f MB").format(megabytes);
+ return WebInspector.UIString("%.1f MB").format(megabytes);
+ }
+});
+
+Object.defineProperty(Number, "abbreviate",
+{
+ value: function(num)
+ {
+ if (num < 1000)
+ return num;
+
+ if (num < 1000000)
+ return WebInspector.UIString("%.1fK").format(Math.round(num / 100) / 10);
+
+ if (num < 1000000000)
+ return WebInspector.UIString("%.1fM").format(Math.round(num / 100000) / 10);
+
+ return WebInspector.UIString("%.1fB").format(Math.round(num / 100000000) / 10);
+ }
+});
+
+Object.defineProperty(Number.prototype, "maxDecimals",
+{
+ value(decimals)
+ {
+ let power = 10 ** decimals;
+ return Math.round(this * power) / power;
+ }
+});
+
+Object.defineProperty(Uint32Array, "isLittleEndian",
+{
+ value: function()
+ {
+ 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: function(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: function(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: function(value, comparator)
+ {
+ var index = this.lowerBound(value, comparator);
+ return index < this.length && comparator(value, this[index]) === 0 ? index : -1;
+ }
+});
+
+(function() {
+ // The `debounce` function lets you call any function on an object with a delay
+ // and if the function keeps getting called, the delay gets reset. Since `debounce`
+ // returns a Proxy, you can cache it and call multiple functions with the same delay.
+
+ // Use: object.debounce(200).foo("Argument 1", "Argument 2")
+ // Note: The last call's arguments get used for the delayed call.
+
+ const debounceTimeoutSymbol = Symbol("debounce-timeout");
+ const debounceSoonProxySymbol = Symbol("debounce-soon-proxy");
+
+ Object.defineProperty(Object.prototype, "soon",
+ {
+ get: function()
+ {
+ if (!this[debounceSoonProxySymbol])
+ this[debounceSoonProxySymbol] = this.debounce(0);
+ return this[debounceSoonProxySymbol];
+ }
+ });
+
+ Object.defineProperty(Object.prototype, "debounce",
+ {
+ value: function(delay)
+ {
+ console.assert(delay >= 0);
+
+ return new Proxy(this, {
+ get(target, property, receiver) {
+ return (...args) => {
+ let original = target[property];
+ console.assert(typeof original === "function");
+
+ if (original[debounceTimeoutSymbol])
+ clearTimeout(original[debounceTimeoutSymbol]);
+
+ let performWork = () => {
+ original[debounceTimeoutSymbol] = undefined;
+ original.apply(target, args);
+ };
+
+ original[debounceTimeoutSymbol] = setTimeout(performWork, delay);
+ };
+ }
+ });
+ }
+ });
+
+ Object.defineProperty(Function.prototype, "cancelDebounce",
+ {
+ value: function()
+ {
+ if (!this[debounceTimeoutSymbol])
+ return;
+
+ clearTimeout(this[debounceTimeoutSymbol]);
+ this[debounceTimeoutSymbol] = undefined;
+ }
+ });
+
+ const requestAnimationFrameSymbol = Symbol("peform-on-animation-frame");
+ const requestAnimationFrameProxySymbol = Symbol("perform-on-animation-frame-proxy");
+
+ Object.defineProperty(Object.prototype, "onNextFrame",
+ {
+ get: function()
+ {
+ if (!this[requestAnimationFrameProxySymbol]) {
+ this[requestAnimationFrameProxySymbol] = new Proxy(this, {
+ get(target, property, receiver) {
+ return (...args) => {
+ let original = target[property];
+ console.assert(typeof original === "function");
+
+ if (original[requestAnimationFrameSymbol])
+ return;
+
+ let performWork = () => {
+ original[requestAnimationFrameSymbol] = undefined;
+ original.apply(target, args);
+ };
+
+ original[requestAnimationFrameSymbol] = requestAnimationFrame(performWork);
+ };
+ }
+ });
+ }
+
+ return this[requestAnimationFrameProxySymbol];
+ }
+ });
+})();
+
+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 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 isTextLikelyMinified(content)
+{
+ const autoFormatMaxCharactersToCheck = 5000;
+ const autoFormatWhitespaceRatio = 0.2;
+
+ let whitespaceScore = 0;
+ let size = Math.min(autoFormatMaxCharactersToCheck, content.length);
+
+ for (let i = 0; i < size; 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 < autoFormatWhitespaceRatio;
+}
+
+function doubleQuotedString(str)
+{
+ return "\"" + str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"";
+}
+
+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);
+}
+
+function decodeBase64ToBlob(base64Data, mimeType)
+{
+ mimeType = mimeType || '';
+
+ const sliceSize = 1024;
+ var byteCharacters = atob(base64Data);
+ var bytesLength = byteCharacters.length;
+ var slicesCount = Math.ceil(bytesLength / sliceSize);
+ var byteArrays = new Array(slicesCount);
+
+ for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
+ var begin = sliceIndex * sliceSize;
+ var end = Math.min(begin + sliceSize, bytesLength);
+
+ var bytes = new Array(end - begin);
+ for (var offset = begin, i = 0 ; offset < end; ++i, ++offset)
+ bytes[i] = byteCharacters[offset].charCodeAt(0);
+
+ byteArrays[sliceIndex] = new Uint8Array(bytes);
+ }
+
+ return new Blob(byteArrays, {type: mimeType});
+}
+
+// FIXME: This can be removed when WEB_TIMING is enabled for all platforms.
+function timestamp()
+{
+ return window.performance ? performance.now() : Date.now();
+}
+
+if (!window.handlePromiseException) {
+ window.handlePromiseException = function handlePromiseException(error)
+ {
+ console.error("Uncaught exception in Promise", error);
+ };
+}