8dd23847dc5fb94d436eb4e1ab0b07b2e48d94ef
[WebKit-https.git] / Source / WebCore / inspector / front-end / utilities.js
1 /*
2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
3  * Copyright (C) 2012 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
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.
17  *
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.
28  */
29
30 Object.isEmpty = function(obj)
31 {
32     for (var i in obj)
33         return false;
34     return true;
35 }
36
37 Object.values = function(obj)
38 {
39     var keys = Object.keys(obj);
40     var result = [];
41
42     for (var i = 0; i < keys.length; ++i)
43         result.push(obj[keys[i]]);
44     return result;
45 }
46
47 String.prototype.hasSubstring = function(string, caseInsensitive)
48 {
49     if (!caseInsensitive)
50         return this.indexOf(string) !== -1;
51     return this.match(new RegExp(string.escapeForRegExp(), "i"));
52 }
53
54 String.prototype.findAll = function(string)
55 {
56     var matches = [];
57     var i = this.indexOf(string);
58     while (i !== -1) {
59         matches.push(i);
60         i = this.indexOf(string, i + string.length);
61     }
62     return matches;
63 }
64
65 String.prototype.lineEndings = function()
66 {
67     if (!this._lineEndings) {
68         this._lineEndings = this.findAll("\n");
69         this._lineEndings.push(this.length);
70     }
71     return this._lineEndings;
72 }
73
74 String.prototype.escapeCharacters = function(chars)
75 {
76     var foundChar = false;
77     for (var i = 0; i < chars.length; ++i) {
78         if (this.indexOf(chars.charAt(i)) !== -1) {
79             foundChar = true;
80             break;
81         }
82     }
83
84     if (!foundChar)
85         return this;
86
87     var result = "";
88     for (var i = 0; i < this.length; ++i) {
89         if (chars.indexOf(this.charAt(i)) !== -1)
90             result += "\\";
91         result += this.charAt(i);
92     }
93
94     return result;
95 }
96
97 String.regexSpecialCharacters = function()
98 {
99     return "^[]{}()\\.$*+?|-,";
100 }
101
102 String.prototype.escapeForRegExp = function()
103 {
104     return this.escapeCharacters(String.regexSpecialCharacters);
105 }
106
107 String.prototype.escapeHTML = function()
108 {
109     return this.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"); //" doublequotes just for editor
110 }
111
112 String.prototype.collapseWhitespace = function()
113 {
114     return this.replace(/[\s\xA0]+/g, " ");
115 }
116
117 String.prototype.trimMiddle = function(maxLength)
118 {
119     if (this.length <= maxLength)
120         return this;
121     var leftHalf = maxLength >> 1;
122     var rightHalf = maxLength - leftHalf - 1;
123     return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf);
124 }
125
126 String.prototype.trimEnd = function(maxLength)
127 {
128     if (this.length <= maxLength)
129         return this;
130     return this.substr(0, maxLength - 1) + "\u2026";
131 }
132
133 String.prototype.trimURL = function(baseURLDomain)
134 {
135     var result = this.replace(/^(https|http|file):\/\//i, "");
136     if (baseURLDomain)
137         result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), "");
138     return result;
139 }
140
141 String.prototype.toTitleCase = function()
142 {
143     return this.substring(0, 1).toUpperCase() + this.substring(1);
144 }
145
146 /**
147  * @param {string} other
148  * @return {number}
149  */
150 String.prototype.compareTo = function(other)
151 {
152     if (this > other)
153         return 1;
154     if (this < other)
155         return -1;
156     return 0;
157 }
158
159 /**
160  * @param {string} href
161  * @return {string}
162  */
163 function sanitizeHref(href)
164 {
165     return href && href.trim().toLowerCase().startsWith("javascript:") ? "" : href;
166 }
167
168 String.prototype.removeURLFragment = function()
169 {
170     var fragmentIndex = this.indexOf("#");
171     if (fragmentIndex == -1)
172         fragmentIndex = this.length;
173     return this.substring(0, fragmentIndex);
174 }
175
176 String.prototype.startsWith = function(substring)
177 {
178     return !this.lastIndexOf(substring, 0);
179 }
180
181 String.prototype.endsWith = function(substring)
182 {
183     return this.indexOf(substring, this.length - substring.length) !== -1;
184 }
185
186 Number.constrain = function(num, min, max)
187 {
188     if (num < min)
189         num = min;
190     else if (num > max)
191         num = max;
192     return num;
193 }
194
195 Date.prototype.toISO8601Compact = function()
196 {
197     function leadZero(x)
198     {
199         return x > 9 ? '' + x : '0' + x
200     }
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());
207 }
208
209 Object.defineProperty(Array.prototype, "remove",
210 {
211     /**
212      * @this {Array.<*>}
213      */
214     value: function(value, onlyFirst)
215     {
216         if (onlyFirst) {
217             var index = this.indexOf(value);
218             if (index !== -1)
219                 this.splice(index, 1);
220             return;
221         }
222
223         var length = this.length;
224         for (var i = 0; i < length; ++i) {
225             if (this[i] === value)
226                 this.splice(i, 1);
227         }
228     }
229 });
230
231 Object.defineProperty(Array.prototype, "keySet",
232 {
233     /**
234      * @this {Array.<*>}
235      */
236     value: function()
237     {
238         var keys = {};
239         for (var i = 0; i < this.length; ++i)
240             keys[this[i]] = true;
241         return keys;
242     }
243 });
244
245 Object.defineProperty(Array.prototype, "upperBound",
246 {
247     /**
248      * @this {Array.<number>}
249      */
250     value: function(value)
251     {
252         var first = 0;
253         var count = this.length;
254         while (count > 0) {
255           var step = count >> 1;
256           var middle = first + step;
257           if (value >= this[middle]) {
258               first = middle + 1;
259               count -= step + 1;
260           } else
261               count = step;
262         }
263         return first;
264     }
265 });
266
267 Object.defineProperty(Array.prototype, "rotate",
268 {
269     /**
270      * @this {Array.<*>}
271      * @param {number} index
272      * @return {Array.<*>}
273      */
274     value: function(index)
275     {
276         var result = [];
277         for (var i = index; i < index + this.length; ++i)
278             result.push(this[i % this.length]);
279         return result;
280     }
281 });
282
283 Object.defineProperty(Uint32Array.prototype, "sort", {
284    value: Array.prototype.sort
285 });
286
287 (function() {
288 var partition = {
289     /**
290      * @this {Array.<number>}
291      * @param {function(number,number):boolean} comparator
292      * @param {number} left
293      * @param {number} right
294      * @param {number} pivotIndex
295      */
296     value: function(comparator, left, right, pivotIndex)
297     {
298         function swap(array, i1, i2)
299         {
300             var temp = array[i1];
301             array[i1] = array[i2];
302             array[i2] = temp;
303         }
304
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);
311                 ++storeIndex;
312             }
313         }
314         swap(this, right, storeIndex);
315         return storeIndex;
316     }
317 };
318 Object.defineProperty(Array.prototype, "partition", partition);
319 Object.defineProperty(Uint32Array.prototype, "partition", partition);
320
321 var sortRange = {
322     /**
323      * @this {Array.<number>}
324      * @param {function(number,number):boolean} comparator
325      * @param {number} leftBound
326      * @param {number} rightBound
327      * @param {number} k
328      */
329     value: function(comparator, leftBound, rightBound, k)
330     {
331         function quickSortFirstK(array, comparator, left, right, k)
332         {
333             if (right <= left)
334                 return;
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);
340         }
341
342         if (leftBound === 0 && rightBound === (this.length - 1) && k >= this.length)
343             this.sort(comparator);
344         else
345             quickSortFirstK(this, comparator, leftBound, rightBound, k);
346         return this;
347     }
348 }
349 Object.defineProperty(Array.prototype, "sortRange", sortRange);
350 Object.defineProperty(Uint32Array.prototype, "sortRange", sortRange);
351 })();
352
353 Object.defineProperty(Array.prototype, "qselect",
354 {
355     /**
356      * @this {Array.<number>}
357      * @param {number} k
358      * @param {function(number,number):boolean=} comparator
359      */
360     value: function(k, comparator)
361     {
362         if (k < 0 || k >= this.length)
363             return;
364         if (!comparator)
365             comparator = function(a, b) { return a - b; }
366
367         var low = 0;
368         var high = this.length - 1;
369         for (;;) {
370             var pivotPosition = this.partition(comparator, low, high, Math.floor((high + low) / 2));
371             if (pivotPosition === k)
372                 return this[k];
373             else if (pivotPosition > k)
374                 high = pivotPosition - 1;
375             else
376                 low = pivotPosition + 1;
377         }
378     }
379 });
380
381 /**
382  * @param {*} object
383  * @param {Array.<*>} array
384  * @param {function(*, *):number} comparator
385  */
386 function binarySearch(object, array, comparator)
387 {
388     var first = 0;
389     var last = array.length - 1;
390
391     while (first <= last) {
392         var mid = (first + last) >> 1;
393         var c = comparator(object, array[mid]);
394         if (c > 0)
395             first = mid + 1;
396         else if (c < 0)
397             last = mid - 1;
398         else
399             return mid;
400     }
401
402     // Return the nearest lesser index, "-1" means "0, "-2" means "1", etc.
403     return -(first + 1);
404 }
405
406 Object.defineProperty(Array.prototype, "binaryIndexOf",
407 {
408     /**
409      * @this {Array.<*>}
410      * @param {function(*, *):number} comparator
411      */
412     value: function(value, comparator)
413     {
414         var result = binarySearch(value, this, comparator);
415         return result >= 0 ? result : -1;
416     }
417 });
418
419 Object.defineProperty(Array.prototype, "select",
420 {
421     /**
422      * @this {Array.<*>}
423      * @param {string} field
424      * @return {Array.<*>}
425      */
426     value: function(field)
427     {
428         var result = new Array(this.length);
429         for (var i = 0; i < this.length; ++i)
430             result[i] = this[i][field];
431         return result;
432     }
433 });
434
435 Object.defineProperty(Array.prototype, "peekLast",
436 {
437     /**
438      * @this {Array.<*>}
439      * @return {*}
440      */
441     value: function()
442     {
443         return this[this.length - 1];
444     }
445 });
446
447 /**
448  * @param {*} anObject
449  * @param {Array.<*>} aList
450  * @param {function(*, *)} aFunction
451  */
452 function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction)
453 {
454     var index = binarySearch(anObject, aList, aFunction);
455     if (index < 0)
456         // See binarySearch implementation.
457         return -index - 1;
458     else {
459         // Return the first occurance of an item in the list.
460         while (index > 0 && aFunction(anObject, aList[index - 1]) === 0)
461             index--;
462         return index;
463     }
464 }
465
466 /**
467  * @param {string} format
468  * @param {...*} var_arg
469  */
470 String.sprintf = function(format, var_arg)
471 {
472     return String.vsprintf(format, Array.prototype.slice.call(arguments, 1));
473 }
474
475 String.tokenizeFormatString = function(format, formatters)
476 {
477     var tokens = [];
478     var substitutionIndex = 0;
479
480     function addStringToken(str)
481     {
482         tokens.push({ type: "string", value: str });
483     }
484
485     function addSpecifierToken(specifier, precision, substitutionIndex)
486     {
487         tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
488     }
489
490     function isDigit(c)
491     {
492         return !!/[0-9]/.exec(c);
493     }
494
495     var index = 0;
496     for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
497         addStringToken(format.substring(index, precentIndex));
498         index = precentIndex + 1;
499
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]))
504                 ++index;
505
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);
510                 ++index;
511             }
512         }
513
514         var precision = -1;
515         if (format[index] === ".") {
516             // This is a precision specifier. If no digit follows the ".",
517             // then the precision should be zero.
518             ++index;
519             precision = parseInt(format.substring(index), 10);
520             if (isNaN(precision))
521                 precision = 0;
522
523             while (isDigit(format[index]))
524                 ++index;
525         }
526
527         if (!(format[index] in formatters)) {
528             addStringToken(format.substring(precentIndex, index + 1));
529             ++index;
530             continue;
531         }
532
533         addSpecifierToken(format[index], precision, substitutionIndex);
534
535         ++substitutionIndex;
536         ++index;
537     }
538
539     addStringToken(format.substring(index));
540
541     return tokens;
542 }
543
544 String.standardFormatters = {
545     d: function(substitution)
546     {
547         return !isNaN(substitution) ? substitution : 0;
548     },
549
550     f: function(substitution, token)
551     {
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);
555     },
556
557     s: function(substitution)
558     {
559         return substitution;
560     }
561 }
562
563 String.vsprintf = function(format, substitutions)
564 {
565     return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
566 }
567
568 String.format = function(format, substitutions, formatters, initialValue, append)
569 {
570     if (!format || !substitutions || !substitutions.length)
571         return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };
572
573     function prettyFunctionName()
574     {
575         return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")";
576     }
577
578     function warn(msg)
579     {
580         console.warn(prettyFunctionName() + ": " + msg);
581     }
582
583     function error(msg)
584     {
585         console.error(prettyFunctionName() + ": " + msg);
586     }
587
588     var result = initialValue;
589     var tokens = String.tokenizeFormatString(format, formatters);
590     var usedSubstitutionIndexes = {};
591
592     for (var i = 0; i < tokens.length; ++i) {
593         var token = tokens[i];
594
595         if (token.type === "string") {
596             result = append(result, token.value);
597             continue;
598         }
599
600         if (token.type !== "specifier") {
601             error("Unknown token type \"" + token.type + "\" found.");
602             continue;
603         }
604
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);
610             continue;
611         }
612
613         usedSubstitutionIndexes[token.substitutionIndex] = true;
614
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]);
619             continue;
620         }
621
622         result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
623     }
624
625     var unusedSubstitutions = [];
626     for (var i = 0; i < substitutions.length; ++i) {
627         if (i in usedSubstitutionIndexes)
628             continue;
629         unusedSubstitutions.push(substitutions[i]);
630     }
631
632     return { formattedResult: result, unusedSubstitutions: unusedSubstitutions };
633 }
634
635 /**
636  * @param {string} query
637  * @param {boolean} caseSensitive
638  * @param {boolean} isRegex
639  * @return {RegExp}
640  */
641 function createSearchRegex(query, caseSensitive, isRegex)
642 {
643     var regexFlags = caseSensitive ? "g" : "gi";
644     var regexObject;
645
646     if (isRegex) {
647         try {
648             regexObject = new RegExp(query, regexFlags);
649         } catch (e) {
650             // Silent catch.
651         }
652     }
653
654     if (!regexObject)
655         regexObject = createPlainTextSearchRegex(query, regexFlags);
656
657     return regexObject;
658 }
659
660 /**
661  * @param {string} query
662  * @param {string=} flags
663  * @return {!RegExp}
664  */
665 function createPlainTextSearchRegex(query, flags)
666 {
667     // This should be kept the same as the one in ContentSearchUtils.cpp.
668     var regexSpecialCharacters = String.regexSpecialCharacters();
669     var regex = "";
670     for (var i = 0; i < query.length; ++i) {
671         var c = query.charAt(i);
672         if (regexSpecialCharacters.indexOf(c) != -1)
673             regex += "\\";
674         regex += c;
675     }
676     return new RegExp(regex, flags || "");
677 }
678
679 /**
680  * @param {RegExp} regex
681  * @param {string} content
682  * @return {number}
683  */
684 function countRegexMatches(regex, content)
685 {
686     var text = content;
687     var result = 0;
688     var match;
689     while (text && (match = regex.exec(text))) {
690         if (match[0].length > 0)
691             ++result;
692         text = text.substring(match.index + 1);
693     }
694     return result;
695 }
696
697 /**
698  * @param {number} value
699  * @param {number} symbolsCount
700  * @return {string}
701  */
702 function numberToStringWithSpacesPadding(value, symbolsCount)
703 {
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;
708 }
709
710 /**
711   * @return {string}
712   */
713 var createObjectIdentifier = function()
714 {
715     // It has to be string for better performance.
716     return '_' + ++createObjectIdentifier._last;
717 }
718
719 createObjectIdentifier._last = 0;
720
721 /**
722  * @constructor
723  */
724 var Set = function()
725 {
726     /** @type !Object.<string, Object> */
727     this._set = {};
728     this._size = 0;
729 }
730
731 Set.prototype = {
732     /**
733      * @param {!Object} item
734      */
735     add: function(item)
736     {
737         var objectIdentifier = item.__identifier;
738         if (!objectIdentifier) {
739             objectIdentifier = createObjectIdentifier();
740             item.__identifier = objectIdentifier;
741         }
742         if (!this._set[objectIdentifier])
743             ++this._size;
744         this._set[objectIdentifier] = item;
745     },
746     
747     /**
748      * @param {!Object} item
749      */
750     remove: function(item)
751     {
752         if (this._set[item.__identifier]) {
753             --this._size;
754             delete this._set[item.__identifier];
755         }
756     },
757
758     /**
759      * @return {!Array.<Object>}
760      */
761     items: function()
762     {
763         var result = new Array(this._size);
764         var i = 0;
765         for (var objectIdentifier in this._set)
766             result[i++] = this._set[objectIdentifier];
767         return result;
768     },
769
770     /**
771      * @param {!Object} item
772      * @return {?Object}
773      */
774     hasItem: function(item)
775     {
776         return this._set[item.__identifier];
777     },
778
779     /**
780      * @return {number}
781      */
782     size: function()
783     {
784         return this._size;
785     },
786
787     clear: function()
788     {
789         this._set = {};
790         this._size = 0;
791     }
792 }
793
794 /**
795  * @constructor
796  */
797 var Map = function()
798 {
799     this._map = {};
800     this._size = 0;
801 }
802
803 Map.prototype = {
804     /**
805      * @param {Object} key
806      * @param {*=} value
807      */
808     put: function(key, value)
809     {
810         var objectIdentifier = key.__identifier;
811         if (!objectIdentifier) {
812             objectIdentifier = createObjectIdentifier();
813             key.__identifier = objectIdentifier;
814         }
815         if (!this._map[objectIdentifier])
816             ++this._size;
817         this._map[objectIdentifier] = [key, value];
818     },
819     
820     /**
821      * @param {Object} key
822      */
823     remove: function(key)
824     {
825         var result = this._map[key.__identifier];
826         if (!result)
827             return undefined;
828         --this._size;
829         delete this._map[key.__identifier];
830         return result[1];
831     },
832
833     /**
834      * @return {Array.<Object>}
835      */
836     keys: function()
837     {
838         return this._list(0);
839     },
840
841     values: function()
842     {
843         return this._list(1);
844     },
845
846     /**
847      * @param {number} index
848      */
849     _list: function(index)
850     {
851         var result = new Array(this._size);
852         var i = 0;
853         for (var objectIdentifier in this._map)
854             result[i++] = this._map[objectIdentifier][index];
855         return result;
856     },
857
858     /**
859      * @param {Object} key
860      */
861     get: function(key)
862     {
863         var entry = this._map[key.__identifier];
864         return entry ? entry[1] : undefined;
865     },
866
867     size: function()
868     {
869         return this._size;
870     },
871
872     clear: function()
873     {
874         this._map = {};
875         this._size = 0;
876     }
877 }
878 /**
879  * @param {string} url
880  * @param {boolean=} async
881  * @param {function(?string)=} callback
882  * @return {?string}
883  */
884 function loadXHR(url, async, callback) 
885 {
886     function onReadyStateChanged() 
887     {
888         if (xhr.readyState !== XMLHttpRequest.DONE)
889             return;
890
891         if (xhr.status === 200) {
892             callback(xhr.responseText);
893             return;
894         }
895
896         callback(null); 
897    }
898
899     var xhr = new XMLHttpRequest();
900     xhr.open("GET", url, async);
901     if (async)
902         xhr.onreadystatechange = onReadyStateChanged;        
903     xhr.send(null);
904
905     if (!async) {
906         if (xhr.status === 200) 
907             return xhr.responseText;
908         return null;
909     }
910     return null;
911 }
912
913 /**
914  * @constructor
915  */
916 function StringPool()
917 {
918     this.reset();
919 }
920
921 StringPool.prototype = {
922     /**
923      * @param {string} string
924      * @return {string}
925      */
926     intern: function(string)
927     {
928         // Do not mess with setting __proto__ to anything but null, just handle it explicitly.
929         if (string === "__proto__")
930             return "__proto__";
931         var result = this._strings[string];
932         if (result === undefined) {
933             this._strings[string] = string;
934             result = string;
935         }
936         return result;
937     },
938
939     reset: function()
940     {
941         this._strings = Object.create(null);
942     },
943
944     /**
945      * @param {Object} obj
946      * @param {number=} depthLimit
947      */
948     internObjectStrings: function(obj, depthLimit)
949     {
950         if (typeof depthLimit !== "number")
951             depthLimit = 100;
952         else if (--depthLimit < 0)
953             throw "recursion depth limit reached in StringPool.deepIntern(), perhaps attempting to traverse cyclical references?";
954
955         for (var field in obj) {
956             switch (typeof obj[field]) {
957             case "string":
958                 obj[field] = this.intern(obj[field]);
959                 break;
960             case "object":
961                 this.internObjectStrings(obj[field], depthLimit);
962                 break;
963             }
964         }
965     }
966 }
967
968 var _importedScripts = {};
969
970 /**
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.
975  *
976  * To load scripts lazily in release mode call "loasScript" function.
977  * @param {string} scriptName
978  */
979 function importScript(scriptName)
980 {
981     if (_importedScripts[scriptName])
982         return;
983     var xhr = new XMLHttpRequest();
984     _importedScripts[scriptName] = true;
985     try {
986         xhr.open("GET", scriptName, false);
987         xhr.send(null);
988     } catch (e) {
989         // Try to load file from the root directory
990         scriptName = scriptName.split("/").reverse()[0];
991         xhr.open("GET", scriptName, false);
992         xhr.send(null);
993     }
994     var sourceURL = WebInspector.ParsedURL.completeURL(window.location.href, scriptName); 
995     window.eval(xhr.responseText + "\n//@ sourceURL=" + sourceURL);
996 }
997
998 var loadScript = importScript;