Web Inspector: Object.shallowEqual([{}], [{}]) should return true
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Base / Utilities.js
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 var emDash = "\u2014";
27 var enDash = "\u2013";
28 var figureDash = "\u2012";
29 var ellipsis = "\u2026";
30
31 Object.defineProperty(Object, "shallowCopy",
32 {
33     value: function(object)
34     {
35         // Make a new object and copy all the key/values. The values are not copied.
36         var copy = {};
37         var keys = Object.keys(object);
38         for (var i = 0; i < keys.length; ++i)
39             copy[keys[i]] = object[keys[i]];
40         return copy;
41     }
42 });
43
44 Object.defineProperty(Object, "shallowEqual",
45 {
46     value: function(a, b)
47     {
48         // Checks if two objects have the same top-level properties.
49
50         // Only objects can proceed.
51         if (!(a instanceof Object) || !(b instanceof Object))
52             return false;
53
54         // Check for strict equality in case they are the same object.
55         if (a === b)
56             return true;
57
58         // Use an optimized version of shallowEqual for arrays.
59         if (Array.isArray(a) && Array.isArray(b))
60             return Array.shallowEqual(a, b);
61
62         if (a.constructor !== b.constructor)
63             return false;
64
65         var aKeys = Object.keys(a);
66         var bKeys = Object.keys(b);
67
68         // Check that each object has the same number of keys.
69         if (aKeys.length !== bKeys.length)
70             return false;
71
72         // Check if all the keys and their values are equal.
73         for (var i = 0; i < aKeys.length; ++i) {
74             // Check that b has the same key as a.
75             if (!(aKeys[i] in b))
76                 return false;
77
78             // Check that the values are strict equal since this is only
79             // a shallow check, not a recursive one.
80             if (a[aKeys[i]] !== b[aKeys[i]])
81                 return false;
82         }
83
84         return true;
85     }
86 });
87
88 Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey",
89 {
90     value: function(key)
91     {
92         if (this.hasOwnProperty(key))
93             return this[key];
94
95         var lowerCaseKey = key.toLowerCase();
96         for (var currentKey in this) {
97             if (currentKey.toLowerCase() === lowerCaseKey)
98                 return this[currentKey];
99         }
100
101         return undefined;
102     }
103 });
104
105 Object.defineProperty(Map, "fromObject",
106 {
107     value: function(object)
108     {
109         let map = new Map;
110         for (let key in object)
111             map.set(key, object[key]);
112         return map;
113     }
114 });
115
116 Object.defineProperty(Map.prototype, "take",
117 {
118     value: function(key)
119     {
120         var deletedValue = this.get(key);
121         this.delete(key);
122         return deletedValue;
123     }
124 });
125
126 Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithClass",
127 {
128     value: function(className)
129     {
130         for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
131             if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className))
132                 return node;
133         return null;
134     }
135 });
136
137 Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeNameInArray",
138 {
139     value: function(nameArray)
140     {
141         var lowerCaseNameArray = nameArray.map(function(name) { return name.toLowerCase(); });
142         for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) {
143             for (var i = 0; i < nameArray.length; ++i) {
144                 if (node.nodeName.toLowerCase() === lowerCaseNameArray[i])
145                     return node;
146             }
147         }
148
149         return null;
150     }
151 });
152
153 Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeName",
154 {
155     value: function(nodeName)
156     {
157         return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
158     }
159 });
160
161 Object.defineProperty(Node.prototype, "isAncestor",
162 {
163     value: function(node)
164     {
165         if (!node)
166             return false;
167
168         var currentNode = node.parentNode;
169         while (currentNode) {
170             if (this === currentNode)
171                 return true;
172             currentNode = currentNode.parentNode;
173         }
174
175         return false;
176     }
177 });
178
179 Object.defineProperty(Node.prototype, "isDescendant",
180 {
181     value: function(descendant)
182     {
183         return !!descendant && descendant.isAncestor(this);
184     }
185 });
186
187
188 Object.defineProperty(Node.prototype, "isSelfOrAncestor",
189 {
190     value: function(node)
191     {
192         return !!node && (node === this || this.isAncestor(node));
193     }
194 });
195
196
197 Object.defineProperty(Node.prototype, "isSelfOrDescendant",
198 {
199     value: function(node)
200     {
201         return !!node && (node === this || this.isDescendant(node));
202     }
203 });
204
205 Object.defineProperty(Node.prototype, "traverseNextNode",
206 {
207     value: function(stayWithin)
208     {
209         var node = this.firstChild;
210         if (node)
211             return node;
212
213         if (stayWithin && this === stayWithin)
214             return null;
215
216         node = this.nextSibling;
217         if (node)
218             return node;
219
220         node = this;
221         while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin))
222             node = node.parentNode;
223         if (!node)
224             return null;
225
226         return node.nextSibling;
227     }
228 });
229
230 Object.defineProperty(Node.prototype, "traversePreviousNode",
231 {
232     value: function(stayWithin)
233     {
234        if (stayWithin && this === stayWithin)
235             return null;
236         var node = this.previousSibling;
237         while (node && node.lastChild)
238             node = node.lastChild;
239         if (node)
240             return node;
241         return this.parentNode;
242     }
243 });
244
245
246 Object.defineProperty(Node.prototype, "rangeOfWord",
247 {
248     value: function(offset, stopCharacters, stayWithinNode, direction)
249     {
250         var startNode;
251         var startOffset = 0;
252         var endNode;
253         var endOffset = 0;
254
255         if (!stayWithinNode)
256             stayWithinNode = this;
257
258         if (!direction || direction === "backward" || direction === "both") {
259             var node = this;
260             while (node) {
261                 if (node === stayWithinNode) {
262                     if (!startNode)
263                         startNode = stayWithinNode;
264                     break;
265                 }
266
267                 if (node.nodeType === Node.TEXT_NODE) {
268                     var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
269                     for (var i = start; i >= 0; --i) {
270                         if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
271                             startNode = node;
272                             startOffset = i + 1;
273                             break;
274                         }
275                     }
276                 }
277
278                 if (startNode)
279                     break;
280
281                 node = node.traversePreviousNode(stayWithinNode);
282             }
283
284             if (!startNode) {
285                 startNode = stayWithinNode;
286                 startOffset = 0;
287             }
288         } else {
289             startNode = this;
290             startOffset = offset;
291         }
292
293         if (!direction || direction === "forward" || direction === "both") {
294             node = this;
295             while (node) {
296                 if (node === stayWithinNode) {
297                     if (!endNode)
298                         endNode = stayWithinNode;
299                     break;
300                 }
301
302                 if (node.nodeType === Node.TEXT_NODE) {
303                     var start = (node === this ? offset : 0);
304                     for (var i = start; i < node.nodeValue.length; ++i) {
305                         if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
306                             endNode = node;
307                             endOffset = i;
308                             break;
309                         }
310                     }
311                 }
312
313                 if (endNode)
314                     break;
315
316                 node = node.traverseNextNode(stayWithinNode);
317             }
318
319             if (!endNode) {
320                 endNode = stayWithinNode;
321                 endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
322             }
323         } else {
324             endNode = this;
325             endOffset = offset;
326         }
327
328         var result = this.ownerDocument.createRange();
329         result.setStart(startNode, startOffset);
330         result.setEnd(endNode, endOffset);
331
332         return result;
333
334     }
335 });
336
337 Object.defineProperty(Element.prototype, "realOffsetWidth",
338 {
339     get: function()
340     {
341         return this.getBoundingClientRect().width;
342     }
343 });
344
345 Object.defineProperty(Element.prototype, "realOffsetHeight",
346 {
347     get: function()
348     {
349         return this.getBoundingClientRect().height;
350     }
351 });
352
353 Object.defineProperty(Element.prototype, "totalOffsetLeft",
354 {
355     get: function()
356     {
357         return this.getBoundingClientRect().left;
358     }
359 });
360
361 Object.defineProperty(Element.prototype, "totalOffsetTop",
362 {
363     get: function()
364     {
365         return this.getBoundingClientRect().top;
366     }
367 });
368
369 Object.defineProperty(Element.prototype, "removeChildren",
370 {
371     value: function()
372     {
373         // This has been tested to be the fastest removal method.
374         if (this.firstChild)
375             this.textContent = "";
376     }
377 });
378
379 Object.defineProperty(Element.prototype, "isInsertionCaretInside",
380 {
381     value: function()
382     {
383         var selection = window.getSelection();
384         if (!selection.rangeCount || !selection.isCollapsed)
385             return false;
386         var selectionRange = selection.getRangeAt(0);
387         return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this);
388     }
389 });
390
391 Object.defineProperty(Element.prototype, "removeMatchingStyleClasses",
392 {
393     value: function(classNameRegex)
394     {
395         var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
396         if (regex.test(this.className))
397             this.className = this.className.replace(regex, " ");
398     }
399 });
400
401 Object.defineProperty(Element.prototype, "createChild",
402 {
403     value: function(elementName, className)
404     {
405         var element = this.ownerDocument.createElement(elementName);
406         if (className)
407             element.className = className;
408         this.appendChild(element);
409         return element;
410     }
411 });
412
413 Object.defineProperty(Element.prototype, "isScrolledToBottom",
414 {
415     value: function()
416     {
417         // This code works only for 0-width border
418         return this.scrollTop + this.clientHeight === this.scrollHeight;
419     }
420 });
421
422 Object.defineProperty(Element.prototype, "recalculateStyles",
423 {
424     value: function()
425     {
426         this.ownerDocument.defaultView.getComputedStyle(this);
427     }
428 });
429
430 Object.defineProperty(DocumentFragment.prototype, "createChild",
431 {
432     value: Element.prototype.createChild
433 });
434
435 Object.defineProperty(Array, "shallowEqual",
436 {
437     value: function(a, b)
438     {
439         if (!Array.isArray(a) || !Array.isArray(b))
440             return false;
441
442         if (a === b)
443             return true;
444
445         let length = a.length;
446
447         if (length !== b.length)
448             return false;
449
450         for (let i = 0; i < length; ++i) {
451             if (a[i] === b[i])
452                 continue;
453
454             if (!Object.shallowEqual(a[i], b[i]))
455                 return false;
456         }
457
458         return true;
459     }
460 });
461
462 Object.defineProperty(Array.prototype, "lastValue",
463 {
464     get: function()
465     {
466         if (!this.length)
467             return undefined;
468         return this[this.length - 1];
469     }
470 });
471
472 Object.defineProperty(Array.prototype, "remove",
473 {
474     value: function(value, onlyFirst)
475     {
476         for (var i = this.length - 1; i >= 0; --i) {
477             if (this[i] === value) {
478                 this.splice(i, 1);
479                 if (onlyFirst)
480                     return;
481             }
482         }
483     }
484 });
485
486 Object.defineProperty(Array.prototype, "toggleIncludes",
487 {
488     value: function(value, force)
489     {
490         let exists = this.includes(value);
491         if (exists === !!force)
492             return;
493
494         if (exists)
495             this.remove(value);
496         else
497             this.push(value);
498     }
499 });
500
501 Object.defineProperty(Array.prototype, "insertAtIndex",
502 {
503     value: function(value, index)
504     {
505         this.splice(index, 0, value);
506     }
507 });
508
509 Object.defineProperty(Array.prototype, "keySet",
510 {
511     value: function()
512     {
513         let keys = Object.create(null);
514         for (var i = 0; i < this.length; ++i)
515             keys[this[i]] = true;
516         return keys;
517     }
518 });
519
520 Object.defineProperty(Array.prototype, "partition",
521 {
522     value: function(callback)
523     {
524         let positive = [];
525         let negative = [];
526         for (let i = 0; i < this.length; ++i) {
527             let value = this[i];
528             if (callback(value))
529                 positive.push(value);
530             else
531                 negative.push(value);
532         }
533         return [positive, negative];
534     }
535 });
536
537 Object.defineProperty(String.prototype, "isLowerCase",
538 {
539     value: function()
540     {
541         return String(this) === this.toLowerCase();
542     }
543 });
544
545 Object.defineProperty(String.prototype, "isUpperCase",
546 {
547     value: function()
548     {
549         return String(this) === this.toUpperCase();
550     }
551 });
552
553 Object.defineProperty(String.prototype, "trimMiddle",
554 {
555     value: function(maxLength)
556     {
557         if (this.length <= maxLength)
558             return this;
559         var leftHalf = maxLength >> 1;
560         var rightHalf = maxLength - leftHalf - 1;
561         return this.substr(0, leftHalf) + ellipsis + this.substr(this.length - rightHalf, rightHalf);
562     }
563 });
564
565 Object.defineProperty(String.prototype, "trimEnd",
566 {
567     value: function(maxLength)
568     {
569         if (this.length <= maxLength)
570             return this;
571         return this.substr(0, maxLength - 1) + ellipsis;
572     }
573 });
574
575 Object.defineProperty(String.prototype, "truncate",
576 {
577     value: function(maxLength)
578     {
579         "use strict";
580
581         if (this.length <= maxLength)
582             return this;
583
584         let clipped = this.slice(0, maxLength);
585         let indexOfLastWhitespace = clipped.search(/\s\S*$/);
586         if (indexOfLastWhitespace > Math.floor(maxLength / 2))
587             clipped = clipped.slice(0, indexOfLastWhitespace - 1);
588
589         return clipped + ellipsis;
590     }
591 });
592
593 Object.defineProperty(String.prototype, "collapseWhitespace",
594 {
595     value: function()
596     {
597         return this.replace(/[\s\xA0]+/g, " ");
598     }
599 });
600
601 Object.defineProperty(String.prototype, "removeWhitespace",
602 {
603     value: function()
604     {
605         return this.replace(/[\s\xA0]+/g, "");
606     }
607 });
608
609 Object.defineProperty(String.prototype, "escapeCharacters",
610 {
611     value: function(chars)
612     {
613         var foundChar = false;
614         for (var i = 0; i < chars.length; ++i) {
615             if (this.indexOf(chars.charAt(i)) !== -1) {
616                 foundChar = true;
617                 break;
618             }
619         }
620
621         if (!foundChar)
622             return this;
623
624         var result = "";
625         for (var i = 0; i < this.length; ++i) {
626             if (chars.indexOf(this.charAt(i)) !== -1)
627                 result += "\\";
628             result += this.charAt(i);
629         }
630
631         return result;
632     }
633 });
634
635 Object.defineProperty(String.prototype, "escapeForRegExp",
636 {
637     value: function()
638     {
639         return this.escapeCharacters("^[]{}()\\.$*+?|");
640     }
641 });
642
643 Object.defineProperty(String.prototype, "capitalize",
644 {
645     value: function()
646     {
647         return this.charAt(0).toUpperCase() + this.slice(1);
648     }
649 });
650
651 Object.defineProperty(String, "tokenizeFormatString",
652 {
653     value: function(format)
654     {
655         var tokens = [];
656         var substitutionIndex = 0;
657
658         function addStringToken(str)
659         {
660             tokens.push({type: "string", value: str});
661         }
662
663         function addSpecifierToken(specifier, precision, substitutionIndex)
664         {
665             tokens.push({type: "specifier", specifier, precision, substitutionIndex});
666         }
667
668         var index = 0;
669         for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
670             addStringToken(format.substring(index, precentIndex));
671             index = precentIndex + 1;
672
673             if (format[index] === "%") {
674                 addStringToken("%");
675                 ++index;
676                 continue;
677             }
678
679             if (!isNaN(format[index])) {
680                 // The first character is a number, it might be a substitution index.
681                 var number = parseInt(format.substring(index), 10);
682                 while (!isNaN(format[index]))
683                     ++index;
684
685                 // If the number is greater than zero and ends with a "$",
686                 // then this is a substitution index.
687                 if (number > 0 && format[index] === "$") {
688                     substitutionIndex = (number - 1);
689                     ++index;
690                 }
691             }
692
693             const defaultPrecision = 6;
694
695             let precision = defaultPrecision;
696             if (format[index] === ".") {
697                 // This is a precision specifier. If no digit follows the ".",
698                 // then use the default precision of six digits (ISO C99 specification).
699                 ++index;
700
701                 precision = parseInt(format.substring(index), 10);
702                 if (isNaN(precision))
703                     precision = defaultPrecision;
704
705                 while (!isNaN(format[index]))
706                     ++index;
707             }
708
709             addSpecifierToken(format[index], precision, substitutionIndex);
710
711             ++substitutionIndex;
712             ++index;
713         }
714
715         addStringToken(format.substring(index));
716
717         return tokens;
718     }
719 });
720
721 Object.defineProperty(String.prototype, "hash",
722 {
723     get: function()
724     {
725         // Matches the wtf/Hasher.h (SuperFastHash) algorithm.
726
727         // Arbitrary start value to avoid mapping all 0's to all 0's.
728         const stringHashingStartValue = 0x9e3779b9;
729
730         var result = stringHashingStartValue;
731         var pendingCharacter = null;
732         for (var i = 0; i < this.length; ++i) {
733             var currentCharacter = this[i].charCodeAt(0);
734             if (pendingCharacter === null) {
735                 pendingCharacter = currentCharacter;
736                 continue;
737             }
738
739             result += pendingCharacter;
740             result = (result << 16) ^ ((currentCharacter << 11) ^ result);
741             result += result >> 11;
742
743             pendingCharacter = null;
744         }
745
746         // Handle the last character in odd length strings.
747         if (pendingCharacter !== null) {
748             result += pendingCharacter;
749             result ^= result << 11;
750             result += result >> 17;
751         }
752
753         // Force "avalanching" of final 31 bits.
754         result ^= result << 3;
755         result += result >> 5;
756         result ^= result << 2;
757         result += result >> 15;
758         result ^= result << 10;
759
760         // Prevent 0 and negative results.
761         return (0xffffffff + result + 1).toString(36);
762     }
763 });
764
765 Object.defineProperty(String, "standardFormatters",
766 {
767     value: {
768         d: function(substitution)
769         {
770             return parseInt(substitution);
771         },
772
773         f: function(substitution, token)
774         {
775             let value = parseFloat(substitution);
776             if (isNaN(value))
777                 return NaN;
778
779             let options = {
780                 minimumFractionDigits: token.precision,
781                 maximumFractionDigits: token.precision,
782                 useGrouping: false
783             };
784             return value.toLocaleString(undefined, options);
785         },
786
787         s: function(substitution)
788         {
789             return substitution;
790         }
791     }
792 });
793
794 Object.defineProperty(String, "format",
795 {
796     value: function(format, substitutions, formatters, initialValue, append)
797     {
798         if (!format || !substitutions || !substitutions.length)
799             return {formattedResult: append(initialValue, format), unusedSubstitutions: substitutions};
800
801         function prettyFunctionName()
802         {
803             return "String.format(\"" + format + "\", \"" + Array.from(substitutions).join("\", \"") + "\")";
804         }
805
806         function warn(msg)
807         {
808             console.warn(prettyFunctionName() + ": " + msg);
809         }
810
811         function error(msg)
812         {
813             console.error(prettyFunctionName() + ": " + msg);
814         }
815
816         var result = initialValue;
817         var tokens = String.tokenizeFormatString(format);
818         var usedSubstitutionIndexes = {};
819
820         for (var i = 0; i < tokens.length; ++i) {
821             var token = tokens[i];
822
823             if (token.type === "string") {
824                 result = append(result, token.value);
825                 continue;
826             }
827
828             if (token.type !== "specifier") {
829                 error("Unknown token type \"" + token.type + "\" found.");
830                 continue;
831             }
832
833             if (token.substitutionIndex >= substitutions.length) {
834                 // If there are not enough substitutions for the current substitutionIndex
835                 // just output the format specifier literally and move on.
836                 error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
837                 result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
838                 continue;
839             }
840
841             usedSubstitutionIndexes[token.substitutionIndex] = true;
842
843             if (!(token.specifier in formatters)) {
844                 // Encountered an unsupported format character, treat as a string.
845                 warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
846                 result = append(result, substitutions[token.substitutionIndex]);
847                 continue;
848             }
849
850             result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
851         }
852
853         var unusedSubstitutions = [];
854         for (var i = 0; i < substitutions.length; ++i) {
855             if (i in usedSubstitutionIndexes)
856                 continue;
857             unusedSubstitutions.push(substitutions[i]);
858         }
859
860         return {formattedResult: result, unusedSubstitutions};
861     }
862 });
863
864 Object.defineProperty(String.prototype, "format",
865 {
866     value: function()
867     {
868         return String.format(this, arguments, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
869     }
870 });
871
872 Object.defineProperty(String.prototype, "insertWordBreakCharacters",
873 {
874     value: function()
875     {
876         // Add zero width spaces after characters that are good to break after.
877         // Otherwise a string with no spaces will not break and overflow its container.
878         // This is mainly used on URL strings, so the characters are tailored for URLs.
879         return this.replace(/([\/;:\)\]\}&?])/g, "$1\u200b");
880     }
881 });
882
883 Object.defineProperty(String.prototype, "removeWordBreakCharacters",
884 {
885     value: function()
886     {
887         // Undoes what insertWordBreakCharacters did.
888         return this.replace(/\u200b/g, "");
889     }
890 });
891
892 Object.defineProperty(String.prototype, "getMatchingIndexes",
893 {
894     value: function(needle)
895     {
896         var indexesOfNeedle = [];
897         var index = this.indexOf(needle);
898
899         while (index >= 0) {
900             indexesOfNeedle.push(index);
901             index = this.indexOf(needle, index + 1);
902         }
903
904         return indexesOfNeedle;
905     }
906 });
907
908 Object.defineProperty(String.prototype, "levenshteinDistance",
909 {
910     value: function(s)
911     {
912         var m = this.length;
913         var n = s.length;
914         var d = new Array(m + 1);
915
916         for (var i = 0; i <= m; ++i) {
917             d[i] = new Array(n + 1);
918             d[i][0] = i;
919         }
920
921         for (var j = 0; j <= n; ++j)
922             d[0][j] = j;
923
924         for (var j = 1; j <= n; ++j) {
925             for (var i = 1; i <= m; ++i) {
926                 if (this[i - 1] === s[j - 1])
927                     d[i][j] = d[i - 1][j - 1];
928                 else {
929                     var deletion = d[i - 1][j] + 1;
930                     var insertion = d[i][j - 1] + 1;
931                     var substitution = d[i - 1][j - 1] + 1;
932                     d[i][j] = Math.min(deletion, insertion, substitution);
933                 }
934             }
935         }
936
937         return d[m][n];
938     }
939 });
940
941 Object.defineProperty(String.prototype, "toCamelCase",
942 {
943     value: function()
944     {
945         return this.toLowerCase().replace(/[^\w]+(\w)/g, (match, group) => group.toUpperCase());
946     }
947 });
948
949 Object.defineProperty(String.prototype, "hasMatchingEscapedQuotes",
950 {
951     value: function()
952     {
953         return /^\"(?:[^\"\\]|\\.)*\"$/.test(this) || /^\'(?:[^\'\\]|\\.)*\'$/.test(this);
954     }
955 });
956
957 Object.defineProperty(Math, "roundTo",
958 {
959     value: function(num, step)
960     {
961         return Math.round(num / step) * step;
962     }
963 });
964
965 Object.defineProperty(Number, "constrain",
966 {
967     value: function(num, min, max)
968     {
969         if (max < min)
970             return min;
971
972         if (num < min)
973             num = min;
974         else if (num > max)
975             num = max;
976         return num;
977     }
978 });
979
980 Object.defineProperty(Number, "percentageString",
981 {
982     value: function(fraction, precision = 1)
983     {
984         console.assert(fraction >= 0 && fraction <= 1);
985         return fraction.toLocaleString(undefined, {minimumFractionDigits: precision, style: "percent"});
986     }
987 });
988
989 Object.defineProperty(Number, "secondsToMillisecondsString",
990 {
991     value: function(seconds, higherResolution)
992     {
993         let ms = seconds * 1000;
994
995         if (higherResolution)
996             return WebInspector.UIString("%.2fms").format(ms);
997         return WebInspector.UIString("%.1fms").format(ms);
998     }
999 });
1000
1001 Object.defineProperty(Number, "secondsToString",
1002 {
1003     value: function(seconds, higherResolution)
1004     {
1005         let ms = seconds * 1000;
1006         if (!ms)
1007             return WebInspector.UIString("%.0fms").format(0);
1008
1009         if (Math.abs(ms) < 10) {
1010             if (higherResolution)
1011                 return WebInspector.UIString("%.3fms").format(ms);
1012             return WebInspector.UIString("%.2fms").format(ms);
1013         }
1014
1015         if (Math.abs(ms) < 100) {
1016             if (higherResolution)
1017                 return WebInspector.UIString("%.2fms").format(ms);
1018             return WebInspector.UIString("%.1fms").format(ms);
1019         }
1020
1021         if (Math.abs(ms) < 1000) {
1022             if (higherResolution)
1023                 return WebInspector.UIString("%.1fms").format(ms);
1024             return WebInspector.UIString("%.0fms").format(ms);
1025         }
1026
1027         // Do not go over seconds when in high resolution mode.
1028         if (higherResolution || Math.abs(seconds) < 60)
1029             return WebInspector.UIString("%.2fs").format(seconds);
1030
1031         let minutes = seconds / 60;
1032         if (Math.abs(minutes) < 60)
1033             return WebInspector.UIString("%.1fmin").format(minutes);
1034
1035         let hours = minutes / 60;
1036         if (Math.abs(hours) < 24)
1037             return WebInspector.UIString("%.1fhrs").format(hours);
1038
1039         let days = hours / 24;
1040         return WebInspector.UIString("%.1f days").format(days);
1041     }
1042 });
1043
1044 Object.defineProperty(Number, "bytesToString",
1045 {
1046     value: function(bytes, higherResolution)
1047     {
1048         if (higherResolution === undefined)
1049             higherResolution = true;
1050
1051         if (Math.abs(bytes) < 1024)
1052             return WebInspector.UIString("%.0f B").format(bytes);
1053
1054         let kilobytes = bytes / 1024;
1055         if (Math.abs(kilobytes) < 1024) {
1056             if (higherResolution || Math.abs(kilobytes) < 10)
1057                 return WebInspector.UIString("%.2f KB").format(kilobytes);
1058             return WebInspector.UIString("%.1f KB").format(kilobytes);
1059         }
1060
1061         let megabytes = kilobytes / 1024;
1062         if (higherResolution || Math.abs(megabytes) < 10)
1063             return WebInspector.UIString("%.2f MB").format(megabytes);
1064         return WebInspector.UIString("%.1f MB").format(megabytes);
1065     }
1066 });
1067
1068 Object.defineProperty(Number, "abbreviate",
1069 {
1070     value: function(num)
1071     {
1072         if (num < 1000)
1073             return num;
1074
1075         if (num < 1000000)
1076             return WebInspector.UIString("%.1fK").format(Math.round(num / 100) / 10);
1077
1078         if (num < 1000000000)
1079             return WebInspector.UIString("%.1fM").format(Math.round(num / 100000) / 10);
1080
1081         return WebInspector.UIString("%.1fB").format(Math.round(num / 100000000) / 10);
1082     }
1083 });
1084
1085 Object.defineProperty(Number.prototype, "maxDecimals",
1086 {
1087     value(decimals)
1088     {
1089         let power = 10 ** decimals;
1090         return Math.round(this * power) / power;
1091     }
1092 });
1093
1094 Object.defineProperty(Uint32Array, "isLittleEndian",
1095 {
1096     value: function()
1097     {
1098         if ("_isLittleEndian" in this)
1099             return this._isLittleEndian;
1100
1101         var buffer = new ArrayBuffer(4);
1102         var longData = new Uint32Array(buffer);
1103         var data = new Uint8Array(buffer);
1104
1105         longData[0] = 0x0a0b0c0d;
1106
1107         this._isLittleEndian = data[0] === 0x0d && data[1] === 0x0c && data[2] === 0x0b && data[3] === 0x0a;
1108
1109         return this._isLittleEndian;
1110     }
1111 });
1112
1113 function isEmptyObject(object)
1114 {
1115     for (var property in object)
1116         return false;
1117     return true;
1118 }
1119
1120 function isEnterKey(event)
1121 {
1122     // Check if this is an IME event.
1123     return event.keyCode !== 229 && event.keyIdentifier === "Enter";
1124 }
1125
1126 function resolveDotsInPath(path)
1127 {
1128     if (!path)
1129         return path;
1130
1131     if (path.indexOf("./") === -1)
1132         return path;
1133
1134     console.assert(path.charAt(0) === "/");
1135
1136     var result = [];
1137
1138     var components = path.split("/");
1139     for (var i = 0; i < components.length; ++i) {
1140         var component = components[i];
1141
1142         // Skip over "./".
1143         if (component === ".")
1144             continue;
1145
1146         // Rewind one component for "../".
1147         if (component === "..") {
1148             if (result.length === 1)
1149                 continue;
1150             result.pop();
1151             continue;
1152         }
1153
1154         result.push(component);
1155     }
1156
1157     return result.join("/");
1158 }
1159
1160 function parseMIMEType(fullMimeType)
1161 {
1162     if (!fullMimeType)
1163         return {type: fullMimeType, boundary: null, encoding: null};
1164
1165     var typeParts = fullMimeType.split(/\s*;\s*/);
1166     console.assert(typeParts.length >= 1);
1167
1168     var type = typeParts[0];
1169     var boundary = null;
1170     var encoding = null;
1171
1172     for (var i = 1; i < typeParts.length; ++i) {
1173         var subparts = typeParts[i].split(/\s*=\s*/);
1174         if (subparts.length !== 2)
1175             continue;
1176
1177         if (subparts[0].toLowerCase() === "boundary")
1178             boundary = subparts[1];
1179         else if (subparts[0].toLowerCase() === "charset")
1180             encoding = subparts[1].replace("^\"|\"$", ""); // Trim quotes.
1181     }
1182
1183     return {type, boundary: boundary || null, encoding: encoding || null};
1184 }
1185
1186 function simpleGlobStringToRegExp(globString, regExpFlags)
1187 {
1188     // Only supports "*" globs.
1189
1190     if (!globString)
1191         return null;
1192
1193     // Escape everything from String.prototype.escapeForRegExp except "*".
1194     var regexString = globString.escapeCharacters("^[]{}()\\.$+?|");
1195
1196     // Unescape all doubly escaped backslashes in front of escaped asterisks.
1197     // So "\\*" will become "\*" again, undoing escapeCharacters escaping of "\".
1198     // This makes "\*" match a literal "*" instead of using the "*" for globbing.
1199     regexString = regexString.replace(/\\\\\*/g, "\\*");
1200
1201     // The following regex doesn't match an asterisk that has a backslash in front.
1202     // It also catches consecutive asterisks so they collapse down when replaced.
1203     var unescapedAsteriskRegex = /(^|[^\\])\*+/g;
1204     if (unescapedAsteriskRegex.test(globString)) {
1205         // Replace all unescaped asterisks with ".*".
1206         regexString = regexString.replace(unescapedAsteriskRegex, "$1.*");
1207
1208         // Match edge boundaries when there is an asterisk to better meet the expectations
1209         // of the user. When someone types "*.js" they don't expect "foo.json" to match. They
1210         // would only expect that if they type "*.js*". We use \b (instead of ^ and $) to allow
1211         // matches inside paths or URLs, so "ba*.js" will match "foo/bar.js" but not "boo/bbar.js".
1212         // When there isn't an asterisk the regexString is just a substring search.
1213         regexString = "\\b" + regexString + "\\b";
1214     }
1215
1216     return new RegExp(regexString, regExpFlags);
1217 }
1218
1219 Object.defineProperty(Array.prototype, "lowerBound",
1220 {
1221     // Return index of the leftmost element that is equal or greater
1222     // than the specimen object. If there's no such element (i.e. all
1223     // elements are smaller than the specimen) returns array.length.
1224     // The function works for sorted array.
1225     value: function(object, comparator)
1226     {
1227         function defaultComparator(a, b)
1228         {
1229             return a - b;
1230         }
1231         comparator = comparator || defaultComparator;
1232         var l = 0;
1233         var r = this.length;
1234         while (l < r) {
1235             var m = (l + r) >> 1;
1236             if (comparator(object, this[m]) > 0)
1237                 l = m + 1;
1238             else
1239                 r = m;
1240         }
1241         return r;
1242     }
1243 });
1244
1245 Object.defineProperty(Array.prototype, "upperBound",
1246 {
1247     // Return index of the leftmost element that is greater
1248     // than the specimen object. If there's no such element (i.e. all
1249     // elements are smaller than the specimen) returns array.length.
1250     // The function works for sorted array.
1251     value: function(object, comparator)
1252     {
1253         function defaultComparator(a, b)
1254         {
1255             return a - b;
1256         }
1257         comparator = comparator || defaultComparator;
1258         var l = 0;
1259         var r = this.length;
1260         while (l < r) {
1261             var m = (l + r) >> 1;
1262             if (comparator(object, this[m]) >= 0)
1263                 l = m + 1;
1264             else
1265                 r = m;
1266         }
1267         return r;
1268     }
1269 });
1270
1271 Object.defineProperty(Array.prototype, "binaryIndexOf",
1272 {
1273     value: function(value, comparator)
1274     {
1275         var index = this.lowerBound(value, comparator);
1276         return index < this.length && comparator(value, this[index]) === 0 ? index : -1;
1277     }
1278 });
1279
1280 (function() {
1281     // The `debounce` function lets you call any function on an object with a delay
1282     // and if the function keeps getting called, the delay gets reset. Since `debounce`
1283     // returns a Proxy, you can cache it and call multiple functions with the same delay.
1284
1285     // Use: object.debounce(200).foo("Argument 1", "Argument 2")
1286     // Note: The last call's arguments get used for the delayed call.
1287
1288     const debounceTimeoutSymbol = Symbol("debounce-timeout");
1289     const debounceSoonProxySymbol = Symbol("debounce-soon-proxy");
1290
1291     Object.defineProperty(Object.prototype, "soon",
1292     {
1293         get: function()
1294         {
1295             if (!this[debounceSoonProxySymbol])
1296                 this[debounceSoonProxySymbol] = this.debounce(0);
1297             return this[debounceSoonProxySymbol];
1298         }
1299     });
1300
1301     Object.defineProperty(Object.prototype, "debounce",
1302     {
1303         value: function(delay)
1304         {
1305             console.assert(delay >= 0);
1306
1307             return new Proxy(this, {
1308                 get(target, property, receiver) {
1309                     return (...args) => {
1310                         let original = target[property];
1311                         console.assert(typeof original === "function");
1312
1313                         if (original[debounceTimeoutSymbol])
1314                             clearTimeout(original[debounceTimeoutSymbol]);
1315
1316                         let performWork = () => {
1317                             original[debounceTimeoutSymbol] = undefined;
1318                             original.apply(target, args);
1319                         };
1320
1321                         original[debounceTimeoutSymbol] = setTimeout(performWork, delay);
1322                     };
1323                 }
1324             });
1325         }
1326     });
1327
1328     Object.defineProperty(Function.prototype, "cancelDebounce",
1329     {
1330         value: function()
1331         {
1332             if (!this[debounceTimeoutSymbol])
1333                 return;
1334
1335             clearTimeout(this[debounceTimeoutSymbol]);
1336             this[debounceTimeoutSymbol] = undefined;
1337         }
1338     });
1339
1340     const requestAnimationFrameSymbol = Symbol("peform-on-animation-frame");
1341     const requestAnimationFrameProxySymbol = Symbol("perform-on-animation-frame-proxy");
1342
1343     Object.defineProperty(Object.prototype, "onNextFrame",
1344     {
1345         get: function()
1346         {
1347             if (!this[requestAnimationFrameProxySymbol]) {
1348                 this[requestAnimationFrameProxySymbol] = new Proxy(this, {
1349                     get(target, property, receiver) {
1350                         return (...args) => {
1351                             let original = target[property];
1352                             console.assert(typeof original === "function");
1353
1354                             if (original[requestAnimationFrameSymbol])
1355                                 return;
1356
1357                             let performWork = () => {
1358                                 original[requestAnimationFrameSymbol] = undefined;
1359                                 original.apply(target, args);
1360                             };
1361
1362                             original[requestAnimationFrameSymbol] = requestAnimationFrame(performWork);
1363                         };
1364                     }
1365                 });
1366             }
1367
1368             return this[requestAnimationFrameProxySymbol];
1369         }
1370     });
1371 })();
1372
1373 function appendWebInspectorSourceURL(string)
1374 {
1375     if (string.includes("//# sourceURL"))
1376         return string;
1377     return "\n//# sourceURL=__WebInspectorInternal__\n" + string;
1378 }
1379
1380 function appendWebInspectorConsoleEvaluationSourceURL(string)
1381 {
1382     if (string.includes("//# sourceURL"))
1383         return string;
1384     return "\n//# sourceURL=__WebInspectorConsoleEvaluation__\n" + string;
1385 }
1386
1387 function isWebInspectorInternalScript(url)
1388 {
1389     return url === "__WebInspectorInternal__";
1390 }
1391
1392 function isWebInspectorConsoleEvaluationScript(url)
1393 {
1394     return url === "__WebInspectorConsoleEvaluation__";
1395 }
1396
1397 function isWebKitInjectedScript(url)
1398 {
1399     return url && url.startsWith("__InjectedScript_") && url.endsWith(".js");
1400 }
1401
1402 function isWebKitInternalScript(url)
1403 {
1404     if (isWebInspectorConsoleEvaluationScript(url))
1405         return false;
1406
1407     if (isWebKitInjectedScript(url))
1408         return true;
1409
1410     return url && url.startsWith("__Web") && url.endsWith("__");
1411 }
1412
1413 function isFunctionStringNativeCode(str)
1414 {
1415     return str.endsWith("{\n    [native code]\n}");
1416 }
1417
1418 function isTextLikelyMinified(content)
1419 {
1420     const autoFormatMaxCharactersToCheck = 5000;
1421     const autoFormatWhitespaceRatio = 0.2;
1422
1423     let whitespaceScore = 0;
1424     let size = Math.min(autoFormatMaxCharactersToCheck, content.length);
1425
1426     for (let i = 0; i < size; i++) {
1427         let char = content[i];
1428
1429         if (char === " ")
1430             whitespaceScore++;
1431         else if (char === "\t")
1432             whitespaceScore += 4;
1433         else if (char === "\n")
1434             whitespaceScore += 8;
1435     }
1436
1437     let ratio = whitespaceScore / size;
1438     return ratio < autoFormatWhitespaceRatio;
1439 }
1440
1441 function doubleQuotedString(str)
1442 {
1443     return "\"" + str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"";
1444 }
1445
1446 function insertionIndexForObjectInListSortedByFunction(object, list, comparator, insertionIndexAfter)
1447 {
1448     if (insertionIndexAfter) {
1449         return list.upperBound(object, comparator);
1450     } else {
1451         return list.lowerBound(object, comparator);
1452     }
1453 }
1454
1455 function insertObjectIntoSortedArray(object, array, comparator)
1456 {
1457     array.splice(insertionIndexForObjectInListSortedByFunction(object, array, comparator), 0, object);
1458 }
1459
1460 function decodeBase64ToBlob(base64Data, mimeType)
1461 {
1462     mimeType = mimeType || '';
1463
1464     const sliceSize = 1024;
1465     var byteCharacters = atob(base64Data);
1466     var bytesLength = byteCharacters.length;
1467     var slicesCount = Math.ceil(bytesLength / sliceSize);
1468     var byteArrays = new Array(slicesCount);
1469
1470     for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
1471         var begin = sliceIndex * sliceSize;
1472         var end = Math.min(begin + sliceSize, bytesLength);
1473
1474         var bytes = new Array(end - begin);
1475         for (var offset = begin, i = 0 ; offset < end; ++i, ++offset)
1476             bytes[i] = byteCharacters[offset].charCodeAt(0);
1477
1478         byteArrays[sliceIndex] = new Uint8Array(bytes);
1479     }
1480
1481     return new Blob(byteArrays, {type: mimeType});
1482 }
1483
1484 // FIXME: This can be removed when WEB_TIMING is enabled for all platforms.
1485 function timestamp()
1486 {
1487     return window.performance ? performance.now() : Date.now();
1488 }
1489
1490 if (!window.handlePromiseException) {
1491     window.handlePromiseException = function handlePromiseException(error)
1492     {
1493         console.error("Uncaught exception in Promise", error);
1494     };
1495 }