/* * 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; }