Home | History | Annotate | Download | only in ui
      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 (at) gmail.com).
      5  * Copyright (C) 2009 Joseph Pecoraro
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  *
     11  * 1.  Redistributions of source code must retain the above copyright
     12  *     notice, this list of conditions and the following disclaimer.
     13  * 2.  Redistributions in binary form must reproduce the above copyright
     14  *     notice, this list of conditions and the following disclaimer in the
     15  *     documentation and/or other materials provided with the distribution.
     16  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     17  *     its contributors may be used to endorse or promote products derived
     18  *     from this software without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     23  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     24  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /**
     33  * @param {!Element} element
     34  * @param {?function(!MouseEvent): boolean} elementDragStart
     35  * @param {function(!MouseEvent)} elementDrag
     36  * @param {?function(!MouseEvent)} elementDragEnd
     37  * @param {string} cursor
     38  * @param {?string=} hoverCursor
     39  */
     40 WebInspector.installDragHandle = function(element, elementDragStart, elementDrag, elementDragEnd, cursor, hoverCursor)
     41 {
     42     element.addEventListener("mousedown", WebInspector.elementDragStart.bind(WebInspector, elementDragStart, elementDrag, elementDragEnd, cursor), false);
     43     if (hoverCursor !== null)
     44         element.style.cursor = hoverCursor || cursor;
     45 }
     46 
     47 /**
     48  * @param {?function(!MouseEvent):boolean} elementDragStart
     49  * @param {function(!MouseEvent)} elementDrag
     50  * @param {?function(!MouseEvent)} elementDragEnd
     51  * @param {string} cursor
     52  * @param {?Event} event
     53  */
     54 WebInspector.elementDragStart = function(elementDragStart, elementDrag, elementDragEnd, cursor, event)
     55 {
     56     // Only drag upon left button. Right will likely cause a context menu. So will ctrl-click on mac.
     57     if (event.button || (WebInspector.isMac() && event.ctrlKey))
     58         return;
     59 
     60     if (WebInspector._elementDraggingEventListener)
     61         return;
     62 
     63     if (elementDragStart && !elementDragStart(/** @type {!MouseEvent} */ (event)))
     64         return;
     65 
     66     if (WebInspector._elementDraggingGlassPane) {
     67         WebInspector._elementDraggingGlassPane.dispose();
     68         delete WebInspector._elementDraggingGlassPane;
     69     }
     70 
     71     var targetDocument = event.target.ownerDocument;
     72 
     73     WebInspector._elementDraggingEventListener = elementDrag;
     74     WebInspector._elementEndDraggingEventListener = elementDragEnd;
     75     WebInspector._mouseOutWhileDraggingTargetDocument = targetDocument;
     76 
     77     targetDocument.addEventListener("mousemove", WebInspector._elementDragMove, true);
     78     targetDocument.addEventListener("mouseup", WebInspector._elementDragEnd, true);
     79     targetDocument.addEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
     80 
     81     targetDocument.body.style.cursor = cursor;
     82 
     83     event.preventDefault();
     84 }
     85 
     86 WebInspector._mouseOutWhileDragging = function()
     87 {
     88     WebInspector._unregisterMouseOutWhileDragging();
     89     WebInspector._elementDraggingGlassPane = new WebInspector.GlassPane();
     90 }
     91 
     92 WebInspector._unregisterMouseOutWhileDragging = function()
     93 {
     94     if (!WebInspector._mouseOutWhileDraggingTargetDocument)
     95         return;
     96     WebInspector._mouseOutWhileDraggingTargetDocument.removeEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
     97     delete WebInspector._mouseOutWhileDraggingTargetDocument;
     98 }
     99 
    100 /**
    101  * @param {!Event} event
    102  */
    103 WebInspector._elementDragMove = function(event)
    104 {
    105     if (WebInspector._elementDraggingEventListener(/** @type {!MouseEvent} */ (event)))
    106         WebInspector._cancelDragEvents(event);
    107 }
    108 
    109 /**
    110  * @param {!Event} event
    111  */
    112 WebInspector._cancelDragEvents = function(event)
    113 {
    114     var targetDocument = event.target.ownerDocument;
    115     targetDocument.removeEventListener("mousemove", WebInspector._elementDragMove, true);
    116     targetDocument.removeEventListener("mouseup", WebInspector._elementDragEnd, true);
    117     WebInspector._unregisterMouseOutWhileDragging();
    118 
    119     targetDocument.body.style.removeProperty("cursor");
    120 
    121     if (WebInspector._elementDraggingGlassPane)
    122         WebInspector._elementDraggingGlassPane.dispose();
    123 
    124     delete WebInspector._elementDraggingGlassPane;
    125     delete WebInspector._elementDraggingEventListener;
    126     delete WebInspector._elementEndDraggingEventListener;
    127 }
    128 
    129 /**
    130  * @param {!Event} event
    131  */
    132 WebInspector._elementDragEnd = function(event)
    133 {
    134     var elementDragEnd = WebInspector._elementEndDraggingEventListener;
    135 
    136     WebInspector._cancelDragEvents(/** @type {!MouseEvent} */ (event));
    137 
    138     event.preventDefault();
    139     if (elementDragEnd)
    140         elementDragEnd(/** @type {!MouseEvent} */ (event));
    141 }
    142 
    143 /**
    144  * @constructor
    145  */
    146 WebInspector.GlassPane = function()
    147 {
    148     this.element = document.createElement("div");
    149     this.element.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;background-color:transparent;z-index:1000;";
    150     this.element.id = "glass-pane";
    151     document.body.appendChild(this.element);
    152     WebInspector._glassPane = this;
    153 }
    154 
    155 WebInspector.GlassPane.prototype = {
    156     dispose: function()
    157     {
    158         delete WebInspector._glassPane;
    159         if (WebInspector.GlassPane.DefaultFocusedViewStack.length)
    160             WebInspector.GlassPane.DefaultFocusedViewStack[0].focus();
    161         this.element.remove();
    162     }
    163 }
    164 
    165 /**
    166  * @type {!Array.<!WebInspector.View>}
    167  */
    168 WebInspector.GlassPane.DefaultFocusedViewStack = [];
    169 
    170 /**
    171  * @param {?Node=} node
    172  * @return {boolean}
    173  */
    174 WebInspector.isBeingEdited = function(node)
    175 {
    176     if (!node || node.nodeType !== Node.ELEMENT_NODE)
    177         return false;
    178     var element = /** {!Element} */ (node);
    179     if (element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA")
    180         return true;
    181 
    182     if (!WebInspector.__editingCount)
    183         return false;
    184 
    185     while (element) {
    186         if (element.__editing)
    187             return true;
    188         element = element.parentElement;
    189     }
    190     return false;
    191 }
    192 
    193 /**
    194  * @param {!Element} element
    195  * @param {boolean} value
    196  * @return {boolean}
    197  */
    198 WebInspector.markBeingEdited = function(element, value)
    199 {
    200     if (value) {
    201         if (element.__editing)
    202             return false;
    203         element.classList.add("being-edited");
    204         element.__editing = true;
    205         WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
    206     } else {
    207         if (!element.__editing)
    208             return false;
    209         element.classList.remove("being-edited");
    210         delete element.__editing;
    211         --WebInspector.__editingCount;
    212     }
    213     return true;
    214 }
    215 
    216 WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
    217 
    218 WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()";
    219 
    220 
    221 /**
    222   * @param {!Event} event
    223   * @return {?string}
    224   */
    225 WebInspector._valueModificationDirection = function(event)
    226 {
    227     var direction = null;
    228     if (event.type === "mousewheel") {
    229         if (event.wheelDeltaY > 0)
    230             direction = "Up";
    231         else if (event.wheelDeltaY < 0)
    232             direction = "Down";
    233     } else {
    234         if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp")
    235             direction = "Up";
    236         else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
    237             direction = "Down";
    238     }
    239     return direction;
    240 }
    241 
    242 /**
    243  * @param {string} hexString
    244  * @param {!Event} event
    245  */
    246 WebInspector._modifiedHexValue = function(hexString, event)
    247 {
    248     var direction = WebInspector._valueModificationDirection(event);
    249     if (!direction)
    250         return hexString;
    251 
    252     var number = parseInt(hexString, 16);
    253     if (isNaN(number) || !isFinite(number))
    254         return hexString;
    255 
    256     var maxValue = Math.pow(16, hexString.length) - 1;
    257     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
    258     var delta;
    259 
    260     if (arrowKeyOrMouseWheelEvent)
    261         delta = (direction === "Up") ? 1 : -1;
    262     else
    263         delta = (event.keyIdentifier === "PageUp") ? 16 : -16;
    264 
    265     if (event.shiftKey)
    266         delta *= 16;
    267 
    268     var result = number + delta;
    269     if (result < 0)
    270         result = 0; // Color hex values are never negative, so clamp to 0.
    271     else if (result > maxValue)
    272         return hexString;
    273 
    274     // Ensure the result length is the same as the original hex value.
    275     var resultString = result.toString(16).toUpperCase();
    276     for (var i = 0, lengthDelta = hexString.length - resultString.length; i < lengthDelta; ++i)
    277         resultString = "0" + resultString;
    278     return resultString;
    279 }
    280 
    281 /**
    282  * @param {number} number
    283  * @param {!Event} event
    284  */
    285 WebInspector._modifiedFloatNumber = function(number, event)
    286 {
    287     var direction = WebInspector._valueModificationDirection(event);
    288     if (!direction)
    289         return number;
    290 
    291     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
    292 
    293     // Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down.
    294     // Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
    295     var changeAmount = 1;
    296     if (event.shiftKey && !arrowKeyOrMouseWheelEvent)
    297         changeAmount = 100;
    298     else if (event.shiftKey || !arrowKeyOrMouseWheelEvent)
    299         changeAmount = 10;
    300     else if (event.altKey)
    301         changeAmount = 0.1;
    302 
    303     if (direction === "Down")
    304         changeAmount *= -1;
    305 
    306     // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
    307     // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
    308     var result = Number((number + changeAmount).toFixed(6));
    309     if (!String(result).match(WebInspector.CSSNumberRegex))
    310         return null;
    311 
    312     return result;
    313 }
    314 
    315 /**
    316   * @param {?Event} event
    317   * @param {!Element} element
    318   * @param {function(string,string)=} finishHandler
    319   * @param {function(string)=} suggestionHandler
    320   * @param {function(number):number=} customNumberHandler
    321   * @return {boolean}
    322  */
    323 WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler)
    324 {
    325     var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
    326     var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
    327     if (!arrowKeyOrMouseWheelEvent && !pageKeyPressed)
    328         return false;
    329 
    330     var selection = window.getSelection();
    331     if (!selection.rangeCount)
    332         return false;
    333 
    334     var selectionRange = selection.getRangeAt(0);
    335     if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element))
    336         return false;
    337 
    338     var originalValue = element.textContent;
    339     var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StyleValueDelimiters, element);
    340     var wordString = wordRange.toString();
    341 
    342     if (suggestionHandler && suggestionHandler(wordString))
    343         return false;
    344 
    345     var replacementString;
    346     var prefix, suffix, number;
    347 
    348     var matches;
    349     matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString);
    350     if (matches && matches.length) {
    351         prefix = matches[1];
    352         suffix = matches[3];
    353         number = WebInspector._modifiedHexValue(matches[2], event);
    354 
    355         if (customNumberHandler)
    356             number = customNumberHandler(number);
    357 
    358         replacementString = prefix + number + suffix;
    359     } else {
    360         matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);
    361         if (matches && matches.length) {
    362             prefix = matches[1];
    363             suffix = matches[3];
    364             number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event);
    365 
    366             // Need to check for null explicitly.
    367             if (number === null)
    368                 return false;
    369 
    370             if (customNumberHandler)
    371                 number = customNumberHandler(number);
    372 
    373             replacementString = prefix + number + suffix;
    374         }
    375     }
    376 
    377     if (replacementString) {
    378         var replacementTextNode = document.createTextNode(replacementString);
    379 
    380         wordRange.deleteContents();
    381         wordRange.insertNode(replacementTextNode);
    382 
    383         var finalSelectionRange = document.createRange();
    384         finalSelectionRange.setStart(replacementTextNode, 0);
    385         finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
    386 
    387         selection.removeAllRanges();
    388         selection.addRange(finalSelectionRange);
    389 
    390         event.handled = true;
    391         event.preventDefault();
    392 
    393         if (finishHandler)
    394             finishHandler(originalValue, replacementString);
    395 
    396         return true;
    397     }
    398     return false;
    399 }
    400 
    401 /**
    402  * @param {number} ms
    403  * @param {number=} precision
    404  * @return {string}
    405  */
    406 Number.preciseMillisToString = function(ms, precision)
    407 {
    408   precision = precision || 0;
    409   var format = "%." + precision + "f\u2009ms";
    410   return WebInspector.UIString(format, ms);
    411 }
    412 
    413 /**
    414  * @param {number} ms
    415  * @param {boolean=} higherResolution
    416  * @return {string}
    417  */
    418 Number.millisToString = function(ms, higherResolution)
    419 {
    420     if (!isFinite(ms))
    421         return "-";
    422 
    423     if (ms === 0)
    424         return "0";
    425 
    426     if (higherResolution && ms < 1000)
    427         return WebInspector.UIString("%.3f\u2009ms", ms);
    428     else if (ms < 1000)
    429         return WebInspector.UIString("%.0f\u2009ms", ms);
    430 
    431     var seconds = ms / 1000;
    432     if (seconds < 60)
    433         return WebInspector.UIString("%.2f\u2009s", seconds);
    434 
    435     var minutes = seconds / 60;
    436     if (minutes < 60)
    437         return WebInspector.UIString("%.1f\u2009min", minutes);
    438 
    439     var hours = minutes / 60;
    440     if (hours < 24)
    441         return WebInspector.UIString("%.1f\u2009hrs", hours);
    442 
    443     var days = hours / 24;
    444     return WebInspector.UIString("%.1f\u2009days", days);
    445 }
    446 
    447 /**
    448  * @param {number} seconds
    449  * @param {boolean=} higherResolution
    450  * @return {string}
    451  */
    452 Number.secondsToString = function(seconds, higherResolution)
    453 {
    454     if (!isFinite(seconds))
    455         return "-";
    456     return Number.millisToString(seconds * 1000, higherResolution);
    457 }
    458 
    459 /**
    460  * @param {number} bytes
    461  * @return {string}
    462  */
    463 Number.bytesToString = function(bytes)
    464 {
    465     if (bytes < 1024)
    466         return WebInspector.UIString("%.0f\u2009B", bytes);
    467 
    468     var kilobytes = bytes / 1024;
    469     if (kilobytes < 100)
    470         return WebInspector.UIString("%.1f\u2009KB", kilobytes);
    471     if (kilobytes < 1024)
    472         return WebInspector.UIString("%.0f\u2009KB", kilobytes);
    473 
    474     var megabytes = kilobytes / 1024;
    475     if (megabytes < 100)
    476         return WebInspector.UIString("%.1f\u2009MB", megabytes);
    477     else
    478         return WebInspector.UIString("%.0f\u2009MB", megabytes);
    479 }
    480 
    481 /**
    482  * @param {number} num
    483  * @return {string}
    484  */
    485 Number.withThousandsSeparator = function(num)
    486 {
    487     var str = num + "";
    488     var re = /(\d+)(\d{3})/;
    489     while (str.match(re))
    490         str = str.replace(re, "$1\u2009$2"); // \u2009 is a thin space.
    491     return str;
    492 }
    493 
    494 /**
    495  * @return {boolean}
    496  */
    497 WebInspector.useLowerCaseMenuTitles = function()
    498 {
    499     return WebInspector.platform() === "windows";
    500 }
    501 
    502 /**
    503  * @param {string} format
    504  * @param {?Array.<string>} substitutions
    505  * @param {!Object.<string, function(string, ...):*>} formatters
    506  * @param {string} initialValue
    507  * @param {function(string, string): ?} append
    508  * @return {!{formattedResult: string, unusedSubstitutions: ?Array.<string>}};
    509  */
    510 WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
    511 {
    512     return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
    513 }
    514 
    515 /**
    516  * @return {string}
    517  */
    518 WebInspector.openLinkExternallyLabel = function()
    519 {
    520     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
    521 }
    522 
    523 /**
    524  * @return {string}
    525  */
    526 WebInspector.copyLinkAddressLabel = function()
    527 {
    528     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
    529 }
    530 
    531 WebInspector.installPortStyles = function()
    532 {
    533     var platform = WebInspector.platform();
    534     document.body.classList.add("platform-" + platform);
    535     var flavor = WebInspector.platformFlavor();
    536     if (flavor)
    537         document.body.classList.add("platform-" + flavor);
    538     var port = WebInspector.port();
    539     document.body.classList.add("port-" + port);
    540 }
    541 
    542 WebInspector._windowFocused = function(event)
    543 {
    544     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
    545         document.body.classList.remove("inactive");
    546 }
    547 
    548 WebInspector._windowBlurred = function(event)
    549 {
    550     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
    551         document.body.classList.add("inactive");
    552 }
    553 
    554 /**
    555  * @return {!Element}
    556  */
    557 WebInspector.previousFocusElement = function()
    558 {
    559     return WebInspector._previousFocusElement;
    560 }
    561 
    562 /**
    563  * @return {!Element}
    564  */
    565 WebInspector.currentFocusElement = function()
    566 {
    567     return WebInspector._currentFocusElement;
    568 }
    569 
    570 WebInspector._focusChanged = function(event)
    571 {
    572     WebInspector.setCurrentFocusElement(event.target);
    573 }
    574 
    575 WebInspector._documentBlurred = function(event)
    576 {
    577     // We want to know when currentFocusElement loses focus to nowhere.
    578     // This is the case when event.relatedTarget is null (no element is being focused)
    579     // and document.activeElement is reset to default (this is not a window blur).
    580     if (!event.relatedTarget && document.activeElement === document.body)
    581       WebInspector.setCurrentFocusElement(null);
    582 }
    583 
    584 WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet();
    585 WebInspector._isTextEditingElement = function(element)
    586 {
    587     if (element instanceof HTMLInputElement)
    588         return element.type in WebInspector._textInputTypes;
    589 
    590     if (element instanceof HTMLTextAreaElement)
    591         return true;
    592 
    593     return false;
    594 }
    595 
    596 WebInspector.setCurrentFocusElement = function(x)
    597 {
    598     if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x))
    599         return;
    600     if (WebInspector._currentFocusElement !== x)
    601         WebInspector._previousFocusElement = WebInspector._currentFocusElement;
    602     WebInspector._currentFocusElement = x;
    603 
    604     if (WebInspector._currentFocusElement) {
    605         WebInspector._currentFocusElement.focus();
    606 
    607         // Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside.
    608         // This is needed (at least) to remove caret from console when focus is moved to some element in the panel.
    609         // The code below should not be applied to text fields and text areas, hence _isTextEditingElement check.
    610         var selection = window.getSelection();
    611         if (!WebInspector._isTextEditingElement(WebInspector._currentFocusElement) && selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) {
    612             var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange();
    613             selectionRange.setStart(WebInspector._currentFocusElement, 0);
    614             selectionRange.setEnd(WebInspector._currentFocusElement, 0);
    615 
    616             selection.removeAllRanges();
    617             selection.addRange(selectionRange);
    618         }
    619     } else if (WebInspector._previousFocusElement)
    620         WebInspector._previousFocusElement.blur();
    621 }
    622 
    623 WebInspector.restoreFocusFromElement = function(element)
    624 {
    625     if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement()))
    626         WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
    627 }
    628 
    629 WebInspector.setToolbarColors = function(backgroundColor, color)
    630 {
    631     if (!WebInspector._themeStyleElement) {
    632         WebInspector._themeStyleElement = document.createElement("style");
    633         document.head.appendChild(WebInspector._themeStyleElement);
    634     }
    635     var parsedColor = WebInspector.Color.parse(color);
    636     var shadowColor = parsedColor ? parsedColor.invert().setAlpha(0.33).toString(WebInspector.Color.Format.RGBA) : "white";
    637     var prefix = WebInspector.isMac() ? "body:not(.undocked)" : "";
    638     WebInspector._themeStyleElement.textContent =
    639         String.sprintf(
    640             "%s .toolbar-background {\
    641                  background-image: none !important;\
    642                  background-color: %s !important;\
    643                  color: %s !important;\
    644              }", prefix, backgroundColor, color) +
    645         String.sprintf(
    646              "%s .toolbar-background button.status-bar-item .glyph, %s .toolbar-background button.status-bar-item .long-click-glyph {\
    647                  background-color: %s;\
    648              }", prefix, prefix, color) +
    649         String.sprintf(
    650              "%s .toolbar-background button.status-bar-item .glyph.shadow, %s .toolbar-background button.status-bar-item .long-click-glyph.shadow {\
    651                  background-color: %s;\
    652              }", prefix, prefix, shadowColor);
    653 }
    654 
    655 WebInspector.resetToolbarColors = function()
    656 {
    657     if (WebInspector._themeStyleElement)
    658         WebInspector._themeStyleElement.textContent = "";
    659 }
    660 
    661 /**
    662  * @param {!Element} element
    663  * @param {number} offset
    664  * @param {number} length
    665  * @param {!Array.<!Object>=} domChanges
    666  * @return {?Element}
    667  */
    668 WebInspector.highlightSearchResult = function(element, offset, length, domChanges)
    669 {
    670     var result = WebInspector.highlightSearchResults(element, [new WebInspector.SourceRange(offset, length)], domChanges);
    671     return result.length ? result[0] : null;
    672 }
    673 
    674 /**
    675  * @param {!Element} element
    676  * @param {!Array.<!WebInspector.SourceRange>} resultRanges
    677  * @param {!Array.<!Object>=} changes
    678  * @return {!Array.<!Element>}
    679  */
    680 WebInspector.highlightSearchResults = function(element, resultRanges, changes)
    681 {
    682     return WebInspector.highlightRangesWithStyleClass(element, resultRanges, "highlighted-search-result", changes);
    683 }
    684 
    685 /**
    686  * @param {!Element} element
    687  * @param {string} className
    688  */
    689 WebInspector.runCSSAnimationOnce = function(element, className)
    690 {
    691     function animationEndCallback()
    692     {
    693         element.classList.remove(className);
    694         element.removeEventListener("animationend", animationEndCallback, false);
    695     }
    696 
    697     if (element.classList.contains(className))
    698         element.classList.remove(className);
    699 
    700     element.addEventListener("animationend", animationEndCallback, false);
    701     element.classList.add(className);
    702 }
    703 
    704 /**
    705  * @param {!Element} element
    706  * @param {!Array.<!WebInspector.SourceRange>} resultRanges
    707  * @param {string} styleClass
    708  * @param {!Array.<!Object>=} changes
    709  * @return {!Array.<!Element>}
    710  */
    711 WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes)
    712 {
    713     changes = changes || [];
    714     var highlightNodes = [];
    715     var lineText = element.textContent;
    716     var ownerDocument = element.ownerDocument;
    717     var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    718 
    719     var snapshotLength = textNodeSnapshot.snapshotLength;
    720     if (snapshotLength === 0)
    721         return highlightNodes;
    722 
    723     var nodeRanges = [];
    724     var rangeEndOffset = 0;
    725     for (var i = 0; i < snapshotLength; ++i) {
    726         var range = {};
    727         range.offset = rangeEndOffset;
    728         range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
    729         rangeEndOffset = range.offset + range.length;
    730         nodeRanges.push(range);
    731     }
    732 
    733     var startIndex = 0;
    734     for (var i = 0; i < resultRanges.length; ++i) {
    735         var startOffset = resultRanges[i].offset;
    736         var endOffset = startOffset + resultRanges[i].length;
    737 
    738         while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
    739             startIndex++;
    740         var endIndex = startIndex;
    741         while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
    742             endIndex++;
    743         if (endIndex === snapshotLength)
    744             break;
    745 
    746         var highlightNode = ownerDocument.createElement("span");
    747         highlightNode.className = styleClass;
    748         highlightNode.textContent = lineText.substring(startOffset, endOffset);
    749 
    750         var lastTextNode = textNodeSnapshot.snapshotItem(endIndex);
    751         var lastText = lastTextNode.textContent;
    752         lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset);
    753         changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent });
    754 
    755         if (startIndex === endIndex) {
    756             lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode);
    757             changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement });
    758             highlightNodes.push(highlightNode);
    759 
    760             var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset));
    761             lastTextNode.parentElement.insertBefore(prefixNode, highlightNode);
    762             changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement });
    763         } else {
    764             var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
    765             var firstText = firstTextNode.textContent;
    766             var anchorElement = firstTextNode.nextSibling;
    767 
    768             firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
    769             changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement });
    770             highlightNodes.push(highlightNode);
    771 
    772             firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
    773             changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent });
    774 
    775             for (var j = startIndex + 1; j < endIndex; j++) {
    776                 var textNode = textNodeSnapshot.snapshotItem(j);
    777                 var text = textNode.textContent;
    778                 textNode.textContent = "";
    779                 changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent });
    780             }
    781         }
    782         startIndex = endIndex;
    783         nodeRanges[startIndex].offset = endOffset;
    784         nodeRanges[startIndex].length = lastTextNode.textContent.length;
    785 
    786     }
    787     return highlightNodes;
    788 }
    789 
    790 WebInspector.applyDomChanges = function(domChanges)
    791 {
    792     for (var i = 0, size = domChanges.length; i < size; ++i) {
    793         var entry = domChanges[i];
    794         switch (entry.type) {
    795         case "added":
    796             entry.parent.insertBefore(entry.node, entry.nextSibling);
    797             break;
    798         case "changed":
    799             entry.node.textContent = entry.newText;
    800             break;
    801         }
    802     }
    803 }
    804 
    805 WebInspector.revertDomChanges = function(domChanges)
    806 {
    807     for (var i = domChanges.length - 1; i >= 0; --i) {
    808         var entry = domChanges[i];
    809         switch (entry.type) {
    810         case "added":
    811             entry.node.remove();
    812             break;
    813         case "changed":
    814             entry.node.textContent = entry.oldText;
    815             break;
    816         }
    817     }
    818 }
    819 
    820 /**
    821  * @constructor
    822  * @param {boolean} autoInvoke
    823  */
    824 WebInspector.InvokeOnceHandlers = function(autoInvoke)
    825 {
    826     this._handlers = null;
    827     this._autoInvoke = autoInvoke;
    828 }
    829 
    830 WebInspector.InvokeOnceHandlers.prototype = {
    831     /**
    832      * @param {!Object} object
    833      * @param {function()} method
    834      */
    835     add: function(object, method)
    836     {
    837         if (!this._handlers) {
    838             this._handlers = new Map();
    839             if (this._autoInvoke)
    840                 this.scheduleInvoke();
    841         }
    842         var methods = this._handlers.get(object);
    843         if (!methods) {
    844             methods = new Set();
    845             this._handlers.put(object, methods);
    846         }
    847         methods.add(method);
    848     },
    849 
    850     scheduleInvoke: function()
    851     {
    852         if (this._handlers)
    853             requestAnimationFrame(this._invoke.bind(this));
    854     },
    855 
    856     _invoke: function()
    857     {
    858         var handlers = this._handlers;
    859         this._handlers = null;
    860         var keys = handlers.keys();
    861         for (var i = 0; i < keys.length; ++i) {
    862             var object = keys[i];
    863             var methods = handlers.get(object).values();
    864             for (var j = 0; j < methods.length; ++j)
    865                 methods[j].call(object);
    866         }
    867     }
    868 }
    869 
    870 WebInspector._coalescingLevel = 0;
    871 WebInspector._postUpdateHandlers = null;
    872 
    873 WebInspector.startBatchUpdate = function()
    874 {
    875     if (!WebInspector._coalescingLevel++)
    876         WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(false);
    877 }
    878 
    879 WebInspector.endBatchUpdate = function()
    880 {
    881     if (--WebInspector._coalescingLevel)
    882         return;
    883     WebInspector._postUpdateHandlers.scheduleInvoke();
    884     WebInspector._postUpdateHandlers = null;
    885 }
    886 
    887 /**
    888  * @param {!Object} object
    889  * @param {function()} method
    890  */
    891 WebInspector.invokeOnceAfterBatchUpdate = function(object, method)
    892 {
    893     if (!WebInspector._postUpdateHandlers)
    894         WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(true);
    895     WebInspector._postUpdateHandlers.add(object, method);
    896 }
    897 
    898 ;(function() {
    899 
    900 function windowLoaded()
    901 {
    902     window.addEventListener("focus", WebInspector._windowFocused, false);
    903     window.addEventListener("blur", WebInspector._windowBlurred, false);
    904     document.addEventListener("focus", WebInspector._focusChanged, true);
    905     document.addEventListener("blur", WebInspector._documentBlurred, true);
    906     window.removeEventListener("DOMContentLoaded", windowLoaded, false);
    907 }
    908 
    909 window.addEventListener("DOMContentLoaded", windowLoaded, false);
    910 
    911 })();
    912