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
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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.
32 WebInspector
.highlightedSearchResultClassName
= "highlighted-search-result";
35 * @param {!Element} element
36 * @param {?function(!MouseEvent): boolean} elementDragStart
37 * @param {function(!MouseEvent)} elementDrag
38 * @param {?function(!MouseEvent)} elementDragEnd
39 * @param {string} cursor
40 * @param {?string=} hoverCursor
42 WebInspector
.installDragHandle = function(element
, elementDragStart
, elementDrag
, elementDragEnd
, cursor
, hoverCursor
)
44 element
.addEventListener("mousedown", WebInspector
.elementDragStart
.bind(WebInspector
, elementDragStart
, elementDrag
, elementDragEnd
, cursor
), false);
45 if (hoverCursor
!== null)
46 element
.style
.cursor
= hoverCursor
|| cursor
;
50 * @param {?function(!MouseEvent):boolean} elementDragStart
51 * @param {function(!MouseEvent)} elementDrag
52 * @param {?function(!MouseEvent)} elementDragEnd
53 * @param {string} cursor
54 * @param {!Event} event
56 WebInspector
.elementDragStart = function(elementDragStart
, elementDrag
, elementDragEnd
, cursor
, event
)
58 // Only drag upon left button. Right will likely cause a context menu. So will ctrl-click on mac.
59 if (event
.button
|| (WebInspector
.isMac() && event
.ctrlKey
))
62 if (WebInspector
._elementDraggingEventListener
)
65 if (elementDragStart
&& !elementDragStart(/** @type {!MouseEvent} */ (event
)))
68 if (WebInspector
._elementDraggingGlassPane
) {
69 WebInspector
._elementDraggingGlassPane
.dispose();
70 delete WebInspector
._elementDraggingGlassPane
;
73 var targetDocument
= event
.target
.ownerDocument
;
75 WebInspector
._elementDraggingEventListener
= elementDrag
;
76 WebInspector
._elementEndDraggingEventListener
= elementDragEnd
;
77 WebInspector
._mouseOutWhileDraggingTargetDocument
= targetDocument
;
78 WebInspector
._dragEventsTargetDocument
= targetDocument
;
79 WebInspector
._dragEventsTargetDocumentTop
= targetDocument
.defaultView
.top
.document
;
81 targetDocument
.addEventListener("mousemove", WebInspector
._elementDragMove
, true);
82 targetDocument
.addEventListener("mouseup", WebInspector
._elementDragEnd
, true);
83 targetDocument
.addEventListener("mouseout", WebInspector
._mouseOutWhileDragging
, true);
84 if (targetDocument
!== WebInspector
._dragEventsTargetDocumentTop
)
85 WebInspector
._dragEventsTargetDocumentTop
.addEventListener("mouseup", WebInspector
._elementDragEnd
, true);
87 var targetElement
= /** @type {!Element} */ (event
.target
);
88 if (typeof cursor
=== "string") {
89 WebInspector
._restoreCursorAfterDrag
= restoreCursor
.bind(null, targetElement
.style
.cursor
);
90 targetElement
.style
.cursor
= cursor
;
91 targetDocument
.body
.style
.cursor
= cursor
;
93 function restoreCursor(oldCursor
)
95 targetDocument
.body
.style
.removeProperty("cursor");
96 targetElement
.style
.cursor
= oldCursor
;
97 WebInspector
._restoreCursorAfterDrag
= null;
99 event
.preventDefault();
102 WebInspector
._mouseOutWhileDragging = function()
104 var document
= WebInspector
._mouseOutWhileDraggingTargetDocument
;
105 WebInspector
._unregisterMouseOutWhileDragging();
106 WebInspector
._elementDraggingGlassPane
= new WebInspector
.GlassPane(document
);
109 WebInspector
._unregisterMouseOutWhileDragging = function()
111 if (!WebInspector
._mouseOutWhileDraggingTargetDocument
)
113 WebInspector
._mouseOutWhileDraggingTargetDocument
.removeEventListener("mouseout", WebInspector
._mouseOutWhileDragging
, true);
114 delete WebInspector
._mouseOutWhileDraggingTargetDocument
;
117 WebInspector
._unregisterDragEvents = function()
119 if (!WebInspector
._dragEventsTargetDocument
)
121 WebInspector
._dragEventsTargetDocument
.removeEventListener("mousemove", WebInspector
._elementDragMove
, true);
122 WebInspector
._dragEventsTargetDocument
.removeEventListener("mouseup", WebInspector
._elementDragEnd
, true);
123 if (WebInspector
._dragEventsTargetDocument
!== WebInspector
._dragEventsTargetDocumentTop
)
124 WebInspector
._dragEventsTargetDocumentTop
.removeEventListener("mouseup", WebInspector
._elementDragEnd
, true);
125 delete WebInspector
._dragEventsTargetDocument
;
126 delete WebInspector
._dragEventsTargetDocumentTop
;
130 * @param {!Event} event
132 WebInspector
._elementDragMove = function(event
)
134 if (event
.buttons
!== 1) {
135 WebInspector
._elementDragEnd(event
);
139 if (WebInspector
._elementDraggingEventListener(/** @type {!MouseEvent} */ (event
)))
140 WebInspector
._cancelDragEvents(event
);
144 * @param {!Event} event
146 WebInspector
._cancelDragEvents = function(event
)
148 WebInspector
._unregisterDragEvents();
149 WebInspector
._unregisterMouseOutWhileDragging();
151 if (WebInspector
._restoreCursorAfterDrag
)
152 WebInspector
._restoreCursorAfterDrag();
154 if (WebInspector
._elementDraggingGlassPane
)
155 WebInspector
._elementDraggingGlassPane
.dispose();
157 delete WebInspector
._elementDraggingGlassPane
;
158 delete WebInspector
._elementDraggingEventListener
;
159 delete WebInspector
._elementEndDraggingEventListener
;
163 * @param {!Event} event
165 WebInspector
._elementDragEnd = function(event
)
167 var elementDragEnd
= WebInspector
._elementEndDraggingEventListener
;
169 WebInspector
._cancelDragEvents(/** @type {!MouseEvent} */ (event
));
171 event
.preventDefault();
173 elementDragEnd(/** @type {!MouseEvent} */ (event
));
178 * @param {!Document} document
180 WebInspector
.GlassPane = function(document
)
182 this.element
= createElement("div");
183 this.element
.style
.cssText
= "position:absolute;top:0;bottom:0;left:0;right:0;background-color:transparent;z-index:1000;overflow:hidden;";
184 this.element
.id
= "glass-pane";
185 document
.body
.appendChild(this.element
);
186 WebInspector
._glassPane
= this;
189 WebInspector
.GlassPane
.prototype = {
192 delete WebInspector
._glassPane
;
193 if (WebInspector
.GlassPane
.DefaultFocusedViewStack
.length
)
194 WebInspector
.GlassPane
.DefaultFocusedViewStack
.peekLast().focus();
195 this.element
.remove();
200 * @type {!Array.<!WebInspector.Widget|!WebInspector.Dialog>}
202 WebInspector
.GlassPane
.DefaultFocusedViewStack
= [];
205 * @param {?Node=} node
208 WebInspector
.isBeingEdited = function(node
)
210 if (!node
|| node
.nodeType
!== Node
.ELEMENT_NODE
)
212 var element
= /** {!Element} */ (node
);
213 if (element
.classList
.contains("text-prompt") || element
.nodeName
=== "INPUT" || element
.nodeName
=== "TEXTAREA")
216 if (!WebInspector
.__editingCount
)
220 if (element
.__editing
)
222 element
= element
.parentElementOrShadowHost();
230 WebInspector
.isEditing = function()
232 if (WebInspector
.__editingCount
)
235 var element
= WebInspector
.currentFocusElement();
238 return element
.classList
.contains("text-prompt") || element
.nodeName
=== "INPUT" || element
.nodeName
=== "TEXTAREA";
242 * @param {!Element} element
243 * @param {boolean} value
246 WebInspector
.markBeingEdited = function(element
, value
)
249 if (element
.__editing
)
251 element
.classList
.add("being-edited");
252 element
.__editing
= true;
253 WebInspector
.__editingCount
= (WebInspector
.__editingCount
|| 0) + 1;
255 if (!element
.__editing
)
257 element
.classList
.remove("being-edited");
258 delete element
.__editing
;
259 --WebInspector
.__editingCount
;
264 WebInspector
.CSSNumberRegex
= /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
266 WebInspector
.StyleValueDelimiters
= " \xA0\t\n\"':;,/()";
270 * @param {!Event} event
273 WebInspector
._valueModificationDirection = function(event
)
275 var direction
= null;
276 if (event
.type
=== "mousewheel") {
277 if (event
.wheelDeltaY
> 0)
279 else if (event
.wheelDeltaY
< 0)
282 if (event
.keyIdentifier
=== "Up" || event
.keyIdentifier
=== "PageUp")
284 else if (event
.keyIdentifier
=== "Down" || event
.keyIdentifier
=== "PageDown")
291 * @param {string} hexString
292 * @param {!Event} event
294 WebInspector
._modifiedHexValue = function(hexString
, event
)
296 var direction
= WebInspector
._valueModificationDirection(event
);
300 var number
= parseInt(hexString
, 16);
301 if (isNaN(number
) || !isFinite(number
))
304 var maxValue
= Math
.pow(16, hexString
.length
) - 1;
305 var arrowKeyOrMouseWheelEvent
= (event
.keyIdentifier
=== "Up" || event
.keyIdentifier
=== "Down" || event
.type
=== "mousewheel");
308 if (arrowKeyOrMouseWheelEvent
)
309 delta
= (direction
=== "Up") ? 1 : -1;
311 delta
= (event
.keyIdentifier
=== "PageUp") ? 16 : -16;
316 var result
= number
+ delta
;
318 result
= 0; // Color hex values are never negative, so clamp to 0.
319 else if (result
> maxValue
)
322 // Ensure the result length is the same as the original hex value.
323 var resultString
= result
.toString(16).toUpperCase();
324 for (var i
= 0, lengthDelta
= hexString
.length
- resultString
.length
; i
< lengthDelta
; ++i
)
325 resultString
= "0" + resultString
;
330 * @param {number} number
331 * @param {!Event} event
333 WebInspector
._modifiedFloatNumber = function(number
, event
)
335 var direction
= WebInspector
._valueModificationDirection(event
);
339 var arrowKeyOrMouseWheelEvent
= (event
.keyIdentifier
=== "Up" || event
.keyIdentifier
=== "Down" || event
.type
=== "mousewheel");
341 // Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down.
342 // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
343 var changeAmount
= 1;
344 if (event
.shiftKey
&& !arrowKeyOrMouseWheelEvent
)
346 else if (event
.shiftKey
|| !arrowKeyOrMouseWheelEvent
)
348 else if (event
.altKey
)
351 if (direction
=== "Down")
354 // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
355 // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
356 var result
= Number((number
+ changeAmount
).toFixed(6));
357 if (!String(result
).match(WebInspector
.CSSNumberRegex
))
364 * @param {string} wordString
365 * @param {!Event} event
366 * @param {function(string, number, string):string=} customNumberHandler
369 WebInspector
.createReplacementString = function(wordString
, event
, customNumberHandler
)
371 var replacementString
;
372 var prefix
, suffix
, number
;
375 matches
= /(.*#)([\da-fA-F]+)(.*)/.exec(wordString
);
376 if (matches
&& matches
.length
) {
379 number
= WebInspector
._modifiedHexValue(matches
[2], event
);
381 replacementString
= customNumberHandler
? customNumberHandler(prefix
, number
, suffix
) : prefix
+ number
+ suffix
;
383 matches
= /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString
);
384 if (matches
&& matches
.length
) {
387 number
= WebInspector
._modifiedFloatNumber(parseFloat(matches
[2]), event
);
389 // Need to check for null explicitly.
393 replacementString
= customNumberHandler
? customNumberHandler(prefix
, number
, suffix
) : prefix
+ number
+ suffix
;
396 return replacementString
|| null;
400 * @param {!Event} event
401 * @param {!Element} element
402 * @param {function(string,string)=} finishHandler
403 * @param {function(string)=} suggestionHandler
404 * @param {function(string, number, string):string=} customNumberHandler
407 WebInspector
.handleElementValueModifications = function(event
, element
, finishHandler
, suggestionHandler
, customNumberHandler
)
411 * @suppressGlobalPropertiesCheck
413 function createRange()
415 return document
.createRange();
418 var arrowKeyOrMouseWheelEvent
= (event
.keyIdentifier
=== "Up" || event
.keyIdentifier
=== "Down" || event
.type
=== "mousewheel");
419 var pageKeyPressed
= (event
.keyIdentifier
=== "PageUp" || event
.keyIdentifier
=== "PageDown");
420 if (!arrowKeyOrMouseWheelEvent
&& !pageKeyPressed
)
423 var selection
= element
.getComponentSelection();
424 if (!selection
.rangeCount
)
427 var selectionRange
= selection
.getRangeAt(0);
428 if (!selectionRange
.commonAncestorContainer
.isSelfOrDescendant(element
))
431 var originalValue
= element
.textContent
;
432 var wordRange
= selectionRange
.startContainer
.rangeOfWord(selectionRange
.startOffset
, WebInspector
.StyleValueDelimiters
, element
);
433 var wordString
= wordRange
.toString();
435 if (suggestionHandler
&& suggestionHandler(wordString
))
438 var replacementString
= WebInspector
.createReplacementString(wordString
, event
, customNumberHandler
);
440 if (replacementString
) {
441 var replacementTextNode
= createTextNode(replacementString
);
443 wordRange
.deleteContents();
444 wordRange
.insertNode(replacementTextNode
);
446 var finalSelectionRange
= createRange();
447 finalSelectionRange
.setStart(replacementTextNode
, 0);
448 finalSelectionRange
.setEnd(replacementTextNode
, replacementString
.length
);
450 selection
.removeAllRanges();
451 selection
.addRange(finalSelectionRange
);
453 event
.handled
= true;
454 event
.preventDefault();
457 finishHandler(originalValue
, replacementString
);
466 * @param {number=} precision
469 Number
.preciseMillisToString = function(ms
, precision
)
471 precision
= precision
|| 0;
472 var format
= "%." + precision
+ "f\u2009ms";
473 return WebInspector
.UIString(format
, ms
);
476 /** @type {!WebInspector.UIStringFormat} */
477 WebInspector
._subMillisFormat
= new WebInspector
.UIStringFormat("%.2f\u2009ms");
479 /** @type {!WebInspector.UIStringFormat} */
480 WebInspector
._millisFormat
= new WebInspector
.UIStringFormat("%.0f\u2009ms");
482 /** @type {!WebInspector.UIStringFormat} */
483 WebInspector
._secondsFormat
= new WebInspector
.UIStringFormat("%.2f\u2009s");
485 /** @type {!WebInspector.UIStringFormat} */
486 WebInspector
._minutesFormat
= new WebInspector
.UIStringFormat("%.1f\u2009min");
488 /** @type {!WebInspector.UIStringFormat} */
489 WebInspector
._hoursFormat
= new WebInspector
.UIStringFormat("%.1f\u2009hrs");
491 /** @type {!WebInspector.UIStringFormat} */
492 WebInspector
._daysFormat
= new WebInspector
.UIStringFormat("%.1f\u2009days");
496 * @param {boolean=} higherResolution
499 Number
.millisToString = function(ms
, higherResolution
)
507 if (higherResolution
&& ms
< 1000)
508 return WebInspector
._subMillisFormat
.format(ms
);
510 return WebInspector
._millisFormat
.format(ms
);
512 var seconds
= ms
/ 1000;
514 return WebInspector
._secondsFormat
.format(seconds
);
516 var minutes
= seconds
/ 60;
518 return WebInspector
._minutesFormat
.format(minutes
);
520 var hours
= minutes
/ 60;
522 return WebInspector
._hoursFormat
.format(hours
);
524 var days
= hours
/ 24;
525 return WebInspector
._daysFormat
.format(days
);
529 * @param {number} seconds
530 * @param {boolean=} higherResolution
533 Number
.secondsToString = function(seconds
, higherResolution
)
535 if (!isFinite(seconds
))
537 return Number
.millisToString(seconds
* 1000, higherResolution
);
541 * @param {number} bytes
544 Number
.bytesToString = function(bytes
)
547 return WebInspector
.UIString("%.0f\u2009B", bytes
);
549 var kilobytes
= bytes
/ 1024;
551 return WebInspector
.UIString("%.1f\u2009KB", kilobytes
);
552 if (kilobytes
< 1024)
553 return WebInspector
.UIString("%.0f\u2009KB", kilobytes
);
555 var megabytes
= kilobytes
/ 1024;
557 return WebInspector
.UIString("%.1f\u2009MB", megabytes
);
559 return WebInspector
.UIString("%.0f\u2009MB", megabytes
);
563 * @param {number} num
566 Number
.withThousandsSeparator = function(num
)
569 var re
= /(\d+)(\d{3})/;
570 while (str
.match(re
))
571 str
= str
.replace(re
, "$1\u2009$2"); // \u2009 is a thin space.
576 * @param {string} format
577 * @param {?ArrayLike} substitutions
578 * @param {?string} initialValue
581 WebInspector
.formatLocalized = function(format
, substitutions
, initialValue
)
583 var element
= createElement("span");
585 s: function(substitution
)
590 function append(a
, b
)
592 if (typeof b
=== "string")
593 b
= createTextNode(b
);
594 else if (b
.shadowRoot
)
595 b
= createTextNode(b
.shadowRoot
.lastChild
.textContent
);
596 element
.appendChild(b
);
598 String
.format(WebInspector
.UIString(format
), substitutions
, formatters
, initialValue
, append
);
605 WebInspector
.openLinkExternallyLabel = function()
607 return WebInspector
.UIString
.capitalize("Open ^link in ^new ^tab");
613 WebInspector
.copyLinkAddressLabel = function()
615 return WebInspector
.UIString
.capitalize("Copy ^link ^address");
621 WebInspector
.anotherProfilerActiveLabel = function()
623 return WebInspector
.UIString("Another profiler is already active");
627 * @param {string|undefined} description
630 WebInspector
.asyncStackTraceLabel = function(description
)
633 return description
+ " " + WebInspector
.UIString("(async)");
634 return WebInspector
.UIString("Async Call");
640 WebInspector
.manageBlackboxingButtonLabel = function()
642 return WebInspector
.UIString("Manage framework blackboxing...");
646 * @param {!Element} element
648 WebInspector
.installComponentRootStyles = function(element
)
650 element
.appendChild(WebInspector
.Widget
.createStyleElement("ui/inspectorCommon.css"));
651 element
.appendChild(WebInspector
.Widget
.createStyleElement("ui/inspectorSyntaxHighlight.css"));
652 element
.classList
.add("platform-" + WebInspector
.platform());
653 if (Runtime
.experiments
.isEnabled("materialDesign"))
654 element
.classList
.add("material");
658 * @param {!Element} element
659 * @return {!DocumentFragment}
661 WebInspector
.createShadowRootWithCoreStyles = function(element
)
663 var shadowRoot
= element
.createShadowRoot();
664 shadowRoot
.appendChild(WebInspector
.Widget
.createStyleElement("ui/inspectorCommon.css"));
665 shadowRoot
.appendChild(WebInspector
.Widget
.createStyleElement("ui/inspectorSyntaxHighlight.css"));
666 shadowRoot
.addEventListener("focus", WebInspector
._focusChanged
.bind(WebInspector
), true);
671 * @param {!Document} document
672 * @param {!Event} event
674 WebInspector
._windowFocused = function(document
, event
)
676 if (event
.target
.document
.nodeType
=== Node
.DOCUMENT_NODE
)
677 document
.body
.classList
.remove("inactive");
681 * @param {!Document} document
682 * @param {!Event} event
684 WebInspector
._windowBlurred = function(document
, event
)
686 if (event
.target
.document
.nodeType
=== Node
.DOCUMENT_NODE
)
687 document
.body
.classList
.add("inactive");
693 WebInspector
.previousFocusElement = function()
695 return WebInspector
._previousFocusElement
;
701 WebInspector
.currentFocusElement = function()
703 return WebInspector
._currentFocusElement
;
707 * @param {!Event} event
709 WebInspector
._focusChanged = function(event
)
711 var node
= event
.deepActiveElement();
712 WebInspector
.setCurrentFocusElement(node
);
716 * @param {!Document} document
717 * @param {!Event} event
719 WebInspector
._documentBlurred = function(document
, event
)
721 // We want to know when currentFocusElement loses focus to nowhere.
722 // This is the case when event.relatedTarget is null (no element is being focused)
723 // and document.activeElement is reset to default (this is not a window blur).
724 if (!event
.relatedTarget
&& document
.activeElement
=== document
.body
)
725 WebInspector
.setCurrentFocusElement(null);
728 WebInspector
._textInputTypes
= ["text", "search", "tel", "url", "email", "password"].keySet();
729 WebInspector
._isTextEditingElement = function(element
)
731 if (element
instanceof HTMLInputElement
)
732 return element
.type
in WebInspector
._textInputTypes
;
734 if (element
instanceof HTMLTextAreaElement
)
743 WebInspector
.setCurrentFocusElement = function(x
)
745 if (WebInspector
._glassPane
&& x
&& !WebInspector
._glassPane
.element
.isAncestor(x
))
747 if (WebInspector
._currentFocusElement
!== x
)
748 WebInspector
._previousFocusElement
= WebInspector
._currentFocusElement
;
749 WebInspector
._currentFocusElement
= x
;
751 if (WebInspector
._currentFocusElement
) {
752 WebInspector
._currentFocusElement
.focus();
754 // Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside.
755 // This is needed (at least) to remove caret from console when focus is moved to some element in the panel.
756 // The code below should not be applied to text fields and text areas, hence _isTextEditingElement check.
757 var selection
= x
.getComponentSelection();
758 if (!WebInspector
._isTextEditingElement(WebInspector
._currentFocusElement
) && selection
.isCollapsed
&& !WebInspector
._currentFocusElement
.isInsertionCaretInside()) {
759 var selectionRange
= WebInspector
._currentFocusElement
.ownerDocument
.createRange();
760 selectionRange
.setStart(WebInspector
._currentFocusElement
, 0);
761 selectionRange
.setEnd(WebInspector
._currentFocusElement
, 0);
763 selection
.removeAllRanges();
764 selection
.addRange(selectionRange
);
766 } else if (WebInspector
._previousFocusElement
)
767 WebInspector
._previousFocusElement
.blur();
770 WebInspector
.restoreFocusFromElement = function(element
)
772 if (element
&& element
.isSelfOrAncestor(WebInspector
.currentFocusElement()))
773 WebInspector
.setCurrentFocusElement(WebInspector
.previousFocusElement());
777 * @param {!Element} element
778 * @param {number} offset
779 * @param {number} length
780 * @param {!Array.<!Object>=} domChanges
783 WebInspector
.highlightSearchResult = function(element
, offset
, length
, domChanges
)
785 var result
= WebInspector
.highlightSearchResults(element
, [new WebInspector
.SourceRange(offset
, length
)], domChanges
);
786 return result
.length
? result
[0] : null;
790 * @param {!Element} element
791 * @param {!Array.<!WebInspector.SourceRange>} resultRanges
792 * @param {!Array.<!Object>=} changes
793 * @return {!Array.<!Element>}
795 WebInspector
.highlightSearchResults = function(element
, resultRanges
, changes
)
797 return WebInspector
.highlightRangesWithStyleClass(element
, resultRanges
, WebInspector
.highlightedSearchResultClassName
, changes
);
801 * @param {!Element} element
802 * @param {string} className
804 WebInspector
.runCSSAnimationOnce = function(element
, className
)
806 function animationEndCallback()
808 element
.classList
.remove(className
);
809 element
.removeEventListener("webkitAnimationEnd", animationEndCallback
, false);
812 if (element
.classList
.contains(className
))
813 element
.classList
.remove(className
);
815 element
.addEventListener("webkitAnimationEnd", animationEndCallback
, false);
816 element
.classList
.add(className
);
820 * @param {!Element} element
821 * @param {!Array.<!WebInspector.SourceRange>} resultRanges
822 * @param {string} styleClass
823 * @param {!Array.<!Object>=} changes
824 * @return {!Array.<!Element>}
826 WebInspector
.highlightRangesWithStyleClass = function(element
, resultRanges
, styleClass
, changes
)
828 changes
= changes
|| [];
829 var highlightNodes
= [];
830 var lineText
= element
.deepTextContent();
831 var ownerDocument
= element
.ownerDocument
;
832 var textNodes
= element
.childTextNodes();
834 if (textNodes
.length
=== 0)
835 return highlightNodes
;
838 var rangeEndOffset
= 0;
839 for (var i
= 0; i
< textNodes
.length
; ++i
) {
841 range
.offset
= rangeEndOffset
;
842 range
.length
= textNodes
[i
].textContent
.length
;
843 rangeEndOffset
= range
.offset
+ range
.length
;
844 nodeRanges
.push(range
);
848 for (var i
= 0; i
< resultRanges
.length
; ++i
) {
849 var startOffset
= resultRanges
[i
].offset
;
850 var endOffset
= startOffset
+ resultRanges
[i
].length
;
852 while (startIndex
< textNodes
.length
&& nodeRanges
[startIndex
].offset
+ nodeRanges
[startIndex
].length
<= startOffset
)
854 var endIndex
= startIndex
;
855 while (endIndex
< textNodes
.length
&& nodeRanges
[endIndex
].offset
+ nodeRanges
[endIndex
].length
< endOffset
)
857 if (endIndex
=== textNodes
.length
)
860 var highlightNode
= ownerDocument
.createElement("span");
861 highlightNode
.className
= styleClass
;
862 highlightNode
.textContent
= lineText
.substring(startOffset
, endOffset
);
864 var lastTextNode
= textNodes
[endIndex
];
865 var lastText
= lastTextNode
.textContent
;
866 lastTextNode
.textContent
= lastText
.substring(endOffset
- nodeRanges
[endIndex
].offset
);
867 changes
.push({ node
: lastTextNode
, type
: "changed", oldText
: lastText
, newText
: lastTextNode
.textContent
});
869 if (startIndex
=== endIndex
) {
870 lastTextNode
.parentElement
.insertBefore(highlightNode
, lastTextNode
);
871 changes
.push({ node
: highlightNode
, type
: "added", nextSibling
: lastTextNode
, parent
: lastTextNode
.parentElement
});
872 highlightNodes
.push(highlightNode
);
874 var prefixNode
= ownerDocument
.createTextNode(lastText
.substring(0, startOffset
- nodeRanges
[startIndex
].offset
));
875 lastTextNode
.parentElement
.insertBefore(prefixNode
, highlightNode
);
876 changes
.push({ node
: prefixNode
, type
: "added", nextSibling
: highlightNode
, parent
: lastTextNode
.parentElement
});
878 var firstTextNode
= textNodes
[startIndex
];
879 var firstText
= firstTextNode
.textContent
;
880 var anchorElement
= firstTextNode
.nextSibling
;
882 firstTextNode
.parentElement
.insertBefore(highlightNode
, anchorElement
);
883 changes
.push({ node
: highlightNode
, type
: "added", nextSibling
: anchorElement
, parent
: firstTextNode
.parentElement
});
884 highlightNodes
.push(highlightNode
);
886 firstTextNode
.textContent
= firstText
.substring(0, startOffset
- nodeRanges
[startIndex
].offset
);
887 changes
.push({ node
: firstTextNode
, type
: "changed", oldText
: firstText
, newText
: firstTextNode
.textContent
});
889 for (var j
= startIndex
+ 1; j
< endIndex
; j
++) {
890 var textNode
= textNodes
[j
];
891 var text
= textNode
.textContent
;
892 textNode
.textContent
= "";
893 changes
.push({ node
: textNode
, type
: "changed", oldText
: text
, newText
: textNode
.textContent
});
896 startIndex
= endIndex
;
897 nodeRanges
[startIndex
].offset
= endOffset
;
898 nodeRanges
[startIndex
].length
= lastTextNode
.textContent
.length
;
901 return highlightNodes
;
904 WebInspector
.applyDomChanges = function(domChanges
)
906 for (var i
= 0, size
= domChanges
.length
; i
< size
; ++i
) {
907 var entry
= domChanges
[i
];
908 switch (entry
.type
) {
910 entry
.parent
.insertBefore(entry
.node
, entry
.nextSibling
);
913 entry
.node
.textContent
= entry
.newText
;
919 WebInspector
.revertDomChanges = function(domChanges
)
921 for (var i
= domChanges
.length
- 1; i
>= 0; --i
) {
922 var entry
= domChanges
[i
];
923 switch (entry
.type
) {
928 entry
.node
.textContent
= entry
.oldText
;
935 * @param {!Element} element
936 * @param {?Element=} containerElement
939 WebInspector
.measurePreferredSize = function(element
, containerElement
)
941 containerElement
= containerElement
|| element
.ownerDocument
.body
;
942 containerElement
.appendChild(element
);
943 element
.positionAt(0, 0);
944 var result
= new Size(element
.offsetWidth
, element
.offsetHeight
);
945 element
.positionAt(undefined, undefined);
952 * @param {boolean} autoInvoke
954 WebInspector
.InvokeOnceHandlers = function(autoInvoke
)
956 this._handlers
= null;
957 this._autoInvoke
= autoInvoke
;
960 WebInspector
.InvokeOnceHandlers
.prototype = {
962 * @param {!Object} object
963 * @param {function()} method
965 add: function(object
, method
)
967 if (!this._handlers
) {
968 this._handlers
= new Map();
969 if (this._autoInvoke
)
970 this.scheduleInvoke();
972 var methods
= this._handlers
.get(object
);
975 this._handlers
.set(object
, methods
);
981 * @suppressGlobalPropertiesCheck
983 scheduleInvoke: function()
986 requestAnimationFrame(this._invoke
.bind(this));
991 var handlers
= this._handlers
;
992 this._handlers
= null;
993 var keys
= handlers
.keysArray();
994 for (var i
= 0; i
< keys
.length
; ++i
) {
995 var object
= keys
[i
];
996 var methods
= handlers
.get(object
).valuesArray();
997 for (var j
= 0; j
< methods
.length
; ++j
)
998 methods
[j
].call(object
);
1003 WebInspector
._coalescingLevel
= 0;
1004 WebInspector
._postUpdateHandlers
= null;
1006 WebInspector
.startBatchUpdate = function()
1008 if (!WebInspector
._coalescingLevel
++)
1009 WebInspector
._postUpdateHandlers
= new WebInspector
.InvokeOnceHandlers(false);
1012 WebInspector
.endBatchUpdate = function()
1014 if (--WebInspector
._coalescingLevel
)
1016 WebInspector
._postUpdateHandlers
.scheduleInvoke();
1017 WebInspector
._postUpdateHandlers
= null;
1021 * @param {!Object} object
1022 * @param {function()} method
1024 WebInspector
.invokeOnceAfterBatchUpdate = function(object
, method
)
1026 if (!WebInspector
._postUpdateHandlers
)
1027 WebInspector
._postUpdateHandlers
= new WebInspector
.InvokeOnceHandlers(true);
1028 WebInspector
._postUpdateHandlers
.add(object
, method
);
1032 * @param {!Window} window
1033 * @param {!Function} func
1034 * @param {!Array.<{from:number, to:number}>} params
1035 * @param {number} frames
1036 * @param {function()=} animationComplete
1037 * @return {function()}
1039 WebInspector
.animateFunction = function(window
, func
, params
, frames
, animationComplete
)
1041 var values
= new Array(params
.length
);
1042 var deltas
= new Array(params
.length
);
1043 for (var i
= 0; i
< params
.length
; ++i
) {
1044 values
[i
] = params
[i
].from;
1045 deltas
[i
] = (params
[i
].to
- params
[i
].from) / frames
;
1048 var raf
= window
.requestAnimationFrame(animationStep
);
1050 var framesLeft
= frames
;
1052 function animationStep()
1054 if (--framesLeft
< 0) {
1055 if (animationComplete
)
1056 animationComplete();
1059 for (var i
= 0; i
< params
.length
; ++i
) {
1060 if (params
[i
].to
> params
[i
].from)
1061 values
[i
] = Number
.constrain(values
[i
] + deltas
[i
], params
[i
].from, params
[i
].to
);
1063 values
[i
] = Number
.constrain(values
[i
] + deltas
[i
], params
[i
].to
, params
[i
].from);
1065 func
.apply(null, values
);
1066 raf
= window
.requestAnimationFrame(animationStep
);
1069 function cancelAnimation()
1071 window
.cancelAnimationFrame(raf
);
1074 return cancelAnimation
;
1079 * @extends {WebInspector.Object}
1080 * @param {!Element} element
1082 WebInspector
.LongClickController = function(element
)
1084 this._element
= element
;
1090 WebInspector
.LongClickController
.Events
= {
1091 LongClick
: "LongClick"
1094 WebInspector
.LongClickController
.prototype = {
1097 if (this._longClickInterval
) {
1098 clearInterval(this._longClickInterval
);
1099 delete this._longClickInterval
;
1105 if (this._longClickData
)
1107 var boundMouseDown
= mouseDown
.bind(this);
1108 var boundMouseUp
= mouseUp
.bind(this);
1109 var boundReset
= this.reset
.bind(this);
1111 this._element
.addEventListener("mousedown", boundMouseDown
, false);
1112 this._element
.addEventListener("mouseout", boundReset
, false);
1113 this._element
.addEventListener("mouseup", boundMouseUp
, false);
1114 this._element
.addEventListener("click", boundReset
, true);
1116 this._longClickData
= { mouseUp
: boundMouseUp
, mouseDown
: boundMouseDown
, reset
: boundReset
};
1120 * @this {WebInspector.LongClickController}
1122 function mouseDown(e
)
1126 this._longClickInterval
= setTimeout(longClicked
.bind(this, e
), 200);
1131 * @this {WebInspector.LongClickController}
1142 * @this {WebInspector.LongClickController}
1144 function longClicked(e
)
1146 this.dispatchEventToListeners(WebInspector
.LongClickController
.Events
.LongClick
, e
);
1152 if (!this._longClickData
)
1154 this._element
.removeEventListener("mousedown", this._longClickData
.mouseDown
, false);
1155 this._element
.removeEventListener("mouseout", this._longClickData
.reset
, false);
1156 this._element
.removeEventListener("mouseup", this._longClickData
.mouseUp
, false);
1157 this._element
.addEventListener("click", this._longClickData
.reset
, true);
1158 delete this._longClickData
;
1161 __proto__
: WebInspector
.Object
.prototype
1165 * @param {!Window} window
1167 WebInspector
.initializeUIUtils = function(window
)
1169 window
.addEventListener("focus", WebInspector
._windowFocused
.bind(WebInspector
, window
.document
), false);
1170 window
.addEventListener("blur", WebInspector
._windowBlurred
.bind(WebInspector
, window
.document
), false);
1171 window
.document
.addEventListener("focus", WebInspector
._focusChanged
.bind(WebInspector
), true);
1172 window
.document
.addEventListener("blur", WebInspector
._documentBlurred
.bind(WebInspector
, window
.document
), true);
1176 * @param {string} name
1179 WebInspector
.beautifyFunctionName = function(name
)
1181 return name
|| WebInspector
.UIString("(anonymous function)");
1185 * @param {string} localName
1186 * @param {string} typeExtension
1187 * @param {!Object} prototype
1188 * @return {function()}
1189 * @suppressGlobalPropertiesCheck
1192 function registerCustomElement(localName
, typeExtension
, prototype)
1194 return document
.registerElement(typeExtension
, {
1195 prototype: Object
.create(prototype),
1201 * @param {string} text
1202 * @param {function(!Event)=} clickHandler
1203 * @param {string=} className
1204 * @param {string=} title
1205 * @return {!Element}
1207 function createTextButton(text
, clickHandler
, className
, title
)
1209 var element
= createElementWithClass("button", className
|| "", "text-button");
1210 element
.textContent
= text
;
1212 element
.addEventListener("click", clickHandler
, false);
1214 element
.title
= title
;
1219 * @param {string} name
1220 * @param {string} title
1221 * @param {boolean=} checked
1222 * @return {!Element}
1224 function createRadioLabel(name
, title
, checked
)
1226 var element
= createElement("label", "dt-radio");
1227 element
.radioElement
.name
= name
;
1228 element
.radioElement
.checked
= !!checked
;
1229 element
.createTextChild(title
);
1234 * @param {string=} title
1235 * @param {boolean=} checked
1236 * @return {!Element}
1238 function createCheckboxLabel(title
, checked
)
1240 var element
= createElement("label", "dt-checkbox");
1241 element
.checkboxElement
.checked
= !!checked
;
1242 if (title
!== undefined) {
1243 element
.textElement
= element
.createChild("div", "dt-checkbox-text");
1244 element
.textElement
.textContent
= title
;
1250 registerCustomElement("button", "text-button", {
1254 createdCallback: function()
1256 this.type
= "button";
1257 var root
= WebInspector
.createShadowRootWithCoreStyles(this);
1258 root
.appendChild(WebInspector
.Widget
.createStyleElement("ui/textButton.css"));
1259 root
.createChild("content");
1262 __proto__
: HTMLButtonElement
.prototype
1265 registerCustomElement("label", "dt-radio", {
1269 createdCallback: function()
1271 this.radioElement
= this.createChild("input", "dt-radio-button");
1272 this.radioElement
.type
= "radio";
1273 var root
= WebInspector
.createShadowRootWithCoreStyles(this);
1274 root
.appendChild(WebInspector
.Widget
.createStyleElement("ui/radioButton.css"));
1275 root
.createChild("content").select
= ".dt-radio-button";
1276 root
.createChild("content");
1277 this.addEventListener("click", radioClickHandler
, false);
1280 __proto__
: HTMLLabelElement
.prototype
1284 * @param {!Event} event
1285 * @suppressReceiverCheck
1288 function radioClickHandler(event
)
1290 if (this.radioElement
.checked
|| this.radioElement
.disabled
)
1292 this.radioElement
.checked
= true;
1293 this.radioElement
.dispatchEvent(new Event("change"));
1296 registerCustomElement("label", "dt-checkbox", {
1300 createdCallback: function()
1302 this._root
= WebInspector
.createShadowRootWithCoreStyles(this);
1303 this._root
.appendChild(WebInspector
.Widget
.createStyleElement("ui/checkboxTextLabel.css"));
1304 var checkboxElement
= createElementWithClass("input", "dt-checkbox-button");
1305 checkboxElement
.type
= "checkbox";
1306 this._root
.appendChild(checkboxElement
);
1307 this.checkboxElement
= checkboxElement
;
1309 this.addEventListener("click", toggleCheckbox
.bind(this));
1312 * @param {!Event} event
1315 function toggleCheckbox(event
)
1317 if (event
.target
!== checkboxElement
&& event
.target
!== this)
1318 checkboxElement
.click();
1321 this._root
.createChild("content");
1325 * @param {string} color
1328 set backgroundColor(color
)
1330 this.checkboxElement
.classList
.add("dt-checkbox-themed");
1331 this.checkboxElement
.style
.backgroundColor
= color
;
1335 * @param {string} color
1338 set checkColor(color
)
1340 this.checkboxElement
.classList
.add("dt-checkbox-themed");
1341 var stylesheet
= createElement("style");
1342 stylesheet
.textContent
= "input.dt-checkbox-themed:checked:after { background-color: " + color
+ "}";
1343 this._root
.appendChild(stylesheet
);
1347 * @param {string} color
1350 set borderColor(color
)
1352 this.checkboxElement
.classList
.add("dt-checkbox-themed");
1353 this.checkboxElement
.style
.borderColor
= color
;
1356 __proto__
: HTMLLabelElement
.prototype
1359 registerCustomElement("label", "dt-icon-label", {
1363 createdCallback: function()
1365 var root
= WebInspector
.createShadowRootWithCoreStyles(this);
1366 root
.appendChild(WebInspector
.Widget
.createStyleElement("ui/smallIcon.css"));
1367 this._iconElement
= root
.createChild("div");
1368 root
.createChild("content");
1372 * @param {string} type
1377 this._iconElement
.className
= type
;
1380 __proto__
: HTMLLabelElement
.prototype
1383 registerCustomElement("div", "dt-close-button", {
1387 createdCallback: function()
1389 var root
= WebInspector
.createShadowRootWithCoreStyles(this);
1390 root
.appendChild(WebInspector
.Widget
.createStyleElement("ui/closeButton.css"));
1391 this._buttonElement
= root
.createChild("div", "close-button");
1395 * @param {boolean} gray
1400 this._buttonElement
.className
= gray
? "close-button-gray" : "close-button";
1403 __proto__
: HTMLDivElement
.prototype
1410 WebInspector
.StringFormatter = function()
1412 this._processors
= [];
1416 WebInspector
.StringFormatter
.prototype = {
1418 * @param {!RegExp} regex
1419 * @param {function(string):!Node} handler
1421 addProcessor: function(regex
, handler
)
1423 this._regexes
.push(regex
);
1424 this._processors
.push(handler
);
1428 * @param {string} text
1431 formatText: function(text
)
1433 return this._runProcessor(0, text
);
1437 * @param {number} processorIndex
1438 * @param {string} text
1441 _runProcessor: function(processorIndex
, text
)
1443 if (processorIndex
>= this._processors
.length
)
1444 return createTextNode(text
);
1446 var container
= createDocumentFragment();
1447 var regex
= this._regexes
[processorIndex
];
1448 var processor
= this._processors
[processorIndex
];
1450 // Due to the nature of regex, |items| array has matched elements on its even indexes.
1451 var items
= text
.replace(regex
, "\0$1\0").split("\0");
1452 for (var i
= 0; i
< items
.length
; ++i
) {
1453 var processedNode
= i
% 2 ? processor(items
[i
]) : this._runProcessor(processorIndex
+ 1, items
[i
]);
1454 container
.appendChild(processedNode
);