2 * Copyright (C) 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 Object.isEmpty = function(obj)
37 Object.values = function(obj)
39 var keys = Object.keys(obj);
42 for (var i = 0; i < keys.length; ++i)
43 result.push(obj[keys[i]]);
47 String.prototype.hasSubstring = function(string, caseInsensitive)
50 return this.indexOf(string) !== -1;
51 return this.match(new RegExp(string.escapeForRegExp(), "i"));
54 String.prototype.findAll = function(string)
57 var i = this.indexOf(string);
60 i = this.indexOf(string, i + string.length);
65 String.prototype.lineEndings = function()
67 if (!this._lineEndings) {
68 this._lineEndings = this.findAll("\n");
69 this._lineEndings.push(this.length);
71 return this._lineEndings;
74 String.prototype.escapeCharacters = function(chars)
76 var foundChar = false;
77 for (var i = 0; i < chars.length; ++i) {
78 if (this.indexOf(chars.charAt(i)) !== -1) {
88 for (var i = 0; i < this.length; ++i) {
89 if (chars.indexOf(this.charAt(i)) !== -1)
91 result += this.charAt(i);
97 String.regexSpecialCharacters = function()
99 return "^[]{}()\\.$*+?|-,";
102 String.prototype.escapeForRegExp = function()
104 return this.escapeCharacters(String.regexSpecialCharacters);
107 String.prototype.escapeHTML = function()
109 return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); //" doublequotes just for editor
112 String.prototype.collapseWhitespace = function()
114 return this.replace(/[\s\xA0]+/g, " ");
117 String.prototype.trimMiddle = function(maxLength)
119 if (this.length <= maxLength)
121 var leftHalf = maxLength >> 1;
122 var rightHalf = maxLength - leftHalf - 1;
123 return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf);
126 String.prototype.trimEnd = function(maxLength)
128 if (this.length <= maxLength)
130 return this.substr(0, maxLength - 1) + "\u2026";
133 String.prototype.trimURL = function(baseURLDomain)
135 var result = this.replace(/^(https|http|file):\/\//i, "");
137 result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), "");
141 String.prototype.toTitleCase = function()
143 return this.substring(0, 1).toUpperCase() + this.substring(1);
147 * @param {string} other
150 String.prototype.compareTo = function(other)
160 * @param {string} href
163 function sanitizeHref(href)
165 return href && href.trim().toLowerCase().startsWith("javascript:") ? "" : href;
168 String.prototype.removeURLFragment = function()
170 var fragmentIndex = this.indexOf("#");
171 if (fragmentIndex == -1)
172 fragmentIndex = this.length;
173 return this.substring(0, fragmentIndex);
176 String.prototype.startsWith = function(substring)
178 return !this.lastIndexOf(substring, 0);
181 String.prototype.endsWith = function(substring)
183 return this.indexOf(substring, this.length - substring.length) !== -1;
186 Number.constrain = function(num, min, max)
195 Date.prototype.toISO8601Compact = function()
199 return x > 9 ? '' + x : '0' + x
201 return this.getFullYear() +
202 leadZero(this.getMonth() + 1) +
203 leadZero(this.getDate()) + 'T' +
204 leadZero(this.getHours()) +
205 leadZero(this.getMinutes()) +
206 leadZero(this.getSeconds());
209 Object.defineProperty(Array.prototype, "remove",
214 value: function(value, onlyFirst)
217 var index = this.indexOf(value);
219 this.splice(index, 1);
223 var length = this.length;
224 for (var i = 0; i < length; ++i) {
225 if (this[i] === value)
231 Object.defineProperty(Array.prototype, "keySet",
239 for (var i = 0; i < this.length; ++i)
240 keys[this[i]] = true;
245 Object.defineProperty(Array.prototype, "upperBound",
248 * @this {Array.<number>}
250 value: function(value)
253 var count = this.length;
255 var step = count >> 1;
256 var middle = first + step;
257 if (value >= this[middle]) {
267 Object.defineProperty(Array.prototype, "rotate",
271 * @param {number} index
272 * @return {Array.<*>}
274 value: function(index)
277 for (var i = index; i < index + this.length; ++i)
278 result.push(this[i % this.length]);
283 Object.defineProperty(Uint32Array.prototype, "sort", {
284 value: Array.prototype.sort
290 * @this {Array.<number>}
291 * @param {function(number,number):boolean} comparator
292 * @param {number} left
293 * @param {number} right
294 * @param {number} pivotIndex
296 value: function(comparator, left, right, pivotIndex)
298 function swap(array, i1, i2)
300 var temp = array[i1];
301 array[i1] = array[i2];
305 var pivotValue = this[pivotIndex];
306 swap(this, right, pivotIndex);
307 var storeIndex = left;
308 for (var i = left; i < right; ++i) {
309 if (comparator(this[i], pivotValue) < 0) {
310 swap(this, storeIndex, i);
314 swap(this, right, storeIndex);
318 Object.defineProperty(Array.prototype, "partition", partition);
319 Object.defineProperty(Uint32Array.prototype, "partition", partition);
323 * @this {Array.<number>}
324 * @param {function(number,number):boolean} comparator
325 * @param {number} leftBound
326 * @param {number} rightBound
329 value: function(comparator, leftBound, rightBound, k)
331 function quickSortFirstK(array, comparator, left, right, k)
335 var pivotIndex = Math.floor(Math.random() * (right - left)) + left;
336 var pivotNewIndex = array.partition(comparator, left, right, pivotIndex);
337 quickSortFirstK(array, comparator, left, pivotNewIndex - 1, k);
338 if (pivotNewIndex < left + k - 1)
339 quickSortFirstK(array, comparator, pivotNewIndex + 1, right, k);
342 if (leftBound === 0 && rightBound === (this.length - 1) && k >= this.length)
343 this.sort(comparator);
345 quickSortFirstK(this, comparator, leftBound, rightBound, k);
349 Object.defineProperty(Array.prototype, "sortRange", sortRange);
350 Object.defineProperty(Uint32Array.prototype, "sortRange", sortRange);
353 Object.defineProperty(Array.prototype, "qselect",
356 * @this {Array.<number>}
358 * @param {function(number,number):boolean=} comparator
360 value: function(k, comparator)
362 if (k < 0 || k >= this.length)
365 comparator = function(a, b) { return a - b; }
368 var high = this.length - 1;
370 var pivotPosition = this.partition(comparator, low, high, Math.floor((high + low) / 2));
371 if (pivotPosition === k)
373 else if (pivotPosition > k)
374 high = pivotPosition - 1;
376 low = pivotPosition + 1;
383 * @param {Array.<*>} array
384 * @param {function(*, *):number} comparator
386 function binarySearch(object, array, comparator)
389 var last = array.length - 1;
391 while (first <= last) {
392 var mid = (first + last) >> 1;
393 var c = comparator(object, array[mid]);
402 // Return the nearest lesser index, "-1" means "0, "-2" means "1", etc.
406 Object.defineProperty(Array.prototype, "binaryIndexOf",
410 * @param {function(*, *):number} comparator
412 value: function(value, comparator)
414 var result = binarySearch(value, this, comparator);
415 return result >= 0 ? result : -1;
419 Object.defineProperty(Array.prototype, "select",
423 * @param {string} field
424 * @return {Array.<*>}
426 value: function(field)
428 var result = new Array(this.length);
429 for (var i = 0; i < this.length; ++i)
430 result[i] = this[i][field];
435 Object.defineProperty(Array.prototype, "peekLast",
443 return this[this.length - 1];
448 * @param {*} anObject
449 * @param {Array.<*>} aList
450 * @param {function(*, *)} aFunction
452 function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction)
454 var index = binarySearch(anObject, aList, aFunction);
456 // See binarySearch implementation.
459 // Return the first occurance of an item in the list.
460 while (index > 0 && aFunction(anObject, aList[index - 1]) === 0)
467 * @param {string} format
468 * @param {...*} var_arg
470 String.sprintf = function(format, var_arg)
472 return String.vsprintf(format, Array.prototype.slice.call(arguments, 1));
475 String.tokenizeFormatString = function(format, formatters)
478 var substitutionIndex = 0;
480 function addStringToken(str)
482 tokens.push({ type: "string", value: str });
485 function addSpecifierToken(specifier, precision, substitutionIndex)
487 tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
492 return !!/[0-9]/.exec(c);
496 for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
497 addStringToken(format.substring(index, precentIndex));
498 index = precentIndex + 1;
500 if (isDigit(format[index])) {
501 // The first character is a number, it might be a substitution index.
502 var number = parseInt(format.substring(index), 10);
503 while (isDigit(format[index]))
506 // If the number is greater than zero and ends with a "$",
507 // then this is a substitution index.
508 if (number > 0 && format[index] === "$") {
509 substitutionIndex = (number - 1);
515 if (format[index] === ".") {
516 // This is a precision specifier. If no digit follows the ".",
517 // then the precision should be zero.
519 precision = parseInt(format.substring(index), 10);
520 if (isNaN(precision))
523 while (isDigit(format[index]))
527 if (!(format[index] in formatters)) {
528 addStringToken(format.substring(precentIndex, index + 1));
533 addSpecifierToken(format[index], precision, substitutionIndex);
539 addStringToken(format.substring(index));
544 String.standardFormatters = {
545 d: function(substitution)
547 return !isNaN(substitution) ? substitution : 0;
550 f: function(substitution, token)
552 if (substitution && token.precision > -1)
553 substitution = substitution.toFixed(token.precision);
554 return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
557 s: function(substitution)
563 String.vsprintf = function(format, substitutions)
565 return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
568 String.format = function(format, substitutions, formatters, initialValue, append)
570 if (!format || !substitutions || !substitutions.length)
571 return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };
573 function prettyFunctionName()
575 return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")";
580 console.warn(prettyFunctionName() + ": " + msg);
585 console.error(prettyFunctionName() + ": " + msg);
588 var result = initialValue;
589 var tokens = String.tokenizeFormatString(format, formatters);
590 var usedSubstitutionIndexes = {};
592 for (var i = 0; i < tokens.length; ++i) {
593 var token = tokens[i];
595 if (token.type === "string") {
596 result = append(result, token.value);
600 if (token.type !== "specifier") {
601 error("Unknown token type \"" + token.type + "\" found.");
605 if (token.substitutionIndex >= substitutions.length) {
606 // If there are not enough substitutions for the current substitutionIndex
607 // just output the format specifier literally and move on.
608 error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
609 result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
613 usedSubstitutionIndexes[token.substitutionIndex] = true;
615 if (!(token.specifier in formatters)) {
616 // Encountered an unsupported format character, treat as a string.
617 warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
618 result = append(result, substitutions[token.substitutionIndex]);
622 result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
625 var unusedSubstitutions = [];
626 for (var i = 0; i < substitutions.length; ++i) {
627 if (i in usedSubstitutionIndexes)
629 unusedSubstitutions.push(substitutions[i]);
632 return { formattedResult: result, unusedSubstitutions: unusedSubstitutions };
636 * @param {string} query
637 * @param {boolean} caseSensitive
638 * @param {boolean} isRegex
641 function createSearchRegex(query, caseSensitive, isRegex)
643 var regexFlags = caseSensitive ? "g" : "gi";
648 regexObject = new RegExp(query, regexFlags);
655 regexObject = createPlainTextSearchRegex(query, regexFlags);
661 * @param {string} query
662 * @param {string=} flags
665 function createPlainTextSearchRegex(query, flags)
667 // This should be kept the same as the one in ContentSearchUtils.cpp.
668 var regexSpecialCharacters = String.regexSpecialCharacters();
670 for (var i = 0; i < query.length; ++i) {
671 var c = query.charAt(i);
672 if (regexSpecialCharacters.indexOf(c) != -1)
676 return new RegExp(regex, flags || "");
680 * @param {RegExp} regex
681 * @param {string} content
684 function countRegexMatches(regex, content)
689 while (text && (match = regex.exec(text))) {
690 if (match[0].length > 0)
692 text = text.substring(match.index + 1);
698 * @param {number} value
699 * @param {number} symbolsCount
702 function numberToStringWithSpacesPadding(value, symbolsCount)
704 var numberString = value.toString();
705 var paddingLength = Math.max(0, symbolsCount - numberString.length);
706 var paddingString = Array(paddingLength + 1).join("\u00a0");
707 return paddingString + numberString;
713 var createObjectIdentifier = function()
715 // It has to be string for better performance.
716 return '_' + ++createObjectIdentifier._last;
719 createObjectIdentifier._last = 0;
726 /** @type !Object.<string, Object> */
733 * @param {!Object} item
737 var objectIdentifier = item.__identifier;
738 if (!objectIdentifier) {
739 objectIdentifier = createObjectIdentifier();
740 item.__identifier = objectIdentifier;
742 if (!this._set[objectIdentifier])
744 this._set[objectIdentifier] = item;
748 * @param {!Object} item
750 remove: function(item)
752 if (this._set[item.__identifier]) {
754 delete this._set[item.__identifier];
759 * @return {!Array.<Object>}
763 var result = new Array(this._size);
765 for (var objectIdentifier in this._set)
766 result[i++] = this._set[objectIdentifier];
771 * @param {!Object} item
774 hasItem: function(item)
776 return this._set[item.__identifier];
805 * @param {Object} key
808 put: function(key, value)
810 var objectIdentifier = key.__identifier;
811 if (!objectIdentifier) {
812 objectIdentifier = createObjectIdentifier();
813 key.__identifier = objectIdentifier;
815 if (!this._map[objectIdentifier])
817 this._map[objectIdentifier] = [key, value];
821 * @param {Object} key
823 remove: function(key)
825 var result = this._map[key.__identifier];
829 delete this._map[key.__identifier];
834 * @return {Array.<Object>}
838 return this._list(0);
843 return this._list(1);
847 * @param {number} index
849 _list: function(index)
851 var result = new Array(this._size);
853 for (var objectIdentifier in this._map)
854 result[i++] = this._map[objectIdentifier][index];
859 * @param {Object} key
863 var entry = this._map[key.__identifier];
864 return entry ? entry[1] : undefined;
879 * @param {string} url
880 * @param {boolean=} async
881 * @param {function(?string)=} callback
884 function loadXHR(url, async, callback)
886 function onReadyStateChanged()
888 if (xhr.readyState !== XMLHttpRequest.DONE)
891 if (xhr.status === 200) {
892 callback(xhr.responseText);
899 var xhr = new XMLHttpRequest();
900 xhr.open("GET", url, async);
902 xhr.onreadystatechange = onReadyStateChanged;
906 if (xhr.status === 200)
907 return xhr.responseText;
916 function StringPool()
921 StringPool.prototype = {
923 * @param {string} string
926 intern: function(string)
928 // Do not mess with setting __proto__ to anything but null, just handle it explicitly.
929 if (string === "__proto__")
931 var result = this._strings[string];
932 if (result === undefined) {
933 this._strings[string] = string;
941 this._strings = Object.create(null);
945 * @param {Object} obj
946 * @param {number=} depthLimit
948 internObjectStrings: function(obj, depthLimit)
950 if (typeof depthLimit !== "number")
952 else if (--depthLimit < 0)
953 throw "recursion depth limit reached in StringPool.deepIntern(), perhaps attempting to traverse cyclical references?";
955 for (var field in obj) {
956 switch (typeof obj[field]) {
958 obj[field] = this.intern(obj[field]);
961 this.internObjectStrings(obj[field], depthLimit);
968 var _importedScripts = {};
971 * This function behavior depends on the "debug_devtools" flag value.
972 * - In debug mode it loads scripts synchronously via xhr request.
973 * - In release mode every occurrence of "importScript" gets replaced with
974 * the script source code on the compilation phase.
976 * To load scripts lazily in release mode call "loasScript" function.
977 * @param {string} scriptName
979 function importScript(scriptName)
981 if (_importedScripts[scriptName])
983 var xhr = new XMLHttpRequest();
984 _importedScripts[scriptName] = true;
985 if (window.flattenImports)
986 scriptName = scriptName.split("/").reverse()[0];
987 xhr.open("GET", scriptName, false);
989 var sourceURL = WebInspector.ParsedURL.completeURL(window.location.href, scriptName);
990 window.eval(xhr.responseText + "\n//@ sourceURL=" + sourceURL);
993 var loadScript = importScript;