Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / ui / UIUtils.js
blob2e93d48589c7b0e88a407171643cedce67009bbe
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
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
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";
34 /**
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;
49 /**
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))
60 return;
62 if (WebInspector._elementDraggingEventListener)
63 return;
65 if (elementDragStart && !elementDragStart(/** @type {!MouseEvent} */ (event)))
66 return;
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)
112 return;
113 WebInspector._mouseOutWhileDraggingTargetDocument.removeEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
114 delete WebInspector._mouseOutWhileDraggingTargetDocument;
117 WebInspector._unregisterDragEvents = function()
119 if (!WebInspector._dragEventsTargetDocument)
120 return;
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);
136 return;
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();
172 if (elementDragEnd)
173 elementDragEnd(/** @type {!MouseEvent} */ (event));
177 * @constructor
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 = {
190 dispose: function()
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
206 * @return {boolean}
208 WebInspector.isBeingEdited = function(node)
210 if (!node || node.nodeType !== Node.ELEMENT_NODE)
211 return false;
212 var element = /** {!Element} */ (node);
213 if (element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA")
214 return true;
216 if (!WebInspector.__editingCount)
217 return false;
219 while (element) {
220 if (element.__editing)
221 return true;
222 element = element.parentElementOrShadowHost();
224 return false;
228 * @return {boolean}
230 WebInspector.isEditing = function()
232 if (WebInspector.__editingCount)
233 return true;
235 var element = WebInspector.currentFocusElement();
236 if (!element)
237 return false;
238 return element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA";
242 * @param {!Element} element
243 * @param {boolean} value
244 * @return {boolean}
246 WebInspector.markBeingEdited = function(element, value)
248 if (value) {
249 if (element.__editing)
250 return false;
251 element.classList.add("being-edited");
252 element.__editing = true;
253 WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
254 } else {
255 if (!element.__editing)
256 return false;
257 element.classList.remove("being-edited");
258 delete element.__editing;
259 --WebInspector.__editingCount;
261 return true;
264 WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
266 WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()";
270 * @param {!Event} event
271 * @return {?string}
273 WebInspector._valueModificationDirection = function(event)
275 var direction = null;
276 if (event.type === "mousewheel") {
277 if (event.wheelDeltaY > 0)
278 direction = "Up";
279 else if (event.wheelDeltaY < 0)
280 direction = "Down";
281 } else {
282 if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp")
283 direction = "Up";
284 else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
285 direction = "Down";
287 return direction;
291 * @param {string} hexString
292 * @param {!Event} event
294 WebInspector._modifiedHexValue = function(hexString, event)
296 var direction = WebInspector._valueModificationDirection(event);
297 if (!direction)
298 return hexString;
300 var number = parseInt(hexString, 16);
301 if (isNaN(number) || !isFinite(number))
302 return hexString;
304 var maxValue = Math.pow(16, hexString.length) - 1;
305 var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
306 var delta;
308 if (arrowKeyOrMouseWheelEvent)
309 delta = (direction === "Up") ? 1 : -1;
310 else
311 delta = (event.keyIdentifier === "PageUp") ? 16 : -16;
313 if (event.shiftKey)
314 delta *= 16;
316 var result = number + delta;
317 if (result < 0)
318 result = 0; // Color hex values are never negative, so clamp to 0.
319 else if (result > maxValue)
320 return hexString;
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;
326 return resultString;
330 * @param {number} number
331 * @param {!Event} event
333 WebInspector._modifiedFloatNumber = function(number, event)
335 var direction = WebInspector._valueModificationDirection(event);
336 if (!direction)
337 return number;
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)
345 changeAmount = 100;
346 else if (event.shiftKey || !arrowKeyOrMouseWheelEvent)
347 changeAmount = 10;
348 else if (event.altKey)
349 changeAmount = 0.1;
351 if (direction === "Down")
352 changeAmount *= -1;
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))
358 return null;
360 return result;
364 * @param {string} wordString
365 * @param {!Event} event
366 * @param {function(string, number, string):string=} customNumberHandler
367 * @return {?string}
369 WebInspector.createReplacementString = function(wordString, event, customNumberHandler)
371 var replacementString;
372 var prefix, suffix, number;
374 var matches;
375 matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString);
376 if (matches && matches.length) {
377 prefix = matches[1];
378 suffix = matches[3];
379 number = WebInspector._modifiedHexValue(matches[2], event);
381 replacementString = customNumberHandler ? customNumberHandler(prefix, number, suffix) : prefix + number + suffix;
382 } else {
383 matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);
384 if (matches && matches.length) {
385 prefix = matches[1];
386 suffix = matches[3];
387 number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event);
389 // Need to check for null explicitly.
390 if (number === null)
391 return null;
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
405 * @return {boolean}
407 WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler)
410 * @return {?Range}
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)
421 return false;
423 var selection = element.getComponentSelection();
424 if (!selection.rangeCount)
425 return false;
427 var selectionRange = selection.getRangeAt(0);
428 if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element))
429 return false;
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))
436 return false;
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();
456 if (finishHandler)
457 finishHandler(originalValue, replacementString);
459 return true;
461 return false;
465 * @param {number} ms
466 * @param {number=} precision
467 * @return {string}
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");
495 * @param {number} ms
496 * @param {boolean=} higherResolution
497 * @return {string}
499 Number.millisToString = function(ms, higherResolution)
501 if (!isFinite(ms))
502 return "-";
504 if (ms === 0)
505 return "0";
507 if (higherResolution && ms < 1000)
508 return WebInspector._subMillisFormat.format(ms);
509 else if (ms < 1000)
510 return WebInspector._millisFormat.format(ms);
512 var seconds = ms / 1000;
513 if (seconds < 60)
514 return WebInspector._secondsFormat.format(seconds);
516 var minutes = seconds / 60;
517 if (minutes < 60)
518 return WebInspector._minutesFormat.format(minutes);
520 var hours = minutes / 60;
521 if (hours < 24)
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
531 * @return {string}
533 Number.secondsToString = function(seconds, higherResolution)
535 if (!isFinite(seconds))
536 return "-";
537 return Number.millisToString(seconds * 1000, higherResolution);
541 * @param {number} bytes
542 * @return {string}
544 Number.bytesToString = function(bytes)
546 if (bytes < 1024)
547 return WebInspector.UIString("%.0f\u2009B", bytes);
549 var kilobytes = bytes / 1024;
550 if (kilobytes < 100)
551 return WebInspector.UIString("%.1f\u2009KB", kilobytes);
552 if (kilobytes < 1024)
553 return WebInspector.UIString("%.0f\u2009KB", kilobytes);
555 var megabytes = kilobytes / 1024;
556 if (megabytes < 100)
557 return WebInspector.UIString("%.1f\u2009MB", megabytes);
558 else
559 return WebInspector.UIString("%.0f\u2009MB", megabytes);
563 * @param {number} num
564 * @return {string}
566 Number.withThousandsSeparator = function(num)
568 var str = 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.
572 return str;
576 * @param {string} format
577 * @param {?ArrayLike} substitutions
578 * @param {?string} initialValue
579 * @return {!Element}
581 WebInspector.formatLocalized = function(format, substitutions, initialValue)
583 var element = createElement("span");
584 var formatters = {
585 s: function(substitution)
587 return 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);
599 return element;
603 * @return {string}
605 WebInspector.openLinkExternallyLabel = function()
607 return WebInspector.UIString.capitalize("Open ^link in ^new ^tab");
611 * @return {string}
613 WebInspector.copyLinkAddressLabel = function()
615 return WebInspector.UIString.capitalize("Copy ^link ^address");
619 * @return {string}
621 WebInspector.anotherProfilerActiveLabel = function()
623 return WebInspector.UIString("Another profiler is already active");
627 * @param {string|undefined} description
628 * @return {string}
630 WebInspector.asyncStackTraceLabel = function(description)
632 if (description)
633 return description + " " + WebInspector.UIString("(async)");
634 return WebInspector.UIString("Async Call");
638 * @return {string}
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);
667 return shadowRoot;
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");
691 * @return {!Element}
693 WebInspector.previousFocusElement = function()
695 return WebInspector._previousFocusElement;
699 * @return {!Element}
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)
735 return true;
737 return false;
741 * @param {?Node} x
743 WebInspector.setCurrentFocusElement = function(x)
745 if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x))
746 return;
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
781 * @return {?Element}
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;
837 var nodeRanges = [];
838 var rangeEndOffset = 0;
839 for (var i = 0; i < textNodes.length; ++i) {
840 var range = {};
841 range.offset = rangeEndOffset;
842 range.length = textNodes[i].textContent.length;
843 rangeEndOffset = range.offset + range.length;
844 nodeRanges.push(range);
847 var startIndex = 0;
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)
853 startIndex++;
854 var endIndex = startIndex;
855 while (endIndex < textNodes.length && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
856 endIndex++;
857 if (endIndex === textNodes.length)
858 break;
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 });
877 } else {
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) {
909 case "added":
910 entry.parent.insertBefore(entry.node, entry.nextSibling);
911 break;
912 case "changed":
913 entry.node.textContent = entry.newText;
914 break;
919 WebInspector.revertDomChanges = function(domChanges)
921 for (var i = domChanges.length - 1; i >= 0; --i) {
922 var entry = domChanges[i];
923 switch (entry.type) {
924 case "added":
925 entry.node.remove();
926 break;
927 case "changed":
928 entry.node.textContent = entry.oldText;
929 break;
935 * @param {!Element} element
936 * @param {?Element=} containerElement
937 * @return {!Size}
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);
946 element.remove();
947 return result;
951 * @constructor
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);
973 if (!methods) {
974 methods = new Set();
975 this._handlers.set(object, methods);
977 methods.add(method);
981 * @suppressGlobalPropertiesCheck
983 scheduleInvoke: function()
985 if (this._handlers)
986 requestAnimationFrame(this._invoke.bind(this));
989 _invoke: function()
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)
1015 return;
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();
1057 return;
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);
1062 else
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;
1078 * @constructor
1079 * @extends {WebInspector.Object}
1080 * @param {!Element} element
1082 WebInspector.LongClickController = function(element)
1084 this._element = element;
1088 * @enum {string}
1090 WebInspector.LongClickController.Events = {
1091 LongClick: "LongClick"
1094 WebInspector.LongClickController.prototype = {
1095 reset: function()
1097 if (this._longClickInterval) {
1098 clearInterval(this._longClickInterval);
1099 delete this._longClickInterval;
1103 enable: function()
1105 if (this._longClickData)
1106 return;
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 };
1119 * @param {!Event} e
1120 * @this {WebInspector.LongClickController}
1122 function mouseDown(e)
1124 if (e.which !== 1)
1125 return;
1126 this._longClickInterval = setTimeout(longClicked.bind(this, e), 200);
1130 * @param {!Event} e
1131 * @this {WebInspector.LongClickController}
1133 function mouseUp(e)
1135 if (e.which !== 1)
1136 return;
1137 this.reset();
1141 * @param {!Event} e
1142 * @this {WebInspector.LongClickController}
1144 function longClicked(e)
1146 this.dispatchEventToListeners(WebInspector.LongClickController.Events.LongClick, e);
1150 disable: function()
1152 if (!this._longClickData)
1153 return;
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
1177 * @return {string}
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
1190 * @template T
1192 function registerCustomElement(localName, typeExtension, prototype)
1194 return document.registerElement(typeExtension, {
1195 prototype: Object.create(prototype),
1196 extends: localName
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;
1211 if (clickHandler)
1212 element.addEventListener("click", clickHandler, false);
1213 if (title)
1214 element.title = title;
1215 return element;
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);
1230 return element;
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;
1246 return element;
1249 ;(function() {
1250 registerCustomElement("button", "text-button", {
1252 * @this {Element}
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", {
1267 * @this {Element}
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
1286 * @this {Element}
1288 function radioClickHandler(event)
1290 if (this.radioElement.checked || this.radioElement.disabled)
1291 return;
1292 this.radioElement.checked = true;
1293 this.radioElement.dispatchEvent(new Event("change"));
1296 registerCustomElement("label", "dt-checkbox", {
1298 * @this {Element}
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
1313 * @this {Node}
1315 function toggleCheckbox(event)
1317 if (event.target !== checkboxElement && event.target !== this)
1318 checkboxElement.click();
1321 this._root.createChild("content");
1325 * @param {string} color
1326 * @this {Element}
1328 set backgroundColor(color)
1330 this.checkboxElement.classList.add("dt-checkbox-themed");
1331 this.checkboxElement.style.backgroundColor = color;
1335 * @param {string} color
1336 * @this {Element}
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
1348 * @this {Element}
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", {
1361 * @this {Element}
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
1373 * @this {Element}
1375 set type(type)
1377 this._iconElement.className = type;
1380 __proto__: HTMLLabelElement.prototype
1383 registerCustomElement("div", "dt-close-button", {
1385 * @this {Element}
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
1396 * @this {Element}
1398 set gray(gray)
1400 this._buttonElement.className = gray ? "close-button-gray" : "close-button";
1403 __proto__: HTMLDivElement.prototype
1405 })();
1408 * @constructor
1410 WebInspector.StringFormatter = function()
1412 this._processors = [];
1413 this._regexes = [];
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
1429 * @return {!Node}
1431 formatText: function(text)
1433 return this._runProcessor(0, text);
1437 * @param {number} processorIndex
1438 * @param {string} text
1439 * @return {!Node}
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);
1457 return container;