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
26Object.defineProperty(Object, "shallowCopy",
27{
28    value: function(object)
29    {
30        // Make a new object and copy all the key/values. The values are not copied.
31        var copy = {};
32        var keys = Object.keys(object);
33        for (var i = 0; i < keys.length; ++i)
34            copy[keys[i]] = object[keys[i]];
35        return copy;
36    }
37});
38
39Object.defineProperty(Object, "shallowEqual",
40{
41    value: function(a, b)
42    {
43        // Checks if two objects have the same top-level properties.
44
45        // Check for strict equality in case they are the same object.
46        if (a === b)
47            return true;
48
49        // Only objects can proceed. null is an object, but Object.keys throws for null.
50        if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
51            return false;
52
53        var aKeys = Object.keys(a);
54        var bKeys = Object.keys(b);
55
56        // Check that each object has the same number of keys.
57        if (aKeys.length !== bKeys.length)
58            return false;
59
60        // Check if all the keys and their values are equal.
61        for (var i = 0; i < aKeys.length; ++i) {
62            // Check that b has the same key as a.
63            if (!(aKeys[i] in b))
64                return false;
65
66            // Check that the values are strict equal since this is only
67            // a shallow check, not a recursive one.
68            if (a[aKeys[i]] !== b[aKeys[i]])
69                return false;
70        }
71
72        return true;
73    }
74});
75
76Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey",
77{
78    value: function(key)
79    {
80        if (this.hasOwnProperty(key))
81            return this[key];
82
83        var lowerCaseKey = key.toLowerCase();
84        for (var currentKey in this) {
85            if (currentKey.toLowerCase() === lowerCaseKey)
86                return this[currentKey];
87        }
88
89        return undefined;
90    }
91});
92
93Object.defineProperty(Map.prototype, "take",
94{
95    value: function(key)
96    {
97        var deletedValue = this.get(key);
98        this.delete(key);
99        return deletedValue;
100    }
101});
102
103Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithClass",
104{
105    value: function(className)
106    {
107        for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
108            if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className))
109                return node;
110        return null;
111    }
112});
113
114Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeNameInArray",
115{
116    value: function(nameArray)
117    {
118        var lowerCaseNameArray = nameArray.map(function(name) { return name.toLowerCase() });
119        for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) {
120            for (var i = 0; i < nameArray.length; ++i) {
121                if (node.nodeName.toLowerCase() === lowerCaseNameArray[i])
122                    return node;
123            }
124        }
125
126        return null;
127    }
128});
129
130Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeName",
131{
132    value: function(nodeName)
133    {
134        return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
135    }
136});
137
138Object.defineProperty(Node.prototype, "isAncestor",
139{
140    value: function(node)
141    {
142        if (!node)
143            return false;
144
145        var currentNode = node.parentNode;
146        while (currentNode) {
147            if (this === currentNode)
148                return true;
149            currentNode = currentNode.parentNode;
150        }
151
152        return false;
153    }
154});
155
156Object.defineProperty(Node.prototype, "isDescendant",
157{
158    value: function(descendant)
159    {
160        return !!descendant && descendant.isAncestor(this);
161    }
162});
163
164
165Object.defineProperty(Node.prototype, "isSelfOrAncestor",
166{
167    value: function(node)
168    {
169        return !!node && (node === this || this.isAncestor(node));
170    }
171});
172
173
174Object.defineProperty(Node.prototype, "isSelfOrDescendant",
175{
176    value: function(node)
177    {
178        return !!node && (node === this || this.isDescendant(node));
179    }
180});
181
182Object.defineProperty(Node.prototype, "traverseNextNode",
183{
184    value: function(stayWithin)
185    {
186        var node = this.firstChild;
187        if (node)
188            return node;
189
190        if (stayWithin && this === stayWithin)
191            return null;
192
193        node = this.nextSibling;
194        if (node)
195            return node;
196
197        node = this;
198        while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin))
199            node = node.parentNode;
200        if (!node)
201            return null;
202
203        return node.nextSibling;
204    }
205});
206
207Object.defineProperty(Node.prototype, "traversePreviousNode",
208{
209    value: function(stayWithin)
210    {
211       if (stayWithin && this === stayWithin)
212            return null;
213        var node = this.previousSibling;
214        while (node && node.lastChild)
215            node = node.lastChild;
216        if (node)
217            return node;
218        return this.parentNode;
219    }
220});
221
222
223Object.defineProperty(Node.prototype, "rangeOfWord",
224{
225    value: function(offset, stopCharacters, stayWithinNode, direction)
226    {
227        var startNode;
228        var startOffset = 0;
229        var endNode;
230        var endOffset = 0;
231
232        if (!stayWithinNode)
233            stayWithinNode = this;
234
235        if (!direction || direction === "backward" || direction === "both") {
236            var node = this;
237            while (node) {
238                if (node === stayWithinNode) {
239                    if (!startNode)
240                        startNode = stayWithinNode;
241                    break;
242                }
243
244                if (node.nodeType === Node.TEXT_NODE) {
245                    var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
246                    for (var i = start; i >= 0; --i) {
247                        if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
248                            startNode = node;
249                            startOffset = i + 1;
250                            break;
251                        }
252                    }
253                }
254
255                if (startNode)
256                    break;
257
258                node = node.traversePreviousNode(stayWithinNode);
259            }
260
261            if (!startNode) {
262                startNode = stayWithinNode;
263                startOffset = 0;
264            }
265        } else {
266            startNode = this;
267            startOffset = offset;
268        }
269
270        if (!direction || direction === "forward" || direction === "both") {
271            node = this;
272            while (node) {
273                if (node === stayWithinNode) {
274                    if (!endNode)
275                        endNode = stayWithinNode;
276                    break;
277                }
278
279                if (node.nodeType === Node.TEXT_NODE) {
280                    var start = (node === this ? offset : 0);
281                    for (var i = start; i < node.nodeValue.length; ++i) {
282                        if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
283                            endNode = node;
284                            endOffset = i;
285                            break;
286                        }
287                    }
288                }
289
290                if (endNode)
291                    break;
292
293                node = node.traverseNextNode(stayWithinNode);
294            }
295
296            if (!endNode) {
297                endNode = stayWithinNode;
298                endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
299            }
300        } else {
301            endNode = this;
302            endOffset = offset;
303        }
304
305        var result = this.ownerDocument.createRange();
306        result.setStart(startNode, startOffset);
307        result.setEnd(endNode, endOffset);
308
309        return result;
310
311    }
312});
313
314Object.defineProperty(Element.prototype, "totalOffsetLeft",
315{
316    get: function()
317    {
318        return this.getBoundingClientRect().left;
319    }
320});
321
322Object.defineProperty(Element.prototype, "totalOffsetTop",
323{
324    get: function()
325    {
326        return this.getBoundingClientRect().top;
327    }
328});
329
330Object.defineProperty(Element.prototype, "removeChildren",
331{
332    value: function()
333    {
334        // This has been tested to be the fastest removal method.
335        if (this.firstChild)
336            this.textContent = "";
337    }
338});
339
340Object.defineProperty(Element.prototype, "isInsertionCaretInside",
341{
342    value: function()
343    {
344        var selection = window.getSelection();
345        if (!selection.rangeCount || !selection.isCollapsed)
346            return false;
347        var selectionRange = selection.getRangeAt(0);
348        return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this);
349    }
350});
351
352Object.defineProperty(Element.prototype, "removeMatchingStyleClasses",
353{
354    value: function(classNameRegex)
355    {
356        var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
357        if (regex.test(this.className))
358            this.className = this.className.replace(regex, " ");
359    }
360});
361
362Object.defineProperty(Element.prototype, "createChild",
363{
364    value: function(elementName, className)
365    {
366        var element = this.ownerDocument.createElement(elementName);
367        if (className)
368            element.className = className;
369        this.appendChild(element);
370        return element;
371    }
372});
373
374Object.defineProperty(Element.prototype, "isScrolledToBottom",
375{
376    value: function()
377    {
378        // This code works only for 0-width border
379        return this.scrollTop + this.clientHeight === this.scrollHeight;
380    }
381});
382
383Object.defineProperty(Element.prototype, "recalculateStyles",
384{
385    value: function()
386    {
387        this.ownerDocument.defaultView.getComputedStyle(this);
388    }
389});
390
391Object.defineProperty(DocumentFragment.prototype, "createChild",
392{
393    value: Element.prototype.createChild
394});
395
396Object.defineProperty(String.prototype, "contains",
397{
398    value: function(value)
399    {
400        return this.indexOf(value) !== -1;
401    }
402});
403
404Object.defineProperty(Array.prototype, "contains",
405{
406    value: function(value)
407    {
408        return this.indexOf(value) !== -1;
409    }
410});
411
412Object.defineProperty(Array.prototype, "lastValue",
413{
414    get: function()
415    {
416        if (!this.length)
417            return undefined;
418        return this[this.length - 1];
419    }
420});
421
422Object.defineProperty(Array.prototype, "remove",
423{
424    value: function(value, onlyFirst)
425    {
426        for (var i = this.length - 1; i >= 0; --i) {
427            if (this[i] === value) {
428                this.splice(i, 1);
429                if (onlyFirst)
430                    return;
431            }
432        }
433    }
434});
435
436Object.defineProperty(Array.prototype, "keySet",
437{
438    value: function()
439    {
440        var keys = {};
441        for (var i = 0; i < this.length; ++i)
442            keys[this[i]] = true;
443        return keys;
444    }
445});
446
447Object.defineProperty(String.prototype, "trimMiddle",
448{
449    value: function(maxLength)
450    {
451        if (this.length <= maxLength)
452            return this;
453        var leftHalf = maxLength >> 1;
454        var rightHalf = maxLength - leftHalf - 1;
455        return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf);
456    }
457});
458
459Object.defineProperty(String.prototype, "trimEnd",
460{
461    value: function(maxLength)
462    {
463        if (this.length <= maxLength)
464            return this;
465        return this.substr(0, maxLength - 1) + "\u2026";
466    }
467});
468
469Object.defineProperty(String.prototype, "collapseWhitespace",
470{
471    value: function()
472    {
473        return this.replace(/[\s\xA0]+/g, " ");
474    }
475});
476
477Object.defineProperty(String.prototype, "escapeCharacters",
478{
479    value: function(chars)
480    {
481        var foundChar = false;
482        for (var i = 0; i < chars.length; ++i) {
483            if (this.indexOf(chars.charAt(i)) !== -1) {
484                foundChar = true;
485                break;
486            }
487        }
488
489        if (!foundChar)
490            return this;
491
492        var result = "";
493        for (var i = 0; i < this.length; ++i) {
494            if (chars.indexOf(this.charAt(i)) !== -1)
495                result += "\\";
496            result += this.charAt(i);
497        }
498
499        return result;
500    }
501});
502
503Object.defineProperty(String.prototype, "escapeForRegExp",
504{
505    value: function()
506    {
507        return this.escapeCharacters("^[]{}()\\.$*+?|");
508    }
509});
510
511Object.defineProperty(String.prototype, "capitalize",
512{
513    value: function()
514    {
515        return this.charAt(0).toUpperCase() + this.slice(1);
516    }
517});
518
519Object.defineProperty(String, "tokenizeFormatString",
520{
521    value: function(format)
522    {
523        var tokens = [];
524        var substitutionIndex = 0;
525
526        function addStringToken(str)
527        {
528            tokens.push({ type: "string", value: str });
529        }
530
531        function addSpecifierToken(specifier, precision, substitutionIndex)
532        {
533            tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
534        }
535
536        var index = 0;
537        for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
538            addStringToken(format.substring(index, precentIndex));
539            index = precentIndex + 1;
540
541            if (format[index] === "%") {
542                addStringToken("%");
543                ++index;
544                continue;
545            }
546
547            if (!isNaN(format[index])) {
548                // The first character is a number, it might be a substitution index.
549                var number = parseInt(format.substring(index), 10);
550                while (!isNaN(format[index]))
551                    ++index;
552
553                // If the number is greater than zero and ends with a "$",
554                // then this is a substitution index.
555                if (number > 0 && format[index] === "$") {
556                    substitutionIndex = (number - 1);
557                    ++index;
558                }
559            }
560
561            var precision = -1;
562            if (format[index] === ".") {
563                // This is a precision specifier. If no digit follows the ".",
564                // then the precision should be zero.
565                ++index;
566
567                precision = parseInt(format.substring(index), 10);
568                if (isNaN(precision))
569                    precision = 0;
570
571                while (!isNaN(format[index]))
572                    ++index;
573            }
574
575            addSpecifierToken(format[index], precision, substitutionIndex);
576
577            ++substitutionIndex;
578            ++index;
579        }
580
581        addStringToken(format.substring(index));
582
583        return tokens;
584    }
585});
586
587Object.defineProperty(String.prototype, "startsWith",
588{
589    value: function(string)
590    {
591        return this.lastIndexOf(string, 0) === 0;
592    }
593});
594
595Object.defineProperty(String.prototype, "hash",
596{
597    get: function()
598    {
599        // Matches the wtf/StringHasher.h (SuperFastHash) algorithm.
600
601        // Arbitrary start value to avoid mapping all 0's to all 0's.
602        const stringHashingStartValue = 0x9e3779b9;
603
604        var result = stringHashingStartValue;
605        var pendingCharacter = null;
606        for (var i = 0; i < this.length; ++i) {
607            var currentCharacter = this[i].charCodeAt(0);
608            if (pendingCharacter === null) {
609                pendingCharacter = currentCharacter;
610                continue;
611            }
612
613            result += pendingCharacter;
614            result = (result << 16) ^ ((currentCharacter << 11) ^ result);
615            result += result >> 11;
616
617            pendingCharacter = null;
618        }
619
620        // Handle the last character in odd length strings.
621        if (pendingCharacter !== null) {
622            result += pendingCharacter;
623            result ^= result << 11;
624            result += result >> 17;
625        }
626
627        // Force "avalanching" of final 31 bits.
628        result ^= result << 3;
629        result += result >> 5;
630        result ^= result << 2;
631        result += result >> 15;
632        result ^= result << 10;
633
634        // Prevent 0 and negative results.
635        return (0xffffffff + result + 1).toString(36);
636    }
637});
638
639Object.defineProperty(String, "standardFormatters",
640{
641    value: {
642        d: function(substitution)
643        {
644            return !isNaN(substitution) ? substitution : 0;
645        },
646
647        f: function(substitution, token)
648        {
649            if (substitution && token.precision > -1)
650                substitution = substitution.toFixed(token.precision);
651            return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
652        },
653
654        s: function(substitution)
655        {
656            return substitution;
657        }
658    }
659});
660
661Object.defineProperty(String, "format",
662{
663    value: function(format, substitutions, formatters, initialValue, append)
664    {
665        if (!format || !substitutions || !substitutions.length)
666            return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };
667
668        function prettyFunctionName()
669        {
670            return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")";
671        }
672
673        function warn(msg)
674        {
675            console.warn(prettyFunctionName() + ": " + msg);
676        }
677
678        function error(msg)
679        {
680            console.error(prettyFunctionName() + ": " + msg);
681        }
682
683        var result = initialValue;
684        var tokens = String.tokenizeFormatString(format);
685        var usedSubstitutionIndexes = {};
686
687        for (var i = 0; i < tokens.length; ++i) {
688            var token = tokens[i];
689
690            if (token.type === "string") {
691                result = append(result, token.value);
692                continue;
693            }
694
695            if (token.type !== "specifier") {
696                error("Unknown token type \"" + token.type + "\" found.");
697                continue;
698            }
699
700            if (token.substitutionIndex >= substitutions.length) {
701                // If there are not enough substitutions for the current substitutionIndex
702                // just output the format specifier literally and move on.
703                error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
704                result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
705                continue;
706            }
707
708            usedSubstitutionIndexes[token.substitutionIndex] = true;
709
710            if (!(token.specifier in formatters)) {
711                // Encountered an unsupported format character, treat as a string.
712                warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
713                result = append(result, substitutions[token.substitutionIndex]);
714                continue;
715            }
716
717            result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
718        }
719
720        var unusedSubstitutions = [];
721        for (var i = 0; i < substitutions.length; ++i) {
722            if (i in usedSubstitutionIndexes)
723                continue;
724            unusedSubstitutions.push(substitutions[i]);
725        }
726
727        return {formattedResult: result, unusedSubstitutions: unusedSubstitutions};
728    }
729});
730
731Object.defineProperty(String.prototype, "format",
732{
733    value: function()
734    {
735        return String.format(this, arguments, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
736    }
737});
738
739Object.defineProperty(String.prototype, "insertWordBreakCharacters",
740{
741    value: function()
742    {
743        // Add zero width spaces after characters that are good to break after.
744        // Otherwise a string with no spaces will not break and overflow its container.
745        // This is mainly used on URL strings, so the characters are tailored for URLs.
746        return this.replace(/([\/;:\)\]\}&?])/g, "$1\u200b");
747    }
748});
749
750Object.defineProperty(String.prototype, "removeWordBreakCharacters",
751{
752    value: function()
753    {
754        // Undoes what insertWordBreakCharacters did.
755        return this.replace(/\u200b/g, "");
756    }
757});
758
759Object.defineProperty(Number, "constrain",
760{
761    value: function(num, min, max)
762    {
763        if (num < min)
764            num = min;
765        else if (num > max)
766            num = max;
767        return num;
768    }
769});
770
771Object.defineProperty(Number, "secondsToString",
772{
773    value: function(seconds, higherResolution)
774    {
775        var ms = seconds * 1000;
776
777        if (higherResolution && Math.abs(ms) < 10)
778            return WebInspector.UIString("%.3fms").format(ms);
779        else if (Math.abs(ms) < 10)
780            return WebInspector.UIString("%.2fms").format(ms);
781
782        if (higherResolution && Math.abs(ms) < 100)
783            return WebInspector.UIString("%.2fms").format(ms);
784        else if (Math.abs(ms) < 100)
785            return WebInspector.UIString("%.1fms").format(ms);
786
787        if (higherResolution && Math.abs(ms) < 1000)
788            return WebInspector.UIString("%.1fms").format(ms);
789        else if (Math.abs(ms) < 1000)
790            return WebInspector.UIString("%.0fms").format(ms);
791
792        if (Math.abs(seconds) < 60)
793            return WebInspector.UIString("%.2fs").format(seconds);
794
795        var minutes = seconds / 60;
796        if (Math.abs(minutes) < 60)
797            return WebInspector.UIString("%.1fmin").format(minutes);
798
799        var hours = minutes / 60;
800        if (Math.abs(hours) < 24)
801            return WebInspector.UIString("%.1fhrs").format(hours);
802
803        var days = hours / 24;
804        return WebInspector.UIString("%.1f days").format(days);
805    }
806});
807
808Object.defineProperty(Number, "bytesToString",
809{
810    value: function(bytes, higherResolution)
811    {
812        if (higherResolution === undefined)
813            higherResolution = true;
814
815        if (Math.abs(bytes) < 1024)
816            return WebInspector.UIString("%.0f B").format(bytes);
817
818        var kilobytes = bytes / 1024;
819        if (Math.abs(kilobytes) < 10 || (higherResolution && Math.abs(kilobytes) < 1024))
820            return WebInspector.UIString("%.2f KB").format(kilobytes);
821        else if (Math.abs(kilobytes) < 1024)
822            return WebInspector.UIString("%.1f KB").format(kilobytes);
823
824        var megabytes = kilobytes / 1024;
825        if (higherResolution || Math.abs(megabytes) < 10)
826            return WebInspector.UIString("%.2f MB").format(megabytes);
827        else
828            return WebInspector.UIString("%.1f MB").format(megabytes);
829    }
830});
831
832Object.defineProperty(Uint32Array, "isLittleEndian",
833{
834    value: function()
835    {
836        if ("_isLittleEndian" in this)
837            return this._isLittleEndian;
838
839        var buffer = new ArrayBuffer(4);
840        var longData = new Uint32Array(buffer);
841        var data = new Uint8Array(buffer);
842
843        longData[0] = 0x0a0b0c0d;
844
845        this._isLittleEndian = data[0] === 0x0d && data[1] === 0x0c && data[2] === 0x0b && data[3] === 0x0a;
846
847        return this._isLittleEndian;
848    }
849});
850
851function isEmptyObject(object)
852{
853    for (var property in object)
854        return false;
855    return true;
856}
857
858function isEnterKey(event)
859{
860    // Check if this is an IME event.
861    return event.keyCode !== 229 && event.keyIdentifier === "Enter";
862}
863
864function resolveDotsInPath(path)
865{
866    if (!path)
867        return path;
868
869    if (path.indexOf("./") === -1)
870        return path;
871
872    console.assert(path.charAt(0) === "/");
873
874    var result = [];
875
876    var components = path.split("/");
877    for (var i = 0; i < components.length; ++i) {
878        var component = components[i];
879
880        // Skip over "./".
881        if (component === ".")
882            continue;
883
884        // Rewind one component for "../".
885        if (component === "..") {
886            if (result.length === 1)
887                continue;
888            result.pop();
889            continue;
890        }
891
892        result.push(component);
893    }
894
895    return result.join("/");
896}
897
898function parseMIMEType(fullMimeType)
899{
900    if (!fullMimeType)
901        return {type: fullMimeType, boundary: null, encoding: null};
902
903    var typeParts = fullMimeType.split(/\s*;\s*/);
904    console.assert(typeParts.length >= 1);
905
906    var type = typeParts[0];
907    var boundary = null;
908    var encoding = null;
909
910    for (var i = 1; i < typeParts.length; ++i) {
911        var subparts = typeParts[i].split(/\s*=\s*/);
912        if (subparts.length !== 2)
913            continue;
914
915        if (subparts[0].toLowerCase() === "boundary")
916            boundary = subparts[1];
917        else if (subparts[0].toLowerCase() === "charset")
918            encoding = subparts[1].replace("^\"|\"$", ""); // Trim quotes.
919    }
920
921    return {type: type, boundary: boundary || null, encoding: encoding || null};
922}
923
924function simpleGlobStringToRegExp(globString, regExpFlags)
925{
926    // Only supports "*" globs.
927
928    if (!globString)
929        return null;
930
931    // Escape everything from String.prototype.escapeForRegExp except "*".
932    var regexString = globString.escapeCharacters("^[]{}()\\.$+?|");
933
934    // Unescape all doubly escaped backslashes in front of escaped asterisks.
935    // So "\\*" will become "\*" again, undoing escapeCharacters escaping of "\".
936    // This makes "\*" match a literal "*" instead of using the "*" for globbing.
937    regexString = regexString.replace(/\\\\\*/g, "\\*");
938
939    // The following regex doesn't match an asterisk that has a backslash in front.
940    // It also catches consecutive asterisks so they collapse down when replaced.
941    var unescapedAsteriskRegex = /(^|[^\\])\*+/g;
942    if (unescapedAsteriskRegex.test(globString)) {
943        // Replace all unescaped asterisks with ".*".
944        regexString = regexString.replace(unescapedAsteriskRegex, "$1.*");
945
946        // Match edge boundaries when there is an asterisk to better meet the expectations
947        // of the user. When someone types "*.js" they don't expect "foo.json" to match. They
948        // would only expect that if they type "*.js*". We use \b (instead of ^ and $) to allow
949        // matches inside paths or URLs, so "ba*.js" will match "foo/bar.js" but not "boo/bbar.js".
950        // When there isn't an asterisk the regexString is just a substring search.
951        regexString = "\\b" + regexString + "\\b";
952    }
953
954    return new RegExp(regexString, regExpFlags);
955}
956
957Object.defineProperty(Array.prototype, "lowerBound",
958{
959    // Return index of the leftmost element that is equal or greater
960    // than the specimen object. If there's no such element (i.e. all
961    // elements are smaller than the specimen) returns array.length.
962    // The function works for sorted array.
963    value: function(object, comparator)
964    {
965        function defaultComparator(a, b)
966        {
967            return a - b;
968        }
969        comparator = comparator || defaultComparator;
970        var l = 0;
971        var r = this.length;
972        while (l < r) {
973            var m = (l + r) >> 1;
974            if (comparator(object, this[m]) > 0)
975                l = m + 1;
976            else
977                r = m;
978        }
979        return r;
980    }
981});
982
983Object.defineProperty(Array.prototype, "upperBound",
984{
985    // Return index of the leftmost element that is greater
986    // than the specimen object. If there's no such element (i.e. all
987    // elements are smaller than the specimen) returns array.length.
988    // The function works for sorted array.
989    value: function(object, comparator)
990    {
991        function defaultComparator(a, b)
992        {
993            return a - b;
994        }
995        comparator = comparator || defaultComparator;
996        var l = 0;
997        var r = this.length;
998        while (l < r) {
999            var m = (l + r) >> 1;
1000            if (comparator(object, this[m]) >= 0)
1001                l = m + 1;
1002            else
1003                r = m;
1004        }
1005        return r;
1006    }
1007});
1008
1009Object.defineProperty(Array.prototype, "binaryIndexOf",
1010{
1011    value: function(value, comparator)
1012    {
1013        var index = this.lowerBound(value, comparator);
1014        return index < this.length && comparator(value, this[index]) === 0 ? index : -1;
1015    }
1016});
1017
1018function clamp(min, value, max)
1019{
1020    return Math.min(Math.max(min, value), max);
1021}
1022
1023function insertionIndexForObjectInListSortedByFunction(object, list, comparator, insertionIndexAfter)
1024{
1025    if (insertionIndexAfter) {
1026        return list.upperBound(object, comparator);
1027    } else {
1028        return list.lowerBound(object, comparator);
1029    }
1030}
1031
1032function insertObjectIntoSortedArray(object, array, comparator)
1033{
1034    array.splice(insertionIndexForObjectInListSortedByFunction(object, array, comparator), 0, object);
1035}
1036