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.
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
40 WebInspector.installDragHandle = function(element, elementDragStart, elementDrag, elementDragEnd, cursor, hoverCursor)
42 element.addEventListener("mousedown", WebInspector.elementDragStart.bind(WebInspector, elementDragStart, elementDrag, elementDragEnd, cursor), false);
43 if (hoverCursor !== null)
44 element.style.cursor = hoverCursor || cursor;
48 * @param {?function(!MouseEvent):boolean} elementDragStart
49 * @param {function(!MouseEvent)} elementDrag
50 * @param {?function(!MouseEvent)} elementDragEnd
51 * @param {string} cursor
52 * @param {!Event} event
54 WebInspector.elementDragStart = function(elementDragStart, elementDrag, elementDragEnd, cursor, event)
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))
60 if (WebInspector._elementDraggingEventListener)
63 if (elementDragStart && !elementDragStart(/** @type {!MouseEvent} */ (event)))
66 if (WebInspector._elementDraggingGlassPane) {
67 WebInspector._elementDraggingGlassPane.dispose();
68 delete WebInspector._elementDraggingGlassPane;
71 var targetDocument = event.target.ownerDocument;
73 WebInspector._elementDraggingEventListener = elementDrag;
74 WebInspector._elementEndDraggingEventListener = elementDragEnd;
75 WebInspector._mouseOutWhileDraggingTargetDocument = targetDocument;
77 targetDocument.addEventListener("mousemove", WebInspector._elementDragMove, true);
78 targetDocument.addEventListener("mouseup", WebInspector._elementDragEnd, true);
79 targetDocument.addEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
81 targetDocument.body.style.cursor = cursor;
83 event.preventDefault();
86 WebInspector._mouseOutWhileDragging = function()
88 WebInspector._unregisterMouseOutWhileDragging();
89 WebInspector._elementDraggingGlassPane = new WebInspector.GlassPane();
92 WebInspector._unregisterMouseOutWhileDragging = function()
94 if (!WebInspector._mouseOutWhileDraggingTargetDocument)
96 WebInspector._mouseOutWhileDraggingTargetDocument.removeEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
97 delete WebInspector._mouseOutWhileDraggingTargetDocument;
101 * @param {!Event} event
103 WebInspector._elementDragMove = function(event)
105 if (WebInspector._elementDraggingEventListener(/** @type {!MouseEvent} */ (event)))
106 WebInspector._cancelDragEvents(event);
110 * @param {!Event} event
112 WebInspector._cancelDragEvents = function(event)
114 var targetDocument = event.target.ownerDocument;
115 targetDocument.removeEventListener("mousemove", WebInspector._elementDragMove, true);
116 targetDocument.removeEventListener("mouseup", WebInspector._elementDragEnd, true);
117 WebInspector._unregisterMouseOutWhileDragging();
119 targetDocument.body.style.removeProperty("cursor");
121 if (WebInspector._elementDraggingGlassPane)
122 WebInspector._elementDraggingGlassPane.dispose();
124 delete WebInspector._elementDraggingGlassPane;
125 delete WebInspector._elementDraggingEventListener;
126 delete WebInspector._elementEndDraggingEventListener;
130 * @param {!Event} event
132 WebInspector._elementDragEnd = function(event)
134 var elementDragEnd = WebInspector._elementEndDraggingEventListener;
136 WebInspector._cancelDragEvents(/** @type {!MouseEvent} */ (event));
138 event.preventDefault();
140 elementDragEnd(/** @type {!MouseEvent} */ (event));
146 WebInspector.GlassPane = function()
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;
155 WebInspector.GlassPane.prototype = {
158 delete WebInspector._glassPane;
159 if (WebInspector.GlassPane.DefaultFocusedViewStack.length)
160 WebInspector.GlassPane.DefaultFocusedViewStack.peekLast().focus();
161 this.element.remove();
166 * @type {!Array.<!WebInspector.View|!WebInspector.Dialog>}
168 WebInspector.GlassPane.DefaultFocusedViewStack = [];
171 * @param {?Node=} node
174 WebInspector.isBeingEdited = function(node)
176 if (!node || node.nodeType !== Node.ELEMENT_NODE)
178 var element = /** {!Element} */ (node);
179 if (element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA")
182 if (!WebInspector.__editingCount)
186 if (element.__editing)
188 element = element.parentElement;
194 * @param {!Element} element
195 * @param {boolean} value
198 WebInspector.markBeingEdited = function(element, value)
201 if (element.__editing)
203 element.classList.add("being-edited");
204 element.__editing = true;
205 WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
207 if (!element.__editing)
209 element.classList.remove("being-edited");
210 delete element.__editing;
211 --WebInspector.__editingCount;
216 WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
218 WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()";
222 * @param {!Event} event
225 WebInspector._valueModificationDirection = function(event)
227 var direction = null;
228 if (event.type === "mousewheel") {
229 if (event.wheelDeltaY > 0)
231 else if (event.wheelDeltaY < 0)
234 if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp")
236 else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
243 * @param {string} hexString
244 * @param {!Event} event
246 WebInspector._modifiedHexValue = function(hexString, event)
248 var direction = WebInspector._valueModificationDirection(event);
252 var number = parseInt(hexString, 16);
253 if (isNaN(number) || !isFinite(number))
256 var maxValue = Math.pow(16, hexString.length) - 1;
257 var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
260 if (arrowKeyOrMouseWheelEvent)
261 delta = (direction === "Up") ? 1 : -1;
263 delta = (event.keyIdentifier === "PageUp") ? 16 : -16;
268 var result = number + delta;
270 result = 0; // Color hex values are never negative, so clamp to 0.
271 else if (result > maxValue)
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;
282 * @param {number} number
283 * @param {!Event} event
285 WebInspector._modifiedFloatNumber = function(number, event)
287 var direction = WebInspector._valueModificationDirection(event);
291 var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
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)
298 else if (event.shiftKey || !arrowKeyOrMouseWheelEvent)
300 else if (event.altKey)
303 if (direction === "Down")
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))
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
323 WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler)
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)
330 var selection = window.getSelection();
331 if (!selection.rangeCount)
334 var selectionRange = selection.getRangeAt(0);
335 if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element))
338 var originalValue = element.textContent;
339 var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StyleValueDelimiters, element);
340 var wordString = wordRange.toString();
342 if (suggestionHandler && suggestionHandler(wordString))
345 var replacementString;
346 var prefix, suffix, number;
349 matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString);
350 if (matches && matches.length) {
353 number = WebInspector._modifiedHexValue(matches[2], event);
355 replacementString = customNumberHandler ? customNumberHandler(prefix, number, suffix) : prefix + number + suffix;
357 matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);
358 if (matches && matches.length) {
361 number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event);
363 // Need to check for null explicitly.
367 replacementString = customNumberHandler ? customNumberHandler(prefix, number, suffix) : prefix + number + suffix;
371 if (replacementString) {
372 var replacementTextNode = document.createTextNode(replacementString);
374 wordRange.deleteContents();
375 wordRange.insertNode(replacementTextNode);
377 var finalSelectionRange = document.createRange();
378 finalSelectionRange.setStart(replacementTextNode, 0);
379 finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
381 selection.removeAllRanges();
382 selection.addRange(finalSelectionRange);
384 event.handled = true;
385 event.preventDefault();
388 finishHandler(originalValue, replacementString);
397 * @param {number=} precision
400 Number.preciseMillisToString = function(ms, precision)
402 precision = precision || 0;
403 var format = "%." + precision + "f\u2009ms";
404 return WebInspector.UIString(format, ms);
407 /** @type {!WebInspector.UIStringFormat} */
408 WebInspector._subMillisFormat = new WebInspector.UIStringFormat("%.3f\u2009ms");
410 /** @type {!WebInspector.UIStringFormat} */
411 WebInspector._millisFormat = new WebInspector.UIStringFormat("%.0f\u2009ms");
413 /** @type {!WebInspector.UIStringFormat} */
414 WebInspector._secondsFormat = new WebInspector.UIStringFormat("%.2f\u2009s");
416 /** @type {!WebInspector.UIStringFormat} */
417 WebInspector._minutesFormat = new WebInspector.UIStringFormat("%.1f\u2009min");
419 /** @type {!WebInspector.UIStringFormat} */
420 WebInspector._hoursFormat = new WebInspector.UIStringFormat("%.1f\u2009hrs");
422 /** @type {!WebInspector.UIStringFormat} */
423 WebInspector._daysFormat = new WebInspector.UIStringFormat("%.1f\u2009days");
427 * @param {boolean=} higherResolution
430 Number.millisToString = function(ms, higherResolution)
438 if (higherResolution && ms < 1000)
439 return WebInspector._subMillisFormat.format(ms);
441 return WebInspector._millisFormat.format(ms);
443 var seconds = ms / 1000;
445 return WebInspector._secondsFormat.format(seconds);
447 var minutes = seconds / 60;
449 return WebInspector._minutesFormat.format(minutes);
451 var hours = minutes / 60;
453 return WebInspector._hoursFormat.format(hours);
455 var days = hours / 24;
456 return WebInspector._daysFormat.format(days);
460 * @param {number} seconds
461 * @param {boolean=} higherResolution
464 Number.secondsToString = function(seconds, higherResolution)
466 if (!isFinite(seconds))
468 return Number.millisToString(seconds * 1000, higherResolution);
472 * @param {number} bytes
475 Number.bytesToString = function(bytes)
478 return WebInspector.UIString("%.0f\u2009B", bytes);
480 var kilobytes = bytes / 1024;
482 return WebInspector.UIString("%.1f\u2009KB", kilobytes);
483 if (kilobytes < 1024)
484 return WebInspector.UIString("%.0f\u2009KB", kilobytes);
486 var megabytes = kilobytes / 1024;
488 return WebInspector.UIString("%.1f\u2009MB", megabytes);
490 return WebInspector.UIString("%.0f\u2009MB", megabytes);
494 * @param {number} num
497 Number.withThousandsSeparator = function(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.
509 WebInspector.useLowerCaseMenuTitles = function()
511 return WebInspector.platform() === "windows";
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}};
522 WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
524 return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
530 WebInspector.openLinkExternallyLabel = function()
532 return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
538 WebInspector.copyLinkAddressLabel = function()
540 return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
546 WebInspector.anotherProfilerActiveLabel = function()
548 return WebInspector.UIString("Another profiler is already active");
552 * @param {string|undefined} description
555 WebInspector.asyncStackTraceLabel = function(description)
558 return description + " " + WebInspector.UIString("(async)");
559 return WebInspector.UIString("Async Call");
565 WebInspector.manageBlackboxingButtonLabel = function()
567 return WebInspector.UIString("Manage framework blackboxing...");
570 WebInspector.installPortStyles = function()
572 var platform = WebInspector.platform();
573 document.body.classList.add("platform-" + platform);
574 var flavor = WebInspector.platformFlavor();
576 document.body.classList.add("platform-" + flavor);
577 var port = WebInspector.port();
578 document.body.classList.add("port-" + port);
581 WebInspector._windowFocused = function(event)
583 if (event.target.document.nodeType === Node.DOCUMENT_NODE)
584 document.body.classList.remove("inactive");
587 WebInspector._windowBlurred = function(event)
589 if (event.target.document.nodeType === Node.DOCUMENT_NODE)
590 document.body.classList.add("inactive");
596 WebInspector.previousFocusElement = function()
598 return WebInspector._previousFocusElement;
604 WebInspector.currentFocusElement = function()
606 return WebInspector._currentFocusElement;
609 WebInspector._focusChanged = function(event)
611 WebInspector.setCurrentFocusElement(event.target);
614 WebInspector._documentBlurred = function(event)
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);
623 WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet();
624 WebInspector._isTextEditingElement = function(element)
626 if (element instanceof HTMLInputElement)
627 return element.type in WebInspector._textInputTypes;
629 if (element instanceof HTMLTextAreaElement)
635 WebInspector.setCurrentFocusElement = function(x)
637 if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x))
639 if (WebInspector._currentFocusElement !== x)
640 WebInspector._previousFocusElement = WebInspector._currentFocusElement;
641 WebInspector._currentFocusElement = x;
643 if (WebInspector._currentFocusElement) {
644 WebInspector._currentFocusElement.focus();
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);
655 selection.removeAllRanges();
656 selection.addRange(selectionRange);
658 } else if (WebInspector._previousFocusElement)
659 WebInspector._previousFocusElement.blur();
662 WebInspector.restoreFocusFromElement = function(element)
664 if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement()))
665 WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
668 WebInspector.setToolbarColors = function(backgroundColor, color)
670 if (!WebInspector._themeStyleElement) {
671 WebInspector._themeStyleElement = document.createElement("style");
672 document.head.appendChild(WebInspector._themeStyleElement);
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 =
679 "%s .toolbar-colors {\
680 background-image: none !important;\
681 background-color: %s !important;\
682 color: %s !important;\
683 }", prefix, backgroundColor, color) +
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) +
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);
694 WebInspector.resetToolbarColors = function()
696 if (WebInspector._themeStyleElement)
697 WebInspector._themeStyleElement.textContent = "";
701 * @param {!Element} element
702 * @param {number} offset
703 * @param {number} length
704 * @param {!Array.<!Object>=} domChanges
707 WebInspector.highlightSearchResult = function(element, offset, length, domChanges)
709 var result = WebInspector.highlightSearchResults(element, [new WebInspector.SourceRange(offset, length)], domChanges);
710 return result.length ? result[0] : null;
714 * @param {!Element} element
715 * @param {!Array.<!WebInspector.SourceRange>} resultRanges
716 * @param {!Array.<!Object>=} changes
717 * @return {!Array.<!Element>}
719 WebInspector.highlightSearchResults = function(element, resultRanges, changes)
721 return WebInspector.highlightRangesWithStyleClass(element, resultRanges, "highlighted-search-result", changes);
725 * @param {!Element} element
726 * @param {string} className
728 WebInspector.runCSSAnimationOnce = function(element, className)
730 function animationEndCallback()
732 element.classList.remove(className);
733 element.removeEventListener("animationend", animationEndCallback, false);
736 if (element.classList.contains(className))
737 element.classList.remove(className);
739 element.addEventListener("animationend", animationEndCallback, false);
740 element.classList.add(className);
744 * @param {!Element} element
745 * @param {!Array.<!WebInspector.SourceRange>} resultRanges
746 * @param {string} styleClass
747 * @param {!Array.<!Object>=} changes
748 * @return {!Array.<!Element>}
750 WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes)
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);
758 var snapshotLength = textNodeSnapshot.snapshotLength;
759 if (snapshotLength === 0)
760 return highlightNodes;
763 var rangeEndOffset = 0;
764 for (var i = 0; i < snapshotLength; ++i) {
766 range.offset = rangeEndOffset;
767 range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
768 rangeEndOffset = range.offset + range.length;
769 nodeRanges.push(range);
773 for (var i = 0; i < resultRanges.length; ++i) {
774 var startOffset = resultRanges[i].offset;
775 var endOffset = startOffset + resultRanges[i].length;
777 while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
779 var endIndex = startIndex;
780 while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
782 if (endIndex === snapshotLength)
785 var highlightNode = ownerDocument.createElement("span");
786 highlightNode.className = styleClass;
787 highlightNode.textContent = lineText.substring(startOffset, endOffset);
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 });
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);
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 });
803 var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
804 var firstText = firstTextNode.textContent;
805 var anchorElement = firstTextNode.nextSibling;
807 firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
808 changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement });
809 highlightNodes.push(highlightNode);
811 firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
812 changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent });
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 });
821 startIndex = endIndex;
822 nodeRanges[startIndex].offset = endOffset;
823 nodeRanges[startIndex].length = lastTextNode.textContent.length;
826 return highlightNodes;
829 WebInspector.applyDomChanges = function(domChanges)
831 for (var i = 0, size = domChanges.length; i < size; ++i) {
832 var entry = domChanges[i];
833 switch (entry.type) {
835 entry.parent.insertBefore(entry.node, entry.nextSibling);
838 entry.node.textContent = entry.newText;
844 WebInspector.revertDomChanges = function(domChanges)
846 for (var i = domChanges.length - 1; i >= 0; --i) {
847 var entry = domChanges[i];
848 switch (entry.type) {
853 entry.node.textContent = entry.oldText;
861 * @param {boolean} autoInvoke
863 WebInspector.InvokeOnceHandlers = function(autoInvoke)
865 this._handlers = null;
866 this._autoInvoke = autoInvoke;
869 WebInspector.InvokeOnceHandlers.prototype = {
871 * @param {!Object} object
872 * @param {function()} method
874 add: function(object, method)
876 if (!this._handlers) {
877 this._handlers = new Map();
878 if (this._autoInvoke)
879 this.scheduleInvoke();
881 var methods = this._handlers.get(object);
884 this._handlers.set(object, methods);
889 scheduleInvoke: function()
892 requestAnimationFrame(this._invoke.bind(this));
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);
909 WebInspector._coalescingLevel = 0;
910 WebInspector._postUpdateHandlers = null;
912 WebInspector.startBatchUpdate = function()
914 if (!WebInspector._coalescingLevel++)
915 WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(false);
918 WebInspector.endBatchUpdate = function()
920 if (--WebInspector._coalescingLevel)
922 WebInspector._postUpdateHandlers.scheduleInvoke();
923 WebInspector._postUpdateHandlers = null;
927 * @param {!Object} object
928 * @param {function()} method
930 WebInspector.invokeOnceAfterBatchUpdate = function(object, method)
932 if (!WebInspector._postUpdateHandlers)
933 WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(true);
934 WebInspector._postUpdateHandlers.add(object, method);
938 * @param {!Function} func
939 * @param {!Array.<{from:number, to:number}>} params
940 * @param {number} frames
941 * @param {function()=} animationComplete
942 * @return {function()}
944 WebInspector.animateFunction = function(func, params, frames, animationComplete)
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;
953 var raf = requestAnimationFrame(animationStep);
955 var framesLeft = frames;
957 function animationStep()
959 if (--framesLeft < 0) {
960 if (animationComplete)
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);
968 values[i] = Number.constrain(values[i] + deltas[i], params[i].to, params[i].from);
970 func.apply(null, values);
971 raf = window.requestAnimationFrame(animationStep);
974 function cancelAnimation()
976 window.cancelAnimationFrame(raf);
979 return cancelAnimation;
984 * @extends {WebInspector.Object}
985 * @param {!Element} element
987 WebInspector.LongClickController = function(element)
989 this._element = element;
995 WebInspector.LongClickController.Events = {
996 LongClick: "LongClick",
997 LongPress: "LongPress"
1000 WebInspector.LongClickController.prototype = {
1003 if (this._longClickInterval) {
1004 clearInterval(this._longClickInterval);
1005 delete this._longClickInterval;
1011 if (this._longClickData)
1013 var boundMouseDown = mouseDown.bind(this);
1014 var boundMouseUp = mouseUp.bind(this);
1015 var boundReset = this.reset.bind(this);
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);
1024 this._longClickData = { mouseUp: boundMouseUp, mouseDown: boundMouseDown, reset: boundReset };
1028 * @this {WebInspector.LongClickController}
1030 function mouseDown(e)
1035 this._longClickInterval = setInterval(longClicked.bind(this, e), 200);
1040 * @this {WebInspector.LongClickController}
1051 * @this {WebInspector.LongClickController}
1053 function longClicked(e)
1056 this.dispatchEventToListeners(longClicks === 1 ? WebInspector.LongClickController.Events.LongClick : WebInspector.LongClickController.Events.LongPress, e);
1062 if (!this._longClickData)
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;
1071 __proto__: WebInspector.Object.prototype
1076 function windowLoaded()
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);
1085 window.addEventListener("DOMContentLoaded", windowLoaded, false);