Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / 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(element)
168 {
169     if (element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA")
170         return true;
171
172     if (!WebInspector.__editingCount)
173         return false;
174
175     while (element) {
176         if (element.__editing)
177             return true;
178         element = element.parentElement;
179     }
180     return false;
181 }
182
183 WebInspector.markBeingEdited = function(element, value)
184 {
185     if (value) {
186         if (element.__editing)
187             return false;
188         element.classList.add("being-edited");
189         element.__editing = true;
190         WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
191     } else {
192         if (!element.__editing)
193             return false;
194         element.classList.remove("being-edited");
195         delete element.__editing;
196         --WebInspector.__editingCount;
197     }
198     return true;
199 }
200
201 WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
202
203 WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()";
204
205
206 /**
207   * @param {!Event} event
208   * @return {?string}
209   */
210 WebInspector._valueModificationDirection = function(event)
211 {
212     var direction = null;
213     if (event.type === "mousewheel") {
214         if (event.wheelDeltaY > 0)
215             direction = "Up";
216         else if (event.wheelDeltaY < 0)
217             direction = "Down";
218     } else {
219         if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp")
220             direction = "Up";
221         else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
222             direction = "Down";        
223     }
224     return direction;
225 }
226
227 /**
228  * @param {string} hexString
229  * @param {!Event} event
230  */
231 WebInspector._modifiedHexValue = function(hexString, event)
232 {
233     var direction = WebInspector._valueModificationDirection(event);
234     if (!direction)
235         return hexString;
236
237     var number = parseInt(hexString, 16);
238     if (isNaN(number) || !isFinite(number))
239         return hexString;
240
241     var maxValue = Math.pow(16, hexString.length) - 1;
242     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
243     var delta;
244
245     if (arrowKeyOrMouseWheelEvent)
246         delta = (direction === "Up") ? 1 : -1;
247     else
248         delta = (event.keyIdentifier === "PageUp") ? 16 : -16;
249
250     if (event.shiftKey)
251         delta *= 16;
252
253     var result = number + delta;
254     if (result < 0)
255         result = 0; // Color hex values are never negative, so clamp to 0.
256     else if (result > maxValue)
257         return hexString;
258
259     // Ensure the result length is the same as the original hex value.
260     var resultString = result.toString(16).toUpperCase();
261     for (var i = 0, lengthDelta = hexString.length - resultString.length; i < lengthDelta; ++i)
262         resultString = "0" + resultString;
263     return resultString;
264 }
265
266 /**
267  * @param {number} number
268  * @param {!Event} event
269  */
270 WebInspector._modifiedFloatNumber = function(number, event)
271 {
272     var direction = WebInspector._valueModificationDirection(event);
273     if (!direction)
274         return number;
275     
276     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
277
278     // Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down.
279     // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
280     var changeAmount = 1;
281     if (event.shiftKey && !arrowKeyOrMouseWheelEvent)
282         changeAmount = 100;
283     else if (event.shiftKey || !arrowKeyOrMouseWheelEvent)
284         changeAmount = 10;
285     else if (event.altKey)
286         changeAmount = 0.1;
287
288     if (direction === "Down")
289         changeAmount *= -1;
290
291     // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
292     // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
293     var result = Number((number + changeAmount).toFixed(6));
294     if (!String(result).match(WebInspector.CSSNumberRegex))
295         return null;
296
297     return result;
298 }
299
300 /**
301   * @param {?Event} event
302   * @param {!Element} element
303   * @param {function(string,string)=} finishHandler
304   * @param {function(string)=} suggestionHandler
305   * @param {function(number):number=} customNumberHandler
306   * @return {boolean}
307  */
308 WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler)
309 {
310     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
311     var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
312     if (!arrowKeyOrMouseWheelEvent && !pageKeyPressed)
313         return false;
314
315     var selection = window.getSelection();
316     if (!selection.rangeCount)
317         return false;
318
319     var selectionRange = selection.getRangeAt(0);
320     if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element))
321         return false;
322
323     var originalValue = element.textContent;
324     var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StyleValueDelimiters, element);
325     var wordString = wordRange.toString();
326     
327     if (suggestionHandler && suggestionHandler(wordString))
328         return false;
329
330     var replacementString;
331     var prefix, suffix, number;
332
333     var matches;
334     matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString);
335     if (matches && matches.length) {
336         prefix = matches[1];
337         suffix = matches[3];
338         number = WebInspector._modifiedHexValue(matches[2], event);
339         
340         if (customNumberHandler)
341             number = customNumberHandler(number);
342
343         replacementString = prefix + number + suffix;
344     } else {
345         matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);
346         if (matches && matches.length) {
347             prefix = matches[1];
348             suffix = matches[3];
349             number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event);
350             
351             // Need to check for null explicitly.
352             if (number === null)                
353                 return false;
354             
355             if (customNumberHandler)
356                 number = customNumberHandler(number);
357
358             replacementString = prefix + number + suffix;
359         }
360     }
361
362     if (replacementString) {
363         var replacementTextNode = document.createTextNode(replacementString);
364
365         wordRange.deleteContents();
366         wordRange.insertNode(replacementTextNode);
367
368         var finalSelectionRange = document.createRange();
369         finalSelectionRange.setStart(replacementTextNode, 0);
370         finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
371
372         selection.removeAllRanges();
373         selection.addRange(finalSelectionRange);
374
375         event.handled = true;
376         event.preventDefault();
377                 
378         if (finishHandler)
379             finishHandler(originalValue, replacementString);
380
381         return true;
382     }
383     return false;
384 }
385
386 /**
387  * @param {number} ms
388  * @param {number=} precision
389  * @return {string}
390  */
391 Number.preciseMillisToString = function(ms, precision)
392 {
393   precision = precision || 0;
394   var format = "%." + precision + "f\u2009ms";
395   return WebInspector.UIString(format, ms);
396 }
397
398 /**
399  * @param {number} ms
400  * @param {boolean=} higherResolution
401  * @return {string}
402  */
403 Number.millisToString = function(ms, higherResolution)
404 {
405     if (!isFinite(ms))
406         return "-";
407
408     if (ms === 0)
409         return "0";
410
411     if (higherResolution && ms < 1000)
412         return WebInspector.UIString("%.3f\u2009ms", ms);
413     else if (ms < 1000)
414         return WebInspector.UIString("%.0f\u2009ms", ms);
415
416     var seconds = ms / 1000;
417     if (seconds < 60)
418         return WebInspector.UIString("%.2f\u2009s", seconds);
419
420     var minutes = seconds / 60;
421     if (minutes < 60)
422         return WebInspector.UIString("%.1f\u2009min", minutes);
423
424     var hours = minutes / 60;
425     if (hours < 24)
426         return WebInspector.UIString("%.1f\u2009hrs", hours);
427
428     var days = hours / 24;
429     return WebInspector.UIString("%.1f\u2009days", days);
430 }
431
432 /**
433  * @param {number} seconds
434  * @param {boolean=} higherResolution
435  * @return {string}
436  */
437 Number.secondsToString = function(seconds, higherResolution)
438 {
439     if (!isFinite(seconds))
440         return "-";
441     return Number.millisToString(seconds * 1000, higherResolution);
442 }
443
444 /**
445  * @param {number} bytes
446  * @return {string}
447  */
448 Number.bytesToString = function(bytes)
449 {
450     if (bytes < 1024)
451         return WebInspector.UIString("%.0f\u2009B", bytes);
452
453     var kilobytes = bytes / 1024;
454     if (kilobytes < 100)
455         return WebInspector.UIString("%.1f\u2009KB", kilobytes);
456     if (kilobytes < 1024)
457         return WebInspector.UIString("%.0f\u2009KB", kilobytes);
458
459     var megabytes = kilobytes / 1024;
460     if (megabytes < 100)
461         return WebInspector.UIString("%.1f\u2009MB", megabytes);
462     else
463         return WebInspector.UIString("%.0f\u2009MB", megabytes);
464 }
465
466 Number.withThousandsSeparator = function(num)
467 {
468     var str = num + "";
469     var re = /(\d+)(\d{3})/;
470     while (str.match(re))
471         str = str.replace(re, "$1\u2009$2"); // \u2009 is a thin space.
472     return str;
473 }
474
475 WebInspector.useLowerCaseMenuTitles = function()
476 {
477     return WebInspector.platform() === "windows";
478 }
479
480 WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
481 {
482     return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
483 }
484
485 WebInspector.openLinkExternallyLabel = function()
486 {
487     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
488 }
489
490 WebInspector.copyLinkAddressLabel = function()
491 {
492     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
493 }
494
495 WebInspector.installPortStyles = function()
496 {
497     var platform = WebInspector.platform();
498     document.body.classList.add("platform-" + platform);
499     var flavor = WebInspector.platformFlavor();
500     if (flavor)
501         document.body.classList.add("platform-" + flavor);
502     var port = WebInspector.port();
503     document.body.classList.add("port-" + port);
504 }
505
506 WebInspector._windowFocused = function(event)
507 {
508     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
509         document.body.classList.remove("inactive");
510 }
511
512 WebInspector._windowBlurred = function(event)
513 {
514     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
515         document.body.classList.add("inactive");
516 }
517
518 WebInspector.previousFocusElement = function()
519 {
520     return WebInspector._previousFocusElement;
521 }
522
523 WebInspector.currentFocusElement = function()
524 {
525     return WebInspector._currentFocusElement;
526 }
527
528 WebInspector._focusChanged = function(event)
529 {
530     WebInspector.setCurrentFocusElement(event.target);
531 }
532
533 WebInspector._documentBlurred = function(event)
534 {
535     // We want to know when currentFocusElement loses focus to nowhere.
536     // This is the case when event.relatedTarget is null (no element is being focused)
537     // and document.activeElement is reset to default (this is not a window blur).
538     if (!event.relatedTarget && document.activeElement === document.body)
539       WebInspector.setCurrentFocusElement(null);
540 }
541
542 WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet(); 
543 WebInspector._isTextEditingElement = function(element)
544 {
545     if (element instanceof HTMLInputElement)
546         return element.type in WebInspector._textInputTypes;
547
548     if (element instanceof HTMLTextAreaElement)
549         return true;
550
551     return false;
552 }
553
554 WebInspector.setCurrentFocusElement = function(x)
555 {
556     if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x))
557         return;
558     if (WebInspector._currentFocusElement !== x)
559         WebInspector._previousFocusElement = WebInspector._currentFocusElement;
560     WebInspector._currentFocusElement = x;
561
562     if (WebInspector._currentFocusElement) {
563         WebInspector._currentFocusElement.focus();
564
565         // Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside.
566         // This is needed (at least) to remove caret from console when focus is moved to some element in the panel.
567         // The code below should not be applied to text fields and text areas, hence _isTextEditingElement check.
568         var selection = window.getSelection();
569         if (!WebInspector._isTextEditingElement(WebInspector._currentFocusElement) && selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) {
570             var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange();
571             selectionRange.setStart(WebInspector._currentFocusElement, 0);
572             selectionRange.setEnd(WebInspector._currentFocusElement, 0);
573
574             selection.removeAllRanges();
575             selection.addRange(selectionRange);
576         }
577     } else if (WebInspector._previousFocusElement)
578         WebInspector._previousFocusElement.blur();
579 }
580
581 WebInspector.restoreFocusFromElement = function(element)
582 {
583     if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement()))
584         WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
585 }
586
587 WebInspector.setToolbarColors = function(backgroundColor, color)
588 {
589     if (!WebInspector._themeStyleElement) {
590         WebInspector._themeStyleElement = document.createElement("style");
591         document.head.appendChild(WebInspector._themeStyleElement);
592     }
593     var parsedColor = WebInspector.Color.parse(color);
594     var shadowColor = parsedColor ? parsedColor.invert().setAlpha(0.33).toString(WebInspector.Color.Format.RGBA) : "white";
595     var prefix = WebInspector.isMac() ? "body:not(.undocked)" : "";
596     WebInspector._themeStyleElement.textContent =
597         String.sprintf(
598             "%s .toolbar-background {\
599                  background-image: none !important;\
600                  background-color: %s !important;\
601                  color: %s !important;\
602              }", prefix, backgroundColor, color) +
603         String.sprintf(
604              "%s .toolbar-background button.status-bar-item .glyph, %s .toolbar-background button.status-bar-item .long-click-glyph {\
605                  background-color: %s;\
606              }", prefix, prefix, color) +
607         String.sprintf(
608              "%s .toolbar-background button.status-bar-item .glyph.shadow, %s .toolbar-background button.status-bar-item .long-click-glyph.shadow {\
609                  background-color: %s;\
610              }", prefix, prefix, shadowColor);
611 }
612
613 WebInspector.resetToolbarColors = function()
614 {
615     if (WebInspector._themeStyleElement)
616         WebInspector._themeStyleElement.textContent = "";
617 }
618
619 /**
620  * @param {!Element} element
621  * @param {number} offset
622  * @param {number} length
623  * @param {!Array.<!Object>=} domChanges
624  */
625 WebInspector.highlightSearchResult = function(element, offset, length, domChanges)
626 {
627     var result = WebInspector.highlightSearchResults(element, [new WebInspector.SourceRange(offset, length)], domChanges);
628     return result.length ? result[0] : null;
629 }
630
631 /**
632  * @param {!Element} element
633  * @param {!Array.<!WebInspector.SourceRange>} resultRanges
634  * @param {!Array.<!Object>=} changes
635  */
636 WebInspector.highlightSearchResults = function(element, resultRanges, changes)
637 {
638     return WebInspector.highlightRangesWithStyleClass(element, resultRanges, "highlighted-search-result", changes);
639 }
640
641 /**
642  * @param {!Element} element
643  * @param {string} className
644  */
645 WebInspector.runCSSAnimationOnce = function(element, className)
646 {
647     function animationEndCallback()
648     {
649         element.classList.remove(className);
650         element.removeEventListener("animationend", animationEndCallback, false);
651     }
652
653     if (element.classList.contains(className))
654         element.classList.remove(className);
655
656     element.addEventListener("animationend", animationEndCallback, false);
657     element.classList.add(className);
658 }
659
660 /**
661  * @param {!Element} element
662  * @param {!Array.<!WebInspector.SourceRange>} resultRanges
663  * @param {string} styleClass
664  * @param {!Array.<!Object>=} changes
665  * @return {!Array.<!Element>}
666  */
667 WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes)
668 {
669     changes = changes || [];
670     var highlightNodes = [];
671     var lineText = element.textContent;
672     var ownerDocument = element.ownerDocument;
673     var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
674
675     var snapshotLength = textNodeSnapshot.snapshotLength;
676     if (snapshotLength === 0)
677         return highlightNodes;
678
679     var nodeRanges = [];
680     var rangeEndOffset = 0;
681     for (var i = 0; i < snapshotLength; ++i) {
682         var range = {};
683         range.offset = rangeEndOffset;
684         range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
685         rangeEndOffset = range.offset + range.length;
686         nodeRanges.push(range);
687     }
688
689     var startIndex = 0;
690     for (var i = 0; i < resultRanges.length; ++i) {
691         var startOffset = resultRanges[i].offset;
692         var endOffset = startOffset + resultRanges[i].length;
693
694         while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
695             startIndex++;
696         var endIndex = startIndex;
697         while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
698             endIndex++;
699         if (endIndex === snapshotLength)
700             break;
701
702         var highlightNode = ownerDocument.createElement("span");
703         highlightNode.className = styleClass;
704         highlightNode.textContent = lineText.substring(startOffset, endOffset);
705
706         var lastTextNode = textNodeSnapshot.snapshotItem(endIndex);
707         var lastText = lastTextNode.textContent;
708         lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset);
709         changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent });
710
711         if (startIndex === endIndex) {
712             lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode);
713             changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement });
714             highlightNodes.push(highlightNode);
715
716             var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset));
717             lastTextNode.parentElement.insertBefore(prefixNode, highlightNode);
718             changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement });
719         } else {
720             var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
721             var firstText = firstTextNode.textContent;
722             var anchorElement = firstTextNode.nextSibling;
723
724             firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
725             changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement });
726             highlightNodes.push(highlightNode);
727
728             firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
729             changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent });
730
731             for (var j = startIndex + 1; j < endIndex; j++) {
732                 var textNode = textNodeSnapshot.snapshotItem(j);
733                 var text = textNode.textContent;
734                 textNode.textContent = "";
735                 changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent });
736             }
737         }
738         startIndex = endIndex;
739         nodeRanges[startIndex].offset = endOffset;
740         nodeRanges[startIndex].length = lastTextNode.textContent.length;
741
742     }
743     return highlightNodes;
744 }
745
746 WebInspector.applyDomChanges = function(domChanges)
747 {
748     for (var i = 0, size = domChanges.length; i < size; ++i) {
749         var entry = domChanges[i];
750         switch (entry.type) {
751         case "added":
752             entry.parent.insertBefore(entry.node, entry.nextSibling);
753             break;
754         case "changed":
755             entry.node.textContent = entry.newText;
756             break;
757         }
758     }
759 }
760
761 WebInspector.revertDomChanges = function(domChanges)
762 {
763     for (var i = domChanges.length - 1; i >= 0; --i) {
764         var entry = domChanges[i];
765         switch (entry.type) {
766         case "added":
767             entry.node.remove();
768             break;
769         case "changed":
770             entry.node.textContent = entry.oldText;
771             break;
772         }
773     }
774 }
775
776 WebInspector._coalescingLevel = 0;
777
778 WebInspector.startBatchUpdate = function()
779 {
780     if (!WebInspector._coalescingLevel)
781         WebInspector._postUpdateHandlers = new Map();
782     WebInspector._coalescingLevel++;
783 }
784
785 WebInspector.endBatchUpdate = function()
786 {
787     if (--WebInspector._coalescingLevel)
788         return;
789
790     var handlers = WebInspector._postUpdateHandlers;
791     delete WebInspector._postUpdateHandlers;
792
793     window.requestAnimationFrame(function() {
794         if (WebInspector._coalescingLevel)
795             return;
796         var keys = handlers.keys();
797         for (var i = 0; i < keys.length; ++i) {
798             var object = keys[i];
799             var methods = handlers.get(object).keys();
800             for (var j = 0; j < methods.length; ++j)
801                 methods[j].call(object);
802         }
803     });
804 }
805
806 /**
807  * @param {!Object} object
808  * @param {function()} method
809  */
810 WebInspector.invokeOnceAfterBatchUpdate = function(object, method)
811 {
812     if (!WebInspector._coalescingLevel) {
813         window.requestAnimationFrame(function() {
814             if (!WebInspector._coalescingLevel)
815                 method.call(object);
816         });
817         return;
818     }
819
820     var methods = WebInspector._postUpdateHandlers.get(object);
821     if (!methods) {
822         methods = new Map();
823         WebInspector._postUpdateHandlers.put(object, methods);
824     }
825     methods.put(method);
826 }
827
828 ;(function() {
829
830 function windowLoaded()
831 {
832     window.addEventListener("focus", WebInspector._windowFocused, false);
833     window.addEventListener("blur", WebInspector._windowBlurred, false);
834     document.addEventListener("focus", WebInspector._focusChanged, true);
835     document.addEventListener("blur", WebInspector._documentBlurred, true);
836     window.removeEventListener("DOMContentLoaded", windowLoaded, false);
837 }
838
839 window.addEventListener("DOMContentLoaded", windowLoaded, false);
840
841 })();