summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js')
-rw-r--r--chromium/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js911
1 files changed, 911 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
new file mode 100644
index 00000000000..a2d7f8feadd
--- /dev/null
+++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
@@ -0,0 +1,911 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @param {!Element} element
+ * @param {?function(!MouseEvent): boolean} elementDragStart
+ * @param {function(!MouseEvent)} elementDrag
+ * @param {?function(!MouseEvent)} elementDragEnd
+ * @param {string} cursor
+ * @param {?string=} hoverCursor
+ */
+WebInspector.installDragHandle = function(element, elementDragStart, elementDrag, elementDragEnd, cursor, hoverCursor)
+{
+ element.addEventListener("mousedown", WebInspector.elementDragStart.bind(WebInspector, elementDragStart, elementDrag, elementDragEnd, cursor), false);
+ if (hoverCursor !== null)
+ element.style.cursor = hoverCursor || cursor;
+}
+
+/**
+ * @param {?function(!MouseEvent):boolean} elementDragStart
+ * @param {function(!MouseEvent)} elementDrag
+ * @param {?function(!MouseEvent)} elementDragEnd
+ * @param {string} cursor
+ * @param {?Event} event
+ */
+WebInspector.elementDragStart = function(elementDragStart, elementDrag, elementDragEnd, cursor, event)
+{
+ // Only drag upon left button. Right will likely cause a context menu. So will ctrl-click on mac.
+ if (event.button || (WebInspector.isMac() && event.ctrlKey))
+ return;
+
+ if (WebInspector._elementDraggingEventListener)
+ return;
+
+ if (elementDragStart && !elementDragStart(/** @type {!MouseEvent} */ (event)))
+ return;
+
+ if (WebInspector._elementDraggingGlassPane) {
+ WebInspector._elementDraggingGlassPane.dispose();
+ delete WebInspector._elementDraggingGlassPane;
+ }
+
+ var targetDocument = event.target.ownerDocument;
+
+ WebInspector._elementDraggingEventListener = elementDrag;
+ WebInspector._elementEndDraggingEventListener = elementDragEnd;
+ WebInspector._mouseOutWhileDraggingTargetDocument = targetDocument;
+
+ targetDocument.addEventListener("mousemove", WebInspector._elementDragMove, true);
+ targetDocument.addEventListener("mouseup", WebInspector._elementDragEnd, true);
+ targetDocument.addEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
+
+ targetDocument.body.style.cursor = cursor;
+
+ event.preventDefault();
+}
+
+WebInspector._mouseOutWhileDragging = function()
+{
+ WebInspector._unregisterMouseOutWhileDragging();
+ WebInspector._elementDraggingGlassPane = new WebInspector.GlassPane();
+}
+
+WebInspector._unregisterMouseOutWhileDragging = function()
+{
+ if (!WebInspector._mouseOutWhileDraggingTargetDocument)
+ return;
+ WebInspector._mouseOutWhileDraggingTargetDocument.removeEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
+ delete WebInspector._mouseOutWhileDraggingTargetDocument;
+}
+
+/**
+ * @param {!Event} event
+ */
+WebInspector._elementDragMove = function(event)
+{
+ if (WebInspector._elementDraggingEventListener(/** @type {!MouseEvent} */ (event)))
+ WebInspector._cancelDragEvents(event);
+}
+
+/**
+ * @param {!Event} event
+ */
+WebInspector._cancelDragEvents = function(event)
+{
+ var targetDocument = event.target.ownerDocument;
+ targetDocument.removeEventListener("mousemove", WebInspector._elementDragMove, true);
+ targetDocument.removeEventListener("mouseup", WebInspector._elementDragEnd, true);
+ WebInspector._unregisterMouseOutWhileDragging();
+
+ targetDocument.body.style.removeProperty("cursor");
+
+ if (WebInspector._elementDraggingGlassPane)
+ WebInspector._elementDraggingGlassPane.dispose();
+
+ delete WebInspector._elementDraggingGlassPane;
+ delete WebInspector._elementDraggingEventListener;
+ delete WebInspector._elementEndDraggingEventListener;
+}
+
+/**
+ * @param {!Event} event
+ */
+WebInspector._elementDragEnd = function(event)
+{
+ var elementDragEnd = WebInspector._elementEndDraggingEventListener;
+
+ WebInspector._cancelDragEvents(/** @type {!MouseEvent} */ (event));
+
+ event.preventDefault();
+ if (elementDragEnd)
+ elementDragEnd(/** @type {!MouseEvent} */ (event));
+}
+
+/**
+ * @constructor
+ */
+WebInspector.GlassPane = function()
+{
+ this.element = document.createElement("div");
+ this.element.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;background-color:transparent;z-index:1000;";
+ this.element.id = "glass-pane";
+ document.body.appendChild(this.element);
+ WebInspector._glassPane = this;
+}
+
+WebInspector.GlassPane.prototype = {
+ dispose: function()
+ {
+ delete WebInspector._glassPane;
+ if (WebInspector.GlassPane.DefaultFocusedViewStack.length)
+ WebInspector.GlassPane.DefaultFocusedViewStack[0].focus();
+ this.element.remove();
+ }
+}
+
+/**
+ * @type {!Array.<!WebInspector.View>}
+ */
+WebInspector.GlassPane.DefaultFocusedViewStack = [];
+
+/**
+ * @param {?Node=} node
+ * @return {boolean}
+ */
+WebInspector.isBeingEdited = function(node)
+{
+ if (!node || node.nodeType !== Node.ELEMENT_NODE)
+ return false;
+ var element = /** {!Element} */ (node);
+ if (element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA")
+ return true;
+
+ if (!WebInspector.__editingCount)
+ return false;
+
+ while (element) {
+ if (element.__editing)
+ return true;
+ element = element.parentElement;
+ }
+ return false;
+}
+
+/**
+ * @param {!Element} element
+ * @param {boolean} value
+ * @return {boolean}
+ */
+WebInspector.markBeingEdited = function(element, value)
+{
+ if (value) {
+ if (element.__editing)
+ return false;
+ element.classList.add("being-edited");
+ element.__editing = true;
+ WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
+ } else {
+ if (!element.__editing)
+ return false;
+ element.classList.remove("being-edited");
+ delete element.__editing;
+ --WebInspector.__editingCount;
+ }
+ return true;
+}
+
+WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
+
+WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()";
+
+
+/**
+ * @param {!Event} event
+ * @return {?string}
+ */
+WebInspector._valueModificationDirection = function(event)
+{
+ var direction = null;
+ if (event.type === "mousewheel") {
+ if (event.wheelDeltaY > 0)
+ direction = "Up";
+ else if (event.wheelDeltaY < 0)
+ direction = "Down";
+ } else {
+ if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp")
+ direction = "Up";
+ else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
+ direction = "Down";
+ }
+ return direction;
+}
+
+/**
+ * @param {string} hexString
+ * @param {!Event} event
+ */
+WebInspector._modifiedHexValue = function(hexString, event)
+{
+ var direction = WebInspector._valueModificationDirection(event);
+ if (!direction)
+ return hexString;
+
+ var number = parseInt(hexString, 16);
+ if (isNaN(number) || !isFinite(number))
+ return hexString;
+
+ var maxValue = Math.pow(16, hexString.length) - 1;
+ var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
+ var delta;
+
+ if (arrowKeyOrMouseWheelEvent)
+ delta = (direction === "Up") ? 1 : -1;
+ else
+ delta = (event.keyIdentifier === "PageUp") ? 16 : -16;
+
+ if (event.shiftKey)
+ delta *= 16;
+
+ var result = number + delta;
+ if (result < 0)
+ result = 0; // Color hex values are never negative, so clamp to 0.
+ else if (result > maxValue)
+ return hexString;
+
+ // Ensure the result length is the same as the original hex value.
+ var resultString = result.toString(16).toUpperCase();
+ for (var i = 0, lengthDelta = hexString.length - resultString.length; i < lengthDelta; ++i)
+ resultString = "0" + resultString;
+ return resultString;
+}
+
+/**
+ * @param {number} number
+ * @param {!Event} event
+ */
+WebInspector._modifiedFloatNumber = function(number, event)
+{
+ var direction = WebInspector._valueModificationDirection(event);
+ if (!direction)
+ return number;
+
+ var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
+
+ // Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down.
+ // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
+ var changeAmount = 1;
+ if (event.shiftKey && !arrowKeyOrMouseWheelEvent)
+ changeAmount = 100;
+ else if (event.shiftKey || !arrowKeyOrMouseWheelEvent)
+ changeAmount = 10;
+ else if (event.altKey)
+ changeAmount = 0.1;
+
+ if (direction === "Down")
+ changeAmount *= -1;
+
+ // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
+ // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
+ var result = Number((number + changeAmount).toFixed(6));
+ if (!String(result).match(WebInspector.CSSNumberRegex))
+ return null;
+
+ return result;
+}
+
+/**
+ * @param {?Event} event
+ * @param {!Element} element
+ * @param {function(string,string)=} finishHandler
+ * @param {function(string)=} suggestionHandler
+ * @param {function(number):number=} customNumberHandler
+ * @return {boolean}
+ */
+WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler)
+{
+ var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
+ var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
+ if (!arrowKeyOrMouseWheelEvent && !pageKeyPressed)
+ return false;
+
+ var selection = window.getSelection();
+ if (!selection.rangeCount)
+ return false;
+
+ var selectionRange = selection.getRangeAt(0);
+ if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element))
+ return false;
+
+ var originalValue = element.textContent;
+ var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StyleValueDelimiters, element);
+ var wordString = wordRange.toString();
+
+ if (suggestionHandler && suggestionHandler(wordString))
+ return false;
+
+ var replacementString;
+ var prefix, suffix, number;
+
+ var matches;
+ matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString);
+ if (matches && matches.length) {
+ prefix = matches[1];
+ suffix = matches[3];
+ number = WebInspector._modifiedHexValue(matches[2], event);
+
+ if (customNumberHandler)
+ number = customNumberHandler(number);
+
+ replacementString = prefix + number + suffix;
+ } else {
+ matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);
+ if (matches && matches.length) {
+ prefix = matches[1];
+ suffix = matches[3];
+ number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event);
+
+ // Need to check for null explicitly.
+ if (number === null)
+ return false;
+
+ if (customNumberHandler)
+ number = customNumberHandler(number);
+
+ replacementString = prefix + number + suffix;
+ }
+ }
+
+ if (replacementString) {
+ var replacementTextNode = document.createTextNode(replacementString);
+
+ wordRange.deleteContents();
+ wordRange.insertNode(replacementTextNode);
+
+ var finalSelectionRange = document.createRange();
+ finalSelectionRange.setStart(replacementTextNode, 0);
+ finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
+
+ selection.removeAllRanges();
+ selection.addRange(finalSelectionRange);
+
+ event.handled = true;
+ event.preventDefault();
+
+ if (finishHandler)
+ finishHandler(originalValue, replacementString);
+
+ return true;
+ }
+ return false;
+}
+
+/**
+ * @param {number} ms
+ * @param {number=} precision
+ * @return {string}
+ */
+Number.preciseMillisToString = function(ms, precision)
+{
+ precision = precision || 0;
+ var format = "%." + precision + "f\u2009ms";
+ return WebInspector.UIString(format, ms);
+}
+
+/**
+ * @param {number} ms
+ * @param {boolean=} higherResolution
+ * @return {string}
+ */
+Number.millisToString = function(ms, higherResolution)
+{
+ if (!isFinite(ms))
+ return "-";
+
+ if (ms === 0)
+ return "0";
+
+ if (higherResolution && ms < 1000)
+ return WebInspector.UIString("%.3f\u2009ms", ms);
+ else if (ms < 1000)
+ return WebInspector.UIString("%.0f\u2009ms", ms);
+
+ var seconds = ms / 1000;
+ if (seconds < 60)
+ return WebInspector.UIString("%.2f\u2009s", seconds);
+
+ var minutes = seconds / 60;
+ if (minutes < 60)
+ return WebInspector.UIString("%.1f\u2009min", minutes);
+
+ var hours = minutes / 60;
+ if (hours < 24)
+ return WebInspector.UIString("%.1f\u2009hrs", hours);
+
+ var days = hours / 24;
+ return WebInspector.UIString("%.1f\u2009days", days);
+}
+
+/**
+ * @param {number} seconds
+ * @param {boolean=} higherResolution
+ * @return {string}
+ */
+Number.secondsToString = function(seconds, higherResolution)
+{
+ if (!isFinite(seconds))
+ return "-";
+ return Number.millisToString(seconds * 1000, higherResolution);
+}
+
+/**
+ * @param {number} bytes
+ * @return {string}
+ */
+Number.bytesToString = function(bytes)
+{
+ if (bytes < 1024)
+ return WebInspector.UIString("%.0f\u2009B", bytes);
+
+ var kilobytes = bytes / 1024;
+ if (kilobytes < 100)
+ return WebInspector.UIString("%.1f\u2009KB", kilobytes);
+ if (kilobytes < 1024)
+ return WebInspector.UIString("%.0f\u2009KB", kilobytes);
+
+ var megabytes = kilobytes / 1024;
+ if (megabytes < 100)
+ return WebInspector.UIString("%.1f\u2009MB", megabytes);
+ else
+ return WebInspector.UIString("%.0f\u2009MB", megabytes);
+}
+
+/**
+ * @param {number} num
+ * @return {string}
+ */
+Number.withThousandsSeparator = function(num)
+{
+ var str = num + "";
+ var re = /(\d+)(\d{3})/;
+ while (str.match(re))
+ str = str.replace(re, "$1\u2009$2"); // \u2009 is a thin space.
+ return str;
+}
+
+/**
+ * @return {boolean}
+ */
+WebInspector.useLowerCaseMenuTitles = function()
+{
+ return WebInspector.platform() === "windows";
+}
+
+/**
+ * @param {string} format
+ * @param {?Array.<string>} substitutions
+ * @param {!Object.<string, function(string, ...):*>} formatters
+ * @param {string} initialValue
+ * @param {function(string, string): ?} append
+ * @return {!{formattedResult: string, unusedSubstitutions: ?Array.<string>}};
+ */
+WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
+{
+ return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
+}
+
+/**
+ * @return {string}
+ */
+WebInspector.openLinkExternallyLabel = function()
+{
+ return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
+}
+
+/**
+ * @return {string}
+ */
+WebInspector.copyLinkAddressLabel = function()
+{
+ return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
+}
+
+WebInspector.installPortStyles = function()
+{
+ var platform = WebInspector.platform();
+ document.body.classList.add("platform-" + platform);
+ var flavor = WebInspector.platformFlavor();
+ if (flavor)
+ document.body.classList.add("platform-" + flavor);
+ var port = WebInspector.port();
+ document.body.classList.add("port-" + port);
+}
+
+WebInspector._windowFocused = function(event)
+{
+ if (event.target.document.nodeType === Node.DOCUMENT_NODE)
+ document.body.classList.remove("inactive");
+}
+
+WebInspector._windowBlurred = function(event)
+{
+ if (event.target.document.nodeType === Node.DOCUMENT_NODE)
+ document.body.classList.add("inactive");
+}
+
+/**
+ * @return {!Element}
+ */
+WebInspector.previousFocusElement = function()
+{
+ return WebInspector._previousFocusElement;
+}
+
+/**
+ * @return {!Element}
+ */
+WebInspector.currentFocusElement = function()
+{
+ return WebInspector._currentFocusElement;
+}
+
+WebInspector._focusChanged = function(event)
+{
+ WebInspector.setCurrentFocusElement(event.target);
+}
+
+WebInspector._documentBlurred = function(event)
+{
+ // We want to know when currentFocusElement loses focus to nowhere.
+ // This is the case when event.relatedTarget is null (no element is being focused)
+ // and document.activeElement is reset to default (this is not a window blur).
+ if (!event.relatedTarget && document.activeElement === document.body)
+ WebInspector.setCurrentFocusElement(null);
+}
+
+WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet();
+WebInspector._isTextEditingElement = function(element)
+{
+ if (element instanceof HTMLInputElement)
+ return element.type in WebInspector._textInputTypes;
+
+ if (element instanceof HTMLTextAreaElement)
+ return true;
+
+ return false;
+}
+
+WebInspector.setCurrentFocusElement = function(x)
+{
+ if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x))
+ return;
+ if (WebInspector._currentFocusElement !== x)
+ WebInspector._previousFocusElement = WebInspector._currentFocusElement;
+ WebInspector._currentFocusElement = x;
+
+ if (WebInspector._currentFocusElement) {
+ WebInspector._currentFocusElement.focus();
+
+ // Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside.
+ // This is needed (at least) to remove caret from console when focus is moved to some element in the panel.
+ // The code below should not be applied to text fields and text areas, hence _isTextEditingElement check.
+ var selection = window.getSelection();
+ if (!WebInspector._isTextEditingElement(WebInspector._currentFocusElement) && selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) {
+ var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange();
+ selectionRange.setStart(WebInspector._currentFocusElement, 0);
+ selectionRange.setEnd(WebInspector._currentFocusElement, 0);
+
+ selection.removeAllRanges();
+ selection.addRange(selectionRange);
+ }
+ } else if (WebInspector._previousFocusElement)
+ WebInspector._previousFocusElement.blur();
+}
+
+WebInspector.restoreFocusFromElement = function(element)
+{
+ if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement()))
+ WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
+}
+
+WebInspector.setToolbarColors = function(backgroundColor, color)
+{
+ if (!WebInspector._themeStyleElement) {
+ WebInspector._themeStyleElement = document.createElement("style");
+ document.head.appendChild(WebInspector._themeStyleElement);
+ }
+ var parsedColor = WebInspector.Color.parse(color);
+ var shadowColor = parsedColor ? parsedColor.invert().setAlpha(0.33).toString(WebInspector.Color.Format.RGBA) : "white";
+ var prefix = WebInspector.isMac() ? "body:not(.undocked)" : "";
+ WebInspector._themeStyleElement.textContent =
+ String.sprintf(
+ "%s .toolbar-background {\
+ background-image: none !important;\
+ background-color: %s !important;\
+ color: %s !important;\
+ }", prefix, backgroundColor, color) +
+ String.sprintf(
+ "%s .toolbar-background button.status-bar-item .glyph, %s .toolbar-background button.status-bar-item .long-click-glyph {\
+ background-color: %s;\
+ }", prefix, prefix, color) +
+ String.sprintf(
+ "%s .toolbar-background button.status-bar-item .glyph.shadow, %s .toolbar-background button.status-bar-item .long-click-glyph.shadow {\
+ background-color: %s;\
+ }", prefix, prefix, shadowColor);
+}
+
+WebInspector.resetToolbarColors = function()
+{
+ if (WebInspector._themeStyleElement)
+ WebInspector._themeStyleElement.textContent = "";
+}
+
+/**
+ * @param {!Element} element
+ * @param {number} offset
+ * @param {number} length
+ * @param {!Array.<!Object>=} domChanges
+ * @return {?Element}
+ */
+WebInspector.highlightSearchResult = function(element, offset, length, domChanges)
+{
+ var result = WebInspector.highlightSearchResults(element, [new WebInspector.SourceRange(offset, length)], domChanges);
+ return result.length ? result[0] : null;
+}
+
+/**
+ * @param {!Element} element
+ * @param {!Array.<!WebInspector.SourceRange>} resultRanges
+ * @param {!Array.<!Object>=} changes
+ * @return {!Array.<!Element>}
+ */
+WebInspector.highlightSearchResults = function(element, resultRanges, changes)
+{
+ return WebInspector.highlightRangesWithStyleClass(element, resultRanges, "highlighted-search-result", changes);
+}
+
+/**
+ * @param {!Element} element
+ * @param {string} className
+ */
+WebInspector.runCSSAnimationOnce = function(element, className)
+{
+ function animationEndCallback()
+ {
+ element.classList.remove(className);
+ element.removeEventListener("animationend", animationEndCallback, false);
+ }
+
+ if (element.classList.contains(className))
+ element.classList.remove(className);
+
+ element.addEventListener("animationend", animationEndCallback, false);
+ element.classList.add(className);
+}
+
+/**
+ * @param {!Element} element
+ * @param {!Array.<!WebInspector.SourceRange>} resultRanges
+ * @param {string} styleClass
+ * @param {!Array.<!Object>=} changes
+ * @return {!Array.<!Element>}
+ */
+WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes)
+{
+ changes = changes || [];
+ var highlightNodes = [];
+ var lineText = element.textContent;
+ var ownerDocument = element.ownerDocument;
+ var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+
+ var snapshotLength = textNodeSnapshot.snapshotLength;
+ if (snapshotLength === 0)
+ return highlightNodes;
+
+ var nodeRanges = [];
+ var rangeEndOffset = 0;
+ for (var i = 0; i < snapshotLength; ++i) {
+ var range = {};
+ range.offset = rangeEndOffset;
+ range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
+ rangeEndOffset = range.offset + range.length;
+ nodeRanges.push(range);
+ }
+
+ var startIndex = 0;
+ for (var i = 0; i < resultRanges.length; ++i) {
+ var startOffset = resultRanges[i].offset;
+ var endOffset = startOffset + resultRanges[i].length;
+
+ while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
+ startIndex++;
+ var endIndex = startIndex;
+ while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
+ endIndex++;
+ if (endIndex === snapshotLength)
+ break;
+
+ var highlightNode = ownerDocument.createElement("span");
+ highlightNode.className = styleClass;
+ highlightNode.textContent = lineText.substring(startOffset, endOffset);
+
+ var lastTextNode = textNodeSnapshot.snapshotItem(endIndex);
+ var lastText = lastTextNode.textContent;
+ lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset);
+ changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent });
+
+ if (startIndex === endIndex) {
+ lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode);
+ changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement });
+ highlightNodes.push(highlightNode);
+
+ var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset));
+ lastTextNode.parentElement.insertBefore(prefixNode, highlightNode);
+ changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement });
+ } else {
+ var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
+ var firstText = firstTextNode.textContent;
+ var anchorElement = firstTextNode.nextSibling;
+
+ firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
+ changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement });
+ highlightNodes.push(highlightNode);
+
+ firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
+ changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent });
+
+ for (var j = startIndex + 1; j < endIndex; j++) {
+ var textNode = textNodeSnapshot.snapshotItem(j);
+ var text = textNode.textContent;
+ textNode.textContent = "";
+ changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent });
+ }
+ }
+ startIndex = endIndex;
+ nodeRanges[startIndex].offset = endOffset;
+ nodeRanges[startIndex].length = lastTextNode.textContent.length;
+
+ }
+ return highlightNodes;
+}
+
+WebInspector.applyDomChanges = function(domChanges)
+{
+ for (var i = 0, size = domChanges.length; i < size; ++i) {
+ var entry = domChanges[i];
+ switch (entry.type) {
+ case "added":
+ entry.parent.insertBefore(entry.node, entry.nextSibling);
+ break;
+ case "changed":
+ entry.node.textContent = entry.newText;
+ break;
+ }
+ }
+}
+
+WebInspector.revertDomChanges = function(domChanges)
+{
+ for (var i = domChanges.length - 1; i >= 0; --i) {
+ var entry = domChanges[i];
+ switch (entry.type) {
+ case "added":
+ entry.node.remove();
+ break;
+ case "changed":
+ entry.node.textContent = entry.oldText;
+ break;
+ }
+ }
+}
+
+/**
+ * @constructor
+ * @param {boolean} autoInvoke
+ */
+WebInspector.InvokeOnceHandlers = function(autoInvoke)
+{
+ this._handlers = null;
+ this._autoInvoke = autoInvoke;
+}
+
+WebInspector.InvokeOnceHandlers.prototype = {
+ /**
+ * @param {!Object} object
+ * @param {function()} method
+ */
+ add: function(object, method)
+ {
+ if (!this._handlers) {
+ this._handlers = new Map();
+ if (this._autoInvoke)
+ this.scheduleInvoke();
+ }
+ var methods = this._handlers.get(object);
+ if (!methods) {
+ methods = new Set();
+ this._handlers.put(object, methods);
+ }
+ methods.add(method);
+ },
+
+ scheduleInvoke: function()
+ {
+ if (this._handlers)
+ requestAnimationFrame(this._invoke.bind(this));
+ },
+
+ _invoke: function()
+ {
+ var handlers = this._handlers;
+ this._handlers = null;
+ var keys = handlers.keys();
+ for (var i = 0; i < keys.length; ++i) {
+ var object = keys[i];
+ var methods = handlers.get(object).values();
+ for (var j = 0; j < methods.length; ++j)
+ methods[j].call(object);
+ }
+ }
+}
+
+WebInspector._coalescingLevel = 0;
+WebInspector._postUpdateHandlers = null;
+
+WebInspector.startBatchUpdate = function()
+{
+ if (!WebInspector._coalescingLevel++)
+ WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(false);
+}
+
+WebInspector.endBatchUpdate = function()
+{
+ if (--WebInspector._coalescingLevel)
+ return;
+ WebInspector._postUpdateHandlers.scheduleInvoke();
+ WebInspector._postUpdateHandlers = null;
+}
+
+/**
+ * @param {!Object} object
+ * @param {function()} method
+ */
+WebInspector.invokeOnceAfterBatchUpdate = function(object, method)
+{
+ if (!WebInspector._postUpdateHandlers)
+ WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(true);
+ WebInspector._postUpdateHandlers.add(object, method);
+}
+
+;(function() {
+
+function windowLoaded()
+{
+ window.addEventListener("focus", WebInspector._windowFocused, false);
+ window.addEventListener("blur", WebInspector._windowBlurred, false);
+ document.addEventListener("focus", WebInspector._focusChanged, true);
+ document.addEventListener("blur", WebInspector._documentBlurred, true);
+ window.removeEventListener("DOMContentLoaded", windowLoaded, false);
+}
+
+window.addEventListener("DOMContentLoaded", windowLoaded, false);
+
+})();