tizen beta release
[framework/web/webkit-efl.git] / debian / tmp / usr / share / ewebkit-0 / webinspector / utilities.js
1 /*
2  * Copyright (C) 2007 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  * Contains diff method based on Javascript Diff Algorithm By John Resig
29  * http://ejohn.org/files/jsdiff.js (released under the MIT license).
30  */
31
32 Function.prototype.bind = function(thisObject)
33 {
34     var func = this;
35     var args = Array.prototype.slice.call(arguments, 1);
36     function bound()
37     {
38         return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0)));
39     }
40     bound.toString = function() {
41         return "bound: " + func;
42     };
43     return bound;
44 }
45
46 /**
47  * @param {string=} direction
48  */
49 Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction)
50 {
51     var startNode;
52     var startOffset = 0;
53     var endNode;
54     var endOffset = 0;
55
56     if (!stayWithinNode)
57         stayWithinNode = this;
58
59     if (!direction || direction === "backward" || direction === "both") {
60         var node = this;
61         while (node) {
62             if (node === stayWithinNode) {
63                 if (!startNode)
64                     startNode = stayWithinNode;
65                 break;
66             }
67
68             if (node.nodeType === Node.TEXT_NODE) {
69                 var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1));
70                 for (var i = start; i >= 0; --i) {
71                     if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
72                         startNode = node;
73                         startOffset = i + 1;
74                         break;
75                     }
76                 }
77             }
78
79             if (startNode)
80                 break;
81
82             node = node.traversePreviousNode(stayWithinNode);
83         }
84
85         if (!startNode) {
86             startNode = stayWithinNode;
87             startOffset = 0;
88         }
89     } else {
90         startNode = this;
91         startOffset = offset;
92     }
93
94     if (!direction || direction === "forward" || direction === "both") {
95         node = this;
96         while (node) {
97             if (node === stayWithinNode) {
98                 if (!endNode)
99                     endNode = stayWithinNode;
100                 break;
101             }
102
103             if (node.nodeType === Node.TEXT_NODE) {
104                 var start = (node === this ? offset : 0);
105                 for (var i = start; i < node.nodeValue.length; ++i) {
106                     if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) {
107                         endNode = node;
108                         endOffset = i;
109                         break;
110                     }
111                 }
112             }
113
114             if (endNode)
115                 break;
116
117             node = node.traverseNextNode(stayWithinNode);
118         }
119
120         if (!endNode) {
121             endNode = stayWithinNode;
122             endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length;
123         }
124     } else {
125         endNode = this;
126         endOffset = offset;
127     }
128
129     var result = this.ownerDocument.createRange();
130     result.setStart(startNode, startOffset);
131     result.setEnd(endNode, endOffset);
132
133     return result;
134 }
135
136 Node.prototype.traverseNextTextNode = function(stayWithin)
137 {
138     var node = this.traverseNextNode(stayWithin);
139     if (!node)
140         return;
141
142     while (node && node.nodeType !== Node.TEXT_NODE)
143         node = node.traverseNextNode(stayWithin);
144
145     return node;
146 }
147
148 Node.prototype.rangeBoundaryForOffset = function(offset)
149 {
150     var node = this.traverseNextTextNode(this);
151     while (node && offset > node.nodeValue.length) {
152         offset -= node.nodeValue.length;
153         node = node.traverseNextTextNode(this);
154     }
155     if (!node)
156         return { container: this, offset: 0 };
157     return { container: node, offset: offset };
158 }
159
160 Element.prototype.removeStyleClass = function(className)
161 {
162     this.classList.remove(className);
163 }
164
165 Element.prototype.removeMatchingStyleClasses = function(classNameRegex)
166 {
167     var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)");
168     if (regex.test(this.className))
169         this.className = this.className.replace(regex, " ");
170 }
171
172 Element.prototype.addStyleClass = function(className)
173 {
174     this.classList.add(className);
175 }
176
177 Element.prototype.hasStyleClass = function(className)
178 {
179     return this.classList.contains(className);
180 }
181
182 Element.prototype.positionAt = function(x, y)
183 {
184     this.style.left = x + "px";
185     this.style.top = y + "px";
186 }
187
188 Element.prototype.pruneEmptyTextNodes = function()
189 {
190     var sibling = this.firstChild;
191     while (sibling) {
192         var nextSibling = sibling.nextSibling;
193         if (sibling.nodeType === this.TEXT_NODE && sibling.nodeValue === "")
194             this.removeChild(sibling);
195         sibling = nextSibling;
196     }
197 }
198
199 Element.prototype.isScrolledToBottom = function()
200 {
201     // This code works only for 0-width border
202     return this.scrollTop + this.clientHeight === this.scrollHeight;
203 }
204
205 Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray)
206 {
207     for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
208         for (var i = 0; i < nameArray.length; ++i)
209             if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase())
210                 return node;
211     return null;
212 }
213
214 Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName)
215 {
216     return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]);
217 }
218
219 Node.prototype.enclosingNodeOrSelfWithClass = function(className)
220 {
221     for (var node = this; node && node !== this.ownerDocument; node = node.parentNode)
222         if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className))
223             return node;
224     return null;
225 }
226
227 Node.prototype.enclosingNodeWithClass = function(className)
228 {
229     if (!this.parentNode)
230         return null;
231     return this.parentNode.enclosingNodeOrSelfWithClass(className);
232 }
233
234 Element.prototype.query = function(query)
235 {
236     return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
237 }
238
239 Element.prototype.removeChildren = function()
240 {
241     if (this.firstChild)
242         this.textContent = "";
243 }
244
245 Element.prototype.isInsertionCaretInside = function()
246 {
247     var selection = window.getSelection();
248     if (!selection.rangeCount || !selection.isCollapsed)
249         return false;
250     var selectionRange = selection.getRangeAt(0);
251     return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this);
252 }
253
254 /**
255  * @param {string=} className
256  */
257 Element.prototype.createChild = function(elementName, className)
258 {
259     var element = this.ownerDocument.createElement(elementName);
260     if (className)
261         element.className = className;
262     this.appendChild(element);
263     return element;
264 }
265
266 DocumentFragment.prototype.createChild = Element.prototype.createChild;
267
268 Element.prototype.totalOffsetLeft = function()
269 {
270     var total = 0;
271     for (var element = this; element; element = element.offsetParent)
272         total += element.offsetLeft + (this !== element ? element.clientLeft : 0);
273     return total;
274 }
275
276 Element.prototype.totalOffsetTop = function()
277 {
278     var total = 0;
279     for (var element = this; element; element = element.offsetParent)
280         total += element.offsetTop + (this !== element ? element.clientTop : 0);
281     return total;
282 }
283
284 /**
285  * @constructor
286  * @param {number=} x
287  * @param {number=} y
288  * @param {number=} width
289  * @param {number=} height
290  */
291 function AnchorBox(x, y, width, height)
292 {
293     this.x = x || 0;
294     this.y = y || 0;
295     this.width = width || 0;
296     this.height = height || 0;
297 }
298
299 /**
300  * @return {AnchorBox}
301  */
302 Element.prototype.offsetRelativeToWindow = function(targetWindow)
303 {
304     var elementOffset = new AnchorBox();
305     var curElement = this;
306     var curWindow = this.ownerDocument.defaultView;
307     while (curWindow && curElement) {
308         elementOffset.x += curElement.totalOffsetLeft();
309         elementOffset.y += curElement.totalOffsetTop();
310         if (curWindow === targetWindow)
311             break;
312
313         curElement = curWindow.frameElement;
314         curWindow = curWindow.parent;
315     }
316
317     return elementOffset;
318 }
319
320 /**
321  * @return {AnchorBox}
322  */
323 Element.prototype.boxInWindow = function(targetWindow, relativeParent)
324 {
325     targetWindow = targetWindow || this.ownerDocument.defaultView;
326     var bodyElement = this.ownerDocument.body;
327     relativeParent = relativeParent || bodyElement;
328
329     var anchorBox = this.offsetRelativeToWindow(window);
330     anchorBox.width = this.offsetWidth;
331     anchorBox.height = this.offsetHeight;
332
333     var anchorElement = this;
334     while (anchorElement && anchorElement !== relativeParent && anchorElement !== bodyElement) {
335         if (anchorElement.scrollLeft)
336             anchorBox.x -= anchorElement.scrollLeft;
337         if (anchorElement.scrollTop)
338             anchorBox.y -= anchorElement.scrollTop;
339         anchorElement = anchorElement.parentElement;
340     }
341
342     var parentOffset = relativeParent.offsetRelativeToWindow(window);
343     anchorBox.x -= parentOffset.x;
344     anchorBox.y -= parentOffset.y;
345     return anchorBox;
346 }
347
348 Element.prototype.setTextAndTitle = function(text)
349 {
350     this.textContent = text;
351     this.title = text;
352 }
353
354 KeyboardEvent.prototype.__defineGetter__("data", function()
355 {
356     // Emulate "data" attribute from DOM 3 TextInput event.
357     // See http://www.w3.org/TR/DOM-Level-3-Events/#events-Events-TextEvent-data
358     switch (this.type) {
359         case "keypress":
360             if (!this.ctrlKey && !this.metaKey)
361                 return String.fromCharCode(this.charCode);
362             else
363                 return "";
364         case "keydown":
365         case "keyup":
366             if (!this.ctrlKey && !this.metaKey && !this.altKey)
367                 return String.fromCharCode(this.which);
368             else
369                 return "";
370     }
371 });
372
373 Text.prototype.select = function(start, end)
374 {
375     start = start || 0;
376     end = end || this.textContent.length;
377
378     if (start < 0)
379         start = end + start;
380
381     var selection = this.ownerDocument.defaultView.getSelection();
382     selection.removeAllRanges();
383     var range = this.ownerDocument.createRange();
384     range.setStart(this, start);
385     range.setEnd(this, end);
386     selection.addRange(range);
387     return this;
388 }
389
390 Element.prototype.selectionLeftOffset = function()
391 {
392     // Calculate selection offset relative to the current element.
393
394     var selection = window.getSelection();
395     if (!selection.containsNode(this, true))
396         return null;
397
398     var leftOffset = selection.anchorOffset;
399     var node = selection.anchorNode;
400
401     while (node !== this) {
402         while (node.previousSibling) {
403             node = node.previousSibling;
404             leftOffset += node.textContent.length;
405         }
406         node = node.parentNode;
407     }
408
409     return leftOffset;
410 }
411
412 String.prototype.hasSubstring = function(string, caseInsensitive)
413 {
414     if (!caseInsensitive)
415         return this.indexOf(string) !== -1;
416     return this.match(new RegExp(string.escapeForRegExp(), "i"));
417 }
418
419 String.prototype.findAll = function(string)
420 {
421     var matches = [];
422     var i = this.indexOf(string);
423     while (i !== -1) {
424         matches.push(i);
425         i = this.indexOf(string, i + string.length);
426     }
427     return matches;
428 }
429
430 String.prototype.lineEndings = function()
431 {
432     if (!this._lineEndings) {
433         this._lineEndings = this.findAll("\n");
434         this._lineEndings.push(this.length);
435     }
436     return this._lineEndings;
437 }
438
439 String.prototype.asParsedURL = function()
440 {
441     // RegExp groups:
442     // 1 - scheme
443     // 2 - hostname
444     // 3 - ?port
445     // 4 - ?path
446     // 5 - ?fragment
447     var match = this.match(/^([^:]+):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i);
448     if (!match)
449         return null;
450     var result = {};
451     result.scheme = match[1].toLowerCase();
452     result.host = match[2];
453     result.port = match[3];
454     result.path = match[4] || "/";
455     result.fragment = match[5];
456
457     result.lastPathComponent = "";
458     if (result.path) {
459         // First cut the query params.
460         var path = result.path;
461         var indexOfQuery = path.indexOf("?");
462         if (indexOfQuery !== -1)
463             path = path.substring(0, indexOfQuery);
464
465         // Then take last path component.
466         var lastSlashIndex = path.lastIndexOf("/");
467         if (lastSlashIndex !== -1)
468             result.lastPathComponent = path.substring(lastSlashIndex + 1);
469     } 
470     return result;
471 }
472
473 String.prototype.escapeCharacters = function(chars)
474 {
475     var foundChar = false;
476     for (var i = 0; i < chars.length; ++i) {
477         if (this.indexOf(chars.charAt(i)) !== -1) {
478             foundChar = true;
479             break;
480         }
481     }
482
483     if (!foundChar)
484         return this;
485
486     var result = "";
487     for (var i = 0; i < this.length; ++i) {
488         if (chars.indexOf(this.charAt(i)) !== -1)
489             result += "\\";
490         result += this.charAt(i);
491     }
492
493     return result;
494 }
495
496 String.prototype.escapeForRegExp = function()
497 {
498     return this.escapeCharacters("^[]{}()\\.$*+?|");
499 }
500
501 String.prototype.escapeHTML = function()
502 {
503     return this.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"); //" doublequotes just for editor
504 }
505
506 String.prototype.collapseWhitespace = function()
507 {
508     return this.replace(/[\s\xA0]+/g, " ");
509 }
510
511 String.prototype.trimMiddle = function(maxLength)
512 {
513     if (this.length <= maxLength)
514         return this;
515     var leftHalf = maxLength >> 1;
516     var rightHalf = maxLength - leftHalf - 1;
517     return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf);
518 }
519
520 String.prototype.trimURL = function(baseURLDomain)
521 {
522     var result = this.replace(/^(https|http|file):\/\//i, "");
523     if (baseURLDomain)
524         result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), "");
525     return result;
526 }
527
528 String.prototype.removeURLFragment = function()
529 {
530     var fragmentIndex = this.indexOf("#");
531     if (fragmentIndex == -1)
532         fragmentIndex = this.length;
533     return this.substring(0, fragmentIndex);
534 }
535
536 Node.prototype.isAncestor = function(node)
537 {
538     if (!node)
539         return false;
540
541     var currentNode = node.parentNode;
542     while (currentNode) {
543         if (this === currentNode)
544             return true;
545         currentNode = currentNode.parentNode;
546     }
547     return false;
548 }
549
550 Node.prototype.isDescendant = function(descendant)
551 {
552     return !!descendant && descendant.isAncestor(this);
553 }
554
555 Node.prototype.traverseNextNode = function(stayWithin)
556 {
557     var node = this.firstChild;
558     if (node)
559         return node;
560
561     if (stayWithin && this === stayWithin)
562         return null;
563
564     node = this.nextSibling;
565     if (node)
566         return node;
567
568     node = this;
569     while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin))
570         node = node.parentNode;
571     if (!node)
572         return null;
573
574     return node.nextSibling;
575 }
576
577 Node.prototype.traversePreviousNode = function(stayWithin)
578 {
579     if (stayWithin && this === stayWithin)
580         return null;
581     var node = this.previousSibling;
582     while (node && node.lastChild)
583         node = node.lastChild;
584     if (node)
585         return node;
586     return this.parentNode;
587 }
588
589 Number.constrain = function(num, min, max)
590 {
591     if (num < min)
592         num = min;
593     else if (num > max)
594         num = max;
595     return num;
596 }
597
598 Date.prototype.toISO8601Compact = function()
599 {
600     function leadZero(x)
601     {
602         return x > 9 ? '' + x : '0' + x
603     }
604     return this.getFullYear() +
605            leadZero(this.getMonth() + 1) +
606            leadZero(this.getDate()) + 'T' +
607            leadZero(this.getHours()) +
608            leadZero(this.getMinutes()) +
609            leadZero(this.getSeconds());
610 }
611
612 HTMLTextAreaElement.prototype.moveCursorToEnd = function()
613 {
614     var length = this.value.length;
615     this.setSelectionRange(length, length);
616 }
617
618 Object.defineProperty(Array.prototype, "remove",
619 {
620     /**
621      * @this {Array.<*>}
622      */
623     value: function(value, onlyFirst)
624     {
625         if (onlyFirst) {
626             var index = this.indexOf(value);
627             if (index !== -1)
628                 this.splice(index, 1);
629             return;
630         }
631
632         var length = this.length;
633         for (var i = 0; i < length; ++i) {
634             if (this[i] === value)
635                 this.splice(i, 1);
636         }
637     }
638 });
639
640 Object.defineProperty(Array.prototype, "keySet",
641 {
642     /**
643      * @this {Array.<*>}
644      */
645     value: function()
646     {
647         var keys = {};
648         for (var i = 0; i < this.length; ++i)
649             keys[this[i]] = true;
650         return keys;
651     }
652 });
653
654 Object.defineProperty(Array.prototype, "upperBound",
655 {
656     /**
657      * @this {Array.<number>}
658      */
659     value: function(value)
660     {
661         var first = 0;
662         var count = this.length;
663         while (count > 0) {
664           var step = count >> 1;
665           var middle = first + step;
666           if (value >= this[middle]) {
667               first = middle + 1;
668               count -= step + 1;
669           } else
670               count = step;
671         }
672         return first;
673     }
674 });
675
676 Array.diff = function(left, right)
677 {
678     var o = left;
679     var n = right;
680
681     var ns = {};
682     var os = {};
683
684     for (var i = 0; i < n.length; i++) {
685         if (ns[n[i]] == null)
686             ns[n[i]] = { rows: [], o: null };
687         ns[n[i]].rows.push(i);
688     }
689
690     for (var i = 0; i < o.length; i++) {
691         if (os[o[i]] == null)
692             os[o[i]] = { rows: [], n: null };
693         os[o[i]].rows.push(i);
694     }
695
696     for (var i in ns) {
697         if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
698             n[ns[i].rows[0]] = { text: n[ns[i].rows[0]], row: os[i].rows[0] };
699             o[os[i].rows[0]] = { text: o[os[i].rows[0]], row: ns[i].rows[0] };
700         }
701     }
702
703     for (var i = 0; i < n.length - 1; i++) {
704         if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && n[i + 1] == o[n[i].row + 1]) {
705             n[i + 1] = { text: n[i + 1], row: n[i].row + 1 };
706             o[n[i].row + 1] = { text: o[n[i].row + 1], row: i + 1 };
707         }
708     }
709
710     for (var i = n.length - 1; i > 0; i--) {
711         if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
712             n[i - 1] == o[n[i].row - 1]) {
713             n[i - 1] = { text: n[i - 1], row: n[i].row - 1 };
714             o[n[i].row - 1] = { text: o[n[i].row - 1], row: i - 1 };
715         }
716     }
717
718     return { left: o, right: n };
719 }
720
721 Array.convert = function(list)
722 {
723     // Cast array-like object to an array.
724     return Array.prototype.slice.call(list);
725 }
726
727 /**
728  * @param {string} format
729  * @param {...*} var_arg
730  */
731 String.sprintf = function(format, var_arg)
732 {
733     return String.vsprintf(format, Array.prototype.slice.call(arguments, 1));
734 }
735
736 String.tokenizeFormatString = function(format)
737 {
738     var tokens = [];
739     var substitutionIndex = 0;
740
741     function addStringToken(str)
742     {
743         tokens.push({ type: "string", value: str });
744     }
745
746     function addSpecifierToken(specifier, precision, substitutionIndex)
747     {
748         tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex });
749     }
750
751     var index = 0;
752     for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) {
753         addStringToken(format.substring(index, precentIndex));
754         index = precentIndex + 1;
755
756         if (format[index] === "%") {
757             addStringToken("%");
758             ++index;
759             continue;
760         }
761
762         if (!isNaN(format[index])) {
763             // The first character is a number, it might be a substitution index.
764             var number = parseInt(format.substring(index), 10);
765             while (!isNaN(format[index]))
766                 ++index;
767             // If the number is greater than zero and ends with a "$",
768             // then this is a substitution index.
769             if (number > 0 && format[index] === "$") {
770                 substitutionIndex = (number - 1);
771                 ++index;
772             }
773         }
774
775         var precision = -1;
776         if (format[index] === ".") {
777             // This is a precision specifier. If no digit follows the ".",
778             // then the precision should be zero.
779             ++index;
780             precision = parseInt(format.substring(index), 10);
781             if (isNaN(precision))
782                 precision = 0;
783             while (!isNaN(format[index]))
784                 ++index;
785         }
786
787         addSpecifierToken(format[index], precision, substitutionIndex);
788
789         ++substitutionIndex;
790         ++index;
791     }
792
793     addStringToken(format.substring(index));
794
795     return tokens;
796 }
797
798 String.standardFormatters = {
799     d: function(substitution)
800     {
801         return !isNaN(substitution) ? substitution : 0;
802     },
803
804     f: function(substitution, token)
805     {
806         if (substitution && token.precision > -1)
807             substitution = substitution.toFixed(token.precision);
808         return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0);
809     },
810
811     s: function(substitution)
812     {
813         return substitution;
814     }
815 }
816
817 String.vsprintf = function(format, substitutions)
818 {
819     return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
820 }
821
822 String.format = function(format, substitutions, formatters, initialValue, append)
823 {
824     if (!format || !substitutions || !substitutions.length)
825         return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions };
826
827     function prettyFunctionName()
828     {
829         return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")";
830     }
831
832     function warn(msg)
833     {
834         console.warn(prettyFunctionName() + ": " + msg);
835     }
836
837     function error(msg)
838     {
839         console.error(prettyFunctionName() + ": " + msg);
840     }
841
842     var result = initialValue;
843     var tokens = String.tokenizeFormatString(format);
844     var usedSubstitutionIndexes = {};
845
846     for (var i = 0; i < tokens.length; ++i) {
847         var token = tokens[i];
848
849         if (token.type === "string") {
850             result = append(result, token.value);
851             continue;
852         }
853
854         if (token.type !== "specifier") {
855             error("Unknown token type \"" + token.type + "\" found.");
856             continue;
857         }
858
859         if (token.substitutionIndex >= substitutions.length) {
860             // If there are not enough substitutions for the current substitutionIndex
861             // just output the format specifier literally and move on.
862             error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped.");
863             result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier);
864             continue;
865         }
866
867         usedSubstitutionIndexes[token.substitutionIndex] = true;
868
869         if (!(token.specifier in formatters)) {
870             // Encountered an unsupported format character, treat as a string.
871             warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string.");
872             result = append(result, substitutions[token.substitutionIndex]);
873             continue;
874         }
875
876         result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token));
877     }
878
879     var unusedSubstitutions = [];
880     for (var i = 0; i < substitutions.length; ++i) {
881         if (i in usedSubstitutionIndexes)
882             continue;
883         unusedSubstitutions.push(substitutions[i]);
884     }
885
886     return { formattedResult: result, unusedSubstitutions: unusedSubstitutions };
887 }
888
889 function isEnterKey(event) {
890     // Check if in IME.
891     return event.keyCode !== 229 && event.keyIdentifier === "Enter";
892 }
893
894 /**
895  * @param {Element} element
896  * @param {number} offset
897  * @param {number} length
898  * @param {Array.<Object>=} domChanges
899  */
900 function highlightSearchResult(element, offset, length, domChanges)
901 {
902     var result = highlightSearchResults(element, [{offset: offset, length: length }], domChanges);
903     return result.length ? result[0] : null;
904 }
905
906 /**
907  * @param {Element} element
908  * @param {Array.<Object>} resultRanges
909  * @param {Array.<Object>=} changes
910  */
911 function highlightSearchResults(element, resultRanges, changes)
912 {
913     return highlightRangesWithStyleClass(element, resultRanges, "webkit-search-result", changes);
914     
915 }
916
917 /**
918  * @param {Element} element
919  * @param {Array.<Object>} resultRanges
920  * @param {string} styleClass
921  * @param {Array.<Object>=} changes
922  */
923 function highlightRangesWithStyleClass(element, resultRanges, styleClass, changes)
924 {
925     changes = changes || [];
926     var highlightNodes = [];
927     var lineText = element.textContent;
928     var ownerDocument = element.ownerDocument;
929     var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
930
931     var snapshotLength = textNodeSnapshot.snapshotLength;
932     if (snapshotLength === 0)
933         return highlightNodes;
934
935     var nodeRanges = [];
936     var rangeEndOffset = 0;
937     for (var i = 0; i < snapshotLength; ++i) {
938         var range = {};
939         range.offset = rangeEndOffset;
940         range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
941         rangeEndOffset = range.offset + range.length;
942         nodeRanges.push(range);
943     }
944
945     var startIndex = 0;
946     for (var i = 0; i < resultRanges.length; ++i) {
947         var startOffset = resultRanges[i].offset;
948         var endOffset = startOffset + resultRanges[i].length;
949
950         while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
951             startIndex++;
952         var endIndex = startIndex; 
953         while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
954             endIndex++;
955         if (endIndex === snapshotLength)
956             break;
957         
958         var highlightNode = ownerDocument.createElement("span");
959         highlightNode.className = styleClass;
960         highlightNode.textContent = lineText.substring(startOffset, endOffset);
961
962         var lastTextNode = textNodeSnapshot.snapshotItem(endIndex);
963         var lastText = lastTextNode.textContent;
964         lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset);
965         changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent });
966         
967         if (startIndex === endIndex) {
968             lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode);
969             changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement });
970             highlightNodes.push(highlightNode);
971             
972             var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset));
973             lastTextNode.parentElement.insertBefore(prefixNode, highlightNode);
974             changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement });
975         } else {
976             var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
977             var firstText = firstTextNode.textContent;
978             var anchorElement = firstTextNode.nextSibling;
979
980             firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
981             changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement });
982             highlightNodes.push(highlightNode);
983
984             firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
985             changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent });
986
987             for (var j = startIndex + 1; j < endIndex; j++) {
988                 var textNode = textNodeSnapshot.snapshotItem(j);
989                 var text = textNode.textContent;
990                 textNode.textContent = "";
991                 changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent });
992             }
993         }
994         startIndex = endIndex;
995         nodeRanges[startIndex].offset = endOffset;
996         nodeRanges[startIndex].length = lastTextNode.textContent.length;
997             
998     }
999     return highlightNodes;
1000 }
1001
1002 function applyDomChanges(domChanges)
1003 {
1004     for (var i = 0, size = domChanges.length; i < size; ++i) {
1005         var entry = domChanges[i];
1006         switch (entry.type) {
1007         case "added":
1008             entry.parent.insertBefore(entry.node, entry.nextSibling);
1009             break;
1010         case "changed":
1011             entry.node.textContent = entry.newText;
1012             break;
1013         }
1014     }
1015 }
1016
1017 function revertDomChanges(domChanges)
1018 {
1019     for (var i = domChanges.length - 1; i >= 0; --i) {
1020         var entry = domChanges[i];
1021         switch (entry.type) {
1022         case "added":
1023             if (entry.node.parentElement)
1024                 entry.node.parentElement.removeChild(entry.node);
1025             break;
1026         case "changed":
1027             entry.node.textContent = entry.oldText;
1028             break;
1029         }
1030     }
1031 }
1032
1033 /**
1034  * @param {string} query
1035  * @param {boolean} caseSensitive
1036  * @param {boolean} isRegex
1037  * @return {RegExp}
1038  */
1039 function createSearchRegex(query, caseSensitive, isRegex)
1040 {
1041     var regexFlags = caseSensitive ? "g" : "gi";
1042     var regexObject;
1043
1044     if (isRegex) {
1045         try {
1046             regexObject = new RegExp(query, regexFlags);
1047         } catch (e) {
1048             // Silent catch.
1049         }
1050     }
1051
1052     if (!regexObject)
1053         regexObject = createPlainTextSearchRegex(query, regexFlags);
1054
1055     return regexObject;
1056 }
1057
1058 /**
1059  * @param {string} query
1060  * @param {string=} flags
1061  * @return {RegExp}
1062  */
1063 function createPlainTextSearchRegex(query, flags)
1064 {
1065     // This should be kept the same as the one in ContentSearchUtils.cpp.
1066     var regexSpecialCharacters = "[](){}+-*.,?\\^$|";
1067     var regex = "";
1068     for (var i = 0; i < query.length; ++i) {
1069         var c = query.charAt(i);
1070         if (regexSpecialCharacters.indexOf(c) != -1)
1071             regex += "\\";
1072         regex += c;
1073     }
1074     return new RegExp(regex, flags || "");
1075 }
1076
1077 /**
1078  * @param {RegExp} regex
1079  * @param {string} content
1080  * @return {number}
1081  */
1082 function countRegexMatches(regex, content)
1083 {
1084     var text = content;
1085     var result = 0;
1086     var match;
1087     while (text && (match = regex.exec(text))) {
1088         if (match[0].length > 0)
1089             ++result;
1090         text = text.substring(match.index + 1);
1091     }
1092     return result;
1093 }
1094
1095 /**
1096  * @param {number} value
1097  * @param {number} symbolsCount
1098  * @return {string}
1099  */
1100 function numberToStringWithSpacesPadding(value, symbolsCount)
1101 {
1102     var numberString = value.toString();
1103     var paddingLength = Math.max(0, symbolsCount - numberString.length);
1104     var paddingString = Array(paddingLength + 1).join("\u00a0");
1105     return paddingString + numberString;
1106 }
1107
1108 /**
1109  * @constructor
1110  */
1111 function TextDiff()
1112 {
1113     this.added = [];
1114     this.removed = [];
1115     this.changed = [];
1116
1117
1118 /**
1119  * @param {string} baseContent
1120  * @param {string} newContent
1121  * @return {TextDiff}
1122  */
1123 TextDiff.compute = function(baseContent, newContent)
1124 {
1125     var oldLines = baseContent.split(/\r?\n/);
1126     var newLines = newContent.split(/\r?\n/);
1127
1128     var diff = Array.diff(oldLines, newLines);
1129
1130     var diffData = new TextDiff();
1131
1132     var offset = 0;
1133     var right = diff.right;
1134     for (var i = 0; i < right.length; ++i) {
1135         if (typeof right[i] === "string") {
1136             if (right.length > i + 1 && right[i + 1].row === i + 1 - offset)
1137                 diffData.changed.push(i);
1138             else {
1139                 diffData.added.push(i);
1140                 offset++;
1141             }
1142         } else
1143             offset = i - right[i].row;
1144     }
1145     return diffData;
1146 }