Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / ui / UIUtils.js
1 /*
2  * Copyright (C) 2011 Google Inc.  All rights reserved.
3  * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
4  * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
5  * Copyright (C) 2009 Joseph Pecoraro
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1.  Redistributions of source code must retain the above copyright
12  *     notice, this list of conditions and the following disclaimer.
13  * 2.  Redistributions in binary form must reproduce the above copyright
14  *     notice, this list of conditions and the following disclaimer in the
15  *     documentation and/or other materials provided with the distribution.
16  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
17  *     its contributors may be used to endorse or promote products derived
18  *     from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
24  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 /**
33  * @param {!Element} element
34  * @param {?function(!MouseEvent): boolean} elementDragStart
35  * @param {function(!MouseEvent)} elementDrag
36  * @param {?function(!MouseEvent)} elementDragEnd
37  * @param {!string} cursor
38  * @param {?string=} hoverCursor
39  */
40 WebInspector.installDragHandle = function(element, elementDragStart, elementDrag, elementDragEnd, cursor, hoverCursor)
41 {
42     element.addEventListener("mousedown", WebInspector.elementDragStart.bind(WebInspector, elementDragStart, elementDrag, elementDragEnd, cursor), false);
43     if (hoverCursor !== null)
44         element.style.cursor = hoverCursor || cursor;
45 }
46
47 /**
48  * @param {?function(!MouseEvent):boolean} elementDragStart
49  * @param {function(!MouseEvent)} elementDrag
50  * @param {?function(!MouseEvent)} elementDragEnd
51  * @param {string} cursor
52  * @param {?Event} event
53  */
54 WebInspector.elementDragStart = function(elementDragStart, elementDrag, elementDragEnd, cursor, event)
55 {
56     // Only drag upon left button. Right will likely cause a context menu. So will ctrl-click on mac.
57     if (event.button || (WebInspector.isMac() && event.ctrlKey))
58         return;
59
60     if (WebInspector._elementDraggingEventListener)
61         return;
62
63     if (elementDragStart && !elementDragStart(/** @type {!MouseEvent} */ (event)))
64         return;
65
66     if (WebInspector._elementDraggingGlassPane) {
67         WebInspector._elementDraggingGlassPane.dispose();
68         delete WebInspector._elementDraggingGlassPane;
69     }
70
71     var targetDocument = event.target.ownerDocument;
72
73     WebInspector._elementDraggingEventListener = elementDrag;
74     WebInspector._elementEndDraggingEventListener = elementDragEnd;
75     WebInspector._mouseOutWhileDraggingTargetDocument = targetDocument;
76
77     targetDocument.addEventListener("mousemove", WebInspector._elementDragMove, true);
78     targetDocument.addEventListener("mouseup", WebInspector._elementDragEnd, true);
79     targetDocument.addEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
80
81     targetDocument.body.style.cursor = cursor;
82
83     event.preventDefault();
84 }
85
86 WebInspector._mouseOutWhileDragging = function()
87 {
88     WebInspector._unregisterMouseOutWhileDragging();
89     WebInspector._elementDraggingGlassPane = new WebInspector.GlassPane();
90 }
91
92 WebInspector._unregisterMouseOutWhileDragging = function()
93 {
94     if (!WebInspector._mouseOutWhileDraggingTargetDocument)
95         return;
96     WebInspector._mouseOutWhileDraggingTargetDocument.removeEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
97     delete WebInspector._mouseOutWhileDraggingTargetDocument;
98 }
99
100 /**
101  * @param {!Event} event
102  */
103 WebInspector._elementDragMove = function(event)
104 {
105     if (WebInspector._elementDraggingEventListener(/** @type {!MouseEvent} */ (event)))
106         WebInspector._cancelDragEvents(event);
107 }
108
109 /**
110  * @param {!Event} event
111  */
112 WebInspector._cancelDragEvents = function(event)
113 {
114     var targetDocument = event.target.ownerDocument;
115     targetDocument.removeEventListener("mousemove", WebInspector._elementDragMove, true);
116     targetDocument.removeEventListener("mouseup", WebInspector._elementDragEnd, true);
117     WebInspector._unregisterMouseOutWhileDragging();
118
119     targetDocument.body.style.removeProperty("cursor");
120
121     if (WebInspector._elementDraggingGlassPane)
122         WebInspector._elementDraggingGlassPane.dispose();
123
124     delete WebInspector._elementDraggingGlassPane;
125     delete WebInspector._elementDraggingEventListener;
126     delete WebInspector._elementEndDraggingEventListener;
127 }
128
129 /**
130  * @param {!Event} event
131  */
132 WebInspector._elementDragEnd = function(event)
133 {
134     var elementDragEnd = WebInspector._elementEndDraggingEventListener;
135
136     WebInspector._cancelDragEvents(/** @type {!MouseEvent} */ (event));
137
138     event.preventDefault();
139     if (elementDragEnd)
140         elementDragEnd(/** @type {!MouseEvent} */ (event));
141 }
142
143 /**
144  * @constructor
145  */
146 WebInspector.GlassPane = function()
147 {
148     this.element = document.createElement("div");
149     this.element.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;background-color:transparent;z-index:1000;";
150     this.element.id = "glass-pane";
151     document.body.appendChild(this.element);
152     WebInspector._glassPane = this;
153 }
154
155 WebInspector.GlassPane.prototype = {
156     dispose: function()
157     {
158         delete WebInspector._glassPane;
159         if (WebInspector.HelpScreen.isVisible())
160             WebInspector.HelpScreen.focus();
161         else
162             WebInspector.inspectorView.focus();
163         this.element.remove();
164     }
165 }
166
167 WebInspector.isBeingEdited = function(node)
168 {
169     if (node.nodeType !== Node.ELEMENT_NODE)
170         return false;
171     var element = /** {!Element} */ (node);
172     if (element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA")
173         return true;
174
175     if (!WebInspector.__editingCount)
176         return false;
177
178     while (element) {
179         if (element.__editing)
180             return true;
181         element = element.parentElement;
182     }
183     return false;
184 }
185
186 WebInspector.markBeingEdited = function(element, value)
187 {
188     if (value) {
189         if (element.__editing)
190             return false;
191         element.classList.add("being-edited");
192         element.__editing = true;
193         WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
194     } else {
195         if (!element.__editing)
196             return false;
197         element.classList.remove("being-edited");
198         delete element.__editing;
199         --WebInspector.__editingCount;
200     }
201     return true;
202 }
203
204 WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
205
206 WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()";
207
208
209 /**
210   * @param {!Event} event
211   * @return {?string}
212   */
213 WebInspector._valueModificationDirection = function(event)
214 {
215     var direction = null;
216     if (event.type === "mousewheel") {
217         if (event.wheelDeltaY > 0)
218             direction = "Up";
219         else if (event.wheelDeltaY < 0)
220             direction = "Down";
221     } else {
222         if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp")
223             direction = "Up";
224         else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
225             direction = "Down";
226     }
227     return direction;
228 }
229
230 /**
231  * @param {string} hexString
232  * @param {!Event} event
233  */
234 WebInspector._modifiedHexValue = function(hexString, event)
235 {
236     var direction = WebInspector._valueModificationDirection(event);
237     if (!direction)
238         return hexString;
239
240     var number = parseInt(hexString, 16);
241     if (isNaN(number) || !isFinite(number))
242         return hexString;
243
244     var maxValue = Math.pow(16, hexString.length) - 1;
245     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
246     var delta;
247
248     if (arrowKeyOrMouseWheelEvent)
249         delta = (direction === "Up") ? 1 : -1;
250     else
251         delta = (event.keyIdentifier === "PageUp") ? 16 : -16;
252
253     if (event.shiftKey)
254         delta *= 16;
255
256     var result = number + delta;
257     if (result < 0)
258         result = 0; // Color hex values are never negative, so clamp to 0.
259     else if (result > maxValue)
260         return hexString;
261
262     // Ensure the result length is the same as the original hex value.
263     var resultString = result.toString(16).toUpperCase();
264     for (var i = 0, lengthDelta = hexString.length - resultString.length; i < lengthDelta; ++i)
265         resultString = "0" + resultString;
266     return resultString;
267 }
268
269 /**
270  * @param {number} number
271  * @param {!Event} event
272  */
273 WebInspector._modifiedFloatNumber = function(number, event)
274 {
275     var direction = WebInspector._valueModificationDirection(event);
276     if (!direction)
277         return number;
278
279     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
280
281     // Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down.
282     // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
283     var changeAmount = 1;
284     if (event.shiftKey && !arrowKeyOrMouseWheelEvent)
285         changeAmount = 100;
286     else if (event.shiftKey || !arrowKeyOrMouseWheelEvent)
287         changeAmount = 10;
288     else if (event.altKey)
289         changeAmount = 0.1;
290
291     if (direction === "Down")
292         changeAmount *= -1;
293
294     // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
295     // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
296     var result = Number((number + changeAmount).toFixed(6));
297     if (!String(result).match(WebInspector.CSSNumberRegex))
298         return null;
299
300     return result;
301 }
302
303 /**
304   * @param {?Event} event
305   * @param {!Element} element
306   * @param {function(string,string)=} finishHandler
307   * @param {function(string)=} suggestionHandler
308   * @param {function(number):number=} customNumberHandler
309   * @return {boolean}
310  */
311 WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler)
312 {
313     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
314     var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
315     if (!arrowKeyOrMouseWheelEvent && !pageKeyPressed)
316         return false;
317
318     var selection = window.getSelection();
319     if (!selection.rangeCount)
320         return false;
321
322     var selectionRange = selection.getRangeAt(0);
323     if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element))
324         return false;
325
326     var originalValue = element.textContent;
327     var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StyleValueDelimiters, element);
328     var wordString = wordRange.toString();
329
330     if (suggestionHandler && suggestionHandler(wordString))
331         return false;
332
333     var replacementString;
334     var prefix, suffix, number;
335
336     var matches;
337     matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString);
338     if (matches && matches.length) {
339         prefix = matches[1];
340         suffix = matches[3];
341         number = WebInspector._modifiedHexValue(matches[2], event);
342
343         if (customNumberHandler)
344             number = customNumberHandler(number);
345
346         replacementString = prefix + number + suffix;
347     } else {
348         matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);
349         if (matches && matches.length) {
350             prefix = matches[1];
351             suffix = matches[3];
352             number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event);
353
354             // Need to check for null explicitly.
355             if (number === null)
356                 return false;
357
358             if (customNumberHandler)
359                 number = customNumberHandler(number);
360
361             replacementString = prefix + number + suffix;
362         }
363     }
364
365     if (replacementString) {
366         var replacementTextNode = document.createTextNode(replacementString);
367
368         wordRange.deleteContents();
369         wordRange.insertNode(replacementTextNode);
370
371         var finalSelectionRange = document.createRange();
372         finalSelectionRange.setStart(replacementTextNode, 0);
373         finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
374
375         selection.removeAllRanges();
376         selection.addRange(finalSelectionRange);
377
378         event.handled = true;
379         event.preventDefault();
380
381         if (finishHandler)
382             finishHandler(originalValue, replacementString);
383
384         return true;
385     }
386     return false;
387 }
388
389 /**
390  * @param {number} ms
391  * @param {number=} precision
392  * @return {string}
393  */
394 Number.preciseMillisToString = function(ms, precision)
395 {
396   precision = precision || 0;
397   var format = "%." + precision + "f\u2009ms";
398   return WebInspector.UIString(format, ms);
399 }
400
401 /**
402  * @param {number} ms
403  * @param {boolean=} higherResolution
404  * @return {string}
405  */
406 Number.millisToString = function(ms, higherResolution)
407 {
408     if (!isFinite(ms))
409         return "-";
410
411     if (ms === 0)
412         return "0";
413
414     if (higherResolution && ms < 1000)
415         return WebInspector.UIString("%.3f\u2009ms", ms);
416     else if (ms < 1000)
417         return WebInspector.UIString("%.0f\u2009ms", ms);
418
419     var seconds = ms / 1000;
420     if (seconds < 60)
421         return WebInspector.UIString("%.2f\u2009s", seconds);
422
423     var minutes = seconds / 60;
424     if (minutes < 60)
425         return WebInspector.UIString("%.1f\u2009min", minutes);
426
427     var hours = minutes / 60;
428     if (hours < 24)
429         return WebInspector.UIString("%.1f\u2009hrs", hours);
430
431     var days = hours / 24;
432     return WebInspector.UIString("%.1f\u2009days", days);
433 }
434
435 /**
436  * @param {number} seconds
437  * @param {boolean=} higherResolution
438  * @return {string}
439  */
440 Number.secondsToString = function(seconds, higherResolution)
441 {
442     if (!isFinite(seconds))
443         return "-";
444     return Number.millisToString(seconds * 1000, higherResolution);
445 }
446
447 /**
448  * @param {number} bytes
449  * @return {string}
450  */
451 Number.bytesToString = function(bytes)
452 {
453     if (bytes < 1024)
454         return WebInspector.UIString("%.0f\u2009B", bytes);
455
456     var kilobytes = bytes / 1024;
457     if (kilobytes < 100)
458         return WebInspector.UIString("%.1f\u2009KB", kilobytes);
459     if (kilobytes < 1024)
460         return WebInspector.UIString("%.0f\u2009KB", kilobytes);
461
462     var megabytes = kilobytes / 1024;
463     if (megabytes < 100)
464         return WebInspector.UIString("%.1f\u2009MB", megabytes);
465     else
466         return WebInspector.UIString("%.0f\u2009MB", megabytes);
467 }
468
469 Number.withThousandsSeparator = function(num)
470 {
471     var str = num + "";
472     var re = /(\d+)(\d{3})/;
473     while (str.match(re))
474         str = str.replace(re, "$1\u2009$2"); // \u2009 is a thin space.
475     return str;
476 }
477
478 WebInspector.useLowerCaseMenuTitles = function()
479 {
480     return WebInspector.platform() === "windows";
481 }
482
483 WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
484 {
485     return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
486 }
487
488 WebInspector.openLinkExternallyLabel = function()
489 {
490     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
491 }
492
493 WebInspector.copyLinkAddressLabel = function()
494 {
495     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
496 }
497
498 WebInspector.installPortStyles = function()
499 {
500     var platform = WebInspector.platform();
501     document.body.classList.add("platform-" + platform);
502     var flavor = WebInspector.platformFlavor();
503     if (flavor)
504         document.body.classList.add("platform-" + flavor);
505     var port = WebInspector.port();
506     document.body.classList.add("port-" + port);
507 }
508
509 WebInspector._windowFocused = function(event)
510 {
511     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
512         document.body.classList.remove("inactive");
513 }
514
515 WebInspector._windowBlurred = function(event)
516 {
517     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
518         document.body.classList.add("inactive");
519 }
520
521 WebInspector.previousFocusElement = function()
522 {
523     return WebInspector._previousFocusElement;
524 }
525
526 WebInspector.currentFocusElement = function()
527 {
528     return WebInspector._currentFocusElement;
529 }
530
531 WebInspector._focusChanged = function(event)
532 {
533     WebInspector.setCurrentFocusElement(event.target);
534 }
535
536 WebInspector._documentBlurred = function(event)
537 {
538     // We want to know when currentFocusElement loses focus to nowhere.
539     // This is the case when event.relatedTarget is null (no element is being focused)
540     // and document.activeElement is reset to default (this is not a window blur).
541     if (!event.relatedTarget && document.activeElement === document.body)
542       WebInspector.setCurrentFocusElement(null);
543 }
544
545 WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet();
546 WebInspector._isTextEditingElement = function(element)
547 {
548     if (element instanceof HTMLInputElement)
549         return element.type in WebInspector._textInputTypes;
550
551     if (element instanceof HTMLTextAreaElement)
552         return true;
553
554     return false;
555 }
556
557 WebInspector.setCurrentFocusElement = function(x)
558 {
559     if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x))
560         return;
561     if (WebInspector._currentFocusElement !== x)
562         WebInspector._previousFocusElement = WebInspector._currentFocusElement;
563     WebInspector._currentFocusElement = x;
564
565     if (WebInspector._currentFocusElement) {
566         WebInspector._currentFocusElement.focus();
567
568         // Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside.
569         // This is needed (at least) to remove caret from console when focus is moved to some element in the panel.
570         // The code below should not be applied to text fields and text areas, hence _isTextEditingElement check.
571         var selection = window.getSelection();
572         if (!WebInspector._isTextEditingElement(WebInspector._currentFocusElement) && selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) {
573             var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange();
574             selectionRange.setStart(WebInspector._currentFocusElement, 0);
575             selectionRange.setEnd(WebInspector._currentFocusElement, 0);
576
577             selection.removeAllRanges();
578             selection.addRange(selectionRange);
579         }
580     } else if (WebInspector._previousFocusElement)
581         WebInspector._previousFocusElement.blur();
582 }
583
584 WebInspector.restoreFocusFromElement = function(element)
585 {
586     if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement()))
587         WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
588 }
589
590 WebInspector.setToolbarColors = function(backgroundColor, color)
591 {
592     if (!WebInspector._themeStyleElement) {
593         WebInspector._themeStyleElement = document.createElement("style");
594         document.head.appendChild(WebInspector._themeStyleElement);
595     }
596     var parsedColor = WebInspector.Color.parse(color);
597     var shadowColor = parsedColor ? parsedColor.invert().setAlpha(0.33).toString(WebInspector.Color.Format.RGBA) : "white";
598     var prefix = WebInspector.isMac() ? "body:not(.undocked)" : "";
599     WebInspector._themeStyleElement.textContent =
600         String.sprintf(
601             "%s .toolbar-background {\
602                  background-image: none !important;\
603                  background-color: %s !important;\
604                  color: %s !important;\
605              }", prefix, backgroundColor, color) +
606         String.sprintf(
607              "%s .toolbar-background button.status-bar-item .glyph, %s .toolbar-background button.status-bar-item .long-click-glyph {\
608                  background-color: %s;\
609              }", prefix, prefix, color) +
610         String.sprintf(
611              "%s .toolbar-background button.status-bar-item .glyph.shadow, %s .toolbar-background button.status-bar-item .long-click-glyph.shadow {\
612                  background-color: %s;\
613              }", prefix, prefix, shadowColor);
614 }
615
616 WebInspector.resetToolbarColors = function()
617 {
618     if (WebInspector._themeStyleElement)
619         WebInspector._themeStyleElement.textContent = "";
620 }
621
622 /**
623  * @param {!Element} element
624  * @param {number} offset
625  * @param {number} length
626  * @param {!Array.<!Object>=} domChanges
627  */
628 WebInspector.highlightSearchResult = function(element, offset, length, domChanges)
629 {
630     var result = WebInspector.highlightSearchResults(element, [new WebInspector.SourceRange(offset, length)], domChanges);
631     return result.length ? result[0] : null;
632 }
633
634 /**
635  * @param {!Element} element
636  * @param {!Array.<!WebInspector.SourceRange>} resultRanges
637  * @param {!Array.<!Object>=} changes
638  */
639 WebInspector.highlightSearchResults = function(element, resultRanges, changes)
640 {
641     return WebInspector.highlightRangesWithStyleClass(element, resultRanges, "highlighted-search-result", changes);
642 }
643
644 /**
645  * @param {!Element} element
646  * @param {string} className
647  */
648 WebInspector.runCSSAnimationOnce = function(element, className)
649 {
650     function animationEndCallback()
651     {
652         element.classList.remove(className);
653         element.removeEventListener("animationend", animationEndCallback, false);
654     }
655
656     if (element.classList.contains(className))
657         element.classList.remove(className);
658
659     element.addEventListener("animationend", animationEndCallback, false);
660     element.classList.add(className);
661 }
662
663 /**
664  * @param {!Element} element
665  * @param {!Array.<!WebInspector.SourceRange>} resultRanges
666  * @param {string} styleClass
667  * @param {!Array.<!Object>=} changes
668  * @return {!Array.<!Element>}
669  */
670 WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes)
671 {
672     changes = changes || [];
673     var highlightNodes = [];
674     var lineText = element.textContent;
675     var ownerDocument = element.ownerDocument;
676     var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
677
678     var snapshotLength = textNodeSnapshot.snapshotLength;
679     if (snapshotLength === 0)
680         return highlightNodes;
681
682     var nodeRanges = [];
683     var rangeEndOffset = 0;
684     for (var i = 0; i < snapshotLength; ++i) {
685         var range = {};
686         range.offset = rangeEndOffset;
687         range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
688         rangeEndOffset = range.offset + range.length;
689         nodeRanges.push(range);
690     }
691
692     var startIndex = 0;
693     for (var i = 0; i < resultRanges.length; ++i) {
694         var startOffset = resultRanges[i].offset;
695         var endOffset = startOffset + resultRanges[i].length;
696
697         while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
698             startIndex++;
699         var endIndex = startIndex;
700         while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
701             endIndex++;
702         if (endIndex === snapshotLength)
703             break;
704
705         var highlightNode = ownerDocument.createElement("span");
706         highlightNode.className = styleClass;
707         highlightNode.textContent = lineText.substring(startOffset, endOffset);
708
709         var lastTextNode = textNodeSnapshot.snapshotItem(endIndex);
710         var lastText = lastTextNode.textContent;
711         lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset);
712         changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent });
713
714         if (startIndex === endIndex) {
715             lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode);
716             changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement });
717             highlightNodes.push(highlightNode);
718
719             var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset));
720             lastTextNode.parentElement.insertBefore(prefixNode, highlightNode);
721             changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement });
722         } else {
723             var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
724             var firstText = firstTextNode.textContent;
725             var anchorElement = firstTextNode.nextSibling;
726
727             firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
728             changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement });
729             highlightNodes.push(highlightNode);
730
731             firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
732             changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent });
733
734             for (var j = startIndex + 1; j < endIndex; j++) {
735                 var textNode = textNodeSnapshot.snapshotItem(j);
736                 var text = textNode.textContent;
737                 textNode.textContent = "";
738                 changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent });
739             }
740         }
741         startIndex = endIndex;
742         nodeRanges[startIndex].offset = endOffset;
743         nodeRanges[startIndex].length = lastTextNode.textContent.length;
744
745     }
746     return highlightNodes;
747 }
748
749 WebInspector.applyDomChanges = function(domChanges)
750 {
751     for (var i = 0, size = domChanges.length; i < size; ++i) {
752         var entry = domChanges[i];
753         switch (entry.type) {
754         case "added":
755             entry.parent.insertBefore(entry.node, entry.nextSibling);
756             break;
757         case "changed":
758             entry.node.textContent = entry.newText;
759             break;
760         }
761     }
762 }
763
764 WebInspector.revertDomChanges = function(domChanges)
765 {
766     for (var i = domChanges.length - 1; i >= 0; --i) {
767         var entry = domChanges[i];
768         switch (entry.type) {
769         case "added":
770             entry.node.remove();
771             break;
772         case "changed":
773             entry.node.textContent = entry.oldText;
774             break;
775         }
776     }
777 }
778
779 /**
780  * @constructor
781  * @param {boolean} autoInvoke
782  */
783 WebInspector.InvokeOnceHandlers = function(autoInvoke)
784 {
785     this._handlers = null;
786     this._autoInvoke = autoInvoke;
787 }
788
789 WebInspector.InvokeOnceHandlers.prototype = {
790     /**
791      * @param {!Object} object
792      * @param {function()} method
793      */
794     add: function(object, method)
795     {
796         if (!this._handlers) {
797             this._handlers = new Map();
798             if (this._autoInvoke)
799                 this.scheduleInvoke();
800         }
801         var methods = this._handlers.get(object);
802         if (!methods) {
803             methods = new Set();
804             this._handlers.put(object, methods);
805         }
806         methods.add(method);
807     },
808
809     scheduleInvoke: function()
810     {
811         if (this._handlers)
812             requestAnimationFrame(this._invoke.bind(this));
813     },
814
815     _invoke: function()
816     {
817         var handlers = this._handlers;
818         this._handlers = null;
819         var keys = handlers.keys();
820         for (var i = 0; i < keys.length; ++i) {
821             var object = keys[i];
822             var methods = handlers.get(object).items();
823             for (var j = 0; j < methods.length; ++j)
824                 methods[j].call(object);
825         }
826     }
827 }
828
829 WebInspector._coalescingLevel = 0;
830 WebInspector._postUpdateHandlers = null;
831
832 WebInspector.startBatchUpdate = function()
833 {
834     if (!WebInspector._coalescingLevel++)
835         WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(false);
836 }
837
838 WebInspector.endBatchUpdate = function()
839 {
840     if (--WebInspector._coalescingLevel)
841         return;
842     WebInspector._postUpdateHandlers.scheduleInvoke();
843     WebInspector._postUpdateHandlers = null;
844 }
845
846 /**
847  * @param {!Object} object
848  * @param {function()} method
849  */
850 WebInspector.invokeOnceAfterBatchUpdate = function(object, method)
851 {
852     if (!WebInspector._postUpdateHandlers)
853         WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(true);
854     WebInspector._postUpdateHandlers.add(object, method);
855 }
856
857 ;(function() {
858
859 function windowLoaded()
860 {
861     window.addEventListener("focus", WebInspector._windowFocused, false);
862     window.addEventListener("blur", WebInspector._windowBlurred, false);
863     document.addEventListener("focus", WebInspector._focusChanged, true);
864     document.addEventListener("blur", WebInspector._documentBlurred, true);
865     window.removeEventListener("DOMContentLoaded", windowLoaded, false);
866 }
867
868 window.addEventListener("DOMContentLoaded", windowLoaded, false);
869
870 })();