1/* 2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30var Preferences = { 31 ignoreWhitespace: true, 32 showUserAgentStyles: true, 33 maxInlineTextChildLength: 80, 34 minConsoleHeight: 75, 35 minSidebarWidth: 100, 36 minElementsSidebarWidth: 200, 37 minScriptsSidebarWidth: 200, 38 showInheritedComputedStyleProperties: false, 39 styleRulesExpandedState: {}, 40 showMissingLocalizedStrings: false 41} 42 43var WebInspector = { 44 resources: [], 45 resourceURLMap: {}, 46 missingLocalizedStrings: {}, 47 48 get previousFocusElement() 49 { 50 return this._previousFocusElement; 51 }, 52 53 get currentFocusElement() 54 { 55 return this._currentFocusElement; 56 }, 57 58 set currentFocusElement(x) 59 { 60 if (this._currentFocusElement !== x) 61 this._previousFocusElement = this._currentFocusElement; 62 this._currentFocusElement = x; 63 64 if (this._currentFocusElement) { 65 this._currentFocusElement.focus(); 66 67 // Make a caret selection inside the new element if there isn't a range selection and 68 // there isn't already a caret selection inside. 69 var selection = window.getSelection(); 70 if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) { 71 var selectionRange = document.createRange(); 72 selectionRange.setStart(this._currentFocusElement, 0); 73 selectionRange.setEnd(this._currentFocusElement, 0); 74 75 selection.removeAllRanges(); 76 selection.addRange(selectionRange); 77 } 78 } else if (this._previousFocusElement) 79 this._previousFocusElement.blur(); 80 }, 81 82 get currentPanel() 83 { 84 return this._currentPanel; 85 }, 86 87 set currentPanel(x) 88 { 89 if (this._currentPanel === x) 90 return; 91 92 if (this._currentPanel) 93 this._currentPanel.hide(); 94 95 this._currentPanel = x; 96 97 this.updateSearchLabel(); 98 99 if (x) { 100 x.show(); 101 102 if (this.currentQuery) { 103 if (x.performSearch) { 104 function performPanelSearch() 105 { 106 this.updateSearchMatchesCount(); 107 108 x.currentQuery = this.currentQuery; 109 x.performSearch(this.currentQuery); 110 } 111 112 // Perform the search on a timeout so the panel switches fast. 113 setTimeout(performPanelSearch.bind(this), 0); 114 } else { 115 // Update to show Not found for panels that can't be searched. 116 this.updateSearchMatchesCount(); 117 } 118 } 119 } 120 }, 121 122 get attached() 123 { 124 return this._attached; 125 }, 126 127 set attached(x) 128 { 129 if (this._attached === x) 130 return; 131 132 this._attached = x; 133 134 this.updateSearchLabel(); 135 136 var dockToggleButton = document.getElementById("dock-status-bar-item"); 137 var body = document.body; 138 139 if (x) { 140 InspectorController.attach(); 141 body.removeStyleClass("detached"); 142 body.addStyleClass("attached"); 143 dockToggleButton.title = WebInspector.UIString("Undock into separate window."); 144 } else { 145 InspectorController.detach(); 146 body.removeStyleClass("attached"); 147 body.addStyleClass("detached"); 148 dockToggleButton.title = WebInspector.UIString("Dock to main window."); 149 } 150 }, 151 152 get errors() 153 { 154 return this._errors || 0; 155 }, 156 157 set errors(x) 158 { 159 x = Math.max(x, 0); 160 161 if (this._errors === x) 162 return; 163 this._errors = x; 164 this._updateErrorAndWarningCounts(); 165 }, 166 167 get warnings() 168 { 169 return this._warnings || 0; 170 }, 171 172 set warnings(x) 173 { 174 x = Math.max(x, 0); 175 176 if (this._warnings === x) 177 return; 178 this._warnings = x; 179 this._updateErrorAndWarningCounts(); 180 }, 181 182 _updateErrorAndWarningCounts: function() 183 { 184 var errorWarningElement = document.getElementById("error-warning-count"); 185 if (!errorWarningElement) 186 return; 187 188 if (!this.errors && !this.warnings) { 189 errorWarningElement.addStyleClass("hidden"); 190 return; 191 } 192 193 errorWarningElement.removeStyleClass("hidden"); 194 195 errorWarningElement.removeChildren(); 196 197 if (this.errors) { 198 var errorElement = document.createElement("span"); 199 errorElement.id = "error-count"; 200 errorElement.textContent = this.errors; 201 errorWarningElement.appendChild(errorElement); 202 } 203 204 if (this.warnings) { 205 var warningsElement = document.createElement("span"); 206 warningsElement.id = "warning-count"; 207 warningsElement.textContent = this.warnings; 208 errorWarningElement.appendChild(warningsElement); 209 } 210 211 if (this.errors) { 212 if (this.warnings) { 213 if (this.errors == 1) { 214 if (this.warnings == 1) 215 errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings); 216 else 217 errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings); 218 } else if (this.warnings == 1) 219 errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings); 220 else 221 errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings); 222 } else if (this.errors == 1) 223 errorWarningElement.title = WebInspector.UIString("%d error", this.errors); 224 else 225 errorWarningElement.title = WebInspector.UIString("%d errors", this.errors); 226 } else if (this.warnings == 1) 227 errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings); 228 else if (this.warnings) 229 errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings); 230 else 231 errorWarningElement.title = null; 232 }, 233 234 get hoveredDOMNode() 235 { 236 return this._hoveredDOMNode; 237 }, 238 239 set hoveredDOMNode(x) 240 { 241 if (objectsAreSame(this._hoveredDOMNode, x)) 242 return; 243 244 this._hoveredDOMNode = x; 245 246 if (this._hoveredDOMNode) 247 this._updateHoverHighlightSoon(this.showingDOMNodeHighlight ? 50 : 500); 248 else 249 this._updateHoverHighlight(); 250 }, 251 252 _updateHoverHighlightSoon: function(delay) 253 { 254 if ("_updateHoverHighlightTimeout" in this) 255 clearTimeout(this._updateHoverHighlightTimeout); 256 this._updateHoverHighlightTimeout = setTimeout(this._updateHoverHighlight.bind(this), delay); 257 }, 258 259 _updateHoverHighlight: function() 260 { 261 if ("_updateHoverHighlightTimeout" in this) { 262 clearTimeout(this._updateHoverHighlightTimeout); 263 delete this._updateHoverHighlightTimeout; 264 } 265 266 if (this._hoveredDOMNode) { 267 InspectorController.highlightDOMNode(this._hoveredDOMNode); 268 this.showingDOMNodeHighlight = true; 269 } else { 270 InspectorController.hideDOMNodeHighlight(); 271 this.showingDOMNodeHighlight = false; 272 } 273 } 274} 275 276WebInspector.loaded = function() 277{ 278 var platform = InspectorController.platform(); 279 document.body.addStyleClass("platform-" + platform); 280 281 this.console = new WebInspector.Console(); 282 this.panels = { 283 elements: new WebInspector.ElementsPanel(), 284 resources: new WebInspector.ResourcesPanel(), 285 scripts: new WebInspector.ScriptsPanel(), 286 profiles: new WebInspector.ProfilesPanel(), 287 databases: new WebInspector.DatabasesPanel() 288 }; 289 290 var toolbarElement = document.getElementById("toolbar"); 291 var previousToolbarItem = toolbarElement.children[0]; 292 293 for (var panelName in this.panels) { 294 var panel = this.panels[panelName]; 295 var panelToolbarItem = panel.toolbarItem; 296 panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this)); 297 if (previousToolbarItem) 298 toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling); 299 else 300 toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild); 301 previousToolbarItem = panelToolbarItem; 302 } 303 304 this.currentPanel = this.panels.elements; 305 306 this.resourceCategories = { 307 documents: new WebInspector.ResourceCategory(WebInspector.UIString("Documents"), "documents"), 308 stylesheets: new WebInspector.ResourceCategory(WebInspector.UIString("Stylesheets"), "stylesheets"), 309 images: new WebInspector.ResourceCategory(WebInspector.UIString("Images"), "images"), 310 scripts: new WebInspector.ResourceCategory(WebInspector.UIString("Scripts"), "scripts"), 311 xhr: new WebInspector.ResourceCategory(WebInspector.UIString("XHR"), "xhr"), 312 fonts: new WebInspector.ResourceCategory(WebInspector.UIString("Fonts"), "fonts"), 313 other: new WebInspector.ResourceCategory(WebInspector.UIString("Other"), "other") 314 }; 315 316 this.Tips = { 317 ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")} 318 }; 319 320 this.Warnings = { 321 IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")} 322 }; 323 324 this.addMainEventListeners(document); 325 326 window.addEventListener("unload", this.windowUnload.bind(this), true); 327 window.addEventListener("resize", this.windowResize.bind(this), true); 328 329 document.addEventListener("focus", this.focusChanged.bind(this), true); 330 document.addEventListener("keydown", this.documentKeyDown.bind(this), true); 331 document.addEventListener("keyup", this.documentKeyUp.bind(this), true); 332 document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true); 333 document.addEventListener("copy", this.documentCopy.bind(this), true); 334 335 var mainPanelsElement = document.getElementById("main-panels"); 336 mainPanelsElement.handleKeyEvent = this.mainKeyDown.bind(this); 337 mainPanelsElement.handleKeyUpEvent = this.mainKeyUp.bind(this); 338 mainPanelsElement.handleCopyEvent = this.mainCopy.bind(this); 339 340 // Focus the mainPanelsElement in a timeout so it happens after the initial focus, 341 // so it doesn't get reset to the first toolbar button. This initial focus happens 342 // on Mac when the window is made key and the WebHTMLView becomes the first responder. 343 setTimeout(function() { WebInspector.currentFocusElement = mainPanelsElement }, 0); 344 345 var dockToggleButton = document.getElementById("dock-status-bar-item"); 346 dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false); 347 348 if (this.attached) 349 dockToggleButton.title = WebInspector.UIString("Undock into separate window."); 350 else 351 dockToggleButton.title = WebInspector.UIString("Dock to main window."); 352 353 var errorWarningCount = document.getElementById("error-warning-count"); 354 errorWarningCount.addEventListener("click", this.console.show.bind(this.console), false); 355 this._updateErrorAndWarningCounts(); 356 357 var searchField = document.getElementById("search"); 358 searchField.addEventListener("keydown", this.searchKeyDown.bind(this), false); 359 searchField.addEventListener("keyup", this.searchKeyUp.bind(this), false); 360 searchField.addEventListener("search", this.performSearch.bind(this), false); // when the search is emptied 361 362 document.getElementById("toolbar").addEventListener("mousedown", this.toolbarDragStart, true); 363 document.getElementById("close-button").addEventListener("click", this.close, true); 364 365 InspectorController.loaded(); 366} 367 368var windowLoaded = function() 369{ 370 var localizedStringsURL = InspectorController.localizedStringsURL(); 371 if (localizedStringsURL) { 372 var localizedStringsScriptElement = document.createElement("script"); 373 localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false); 374 localizedStringsScriptElement.type = "text/javascript"; 375 localizedStringsScriptElement.src = localizedStringsURL; 376 document.getElementsByTagName("head").item(0).appendChild(localizedStringsScriptElement); 377 } else 378 WebInspector.loaded(); 379 380 window.removeEventListener("load", windowLoaded, false); 381 delete windowLoaded; 382}; 383 384window.addEventListener("load", windowLoaded, false); 385 386WebInspector.windowUnload = function(event) 387{ 388 InspectorController.windowUnloading(); 389} 390 391WebInspector.windowResize = function(event) 392{ 393 if (this.currentPanel && this.currentPanel.resize) 394 this.currentPanel.resize(); 395} 396 397WebInspector.windowFocused = function(event) 398{ 399 if (event.target.nodeType === Node.DOCUMENT_NODE) 400 document.body.removeStyleClass("inactive"); 401} 402 403WebInspector.windowBlured = function(event) 404{ 405 if (event.target.nodeType === Node.DOCUMENT_NODE) 406 document.body.addStyleClass("inactive"); 407} 408 409WebInspector.focusChanged = function(event) 410{ 411 this.currentFocusElement = event.target; 412} 413 414WebInspector.setAttachedWindow = function(attached) 415{ 416 this.attached = attached; 417} 418 419WebInspector.close = function(event) 420{ 421 InspectorController.closeWindow(); 422} 423 424WebInspector.documentClick = function(event) 425{ 426 var anchor = event.target.enclosingNodeOrSelfWithNodeName("a"); 427 if (!anchor) 428 return; 429 430 // Prevent the link from navigating, since we don't do any navigation by following links normally. 431 event.preventDefault(); 432 433 function followLink() 434 { 435 // FIXME: support webkit-html-external-link links here. 436 if (anchor.href in WebInspector.resourceURLMap) { 437 if (anchor.hasStyleClass("webkit-html-external-link")) { 438 anchor.removeStyleClass("webkit-html-external-link"); 439 anchor.addStyleClass("webkit-html-resource-link"); 440 } 441 442 WebInspector.showResourceForURL(anchor.href, anchor.lineNumber, anchor.preferredPanel); 443 } else { 444 var profileStringRegEx = new RegExp("webkit-profile://.+/([0-9]+)"); 445 var profileString = profileStringRegEx.exec(anchor.href); 446 if (profileString) 447 WebInspector.showProfileById(profileString[1]) 448 } 449 } 450 451 if (WebInspector.followLinkTimeout) 452 clearTimeout(WebInspector.followLinkTimeout); 453 454 if (anchor.preventFollowOnDoubleClick) { 455 // Start a timeout if this is the first click, if the timeout is canceled 456 // before it fires, then a double clicked happened or another link was clicked. 457 if (event.detail === 1) 458 WebInspector.followLinkTimeout = setTimeout(followLink, 333); 459 return; 460 } 461 462 followLink(); 463} 464 465WebInspector.documentKeyDown = function(event) 466{ 467 if (!this.currentFocusElement) 468 return; 469 if (this.currentFocusElement.handleKeyEvent) 470 this.currentFocusElement.handleKeyEvent(event); 471 else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "KeyDown"]) 472 WebInspector[this.currentFocusElement.id + "KeyDown"](event); 473 474 if (!event.handled) { 475 var isMac = InspectorController.platform().indexOf("mac-") === 0; 476 477 switch (event.keyIdentifier) { 478 case "U+001B": // Escape key 479 this.console.visible = !this.console.visible; 480 event.preventDefault(); 481 break; 482 483 case "U+0046": // F key 484 if (isMac) 485 var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey; 486 else 487 var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey; 488 489 if (isFindKey) { 490 var searchField = document.getElementById("search"); 491 searchField.focus(); 492 searchField.select(); 493 event.preventDefault(); 494 } 495 496 break; 497 498 case "U+0047": // G key 499 if (isMac) 500 var isFindAgainKey = event.metaKey && !event.ctrlKey && !event.altKey; 501 else 502 var isFindAgainKey = event.ctrlKey && !event.metaKey && !event.altKey; 503 504 if (isFindAgainKey) { 505 if (event.shiftKey) { 506 if (this.currentPanel.jumpToPreviousSearchResult) 507 this.currentPanel.jumpToPreviousSearchResult(); 508 } else if (this.currentPanel.jumpToNextSearchResult) 509 this.currentPanel.jumpToNextSearchResult(); 510 event.preventDefault(); 511 } 512 513 break; 514 } 515 } 516} 517 518WebInspector.documentKeyUp = function(event) 519{ 520 if (!this.currentFocusElement || !this.currentFocusElement.handleKeyUpEvent) 521 return; 522 this.currentFocusElement.handleKeyUpEvent(event); 523} 524 525WebInspector.documentCanCopy = function(event) 526{ 527 if (!this.currentFocusElement) 528 return; 529 // Calling preventDefault() will say "we support copying, so enable the Copy menu". 530 if (this.currentFocusElement.handleCopyEvent) 531 event.preventDefault(); 532 else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"]) 533 event.preventDefault(); 534} 535 536WebInspector.documentCopy = function(event) 537{ 538 if (!this.currentFocusElement) 539 return; 540 if (this.currentFocusElement.handleCopyEvent) 541 this.currentFocusElement.handleCopyEvent(event); 542 else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"]) 543 WebInspector[this.currentFocusElement.id + "Copy"](event); 544} 545 546WebInspector.mainKeyDown = function(event) 547{ 548 if (this.currentPanel && this.currentPanel.handleKeyEvent) 549 this.currentPanel.handleKeyEvent(event); 550} 551 552WebInspector.mainKeyUp = function(event) 553{ 554 if (this.currentPanel && this.currentPanel.handleKeyUpEvent) 555 this.currentPanel.handleKeyUpEvent(event); 556} 557 558WebInspector.mainCopy = function(event) 559{ 560 if (this.currentPanel && this.currentPanel.handleCopyEvent) 561 this.currentPanel.handleCopyEvent(event); 562} 563 564WebInspector.animateStyle = function(animations, duration, callback, complete) 565{ 566 if (complete === undefined) 567 complete = 0; 568 var slice = (1000 / 30); // 30 frames per second 569 570 var defaultUnit = "px"; 571 var propertyUnit = {opacity: ""}; 572 573 for (var i = 0; i < animations.length; ++i) { 574 var animation = animations[i]; 575 var element = null; 576 var start = null; 577 var current = null; 578 var end = null; 579 for (key in animation) { 580 if (key === "element") 581 element = animation[key]; 582 else if (key === "start") 583 start = animation[key]; 584 else if (key === "current") 585 current = animation[key]; 586 else if (key === "end") 587 end = animation[key]; 588 } 589 590 if (!element || !end) 591 continue; 592 593 var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element); 594 if (!start) { 595 start = {}; 596 for (key in end) 597 start[key] = parseInt(computedStyle.getPropertyValue(key)); 598 animation.start = start; 599 } else if (complete == 0) 600 for (key in start) 601 element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); 602 603 if (!current) { 604 current = {}; 605 for (key in start) 606 current[key] = start[key]; 607 animation.current = current; 608 } 609 610 function cubicInOut(t, b, c, d) 611 { 612 if ((t/=d/2) < 1) return c/2*t*t*t + b; 613 return c/2*((t-=2)*t*t + 2) + b; 614 } 615 616 var style = element.style; 617 for (key in end) { 618 var startValue = start[key]; 619 var currentValue = current[key]; 620 var endValue = end[key]; 621 if ((complete + slice) < duration) { 622 var delta = (endValue - startValue) / (duration / slice); 623 var newValue = cubicInOut(complete, startValue, endValue - startValue, duration); 624 style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); 625 current[key] = newValue; 626 } else { 627 style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); 628 } 629 } 630 } 631 632 if (complete < duration) 633 setTimeout(WebInspector.animateStyle, slice, animations, duration, callback, complete + slice); 634 else if (callback) 635 callback(); 636} 637 638WebInspector.updateSearchLabel = function() 639{ 640 if (!this.currentPanel) 641 return; 642 643 var newLabel = WebInspector.UIString("Search %s", this.currentPanel.toolbarItemLabel); 644 if (this.attached) 645 document.getElementById("search").setAttribute("placeholder", newLabel); 646 else { 647 document.getElementById("search").removeAttribute("placeholder"); 648 document.getElementById("search-toolbar-label").textContent = newLabel; 649 } 650} 651 652WebInspector.toggleAttach = function() 653{ 654 this.attached = !this.attached; 655} 656 657WebInspector.toolbarDragStart = function(event) 658{ 659 if (!WebInspector.attached && InspectorController.platform() !== "mac-leopard") 660 return; 661 662 var target = event.target; 663 if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable")) 664 return; 665 666 var toolbar = document.getElementById("toolbar"); 667 if (target !== toolbar && !target.hasStyleClass("toolbar-item")) 668 return; 669 670 toolbar.lastScreenX = event.screenX; 671 toolbar.lastScreenY = event.screenY; 672 673 WebInspector.elementDragStart(toolbar, WebInspector.toolbarDrag, WebInspector.toolbarDragEnd, event, (WebInspector.attached ? "row-resize" : "default")); 674} 675 676WebInspector.toolbarDragEnd = function(event) 677{ 678 var toolbar = document.getElementById("toolbar"); 679 680 WebInspector.elementDragEnd(event); 681 682 delete toolbar.lastScreenX; 683 delete toolbar.lastScreenY; 684} 685 686WebInspector.toolbarDrag = function(event) 687{ 688 var toolbar = document.getElementById("toolbar"); 689 690 if (WebInspector.attached) { 691 var height = window.innerHeight - (event.screenY - toolbar.lastScreenY); 692 693 InspectorController.setAttachedWindowHeight(height); 694 } else { 695 var x = event.screenX - toolbar.lastScreenX; 696 var y = event.screenY - toolbar.lastScreenY; 697 698 // We cannot call window.moveBy here because it restricts the movement 699 // of the window at the edges. 700 InspectorController.moveByUnrestricted(x, y); 701 } 702 703 toolbar.lastScreenX = event.screenX; 704 toolbar.lastScreenY = event.screenY; 705 706 event.preventDefault(); 707} 708 709WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor) 710{ 711 if (this._elementDraggingEventListener || this._elementEndDraggingEventListener) 712 this.elementDragEnd(event); 713 714 this._elementDraggingEventListener = dividerDrag; 715 this._elementEndDraggingEventListener = elementDragEnd; 716 717 document.addEventListener("mousemove", dividerDrag, true); 718 document.addEventListener("mouseup", elementDragEnd, true); 719 720 document.body.style.cursor = cursor; 721 722 event.preventDefault(); 723} 724 725WebInspector.elementDragEnd = function(event) 726{ 727 document.removeEventListener("mousemove", this._elementDraggingEventListener, true); 728 document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true); 729 730 document.body.style.removeProperty("cursor"); 731 732 delete this._elementDraggingEventListener; 733 delete this._elementEndDraggingEventListener; 734 735 event.preventDefault(); 736} 737 738WebInspector.showConsole = function() 739{ 740 this.console.show(); 741} 742 743WebInspector.showElementsPanel = function() 744{ 745 this.currentPanel = this.panels.elements; 746} 747 748WebInspector.showResourcesPanel = function() 749{ 750 this.currentPanel = this.panels.resources; 751} 752 753WebInspector.showScriptsPanel = function() 754{ 755 this.currentPanel = this.panels.scripts; 756} 757 758WebInspector.showProfilesPanel = function() 759{ 760 this.currentPanel = this.panels.profiles; 761} 762 763WebInspector.showDatabasesPanel = function() 764{ 765 this.currentPanel = this.panels.databases; 766} 767 768WebInspector.addResource = function(resource) 769{ 770 this.resources.push(resource); 771 this.resourceURLMap[resource.url] = resource; 772 773 if (resource.mainResource) { 774 this.mainResource = resource; 775 this.panels.elements.reset(); 776 } 777 778 if (this.panels.resources) 779 this.panels.resources.addResource(resource); 780} 781 782WebInspector.removeResource = function(resource) 783{ 784 resource.category.removeResource(resource); 785 delete this.resourceURLMap[resource.url]; 786 787 this.resources.remove(resource, true); 788 789 if (this.panels.resources) 790 this.panels.resources.removeResource(resource); 791} 792 793WebInspector.addDatabase = function(database) 794{ 795 this.panels.databases.addDatabase(database); 796} 797 798WebInspector.debuggerWasEnabled = function() 799{ 800 this.panels.scripts.debuggerWasEnabled(); 801} 802 803WebInspector.debuggerWasDisabled = function() 804{ 805 this.panels.scripts.debuggerWasDisabled(); 806} 807 808WebInspector.profilerWasEnabled = function() 809{ 810 this.panels.profiles.profilerWasEnabled(); 811} 812 813WebInspector.profilerWasDisabled = function() 814{ 815 this.panels.profiles.profilerWasDisabled(); 816} 817 818WebInspector.parsedScriptSource = function(sourceID, sourceURL, source, startingLine) 819{ 820 this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine); 821} 822 823WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage) 824{ 825 this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage); 826} 827 828WebInspector.pausedScript = function() 829{ 830 this.panels.scripts.debuggerPaused(); 831} 832 833WebInspector.populateInterface = function() 834{ 835 for (var panelName in this.panels) { 836 var panel = this.panels[panelName]; 837 if ("populateInterface" in panel) 838 panel.populateInterface(); 839 } 840} 841 842WebInspector.reset = function() 843{ 844 for (var panelName in this.panels) { 845 var panel = this.panels[panelName]; 846 if ("reset" in panel) 847 panel.reset(); 848 } 849 850 for (var category in this.resourceCategories) 851 this.resourceCategories[category].removeAllResources(); 852 853 this.resources = []; 854 this.resourceURLMap = {}; 855 this.hoveredDOMNode = null; 856 857 delete this.mainResource; 858 859 this.console.clearMessages(); 860} 861 862WebInspector.inspectedWindowCleared = function(inspectedWindow) 863{ 864 this.panels.elements.inspectedWindowCleared(inspectedWindow); 865} 866 867WebInspector.resourceURLChanged = function(resource, oldURL) 868{ 869 delete this.resourceURLMap[oldURL]; 870 this.resourceURLMap[resource.url] = resource; 871} 872 873WebInspector.addMessageToConsole = function(msg) 874{ 875 this.console.addMessage(msg); 876} 877 878WebInspector.addProfile = function(profile) 879{ 880 this.panels.profiles.addProfile(profile); 881} 882 883WebInspector.setRecordingProfile = function(isProfiling) 884{ 885 this.panels.profiles.setRecordingProfile(isProfiling); 886} 887 888WebInspector.drawLoadingPieChart = function(canvas, percent) { 889 var g = canvas.getContext("2d"); 890 var darkColor = "rgb(122, 168, 218)"; 891 var lightColor = "rgb(228, 241, 251)"; 892 var cx = 8; 893 var cy = 8; 894 var r = 7; 895 896 g.beginPath(); 897 g.arc(cx, cy, r, 0, Math.PI * 2, false); 898 g.closePath(); 899 900 g.lineWidth = 1; 901 g.strokeStyle = darkColor; 902 g.fillStyle = lightColor; 903 g.fill(); 904 g.stroke(); 905 906 var startangle = -Math.PI / 2; 907 var endangle = startangle + (percent * Math.PI * 2); 908 909 g.beginPath(); 910 g.moveTo(cx, cy); 911 g.arc(cx, cy, r, startangle, endangle, false); 912 g.closePath(); 913 914 g.fillStyle = darkColor; 915 g.fill(); 916} 917 918WebInspector.updateFocusedNode = function(node) 919{ 920 if (!node) 921 // FIXME: Should we deselect if null is passed in? 922 return; 923 924 this.currentPanel = this.panels.elements; 925 this.panels.elements.focusedDOMNode = node; 926} 927 928WebInspector.displayNameForURL = function(url) 929{ 930 if (!url) 931 return ""; 932 var resource = this.resourceURLMap[url]; 933 if (resource) 934 return resource.displayName; 935 return url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : ""); 936} 937 938WebInspector.resourceForURL = function(url) 939{ 940 if (url in this.resourceURLMap) 941 return this.resourceURLMap[url]; 942 943 // No direct match found. Search for resources that contain 944 // a substring of the URL. 945 for (var resourceURL in this.resourceURLMap) { 946 if (resourceURL.hasSubstring(url)) 947 return this.resourceURLMap[resourceURL]; 948 } 949 950 return null; 951} 952 953WebInspector.showResourceForURL = function(url, line, preferredPanel) 954{ 955 var resource = this.resourceForURL(url); 956 if (!resource) 957 return false; 958 959 if (preferredPanel && preferredPanel in WebInspector.panels) { 960 var panel = this.panels[preferredPanel]; 961 if (!("showResource" in panel)) 962 panel = null; 963 else if ("canShowResource" in panel && !panel.canShowResource(resource)) 964 panel = null; 965 } 966 967 this.currentPanel = panel || this.panels.resources; 968 if (!this.currentPanel) 969 return false; 970 this.currentPanel.showResource(resource, line); 971 return true; 972} 973 974WebInspector.linkifyStringAsFragment = function(string) 975{ 976 var container = document.createDocumentFragment(); 977 var linkStringRegEx = new RegExp("(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}://|www\\.)[\\w$\\-_+*'=\\|/\\\\(){}[\\]%@&#~,:;.!?]{2,}[\\w$\\-_+*=\\|/\\\\({%@&#~]"); 978 979 while (string) { 980 var linkString = linkStringRegEx.exec(string); 981 if (!linkString) 982 break; 983 984 linkString = linkString[0]; 985 var title = linkString; 986 var linkIndex = string.indexOf(linkString); 987 var nonLink = string.substring(0, linkIndex); 988 container.appendChild(document.createTextNode(nonLink)); 989 990 var profileStringRegEx = new RegExp("webkit-profile://(.+)/[0-9]+"); 991 var profileStringMatches = profileStringRegEx.exec(title); 992 var profileTitle; 993 if (profileStringMatches) 994 profileTitle = profileStringMatches[1]; 995 if (profileTitle) 996 title = WebInspector.panels.profiles.displayTitleForProfileLink(profileTitle); 997 998 var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString); 999 container.appendChild(WebInspector.linkifyURLAsNode(realURL, title, null, (realURL in WebInspector.resourceURLMap))); 1000 string = string.substring(linkIndex + linkString.length, string.length); 1001 } 1002 1003 if (string) 1004 container.appendChild(document.createTextNode(string)); 1005 1006 return container; 1007} 1008 1009WebInspector.showProfileById = function(uid) { 1010 WebInspector.showProfilesPanel(); 1011 WebInspector.panels.profiles.showProfileById(uid); 1012} 1013 1014WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal) 1015{ 1016 if (!linkText) 1017 linkText = url; 1018 classes = (classes ? classes + " " : ""); 1019 classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link"; 1020 1021 var a = document.createElement("a"); 1022 a.href = url; 1023 a.className = classes; 1024 a.title = url; 1025 a.target = "_blank"; 1026 a.textContent = linkText; 1027 1028 return a; 1029} 1030 1031WebInspector.linkifyURL = function(url, linkText, classes, isExternal) 1032{ 1033 // Use the DOM version of this function so as to avoid needing to escape attributes. 1034 // FIXME: Get rid of linkifyURL entirely. 1035 return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal).outerHTML; 1036} 1037 1038WebInspector.addMainEventListeners = function(doc) 1039{ 1040 doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), true); 1041 doc.defaultView.addEventListener("blur", this.windowBlured.bind(this), true); 1042 doc.addEventListener("click", this.documentClick.bind(this), true); 1043} 1044 1045WebInspector.searchKeyDown = function(event) 1046{ 1047 if (event.keyIdentifier !== "Enter") 1048 return; 1049 1050 // Call preventDefault since this was the Enter key. This prevents a "search" event 1051 // from firing for key down. We handle the Enter key on key up in searchKeyUp. This 1052 // stops performSearch from being called twice in a row. 1053 event.preventDefault(); 1054} 1055 1056WebInspector.searchKeyUp = function(event) 1057{ 1058 if (event.keyIdentifier !== "Enter") 1059 return; 1060 1061 // Select all of the text so the user can easily type an entirely new query. 1062 event.target.select(); 1063 1064 // Only call performSearch if the Enter key was pressed. Otherwise the search 1065 // performance is poor because of searching on every key. The search field has 1066 // the incremental attribute set, so we still get incremental searches. 1067 this.performSearch(event); 1068} 1069 1070WebInspector.performSearch = function(event) 1071{ 1072 var query = event.target.value; 1073 var forceSearch = event.keyIdentifier === "Enter"; 1074 1075 if (!query || !query.length || (!forceSearch && query.length < 3)) { 1076 delete this.currentQuery; 1077 1078 for (var panelName in this.panels) { 1079 var panel = this.panels[panelName]; 1080 if (panel.currentQuery && panel.searchCanceled) 1081 panel.searchCanceled(); 1082 delete panel.currentQuery; 1083 } 1084 1085 this.updateSearchMatchesCount(); 1086 1087 return; 1088 } 1089 1090 if (query === this.currentPanel.currentQuery && this.currentPanel.currentQuery === this.currentQuery) { 1091 // When this is the same query and a forced search, jump to the next 1092 // search result for a good user experience. 1093 if (forceSearch && this.currentPanel.jumpToNextSearchResult) 1094 this.currentPanel.jumpToNextSearchResult(); 1095 return; 1096 } 1097 1098 this.currentQuery = query; 1099 1100 this.updateSearchMatchesCount(); 1101 1102 if (!this.currentPanel.performSearch) 1103 return; 1104 1105 this.currentPanel.currentQuery = query; 1106 this.currentPanel.performSearch(query); 1107} 1108 1109WebInspector.updateSearchMatchesCount = function(matches, panel) 1110{ 1111 if (!panel) 1112 panel = this.currentPanel; 1113 1114 panel.currentSearchMatches = matches; 1115 1116 if (panel !== this.currentPanel) 1117 return; 1118 1119 if (!this.currentPanel.currentQuery) { 1120 document.getElementById("search-results-matches").addStyleClass("hidden"); 1121 return; 1122 } 1123 1124 if (matches) { 1125 if (matches === 1) 1126 var matchesString = WebInspector.UIString("1 match"); 1127 else 1128 var matchesString = WebInspector.UIString("%d matches", matches); 1129 } else 1130 var matchesString = WebInspector.UIString("Not Found"); 1131 1132 var matchesToolbarElement = document.getElementById("search-results-matches"); 1133 matchesToolbarElement.removeStyleClass("hidden"); 1134 matchesToolbarElement.textContent = matchesString; 1135} 1136 1137WebInspector.UIString = function(string) 1138{ 1139 if (window.localizedStrings && string in window.localizedStrings) 1140 string = window.localizedStrings[string]; 1141 else { 1142 if (!(string in this.missingLocalizedStrings)) { 1143 console.error("Localized string \"" + string + "\" not found."); 1144 this.missingLocalizedStrings[string] = true; 1145 } 1146 1147 if (Preferences.showMissingLocalizedStrings) 1148 string += " (not localized)"; 1149 } 1150 1151 return String.vsprintf(string, Array.prototype.slice.call(arguments, 1)); 1152} 1153 1154WebInspector.isBeingEdited = function(element) 1155{ 1156 return element.__editing; 1157} 1158 1159WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context) 1160{ 1161 if (element.__editing) 1162 return; 1163 element.__editing = true; 1164 1165 var oldText = element.textContent; 1166 var oldHandleKeyEvent = element.handleKeyEvent; 1167 1168 element.addStyleClass("editing"); 1169 1170 var oldTabIndex = element.tabIndex; 1171 if (element.tabIndex < 0) 1172 element.tabIndex = 0; 1173 1174 function blurEventListener() { 1175 editingCommitted.call(element); 1176 } 1177 1178 function cleanUpAfterEditing() { 1179 delete this.__editing; 1180 1181 this.removeStyleClass("editing"); 1182 this.tabIndex = oldTabIndex; 1183 this.scrollTop = 0; 1184 this.scrollLeft = 0; 1185 1186 this.handleKeyEvent = oldHandleKeyEvent; 1187 element.removeEventListener("blur", blurEventListener, false); 1188 1189 if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement)) 1190 WebInspector.currentFocusElement = WebInspector.previousFocusElement; 1191 } 1192 1193 function editingCancelled() { 1194 this.innerText = oldText; 1195 1196 cleanUpAfterEditing.call(this); 1197 1198 cancelledCallback(this, context); 1199 } 1200 1201 function editingCommitted() { 1202 cleanUpAfterEditing.call(this); 1203 1204 committedCallback(this, this.textContent, oldText, context); 1205 } 1206 1207 element.handleKeyEvent = function(event) { 1208 if (oldHandleKeyEvent) 1209 oldHandleKeyEvent(event); 1210 if (event.handled) 1211 return; 1212 1213 if (event.keyIdentifier === "Enter") { 1214 editingCommitted.call(element); 1215 event.preventDefault(); 1216 } else if (event.keyCode === 27) { // Escape key 1217 editingCancelled.call(element); 1218 event.preventDefault(); 1219 event.handled = true; 1220 } 1221 } 1222 1223 element.addEventListener("blur", blurEventListener, false); 1224 1225 WebInspector.currentFocusElement = element; 1226} 1227 1228WebInspector._toolbarItemClicked = function(event) 1229{ 1230 var toolbarItem = event.currentTarget; 1231 this.currentPanel = toolbarItem.panel; 1232} 1233 1234// This table maps MIME types to the Resource.Types which are valid for them. 1235// The following line: 1236// "text/html": {0: 1}, 1237// means that text/html is a valid MIME type for resources that have type 1238// WebInspector.Resource.Type.Document (which has a value of 0). 1239WebInspector.MIMETypes = { 1240 "text/html": {0: true}, 1241 "text/xml": {0: true}, 1242 "text/plain": {0: true}, 1243 "application/xhtml+xml": {0: true}, 1244 "text/css": {1: true}, 1245 "text/xsl": {1: true}, 1246 "image/jpeg": {2: true}, 1247 "image/png": {2: true}, 1248 "image/gif": {2: true}, 1249 "image/bmp": {2: true}, 1250 "image/x-icon": {2: true}, 1251 "image/x-xbitmap": {2: true}, 1252 "font/ttf": {3: true}, 1253 "font/opentype": {3: true}, 1254 "application/x-font-type1": {3: true}, 1255 "application/x-font-ttf": {3: true}, 1256 "application/x-truetype-font": {3: true}, 1257 "text/javascript": {4: true}, 1258 "text/ecmascript": {4: true}, 1259 "application/javascript": {4: true}, 1260 "application/ecmascript": {4: true}, 1261 "application/x-javascript": {4: true}, 1262 "text/javascript1.1": {4: true}, 1263 "text/javascript1.2": {4: true}, 1264 "text/javascript1.3": {4: true}, 1265 "text/jscript": {4: true}, 1266 "text/livescript": {4: true}, 1267} 1268