Upstream version 10.39.225.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.GlassPane.DefaultFocusedViewStack.length)
160             WebInspector.GlassPane.DefaultFocusedViewStack.peekLast().focus();
161         this.element.remove();
162     }
163 }
164
165 /**
166  * @type {!Array.<!WebInspector.View|!WebInspector.Dialog>}
167  */
168 WebInspector.GlassPane.DefaultFocusedViewStack = [];
169
170 /**
171  * @param {?Node=} node
172  * @return {boolean}
173  */
174 WebInspector.isBeingEdited = function(node)
175 {
176     if (!node || node.nodeType !== Node.ELEMENT_NODE)
177         return false;
178     var element = /** {!Element} */ (node);
179     if (element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA")
180         return true;
181
182     if (!WebInspector.__editingCount)
183         return false;
184
185     while (element) {
186         if (element.__editing)
187             return true;
188         element = element.parentElement;
189     }
190     return false;
191 }
192
193 /**
194  * @param {!Element} element
195  * @param {boolean} value
196  * @return {boolean}
197  */
198 WebInspector.markBeingEdited = function(element, value)
199 {
200     if (value) {
201         if (element.__editing)
202             return false;
203         element.classList.add("being-edited");
204         element.__editing = true;
205         WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
206     } else {
207         if (!element.__editing)
208             return false;
209         element.classList.remove("being-edited");
210         delete element.__editing;
211         --WebInspector.__editingCount;
212     }
213     return true;
214 }
215
216 WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
217
218 WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()";
219
220
221 /**
222   * @param {!Event} event
223   * @return {?string}
224   */
225 WebInspector._valueModificationDirection = function(event)
226 {
227     var direction = null;
228     if (event.type === "mousewheel") {
229         if (event.wheelDeltaY > 0)
230             direction = "Up";
231         else if (event.wheelDeltaY < 0)
232             direction = "Down";
233     } else {
234         if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp")
235             direction = "Up";
236         else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
237             direction = "Down";
238     }
239     return direction;
240 }
241
242 /**
243  * @param {string} hexString
244  * @param {!Event} event
245  */
246 WebInspector._modifiedHexValue = function(hexString, event)
247 {
248     var direction = WebInspector._valueModificationDirection(event);
249     if (!direction)
250         return hexString;
251
252     var number = parseInt(hexString, 16);
253     if (isNaN(number) || !isFinite(number))
254         return hexString;
255
256     var maxValue = Math.pow(16, hexString.length) - 1;
257     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
258     var delta;
259
260     if (arrowKeyOrMouseWheelEvent)
261         delta = (direction === "Up") ? 1 : -1;
262     else
263         delta = (event.keyIdentifier === "PageUp") ? 16 : -16;
264
265     if (event.shiftKey)
266         delta *= 16;
267
268     var result = number + delta;
269     if (result < 0)
270         result = 0; // Color hex values are never negative, so clamp to 0.
271     else if (result > maxValue)
272         return hexString;
273
274     // Ensure the result length is the same as the original hex value.
275     var resultString = result.toString(16).toUpperCase();
276     for (var i = 0, lengthDelta = hexString.length - resultString.length; i < lengthDelta; ++i)
277         resultString = "0" + resultString;
278     return resultString;
279 }
280
281 /**
282  * @param {number} number
283  * @param {!Event} event
284  */
285 WebInspector._modifiedFloatNumber = function(number, event)
286 {
287     var direction = WebInspector._valueModificationDirection(event);
288     if (!direction)
289         return number;
290
291     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
292
293     // Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down.
294     // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
295     var changeAmount = 1;
296     if (event.shiftKey && !arrowKeyOrMouseWheelEvent)
297         changeAmount = 100;
298     else if (event.shiftKey || !arrowKeyOrMouseWheelEvent)
299         changeAmount = 10;
300     else if (event.altKey)
301         changeAmount = 0.1;
302
303     if (direction === "Down")
304         changeAmount *= -1;
305
306     // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
307     // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
308     var result = Number((number + changeAmount).toFixed(6));
309     if (!String(result).match(WebInspector.CSSNumberRegex))
310         return null;
311
312     return result;
313 }
314
315 /**
316   * @param {!Event} event
317   * @param {!Element} element
318   * @param {function(string,string)=} finishHandler
319   * @param {function(string)=} suggestionHandler
320   * @param {function(string, number, string):string=} customNumberHandler
321   * @return {boolean}
322  */
323 WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler)
324 {
325     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
326     var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
327     if (!arrowKeyOrMouseWheelEvent && !pageKeyPressed)
328         return false;
329
330     var selection = window.getSelection();
331     if (!selection.rangeCount)
332         return false;
333
334     var selectionRange = selection.getRangeAt(0);
335     if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element))
336         return false;
337
338     var originalValue = element.textContent;
339     var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StyleValueDelimiters, element);
340     var wordString = wordRange.toString();
341
342     if (suggestionHandler && suggestionHandler(wordString))
343         return false;
344
345     var replacementString;
346     var prefix, suffix, number;
347
348     var matches;
349     matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString);
350     if (matches && matches.length) {
351         prefix = matches[1];
352         suffix = matches[3];
353         number = WebInspector._modifiedHexValue(matches[2], event);
354
355         replacementString = customNumberHandler ? customNumberHandler(prefix, number, suffix) : prefix + number + suffix;
356     } else {
357         matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);
358         if (matches && matches.length) {
359             prefix = matches[1];
360             suffix = matches[3];
361             number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event);
362
363             // Need to check for null explicitly.
364             if (number === null)
365                 return false;
366
367             replacementString = customNumberHandler ? customNumberHandler(prefix, number, suffix) : prefix + number + suffix;
368         }
369     }
370
371     if (replacementString) {
372         var replacementTextNode = document.createTextNode(replacementString);
373
374         wordRange.deleteContents();
375         wordRange.insertNode(replacementTextNode);
376
377         var finalSelectionRange = document.createRange();
378         finalSelectionRange.setStart(replacementTextNode, 0);
379         finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
380
381         selection.removeAllRanges();
382         selection.addRange(finalSelectionRange);
383
384         event.handled = true;
385         event.preventDefault();
386
387         if (finishHandler)
388             finishHandler(originalValue, replacementString);
389
390         return true;
391     }
392     return false;
393 }
394
395 /**
396  * @param {number} ms
397  * @param {number=} precision
398  * @return {string}
399  */
400 Number.preciseMillisToString = function(ms, precision)
401 {
402   precision = precision || 0;
403   var format = "%." + precision + "f\u2009ms";
404   return WebInspector.UIString(format, ms);
405 }
406
407 /** @type {!WebInspector.UIStringFormat} */
408 WebInspector._subMillisFormat = new WebInspector.UIStringFormat("%.3f\u2009ms");
409
410 /** @type {!WebInspector.UIStringFormat} */
411 WebInspector._millisFormat = new WebInspector.UIStringFormat("%.0f\u2009ms");
412
413 /** @type {!WebInspector.UIStringFormat} */
414 WebInspector._secondsFormat = new WebInspector.UIStringFormat("%.2f\u2009s");
415
416 /** @type {!WebInspector.UIStringFormat} */
417 WebInspector._minutesFormat = new WebInspector.UIStringFormat("%.1f\u2009min");
418
419 /** @type {!WebInspector.UIStringFormat} */
420 WebInspector._hoursFormat = new WebInspector.UIStringFormat("%.1f\u2009hrs");
421
422 /** @type {!WebInspector.UIStringFormat} */
423 WebInspector._daysFormat = new WebInspector.UIStringFormat("%.1f\u2009days");
424
425 /**
426  * @param {number} ms
427  * @param {boolean=} higherResolution
428  * @return {string}
429  */
430 Number.millisToString = function(ms, higherResolution)
431 {
432     if (!isFinite(ms))
433         return "-";
434
435     if (ms === 0)
436         return "0";
437
438     if (higherResolution && ms < 1000)
439         return WebInspector._subMillisFormat.format(ms);
440     else if (ms < 1000)
441         return WebInspector._millisFormat.format(ms);
442
443     var seconds = ms / 1000;
444     if (seconds < 60)
445         return WebInspector._secondsFormat.format(seconds);
446
447     var minutes = seconds / 60;
448     if (minutes < 60)
449         return WebInspector._minutesFormat.format(minutes);
450
451     var hours = minutes / 60;
452     if (hours < 24)
453         return WebInspector._hoursFormat.format(hours);
454
455     var days = hours / 24;
456     return WebInspector._daysFormat.format(days);
457 }
458
459 /**
460  * @param {number} seconds
461  * @param {boolean=} higherResolution
462  * @return {string}
463  */
464 Number.secondsToString = function(seconds, higherResolution)
465 {
466     if (!isFinite(seconds))
467         return "-";
468     return Number.millisToString(seconds * 1000, higherResolution);
469 }
470
471 /**
472  * @param {number} bytes
473  * @return {string}
474  */
475 Number.bytesToString = function(bytes)
476 {
477     if (bytes < 1024)
478         return WebInspector.UIString("%.0f\u2009B", bytes);
479
480     var kilobytes = bytes / 1024;
481     if (kilobytes < 100)
482         return WebInspector.UIString("%.1f\u2009KB", kilobytes);
483     if (kilobytes < 1024)
484         return WebInspector.UIString("%.0f\u2009KB", kilobytes);
485
486     var megabytes = kilobytes / 1024;
487     if (megabytes < 100)
488         return WebInspector.UIString("%.1f\u2009MB", megabytes);
489     else
490         return WebInspector.UIString("%.0f\u2009MB", megabytes);
491 }
492
493 /**
494  * @param {number} num
495  * @return {string}
496  */
497 Number.withThousandsSeparator = function(num)
498 {
499     var str = num + "";
500     var re = /(\d+)(\d{3})/;
501     while (str.match(re))
502         str = str.replace(re, "$1\u2009$2"); // \u2009 is a thin space.
503     return str;
504 }
505
506 /**
507  * @return {boolean}
508  */
509 WebInspector.useLowerCaseMenuTitles = function()
510 {
511     return WebInspector.platform() === "windows";
512 }
513
514 /**
515  * @param {string} format
516  * @param {?ArrayLike} substitutions
517  * @param {!Object.<string, function(string, ...):*>} formatters
518  * @param {string} initialValue
519  * @param {function(string, string): ?} append
520  * @return {!{formattedResult: string, unusedSubstitutions: ?ArrayLike}};
521  */
522 WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
523 {
524     return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
525 }
526
527 /**
528  * @return {string}
529  */
530 WebInspector.openLinkExternallyLabel = function()
531 {
532     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
533 }
534
535 /**
536  * @return {string}
537  */
538 WebInspector.copyLinkAddressLabel = function()
539 {
540     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
541 }
542
543 /**
544  * @return {string}
545  */
546 WebInspector.anotherProfilerActiveLabel = function()
547 {
548     return WebInspector.UIString("Another profiler is already active");
549 }
550
551 /**
552  * @param {string|undefined} description
553  * @return {string}
554  */
555 WebInspector.asyncStackTraceLabel = function(description)
556 {
557     if (description)
558         return description + " " + WebInspector.UIString("(async)");
559     return WebInspector.UIString("Async Call");
560 }
561
562 /**
563  * @return {string}
564  */
565 WebInspector.manageBlackboxingButtonLabel = function()
566 {
567     return WebInspector.UIString("Manage framework blackboxing...");
568 }
569
570 WebInspector.installPortStyles = function()
571 {
572     var platform = WebInspector.platform();
573     document.body.classList.add("platform-" + platform);
574     var flavor = WebInspector.platformFlavor();
575     if (flavor)
576         document.body.classList.add("platform-" + flavor);
577     var port = WebInspector.port();
578     document.body.classList.add("port-" + port);
579 }
580
581 WebInspector._windowFocused = function(event)
582 {
583     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
584         document.body.classList.remove("inactive");
585 }
586
587 WebInspector._windowBlurred = function(event)
588 {
589     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
590         document.body.classList.add("inactive");
591 }
592
593 /**
594  * @return {!Element}
595  */
596 WebInspector.previousFocusElement = function()
597 {
598     return WebInspector._previousFocusElement;
599 }
600
601 /**
602  * @return {!Element}
603  */
604 WebInspector.currentFocusElement = function()
605 {
606     return WebInspector._currentFocusElement;
607 }
608
609 WebInspector._focusChanged = function(event)
610 {
611     WebInspector.setCurrentFocusElement(event.target);
612 }
613
614 WebInspector._documentBlurred = function(event)
615 {
616     // We want to know when currentFocusElement loses focus to nowhere.
617     // This is the case when event.relatedTarget is null (no element is being focused)
618     // and document.activeElement is reset to default (this is not a window blur).
619     if (!event.relatedTarget && document.activeElement === document.body)
620       WebInspector.setCurrentFocusElement(null);
621 }
622
623 WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet();
624 WebInspector._isTextEditingElement = function(element)
625 {
626     if (element instanceof HTMLInputElement)
627         return element.type in WebInspector._textInputTypes;
628
629     if (element instanceof HTMLTextAreaElement)
630         return true;
631
632     return false;
633 }
634
635 WebInspector.setCurrentFocusElement = function(x)
636 {
637     if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x))
638         return;
639     if (WebInspector._currentFocusElement !== x)
640         WebInspector._previousFocusElement = WebInspector._currentFocusElement;
641     WebInspector._currentFocusElement = x;
642
643     if (WebInspector._currentFocusElement) {
644         WebInspector._currentFocusElement.focus();
645
646         // Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside.
647         // This is needed (at least) to remove caret from console when focus is moved to some element in the panel.
648         // The code below should not be applied to text fields and text areas, hence _isTextEditingElement check.
649         var selection = window.getSelection();
650         if (!WebInspector._isTextEditingElement(WebInspector._currentFocusElement) && selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) {
651             var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange();
652             selectionRange.setStart(WebInspector._currentFocusElement, 0);
653             selectionRange.setEnd(WebInspector._currentFocusElement, 0);
654
655             selection.removeAllRanges();
656             selection.addRange(selectionRange);
657         }
658     } else if (WebInspector._previousFocusElement)
659         WebInspector._previousFocusElement.blur();
660 }
661
662 WebInspector.restoreFocusFromElement = function(element)
663 {
664     if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement()))
665         WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
666 }
667
668 WebInspector.setToolbarColors = function(backgroundColor, color)
669 {
670     if (!WebInspector._themeStyleElement) {
671         WebInspector._themeStyleElement = document.createElement("style");
672         document.head.appendChild(WebInspector._themeStyleElement);
673     }
674     var parsedColor = WebInspector.Color.parse(color);
675     var shadowColor = parsedColor ? parsedColor.invert().setAlpha(0.33).toString(WebInspector.Color.Format.RGBA) : "white";
676     var prefix = WebInspector.isMac() ? "body:not(.undocked)" : "";
677     WebInspector._themeStyleElement.textContent =
678         String.sprintf(
679             "%s .toolbar-colors {\
680                  background-image: none !important;\
681                  background-color: %s !important;\
682                  color: %s !important;\
683              }", prefix, backgroundColor, color) +
684         String.sprintf(
685              "%s .toolbar-colors button.status-bar-item .glyph, %s .toolbar-colors button.status-bar-item .long-click-glyph {\
686                  background-color: %s;\
687              }", prefix, prefix, color) +
688         String.sprintf(
689              "%s .toolbar-colors button.status-bar-item .glyph.shadow, %s .toolbar-colors button.status-bar-item .long-click-glyph.shadow {\
690                  background-color: %s;\
691              }", prefix, prefix, shadowColor);
692 }
693
694 WebInspector.resetToolbarColors = function()
695 {
696     if (WebInspector._themeStyleElement)
697         WebInspector._themeStyleElement.textContent = "";
698 }
699
700 /**
701  * @param {!Element} element
702  * @param {number} offset
703  * @param {number} length
704  * @param {!Array.<!Object>=} domChanges
705  * @return {?Element}
706  */
707 WebInspector.highlightSearchResult = function(element, offset, length, domChanges)
708 {
709     var result = WebInspector.highlightSearchResults(element, [new WebInspector.SourceRange(offset, length)], domChanges);
710     return result.length ? result[0] : null;
711 }
712
713 /**
714  * @param {!Element} element
715  * @param {!Array.<!WebInspector.SourceRange>} resultRanges
716  * @param {!Array.<!Object>=} changes
717  * @return {!Array.<!Element>}
718  */
719 WebInspector.highlightSearchResults = function(element, resultRanges, changes)
720 {
721     return WebInspector.highlightRangesWithStyleClass(element, resultRanges, "highlighted-search-result", changes);
722 }
723
724 /**
725  * @param {!Element} element
726  * @param {string} className
727  */
728 WebInspector.runCSSAnimationOnce = function(element, className)
729 {
730     function animationEndCallback()
731     {
732         element.classList.remove(className);
733         element.removeEventListener("animationend", animationEndCallback, false);
734     }
735
736     if (element.classList.contains(className))
737         element.classList.remove(className);
738
739     element.addEventListener("animationend", animationEndCallback, false);
740     element.classList.add(className);
741 }
742
743 /**
744  * @param {!Element} element
745  * @param {!Array.<!WebInspector.SourceRange>} resultRanges
746  * @param {string} styleClass
747  * @param {!Array.<!Object>=} changes
748  * @return {!Array.<!Element>}
749  */
750 WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes)
751 {
752     changes = changes || [];
753     var highlightNodes = [];
754     var lineText = element.textContent;
755     var ownerDocument = element.ownerDocument;
756     var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
757
758     var snapshotLength = textNodeSnapshot.snapshotLength;
759     if (snapshotLength === 0)
760         return highlightNodes;
761
762     var nodeRanges = [];
763     var rangeEndOffset = 0;
764     for (var i = 0; i < snapshotLength; ++i) {
765         var range = {};
766         range.offset = rangeEndOffset;
767         range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
768         rangeEndOffset = range.offset + range.length;
769         nodeRanges.push(range);
770     }
771
772     var startIndex = 0;
773     for (var i = 0; i < resultRanges.length; ++i) {
774         var startOffset = resultRanges[i].offset;
775         var endOffset = startOffset + resultRanges[i].length;
776
777         while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
778             startIndex++;
779         var endIndex = startIndex;
780         while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
781             endIndex++;
782         if (endIndex === snapshotLength)
783             break;
784
785         var highlightNode = ownerDocument.createElement("span");
786         highlightNode.className = styleClass;
787         highlightNode.textContent = lineText.substring(startOffset, endOffset);
788
789         var lastTextNode = textNodeSnapshot.snapshotItem(endIndex);
790         var lastText = lastTextNode.textContent;
791         lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset);
792         changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent });
793
794         if (startIndex === endIndex) {
795             lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode);
796             changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement });
797             highlightNodes.push(highlightNode);
798
799             var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset));
800             lastTextNode.parentElement.insertBefore(prefixNode, highlightNode);
801             changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement });
802         } else {
803             var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
804             var firstText = firstTextNode.textContent;
805             var anchorElement = firstTextNode.nextSibling;
806
807             firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
808             changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement });
809             highlightNodes.push(highlightNode);
810
811             firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
812             changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent });
813
814             for (var j = startIndex + 1; j < endIndex; j++) {
815                 var textNode = textNodeSnapshot.snapshotItem(j);
816                 var text = textNode.textContent;
817                 textNode.textContent = "";
818                 changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent });
819             }
820         }
821         startIndex = endIndex;
822         nodeRanges[startIndex].offset = endOffset;
823         nodeRanges[startIndex].length = lastTextNode.textContent.length;
824
825     }
826     return highlightNodes;
827 }
828
829 WebInspector.applyDomChanges = function(domChanges)
830 {
831     for (var i = 0, size = domChanges.length; i < size; ++i) {
832         var entry = domChanges[i];
833         switch (entry.type) {
834         case "added":
835             entry.parent.insertBefore(entry.node, entry.nextSibling);
836             break;
837         case "changed":
838             entry.node.textContent = entry.newText;
839             break;
840         }
841     }
842 }
843
844 WebInspector.revertDomChanges = function(domChanges)
845 {
846     for (var i = domChanges.length - 1; i >= 0; --i) {
847         var entry = domChanges[i];
848         switch (entry.type) {
849         case "added":
850             entry.node.remove();
851             break;
852         case "changed":
853             entry.node.textContent = entry.oldText;
854             break;
855         }
856     }
857 }
858
859 /**
860  * @constructor
861  * @param {boolean} autoInvoke
862  */
863 WebInspector.InvokeOnceHandlers = function(autoInvoke)
864 {
865     this._handlers = null;
866     this._autoInvoke = autoInvoke;
867 }
868
869 WebInspector.InvokeOnceHandlers.prototype = {
870     /**
871      * @param {!Object} object
872      * @param {function()} method
873      */
874     add: function(object, method)
875     {
876         if (!this._handlers) {
877             this._handlers = new Map();
878             if (this._autoInvoke)
879                 this.scheduleInvoke();
880         }
881         var methods = this._handlers.get(object);
882         if (!methods) {
883             methods = new Set();
884             this._handlers.set(object, methods);
885         }
886         methods.add(method);
887     },
888
889     scheduleInvoke: function()
890     {
891         if (this._handlers)
892             requestAnimationFrame(this._invoke.bind(this));
893     },
894
895     _invoke: function()
896     {
897         var handlers = this._handlers;
898         this._handlers = null;
899         var keys = handlers.keys();
900         for (var i = 0; i < keys.length; ++i) {
901             var object = keys[i];
902             var methods = handlers.get(object).values();
903             for (var j = 0; j < methods.length; ++j)
904                 methods[j].call(object);
905         }
906     }
907 }
908
909 WebInspector._coalescingLevel = 0;
910 WebInspector._postUpdateHandlers = null;
911
912 WebInspector.startBatchUpdate = function()
913 {
914     if (!WebInspector._coalescingLevel++)
915         WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(false);
916 }
917
918 WebInspector.endBatchUpdate = function()
919 {
920     if (--WebInspector._coalescingLevel)
921         return;
922     WebInspector._postUpdateHandlers.scheduleInvoke();
923     WebInspector._postUpdateHandlers = null;
924 }
925
926 /**
927  * @param {!Object} object
928  * @param {function()} method
929  */
930 WebInspector.invokeOnceAfterBatchUpdate = function(object, method)
931 {
932     if (!WebInspector._postUpdateHandlers)
933         WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(true);
934     WebInspector._postUpdateHandlers.add(object, method);
935 }
936
937 /**
938  * @param {!Function} func
939  * @param {!Array.<{from:number, to:number}>} params
940  * @param {number} frames
941  * @param {function()=} animationComplete
942  * @return {function()}
943  */
944 WebInspector.animateFunction = function(func, params, frames, animationComplete)
945 {
946     var values = new Array(params.length);
947     var deltas = new Array(params.length);
948     for (var i = 0; i < params.length; ++i) {
949         values[i] = params[i].from;
950         deltas[i] = (params[i].to - params[i].from) / frames;
951     }
952
953     var raf = requestAnimationFrame(animationStep);
954
955     var framesLeft = frames;
956
957     function animationStep()
958     {
959         if (--framesLeft < 0) {
960             if (animationComplete)
961                 animationComplete();
962             return;
963         }
964         for (var i = 0; i < params.length; ++i) {
965             if (params[i].to > params[i].from)
966                 values[i] = Number.constrain(values[i] + deltas[i], params[i].from, params[i].to);
967             else
968                 values[i] = Number.constrain(values[i] + deltas[i], params[i].to, params[i].from);
969         }
970         func.apply(null, values);
971         raf = window.requestAnimationFrame(animationStep);
972     }
973
974     function cancelAnimation()
975     {
976         window.cancelAnimationFrame(raf);
977     }
978
979     return cancelAnimation;
980 }
981
982 /**
983  * @constructor
984  * @extends {WebInspector.Object}
985  * @param {!Element} element
986  */
987 WebInspector.LongClickController = function(element)
988 {
989     this._element = element;
990 }
991
992 /**
993  * @enum {string}
994  */
995 WebInspector.LongClickController.Events = {
996     LongClick: "LongClick",
997     LongPress: "LongPress"
998 };
999
1000 WebInspector.LongClickController.prototype = {
1001     reset: function()
1002     {
1003         if (this._longClickInterval) {
1004             clearInterval(this._longClickInterval);
1005             delete this._longClickInterval;
1006         }
1007     },
1008
1009     enable: function()
1010     {
1011         if (this._longClickData)
1012             return;
1013         var boundMouseDown = mouseDown.bind(this);
1014         var boundMouseUp = mouseUp.bind(this);
1015         var boundReset = this.reset.bind(this);
1016
1017         this._element.addEventListener("mousedown", boundMouseDown, false);
1018         this._element.addEventListener("mouseout", boundReset, false);
1019         this._element.addEventListener("mouseup", boundMouseUp, false);
1020         this._element.addEventListener("click", boundReset, true);
1021
1022         var longClicks = 0;
1023
1024         this._longClickData = { mouseUp: boundMouseUp, mouseDown: boundMouseDown, reset: boundReset };
1025
1026         /**
1027          * @param {!Event} e
1028          * @this {WebInspector.LongClickController}
1029          */
1030         function mouseDown(e)
1031         {
1032             if (e.which !== 1)
1033                 return;
1034             longClicks = 0;
1035             this._longClickInterval = setInterval(longClicked.bind(this, e), 200);
1036         }
1037
1038         /**
1039          * @param {!Event} e
1040          * @this {WebInspector.LongClickController}
1041          */
1042         function mouseUp(e)
1043         {
1044             if (e.which !== 1)
1045                 return;
1046             this.reset();
1047         }
1048
1049         /**
1050          * @param {!Event} e
1051          * @this {WebInspector.LongClickController}
1052          */
1053         function longClicked(e)
1054         {
1055             ++longClicks;
1056             this.dispatchEventToListeners(longClicks === 1 ? WebInspector.LongClickController.Events.LongClick : WebInspector.LongClickController.Events.LongPress, e);
1057         }
1058     },
1059
1060     disable: function()
1061     {
1062         if (!this._longClickData)
1063             return;
1064         this._element.removeEventListener("mousedown", this._longClickData.mouseDown, false);
1065         this._element.removeEventListener("mouseout", this._longClickData.reset, false);
1066         this._element.removeEventListener("mouseup", this._longClickData.mouseUp, false);
1067         this._element.addEventListener("click", this._longClickData.reset, true);
1068         delete this._longClickData;
1069     },
1070
1071     __proto__: WebInspector.Object.prototype
1072 }
1073
1074 ;(function() {
1075
1076 function windowLoaded()
1077 {
1078     window.addEventListener("focus", WebInspector._windowFocused, false);
1079     window.addEventListener("blur", WebInspector._windowBlurred, false);
1080     document.addEventListener("focus", WebInspector._focusChanged, true);
1081     document.addEventListener("blur", WebInspector._documentBlurred, true);
1082     window.removeEventListener("DOMContentLoaded", windowLoaded, false);
1083 }
1084
1085 window.addEventListener("DOMContentLoaded", windowLoaded, false);
1086
1087 })();