blob: 322e260f50c0e410663011c3f88da90b8fdbe5e7 [file] [log] [blame] [edit]
/*
* 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.
*/
if (!("bind" in Function.prototype)) {
Object.defineProperty(Function.prototype, "bind",
{
value: function(thisObject)
{
var func = this;
var args = Array.prototype.slice.call(arguments, 1);
return function bound()
{
return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0)));
};
}
});
}
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.
// Check for strict equality in case they are the same object.
if (a === b)
return true;
// Only objects can proceed. null is an object, but Object.keys throws for null.
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
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.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(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, "enclosingNodeWithClass",
{
value: function(className)
{
if (!this.parentNode)
return null;
return this.parentNode.enclosingNodeOrSelfWithClass(className);
}
});
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, "traverseNextTextNode",
{
value: function(stayWithin)
{
var node = this.traverseNextNode(stayWithin);
if (!node)
return;
while (node && node.nodeType !== Node.TEXT_NODE)
node = node.traverseNextNode(stayWithin);
return node;
}
});
Object.defineProperty(Node.prototype, "rangeBoundaryForOffset",
{
value: function(offset)
{
var textNode = this.traverseNextTextNode(this);
while (textNode && offset > textNode.data.length) {
offset -= textNode.data.length;
textNode = textNode.traverseNextTextNode(this);
}
if (!textNode)
return {container: this, offset: 0};
return {container: textNode, offset: offset};
}
});
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;
}
});
if (!("remove" in Element.prototype)) {
Object.defineProperty(Element.prototype, "remove",
{
value: function()
{
if (this.parentNode)
this.parentNode.removeChild(this);
}
});
}
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;
}
});
function AnchorBox(x, y, width, height)
{
this.x = x || 0;
this.y = y || 0;
this.width = width || 0;
this.height = height || 0;
}
Object.defineProperty(Element.prototype, "boxInWindow",
{
value: function()
{
var anchorBox = new AnchorBox;
var clientRect = this.getBoundingClientRect();
console.assert(clientRect);
anchorBox.x = clientRect.left;
anchorBox.y = clientRect.top;
anchorBox.width = clientRect.width;
anchorBox.height = clientRect.height;
return anchorBox;
}
});
Object.defineProperty(Element.prototype, "positionAt",
{
value: function(x, y)
{
this.style.left = x + "px";
this.style.top = y + "px";
}
});
Object.defineProperty(Element.prototype, "pruneEmptyTextNodes",
{
value: function()
{
var sibling = this.firstChild;
while (sibling) {
var nextSibling = sibling.nextSibling;
if (sibling.nodeType === this.TEXT_NODE && sibling.nodeValue === "")
this.removeChild(sibling);
sibling = nextSibling;
}
}
});
Object.defineProperty(Element.prototype, "isScrolledToBottom",
{
value: function()
{
// This code works only for 0-width border
return this.scrollTop + this.clientHeight === this.scrollHeight;
}
});
Object.defineProperty(DocumentFragment.prototype, "createChild",
{
value: Element.prototype.createChild
});
Object.defineProperty(Array.prototype, "contains",
{
value: function(value)
{
return this.indexOf(value) !== -1;
}
});
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, "keySet",
{
value: function()
{
var keys = {};
for (var i = 0; i < this.length; ++i)
keys[this[i]] = true;
return keys;
}
});
Object.defineProperty(Array.prototype, "upperBound",
{
value: function(value)
{
var first = 0;
var count = this.length;
while (count > 0) {
var step = count >> 1;
var middle = first + step;
if (value >= this[middle]) {
first = middle + 1;
count -= step + 1;
} else
count = step;
}
return first;
}
});
Object.defineProperty(Array, "convert",
{
value: function(list, startIndex, endIndex)
{
return Array.prototype.slice.call(list, startIndex, endIndex);
}
});
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) + "\u2026" + 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) + "\u2026";
}
});
Object.defineProperty(String.prototype, "collapseWhitespace",
{
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: specifier, precision: precision, substitutionIndex: 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;
}
}
var precision = -1;
if (format[index] === ".") {
// This is a precision specifier. If no digit follows the ".",
// then the precision should be zero.
++index;
precision = parseInt(format.substring(index), 10);
if (isNaN(precision))
precision = 0;
while (!isNaN(format[index]))
++index;
}
addSpecifierToken(format[index], precision, substitutionIndex);
++substitutionIndex;
++index;
}
addStringToken(format.substring(index));
return tokens;
}
});
Object.defineProperty(String.prototype, "startsWith",
{
value: function(string)
{
return this.lastIndexOf(string, 0) === 0;
}
});
Object.defineProperty(String.prototype, "hash",
{
get: function()
{
// Matches the wtf/StringHasher.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 !isNaN(substitution) ? substitution : 0;
},
f: function(substitution, token)
{
if (substitution && token.precision > -1)
substitution = substitution.toFixed(token.precision);
return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
},
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 + "\", \"" + 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: 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(Number, "constrain",
{
value: function(num, min, max)
{
if (num < min)
num = min;
else if (num > max)
num = max;
return num;
}
});
Object.defineProperty(Number, "secondsToString",
{
value: function(seconds, higherResolution)
{
var ms = seconds * 1000;
if (higherResolution && ms < 100)
return WebInspector.UIString("%.2fms").format(ms);
else if (ms < 100)
return WebInspector.UIString("%.1fms").format(ms);
if (higherResolution && ms < 1000)
return WebInspector.UIString("%.1fms").format(ms);
else if (ms < 1000)
return WebInspector.UIString("%.0fms").format(ms);
if (seconds < 60)
return WebInspector.UIString("%.2fs").format(seconds);
var minutes = seconds / 60;
if (minutes < 60)
return WebInspector.UIString("%.1fmin").format(minutes);
var hours = minutes / 60;
if (hours < 24)
return WebInspector.UIString("%.1fhrs").format(hours);
var days = hours / 24;
return WebInspector.UIString("%.1f days").format(days);
}
});
Object.defineProperty(Number, "bytesToString",
{
value: function(bytes, higherResolution)
{
if (higherResolution === undefined)
higherResolution = true;
if (bytes < 1024)
return WebInspector.UIString("%.0f B").format(bytes);
var kilobytes = bytes / 1024;
if (higherResolution && kilobytes < 1024)
return WebInspector.UIString("%.2f KB").format(kilobytes);
else if (kilobytes < 1024)
return WebInspector.UIString("%.0f KB").format(kilobytes);
var megabytes = kilobytes / 1024;
if (higherResolution)
return WebInspector.UIString("%.2f MB").format(megabytes);
else
return WebInspector.UIString("%.0f MB").format(megabytes);
}
});
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 removeURLFragment(url)
{
var hashIndex = url.indexOf("#");
if (hashIndex >= 0)
return url.substring(0, hashIndex);
return url;
}
function relativePath(path, basePath)
{
console.assert(path.charAt(0) === "/");
console.assert(basePath.charAt(0) === "/");
var pathComponents = path.split("/");
var baseComponents = basePath.replace(/\/$/, "").split("/");
var finalComponents = [];
var index = 1;
for (; index < pathComponents.length && index < baseComponents.length; ++index) {
if (pathComponents[index] !== baseComponents[index])
break;
}
for (var i = index; i < baseComponents.length; ++i)
finalComponents.push("..");
for (var i = index; i < pathComponents.length; ++i)
finalComponents.push(pathComponents[i]);
return finalComponents.join("/");
}
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 parseURL(url)
{
url = url ? url.trim() : "";
var match = url.match(/^([^:]+):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i);
if (!match)
return {scheme: null, host: null, port: null, path: null, queryString: null, fragment: null, lastPathComponent: null};
var scheme = match[1].toLowerCase();
var host = match[2].toLowerCase();
var port = Number(match[3]) || null;
var wholePath = match[4] || null;
var fragment = match[5] || null;
var path = wholePath;
var queryString = null;
// Split the path and the query string.
if (wholePath) {
var indexOfQuery = wholePath.indexOf("?");
if (indexOfQuery !== -1) {
path = wholePath.substring(0, indexOfQuery);
queryString = wholePath.substring(indexOfQuery + 1);
}
path = resolveDotsInPath(path);
}
// Find last path component.
var lastPathComponent = null;
if (path && path !== "/") {
// Skip the trailing slash if there is one.
var endOffset = path[path.length - 1] === "/" ? 1 : 0;
var lastSlashIndex = path.lastIndexOf("/", path.length - 1 - endOffset);
if (lastSlashIndex !== -1)
lastPathComponent = path.substring(lastSlashIndex + 1, path.length - endOffset);
}
return {scheme: scheme, host: host, port: port, path: path, queryString: queryString, fragment: fragment, lastPathComponent: lastPathComponent};
}
function absoluteURL(partialURL, baseURL)
{
partialURL = partialURL ? partialURL.trim() : "";
// Return data and javascript URLs as-is.
if (partialURL.startsWith("data:") || partialURL.startsWith("javascript:") || partialURL.startsWith("mailto:"))
return partialURL;
// If the URL has a scheme it is already a full URL, so return it.
if (parseURL(partialURL).scheme)
return partialURL;
// If there is no partial URL, just return the base URL.
if (!partialURL)
return baseURL || null;
var baseURLComponents = parseURL(baseURL);
// The base URL needs to be an absolute URL. Return null if it isn't.
if (!baseURLComponents.scheme)
return null;
// A URL that starts with "//" is a full URL without the scheme. Use the base URL scheme.
if (partialURL[0] === "/" && partialURL[1] === "/")
return baseURLComponents.scheme + ":" + partialURL;
// The path can be null for URLs that have just a scheme and host (like "http://apple.com"). So make the path be "/".
if (!baseURLComponents.path)
baseURLComponents.path = "/";
// Generate the base URL prefix that is used in the rest of the cases.
var baseURLPrefix = baseURLComponents.scheme + "://" + baseURLComponents.host + (baseURLComponents.port ? (":" + baseURLComponents.port) : "");
// A URL that starts with "?" is just a query string that gets applied to the base URL (replacing the base URL query string and fragment).
if (partialURL[0] === "?")
return baseURLPrefix + baseURLComponents.path + partialURL;
// A URL that starts with "/" is an absolute path that gets applied to the base URL (replacing the base URL path, query string and fragment).
if (partialURL[0] === "/")
return baseURLPrefix + resolveDotsInPath(partialURL);
// Generate the base path that is used in the final case by removing everything after the last "/" from the base URL's path.
var basePath = baseURLComponents.path.substring(0, baseURLComponents.path.lastIndexOf("/")) + "/";
return baseURLPrefix + resolveDotsInPath(basePath + partialURL);
}
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);
}
function parseLocationQueryParameters(arrayResult)
{
// The first character is always the "?".
return parseQueryString(window.location.search.substring(1), arrayResult);
}
function parseQueryString(queryString, arrayResult)
{
if (!queryString)
return arrayResult ? [] : {};
function decode(string)
{
try {
// Replace "+" with " " then decode precent encoded values.
return decodeURIComponent(string.replace(/\+/g, " "));
} catch (e) {
return string;
}
}
var parameters = arrayResult ? [] : {};
var parameterStrings = queryString.split("&");
for (var i = 0; i < parameterStrings.length; ++i) {
var pair = parameterStrings[i].split("=").map(decode);
if (arrayResult)
parameters.push({name: pair[0], value: pair[1]});
else
parameters[pair[0]] = pair[1];
}
return parameters;
}