1/* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. 4 * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). 5 * Copyright (C) 2009 Joseph Pecoraro 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 */ 40WebInspector.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 */ 54WebInspector.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 86WebInspector._mouseOutWhileDragging = function() 87{ 88 WebInspector._unregisterMouseOutWhileDragging(); 89 WebInspector._elementDraggingGlassPane = new WebInspector.GlassPane(); 90} 91 92WebInspector._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 */ 103WebInspector._elementDragMove = function(event) 104{ 105 if (WebInspector._elementDraggingEventListener(/** @type {!MouseEvent} */ (event))) 106 WebInspector._cancelDragEvents(event); 107} 108 109/** 110 * @param {!Event} event 111 */ 112WebInspector._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 */ 132WebInspector._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 */ 146WebInspector.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 155WebInspector.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 */ 168WebInspector.GlassPane.DefaultFocusedViewStack = []; 169 170/** 171 * @param {?Node=} node 172 * @return {boolean} 173 */ 174WebInspector.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 */ 198WebInspector.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 216WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/; 217 218WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()"; 219 220 221/** 222 * @param {!Event} event 223 * @return {?string} 224 */ 225WebInspector._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 */ 246WebInspector._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 */ 285WebInspector._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 */ 323WebInspector.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 */ 406Number.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 */ 418Number.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 */ 452Number.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 */ 463Number.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 */ 485Number.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 */ 497WebInspector.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 */ 510WebInspector.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 */ 518WebInspector.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 */ 526WebInspector.copyLinkAddressLabel = function() 527{ 528 return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address"); 529} 530 531WebInspector.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 542WebInspector._windowFocused = function(event) 543{ 544 if (event.target.document.nodeType === Node.DOCUMENT_NODE) 545 document.body.classList.remove("inactive"); 546} 547 548WebInspector._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 */ 557WebInspector.previousFocusElement = function() 558{ 559 return WebInspector._previousFocusElement; 560} 561 562/** 563 * @return {!Element} 564 */ 565WebInspector.currentFocusElement = function() 566{ 567 return WebInspector._currentFocusElement; 568} 569 570WebInspector._focusChanged = function(event) 571{ 572 WebInspector.setCurrentFocusElement(event.target); 573} 574 575WebInspector._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 584WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet(); 585WebInspector._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 596WebInspector.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 623WebInspector.restoreFocusFromElement = function(element) 624{ 625 if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement())) 626 WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement()); 627} 628 629WebInspector.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 655WebInspector.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 */ 668WebInspector.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 */ 680WebInspector.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 */ 689WebInspector.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 */ 711WebInspector.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 790WebInspector.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 805WebInspector.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 */ 824WebInspector.InvokeOnceHandlers = function(autoInvoke) 825{ 826 this._handlers = null; 827 this._autoInvoke = autoInvoke; 828} 829 830WebInspector.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 870WebInspector._coalescingLevel = 0; 871WebInspector._postUpdateHandlers = null; 872 873WebInspector.startBatchUpdate = function() 874{ 875 if (!WebInspector._coalescingLevel++) 876 WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(false); 877} 878 879WebInspector.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 */ 891WebInspector.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 900function 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 909window.addEventListener("DOMContentLoaded", windowLoaded, false); 910 911})(); 912