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