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.peekLast().focus(); 161 this.element.remove(); 162 } 163 } 164 165 /** 166 * @type {!Array.<!WebInspector.View|!WebInspector.Dialog>} 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(string, number, string):string=} 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 replacementString = customNumberHandler ? customNumberHandler(prefix, number, suffix) : prefix + number + suffix; 356 } else { 357 matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString); 358 if (matches && matches.length) { 359 prefix = matches[1]; 360 suffix = matches[3]; 361 number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event); 362 363 // Need to check for null explicitly. 364 if (number === null) 365 return false; 366 367 replacementString = customNumberHandler ? customNumberHandler(prefix, number, suffix) : prefix + number + suffix; 368 } 369 } 370 371 if (replacementString) { 372 var replacementTextNode = document.createTextNode(replacementString); 373 374 wordRange.deleteContents(); 375 wordRange.insertNode(replacementTextNode); 376 377 var finalSelectionRange = document.createRange(); 378 finalSelectionRange.setStart(replacementTextNode, 0); 379 finalSelectionRange.setEnd(replacementTextNode, replacementString.length); 380 381 selection.removeAllRanges(); 382 selection.addRange(finalSelectionRange); 383 384 event.handled = true; 385 event.preventDefault(); 386 387 if (finishHandler) 388 finishHandler(originalValue, replacementString); 389 390 return true; 391 } 392 return false; 393 } 394 395 /** 396 * @param {number} ms 397 * @param {number=} precision 398 * @return {string} 399 */ 400 Number.preciseMillisToString = function(ms, precision) 401 { 402 precision = precision || 0; 403 var format = "%." + precision + "f\u2009ms"; 404 return WebInspector.UIString(format, ms); 405 } 406 407 /** @type {!WebInspector.UIStringFormat} */ 408 WebInspector._subMillisFormat = new WebInspector.UIStringFormat("%.3f\u2009ms"); 409 410 /** @type {!WebInspector.UIStringFormat} */ 411 WebInspector._millisFormat = new WebInspector.UIStringFormat("%.0f\u2009ms"); 412 413 /** @type {!WebInspector.UIStringFormat} */ 414 WebInspector._secondsFormat = new WebInspector.UIStringFormat("%.2f\u2009s"); 415 416 /** @type {!WebInspector.UIStringFormat} */ 417 WebInspector._minutesFormat = new WebInspector.UIStringFormat("%.1f\u2009min"); 418 419 /** @type {!WebInspector.UIStringFormat} */ 420 WebInspector._hoursFormat = new WebInspector.UIStringFormat("%.1f\u2009hrs"); 421 422 /** @type {!WebInspector.UIStringFormat} */ 423 WebInspector._daysFormat = new WebInspector.UIStringFormat("%.1f\u2009days"); 424 425 /** 426 * @param {number} ms 427 * @param {boolean=} higherResolution 428 * @return {string} 429 */ 430 Number.millisToString = function(ms, higherResolution) 431 { 432 if (!isFinite(ms)) 433 return "-"; 434 435 if (ms === 0) 436 return "0"; 437 438 if (higherResolution && ms < 1000) 439 return WebInspector._subMillisFormat.format(ms); 440 else if (ms < 1000) 441 return WebInspector._millisFormat.format(ms); 442 443 var seconds = ms / 1000; 444 if (seconds < 60) 445 return WebInspector._secondsFormat.format(seconds); 446 447 var minutes = seconds / 60; 448 if (minutes < 60) 449 return WebInspector._minutesFormat.format(minutes); 450 451 var hours = minutes / 60; 452 if (hours < 24) 453 return WebInspector._hoursFormat.format(hours); 454 455 var days = hours / 24; 456 return WebInspector._daysFormat.format(days); 457 } 458 459 /** 460 * @param {number} seconds 461 * @param {boolean=} higherResolution 462 * @return {string} 463 */ 464 Number.secondsToString = function(seconds, higherResolution) 465 { 466 if (!isFinite(seconds)) 467 return "-"; 468 return Number.millisToString(seconds * 1000, higherResolution); 469 } 470 471 /** 472 * @param {number} bytes 473 * @return {string} 474 */ 475 Number.bytesToString = function(bytes) 476 { 477 if (bytes < 1024) 478 return WebInspector.UIString("%.0f\u2009B", bytes); 479 480 var kilobytes = bytes / 1024; 481 if (kilobytes < 100) 482 return WebInspector.UIString("%.1f\u2009KB", kilobytes); 483 if (kilobytes < 1024) 484 return WebInspector.UIString("%.0f\u2009KB", kilobytes); 485 486 var megabytes = kilobytes / 1024; 487 if (megabytes < 100) 488 return WebInspector.UIString("%.1f\u2009MB", megabytes); 489 else 490 return WebInspector.UIString("%.0f\u2009MB", megabytes); 491 } 492 493 /** 494 * @param {number} num 495 * @return {string} 496 */ 497 Number.withThousandsSeparator = function(num) 498 { 499 var str = num + ""; 500 var re = /(\d+)(\d{3})/; 501 while (str.match(re)) 502 str = str.replace(re, "$1\u2009$2"); // \u2009 is a thin space. 503 return str; 504 } 505 506 /** 507 * @return {boolean} 508 */ 509 WebInspector.useLowerCaseMenuTitles = function() 510 { 511 return WebInspector.platform() === "windows"; 512 } 513 514 /** 515 * @param {string} format 516 * @param {?ArrayLike} substitutions 517 * @param {!Object.<string, function(string, ...):*>} formatters 518 * @param {string} initialValue 519 * @param {function(string, string): ?} append 520 * @return {!{formattedResult: string, unusedSubstitutions: ?ArrayLike}}; 521 */ 522 WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append) 523 { 524 return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append); 525 } 526 527 /** 528 * @return {string} 529 */ 530 WebInspector.openLinkExternallyLabel = function() 531 { 532 return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab"); 533 } 534 535 /** 536 * @return {string} 537 */ 538 WebInspector.copyLinkAddressLabel = function() 539 { 540 return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address"); 541 } 542 543 /** 544 * @return {string} 545 */ 546 WebInspector.anotherProfilerActiveLabel = function() 547 { 548 return WebInspector.UIString("Another profiler is already active"); 549 } 550 551 /** 552 * @param {string|undefined} description 553 * @return {string} 554 */ 555 WebInspector.asyncStackTraceLabel = function(description) 556 { 557 if (description) 558 return description + " " + WebInspector.UIString("(async)"); 559 return WebInspector.UIString("Async Call"); 560 } 561 562 /** 563 * @return {string} 564 */ 565 WebInspector.manageBlackboxingButtonLabel = function() 566 { 567 return WebInspector.UIString("Manage framework blackboxing..."); 568 } 569 570 WebInspector.installPortStyles = function() 571 { 572 var platform = WebInspector.platform(); 573 document.body.classList.add("platform-" + platform); 574 var flavor = WebInspector.platformFlavor(); 575 if (flavor) 576 document.body.classList.add("platform-" + flavor); 577 var port = WebInspector.port(); 578 document.body.classList.add("port-" + port); 579 } 580 581 WebInspector._windowFocused = function(event) 582 { 583 if (event.target.document.nodeType === Node.DOCUMENT_NODE) 584 document.body.classList.remove("inactive"); 585 } 586 587 WebInspector._windowBlurred = function(event) 588 { 589 if (event.target.document.nodeType === Node.DOCUMENT_NODE) 590 document.body.classList.add("inactive"); 591 } 592 593 /** 594 * @return {!Element} 595 */ 596 WebInspector.previousFocusElement = function() 597 { 598 return WebInspector._previousFocusElement; 599 } 600 601 /** 602 * @return {!Element} 603 */ 604 WebInspector.currentFocusElement = function() 605 { 606 return WebInspector._currentFocusElement; 607 } 608 609 WebInspector._focusChanged = function(event) 610 { 611 WebInspector.setCurrentFocusElement(event.target); 612 } 613 614 WebInspector._documentBlurred = function(event) 615 { 616 // We want to know when currentFocusElement loses focus to nowhere. 617 // This is the case when event.relatedTarget is null (no element is being focused) 618 // and document.activeElement is reset to default (this is not a window blur). 619 if (!event.relatedTarget && document.activeElement === document.body) 620 WebInspector.setCurrentFocusElement(null); 621 } 622 623 WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet(); 624 WebInspector._isTextEditingElement = function(element) 625 { 626 if (element instanceof HTMLInputElement) 627 return element.type in WebInspector._textInputTypes; 628 629 if (element instanceof HTMLTextAreaElement) 630 return true; 631 632 return false; 633 } 634 635 WebInspector.setCurrentFocusElement = function(x) 636 { 637 if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x)) 638 return; 639 if (WebInspector._currentFocusElement !== x) 640 WebInspector._previousFocusElement = WebInspector._currentFocusElement; 641 WebInspector._currentFocusElement = x; 642 643 if (WebInspector._currentFocusElement) { 644 WebInspector._currentFocusElement.focus(); 645 646 // Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside. 647 // This is needed (at least) to remove caret from console when focus is moved to some element in the panel. 648 // The code below should not be applied to text fields and text areas, hence _isTextEditingElement check. 649 var selection = window.getSelection(); 650 if (!WebInspector._isTextEditingElement(WebInspector._currentFocusElement) && selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) { 651 var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange(); 652 selectionRange.setStart(WebInspector._currentFocusElement, 0); 653 selectionRange.setEnd(WebInspector._currentFocusElement, 0); 654 655 selection.removeAllRanges(); 656 selection.addRange(selectionRange); 657 } 658 } else if (WebInspector._previousFocusElement) 659 WebInspector._previousFocusElement.blur(); 660 } 661 662 WebInspector.restoreFocusFromElement = function(element) 663 { 664 if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement())) 665 WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement()); 666 } 667 668 WebInspector.setToolbarColors = function(backgroundColor, color) 669 { 670 if (!WebInspector._themeStyleElement) { 671 WebInspector._themeStyleElement = document.createElement("style"); 672 document.head.appendChild(WebInspector._themeStyleElement); 673 } 674 var parsedColor = WebInspector.Color.parse(color); 675 var shadowColor = parsedColor ? parsedColor.invert().setAlpha(0.33).toString(WebInspector.Color.Format.RGBA) : "white"; 676 var prefix = WebInspector.isMac() ? "body:not(.undocked)" : ""; 677 WebInspector._themeStyleElement.textContent = 678 String.sprintf( 679 "%s .toolbar-colors {\ 680 background-image: none !important;\ 681 background-color: %s !important;\ 682 color: %s !important;\ 683 }", prefix, backgroundColor, color) + 684 String.sprintf( 685 "%s .toolbar-colors button.status-bar-item .glyph, %s .toolbar-colors button.status-bar-item .long-click-glyph {\ 686 background-color: %s;\ 687 }", prefix, prefix, color) + 688 String.sprintf( 689 "%s .toolbar-colors button.status-bar-item .glyph.shadow, %s .toolbar-colors button.status-bar-item .long-click-glyph.shadow {\ 690 background-color: %s;\ 691 }", prefix, prefix, shadowColor); 692 } 693 694 WebInspector.resetToolbarColors = function() 695 { 696 if (WebInspector._themeStyleElement) 697 WebInspector._themeStyleElement.textContent = ""; 698 } 699 700 /** 701 * @param {!Element} element 702 * @param {number} offset 703 * @param {number} length 704 * @param {!Array.<!Object>=} domChanges 705 * @return {?Element} 706 */ 707 WebInspector.highlightSearchResult = function(element, offset, length, domChanges) 708 { 709 var result = WebInspector.highlightSearchResults(element, [new WebInspector.SourceRange(offset, length)], domChanges); 710 return result.length ? result[0] : null; 711 } 712 713 /** 714 * @param {!Element} element 715 * @param {!Array.<!WebInspector.SourceRange>} resultRanges 716 * @param {!Array.<!Object>=} changes 717 * @return {!Array.<!Element>} 718 */ 719 WebInspector.highlightSearchResults = function(element, resultRanges, changes) 720 { 721 return WebInspector.highlightRangesWithStyleClass(element, resultRanges, "highlighted-search-result", changes); 722 } 723 724 /** 725 * @param {!Element} element 726 * @param {string} className 727 */ 728 WebInspector.runCSSAnimationOnce = function(element, className) 729 { 730 function animationEndCallback() 731 { 732 element.classList.remove(className); 733 element.removeEventListener("animationend", animationEndCallback, false); 734 } 735 736 if (element.classList.contains(className)) 737 element.classList.remove(className); 738 739 element.addEventListener("animationend", animationEndCallback, false); 740 element.classList.add(className); 741 } 742 743 /** 744 * @param {!Element} element 745 * @param {!Array.<!WebInspector.SourceRange>} resultRanges 746 * @param {string} styleClass 747 * @param {!Array.<!Object>=} changes 748 * @return {!Array.<!Element>} 749 */ 750 WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes) 751 { 752 changes = changes || []; 753 var highlightNodes = []; 754 var lineText = element.textContent; 755 var ownerDocument = element.ownerDocument; 756 var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 757 758 var snapshotLength = textNodeSnapshot.snapshotLength; 759 if (snapshotLength === 0) 760 return highlightNodes; 761 762 var nodeRanges = []; 763 var rangeEndOffset = 0; 764 for (var i = 0; i < snapshotLength; ++i) { 765 var range = {}; 766 range.offset = rangeEndOffset; 767 range.length = textNodeSnapshot.snapshotItem(i).textContent.length; 768 rangeEndOffset = range.offset + range.length; 769 nodeRanges.push(range); 770 } 771 772 var startIndex = 0; 773 for (var i = 0; i < resultRanges.length; ++i) { 774 var startOffset = resultRanges[i].offset; 775 var endOffset = startOffset + resultRanges[i].length; 776 777 while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset) 778 startIndex++; 779 var endIndex = startIndex; 780 while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset) 781 endIndex++; 782 if (endIndex === snapshotLength) 783 break; 784 785 var highlightNode = ownerDocument.createElement("span"); 786 highlightNode.className = styleClass; 787 highlightNode.textContent = lineText.substring(startOffset, endOffset); 788 789 var lastTextNode = textNodeSnapshot.snapshotItem(endIndex); 790 var lastText = lastTextNode.textContent; 791 lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset); 792 changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent }); 793 794 if (startIndex === endIndex) { 795 lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode); 796 changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement }); 797 highlightNodes.push(highlightNode); 798 799 var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset)); 800 lastTextNode.parentElement.insertBefore(prefixNode, highlightNode); 801 changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement }); 802 } else { 803 var firstTextNode = textNodeSnapshot.snapshotItem(startIndex); 804 var firstText = firstTextNode.textContent; 805 var anchorElement = firstTextNode.nextSibling; 806 807 firstTextNode.parentElement.insertBefore(highlightNode, anchorElement); 808 changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement }); 809 highlightNodes.push(highlightNode); 810 811 firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset); 812 changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent }); 813 814 for (var j = startIndex + 1; j < endIndex; j++) { 815 var textNode = textNodeSnapshot.snapshotItem(j); 816 var text = textNode.textContent; 817 textNode.textContent = ""; 818 changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent }); 819 } 820 } 821 startIndex = endIndex; 822 nodeRanges[startIndex].offset = endOffset; 823 nodeRanges[startIndex].length = lastTextNode.textContent.length; 824 825 } 826 return highlightNodes; 827 } 828 829 WebInspector.applyDomChanges = function(domChanges) 830 { 831 for (var i = 0, size = domChanges.length; i < size; ++i) { 832 var entry = domChanges[i]; 833 switch (entry.type) { 834 case "added": 835 entry.parent.insertBefore(entry.node, entry.nextSibling); 836 break; 837 case "changed": 838 entry.node.textContent = entry.newText; 839 break; 840 } 841 } 842 } 843 844 WebInspector.revertDomChanges = function(domChanges) 845 { 846 for (var i = domChanges.length - 1; i >= 0; --i) { 847 var entry = domChanges[i]; 848 switch (entry.type) { 849 case "added": 850 entry.node.remove(); 851 break; 852 case "changed": 853 entry.node.textContent = entry.oldText; 854 break; 855 } 856 } 857 } 858 859 /** 860 * @constructor 861 * @param {boolean} autoInvoke 862 */ 863 WebInspector.InvokeOnceHandlers = function(autoInvoke) 864 { 865 this._handlers = null; 866 this._autoInvoke = autoInvoke; 867 } 868 869 WebInspector.InvokeOnceHandlers.prototype = { 870 /** 871 * @param {!Object} object 872 * @param {function()} method 873 */ 874 add: function(object, method) 875 { 876 if (!this._handlers) { 877 this._handlers = new Map(); 878 if (this._autoInvoke) 879 this.scheduleInvoke(); 880 } 881 var methods = this._handlers.get(object); 882 if (!methods) { 883 methods = new Set(); 884 this._handlers.set(object, methods); 885 } 886 methods.add(method); 887 }, 888 889 scheduleInvoke: function() 890 { 891 if (this._handlers) 892 requestAnimationFrame(this._invoke.bind(this)); 893 }, 894 895 _invoke: function() 896 { 897 var handlers = this._handlers; 898 this._handlers = null; 899 var keys = handlers.keys(); 900 for (var i = 0; i < keys.length; ++i) { 901 var object = keys[i]; 902 var methods = handlers.get(object).values(); 903 for (var j = 0; j < methods.length; ++j) 904 methods[j].call(object); 905 } 906 } 907 } 908 909 WebInspector._coalescingLevel = 0; 910 WebInspector._postUpdateHandlers = null; 911 912 WebInspector.startBatchUpdate = function() 913 { 914 if (!WebInspector._coalescingLevel++) 915 WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(false); 916 } 917 918 WebInspector.endBatchUpdate = function() 919 { 920 if (--WebInspector._coalescingLevel) 921 return; 922 WebInspector._postUpdateHandlers.scheduleInvoke(); 923 WebInspector._postUpdateHandlers = null; 924 } 925 926 /** 927 * @param {!Object} object 928 * @param {function()} method 929 */ 930 WebInspector.invokeOnceAfterBatchUpdate = function(object, method) 931 { 932 if (!WebInspector._postUpdateHandlers) 933 WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(true); 934 WebInspector._postUpdateHandlers.add(object, method); 935 } 936 937 /** 938 * @param {!Function} func 939 * @param {!Array.<{from:number, to:number}>} params 940 * @param {number} frames 941 * @param {function()=} animationComplete 942 * @return {function()} 943 */ 944 WebInspector.animateFunction = function(func, params, frames, animationComplete) 945 { 946 var values = new Array(params.length); 947 var deltas = new Array(params.length); 948 for (var i = 0; i < params.length; ++i) { 949 values[i] = params[i].from; 950 deltas[i] = (params[i].to - params[i].from) / frames; 951 } 952 953 var raf = requestAnimationFrame(animationStep); 954 955 var framesLeft = frames; 956 957 function animationStep() 958 { 959 if (--framesLeft < 0) { 960 if (animationComplete) 961 animationComplete(); 962 return; 963 } 964 for (var i = 0; i < params.length; ++i) { 965 if (params[i].to > params[i].from) 966 values[i] = Number.constrain(values[i] + deltas[i], params[i].from, params[i].to); 967 else 968 values[i] = Number.constrain(values[i] + deltas[i], params[i].to, params[i].from); 969 } 970 func.apply(null, values); 971 raf = window.requestAnimationFrame(animationStep); 972 } 973 974 function cancelAnimation() 975 { 976 window.cancelAnimationFrame(raf); 977 } 978 979 return cancelAnimation; 980 } 981 982 /** 983 * @constructor 984 * @extends {WebInspector.Object} 985 * @param {!Element} element 986 */ 987 WebInspector.LongClickController = function(element) 988 { 989 this._element = element; 990 } 991 992 /** 993 * @enum {string} 994 */ 995 WebInspector.LongClickController.Events = { 996 LongClick: "LongClick", 997 LongPress: "LongPress" 998 }; 999 1000 WebInspector.LongClickController.prototype = { 1001 reset: function() 1002 { 1003 if (this._longClickInterval) { 1004 clearInterval(this._longClickInterval); 1005 delete this._longClickInterval; 1006 } 1007 }, 1008 1009 enable: function() 1010 { 1011 if (this._longClickData) 1012 return; 1013 var boundMouseDown = mouseDown.bind(this); 1014 var boundMouseUp = mouseUp.bind(this); 1015 var boundReset = this.reset.bind(this); 1016 1017 this._element.addEventListener("mousedown", boundMouseDown, false); 1018 this._element.addEventListener("mouseout", boundReset, false); 1019 this._element.addEventListener("mouseup", boundMouseUp, false); 1020 this._element.addEventListener("click", boundReset, true); 1021 1022 var longClicks = 0; 1023 1024 this._longClickData = { mouseUp: boundMouseUp, mouseDown: boundMouseDown, reset: boundReset }; 1025 1026 /** 1027 * @param {!Event} e 1028 * @this {WebInspector.LongClickController} 1029 */ 1030 function mouseDown(e) 1031 { 1032 if (e.which !== 1) 1033 return; 1034 longClicks = 0; 1035 this._longClickInterval = setInterval(longClicked.bind(this, e), 200); 1036 } 1037 1038 /** 1039 * @param {!Event} e 1040 * @this {WebInspector.LongClickController} 1041 */ 1042 function mouseUp(e) 1043 { 1044 if (e.which !== 1) 1045 return; 1046 this.reset(); 1047 } 1048 1049 /** 1050 * @param {!Event} e 1051 * @this {WebInspector.LongClickController} 1052 */ 1053 function longClicked(e) 1054 { 1055 ++longClicks; 1056 this.dispatchEventToListeners(longClicks === 1 ? WebInspector.LongClickController.Events.LongClick : WebInspector.LongClickController.Events.LongPress, e); 1057 } 1058 }, 1059 1060 disable: function() 1061 { 1062 if (!this._longClickData) 1063 return; 1064 this._element.removeEventListener("mousedown", this._longClickData.mouseDown, false); 1065 this._element.removeEventListener("mouseout", this._longClickData.reset, false); 1066 this._element.removeEventListener("mouseup", this._longClickData.mouseUp, false); 1067 this._element.addEventListener("click", this._longClickData.reset, true); 1068 delete this._longClickData; 1069 }, 1070 1071 __proto__: WebInspector.Object.prototype 1072 } 1073 1074 ;(function() { 1075 1076 function windowLoaded() 1077 { 1078 window.addEventListener("focus", WebInspector._windowFocused, false); 1079 window.addEventListener("blur", WebInspector._windowBlurred, false); 1080 document.addEventListener("focus", WebInspector._focusChanged, true); 1081 document.addEventListener("blur", WebInspector._documentBlurred, true); 1082 window.removeEventListener("DOMContentLoaded", windowLoaded, false); 1083 } 1084 1085 window.addEventListener("DOMContentLoaded", windowLoaded, false); 1086 1087 })(); 1088