inspector.js revision 65f03d4f644ce73618e5f4f50dd694b26f55ae12
1/* 2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). 4 * Copyright (C) 2009 Joseph Pecoraro 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 16 * its contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31// Keep this ; so that concatenated version of the script worked. 32;(function preloadImages() 33{ 34 (new Image()).src = "Images/clearConsoleButtonGlyph.png"; 35 (new Image()).src = "Images/consoleButtonGlyph.png"; 36 (new Image()).src = "Images/dockButtonGlyph.png"; 37 (new Image()).src = "Images/enableOutlineButtonGlyph.png"; 38 (new Image()).src = "Images/enableSolidButtonGlyph.png"; 39 (new Image()).src = "Images/excludeButtonGlyph.png"; 40 (new Image()).src = "Images/focusButtonGlyph.png"; 41 (new Image()).src = "Images/largerResourcesButtonGlyph.png"; 42 (new Image()).src = "Images/nodeSearchButtonGlyph.png"; 43 (new Image()).src = "Images/pauseOnExceptionButtonGlyph.png"; 44 (new Image()).src = "Images/percentButtonGlyph.png"; 45 (new Image()).src = "Images/recordButtonGlyph.png"; 46 (new Image()).src = "Images/recordToggledButtonGlyph.png"; 47 (new Image()).src = "Images/reloadButtonGlyph.png"; 48 (new Image()).src = "Images/undockButtonGlyph.png"; 49})(); 50 51var WebInspector = { 52 resources: {}, 53 missingLocalizedStrings: {}, 54 pendingDispatches: 0, 55 56 get platform() 57 { 58 if (!("_platform" in this)) 59 this._platform = InspectorFrontendHost.platform(); 60 61 return this._platform; 62 }, 63 64 get platformFlavor() 65 { 66 if (!("_platformFlavor" in this)) 67 this._platformFlavor = this._detectPlatformFlavor(); 68 69 return this._platformFlavor; 70 }, 71 72 _detectPlatformFlavor: function() 73 { 74 const userAgent = navigator.userAgent; 75 76 if (this.platform === "windows") { 77 var match = userAgent.match(/Windows NT (\d+)\.(?:\d+)/); 78 if (match && match[1] >= 6) 79 return WebInspector.PlatformFlavor.WindowsVista; 80 return null; 81 } else if (this.platform === "mac") { 82 var match = userAgent.match(/Mac OS X\s*(?:(\d+)_(\d+))?/); 83 if (!match || match[1] != 10) 84 return WebInspector.PlatformFlavor.MacSnowLeopard; 85 switch (Number(match[2])) { 86 case 4: 87 return WebInspector.PlatformFlavor.MacTiger; 88 case 5: 89 return WebInspector.PlatformFlavor.MacLeopard; 90 case 6: 91 default: 92 return WebInspector.PlatformFlavor.MacSnowLeopard; 93 } 94 } 95 96 return null; 97 }, 98 99 get port() 100 { 101 if (!("_port" in this)) 102 this._port = InspectorFrontendHost.port(); 103 104 return this._port; 105 }, 106 107 get previousFocusElement() 108 { 109 return this._previousFocusElement; 110 }, 111 112 get currentFocusElement() 113 { 114 return this._currentFocusElement; 115 }, 116 117 set currentFocusElement(x) 118 { 119 if (this._currentFocusElement !== x) 120 this._previousFocusElement = this._currentFocusElement; 121 this._currentFocusElement = x; 122 123 if (this._currentFocusElement) { 124 this._currentFocusElement.focus(); 125 126 // Make a caret selection inside the new element if there isn't a range selection and 127 // there isn't already a caret selection inside. 128 var selection = window.getSelection(); 129 if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) { 130 var selectionRange = this._currentFocusElement.ownerDocument.createRange(); 131 selectionRange.setStart(this._currentFocusElement, 0); 132 selectionRange.setEnd(this._currentFocusElement, 0); 133 134 selection.removeAllRanges(); 135 selection.addRange(selectionRange); 136 } 137 } else if (this._previousFocusElement) 138 this._previousFocusElement.blur(); 139 }, 140 141 get currentPanel() 142 { 143 return this._currentPanel; 144 }, 145 146 set currentPanel(x) 147 { 148 if (this._currentPanel === x) 149 return; 150 151 if (this._currentPanel) 152 this._currentPanel.hide(); 153 154 this._currentPanel = x; 155 156 this.updateSearchLabel(); 157 158 if (x) { 159 x.show(); 160 161 if (this.currentQuery) { 162 if (x.performSearch) { 163 function performPanelSearch() 164 { 165 this.updateSearchMatchesCount(); 166 167 x.currentQuery = this.currentQuery; 168 x.performSearch(this.currentQuery); 169 } 170 171 // Perform the search on a timeout so the panel switches fast. 172 setTimeout(performPanelSearch.bind(this), 0); 173 } else { 174 // Update to show Not found for panels that can't be searched. 175 this.updateSearchMatchesCount(); 176 } 177 } 178 } 179 180 for (var panelName in WebInspector.panels) { 181 if (WebInspector.panels[panelName] === x) { 182 WebInspector.settings.lastActivePanel = panelName; 183 this._panelHistory.setPanel(panelName); 184 } 185 } 186 }, 187 188 createJSBreakpointsSidebarPane: function() 189 { 190 var pane = new WebInspector.BreakpointsSidebarPane(WebInspector.UIString("Breakpoints")); 191 function breakpointAdded(event) 192 { 193 pane.addBreakpointItem(new WebInspector.BreakpointItem(event.data)); 194 } 195 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointAdded, breakpointAdded); 196 return pane; 197 }, 198 199 createDOMBreakpointsSidebarPane: function() 200 { 201 var pane = new WebInspector.BreakpointsSidebarPane(WebInspector.UIString("DOM Breakpoints")); 202 function breakpointAdded(event) 203 { 204 pane.addBreakpointItem(new WebInspector.BreakpointItem(event.data)); 205 } 206 WebInspector.breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.DOMBreakpointAdded, breakpointAdded); 207 return pane; 208 }, 209 210 createXHRBreakpointsSidebarPane: function() 211 { 212 var pane = new WebInspector.XHRBreakpointsSidebarPane(); 213 function breakpointAdded(event) 214 { 215 pane.addBreakpointItem(new WebInspector.BreakpointItem(event.data)); 216 } 217 WebInspector.breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.XHRBreakpointAdded, breakpointAdded); 218 return pane; 219 }, 220 221 _createPanels: function() 222 { 223 var hiddenPanels = (InspectorFrontendHost.hiddenPanels() || "").split(','); 224 if (hiddenPanels.indexOf("elements") === -1) 225 this.panels.elements = new WebInspector.ElementsPanel(); 226 if (hiddenPanels.indexOf("resources") === -1) 227 this.panels.resources = new WebInspector.ResourcesPanel(); 228 if (hiddenPanels.indexOf("network") === -1) 229 this.panels.network = new WebInspector.NetworkPanel(); 230 if (hiddenPanels.indexOf("scripts") === -1) 231 this.panels.scripts = new WebInspector.ScriptsPanel(); 232 if (hiddenPanels.indexOf("timeline") === -1) 233 this.panels.timeline = new WebInspector.TimelinePanel(); 234 if (hiddenPanels.indexOf("profiles") === -1) { 235 this.panels.profiles = new WebInspector.ProfilesPanel(); 236 this.panels.profiles.registerProfileType(new WebInspector.CPUProfileType()); 237 if (Preferences.heapProfilerPresent) 238 this.panels.profiles.registerProfileType(new WebInspector.HeapSnapshotProfileType()); 239 } 240 if (hiddenPanels.indexOf("audits") === -1) 241 this.panels.audits = new WebInspector.AuditsPanel(); 242 if (hiddenPanels.indexOf("console") === -1) 243 this.panels.console = new WebInspector.ConsolePanel(); 244 }, 245 246 get attached() 247 { 248 return this._attached; 249 }, 250 251 set attached(x) 252 { 253 if (this._attached === x) 254 return; 255 256 this._attached = x; 257 258 this.updateSearchLabel(); 259 260 var dockToggleButton = document.getElementById("dock-status-bar-item"); 261 var body = document.body; 262 263 if (x) { 264 body.removeStyleClass("detached"); 265 body.addStyleClass("attached"); 266 dockToggleButton.title = WebInspector.UIString("Undock into separate window."); 267 } else { 268 body.removeStyleClass("attached"); 269 body.addStyleClass("detached"); 270 dockToggleButton.title = WebInspector.UIString("Dock to main window."); 271 } 272 if (this.drawer) 273 this.drawer.resize(); 274 }, 275 276 get errors() 277 { 278 return this._errors || 0; 279 }, 280 281 set errors(x) 282 { 283 x = Math.max(x, 0); 284 285 if (this._errors === x) 286 return; 287 this._errors = x; 288 this._updateErrorAndWarningCounts(); 289 }, 290 291 get warnings() 292 { 293 return this._warnings || 0; 294 }, 295 296 set warnings(x) 297 { 298 x = Math.max(x, 0); 299 300 if (this._warnings === x) 301 return; 302 this._warnings = x; 303 this._updateErrorAndWarningCounts(); 304 }, 305 306 _updateErrorAndWarningCounts: function() 307 { 308 var errorWarningElement = document.getElementById("error-warning-count"); 309 if (!errorWarningElement) 310 return; 311 312 if (!this.errors && !this.warnings) { 313 errorWarningElement.addStyleClass("hidden"); 314 return; 315 } 316 317 errorWarningElement.removeStyleClass("hidden"); 318 319 errorWarningElement.removeChildren(); 320 321 if (this.errors) { 322 var errorElement = document.createElement("span"); 323 errorElement.id = "error-count"; 324 errorElement.textContent = this.errors; 325 errorWarningElement.appendChild(errorElement); 326 } 327 328 if (this.warnings) { 329 var warningsElement = document.createElement("span"); 330 warningsElement.id = "warning-count"; 331 warningsElement.textContent = this.warnings; 332 errorWarningElement.appendChild(warningsElement); 333 } 334 335 if (this.errors) { 336 if (this.warnings) { 337 if (this.errors == 1) { 338 if (this.warnings == 1) 339 errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings); 340 else 341 errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings); 342 } else if (this.warnings == 1) 343 errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings); 344 else 345 errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings); 346 } else if (this.errors == 1) 347 errorWarningElement.title = WebInspector.UIString("%d error", this.errors); 348 else 349 errorWarningElement.title = WebInspector.UIString("%d errors", this.errors); 350 } else if (this.warnings == 1) 351 errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings); 352 else if (this.warnings) 353 errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings); 354 else 355 errorWarningElement.title = null; 356 }, 357 358 get styleChanges() 359 { 360 return this._styleChanges; 361 }, 362 363 set styleChanges(x) 364 { 365 x = Math.max(x, 0); 366 367 if (this._styleChanges === x) 368 return; 369 this._styleChanges = x; 370 this._updateChangesCount(); 371 }, 372 373 _updateChangesCount: function() 374 { 375 // TODO: Remove immediate return when enabling the Changes Panel 376 return; 377 378 var changesElement = document.getElementById("changes-count"); 379 if (!changesElement) 380 return; 381 382 if (!this.styleChanges) { 383 changesElement.addStyleClass("hidden"); 384 return; 385 } 386 387 changesElement.removeStyleClass("hidden"); 388 changesElement.removeChildren(); 389 390 if (this.styleChanges) { 391 var styleChangesElement = document.createElement("span"); 392 styleChangesElement.id = "style-changes-count"; 393 styleChangesElement.textContent = this.styleChanges; 394 changesElement.appendChild(styleChangesElement); 395 } 396 397 if (this.styleChanges) { 398 if (this.styleChanges === 1) 399 changesElement.title = WebInspector.UIString("%d style change", this.styleChanges); 400 else 401 changesElement.title = WebInspector.UIString("%d style changes", this.styleChanges); 402 } 403 }, 404 405 highlightDOMNode: function(nodeId) 406 { 407 if ("_hideDOMNodeHighlightTimeout" in this) { 408 clearTimeout(this._hideDOMNodeHighlightTimeout); 409 delete this._hideDOMNodeHighlightTimeout; 410 } 411 412 if (this._highlightedDOMNodeId === nodeId) 413 return; 414 415 this._highlightedDOMNodeId = nodeId; 416 if (nodeId) 417 InspectorBackend.highlightDOMNode(nodeId); 418 else 419 InspectorBackend.hideDOMNodeHighlight(); 420 }, 421 422 highlightDOMNodeForTwoSeconds: function(nodeId) 423 { 424 this.highlightDOMNode(nodeId); 425 this._hideDOMNodeHighlightTimeout = setTimeout(this.highlightDOMNode.bind(this, 0), 2000); 426 }, 427 428 wireElementWithDOMNode: function(element, nodeId) 429 { 430 element.addEventListener("click", this._updateFocusedNode.bind(this, nodeId), false); 431 element.addEventListener("mouseover", this.highlightDOMNode.bind(this, nodeId), false); 432 element.addEventListener("mouseout", this.highlightDOMNode.bind(this, 0), false); 433 }, 434 435 _updateFocusedNode: function(nodeId) 436 { 437 this.currentPanel = this.panels.elements; 438 this.panels.elements.updateFocusedNode(nodeId); 439 }, 440 441 get networkResources() 442 { 443 return this.panels.network.resources; 444 }, 445 446 networkResourceById: function(id) 447 { 448 return this.panels.network.resourceById(id); 449 }, 450 451 forAllResources: function(callback) 452 { 453 WebInspector.resourceTreeModel.forAllResources(callback); 454 }, 455 456 resourceForURL: function(url) 457 { 458 return this.resourceTreeModel.resourceForURL(url); 459 } 460} 461 462WebInspector.PlatformFlavor = { 463 WindowsVista: "windows-vista", 464 MacTiger: "mac-tiger", 465 MacLeopard: "mac-leopard", 466 MacSnowLeopard: "mac-snowleopard" 467}; 468 469(function parseQueryParameters() 470{ 471 WebInspector.queryParamsObject = {}; 472 var queryParams = window.location.search; 473 if (!queryParams) 474 return; 475 var params = queryParams.substring(1).split("&"); 476 for (var i = 0; i < params.length; ++i) { 477 var pair = params[i].split("="); 478 WebInspector.queryParamsObject[pair[0]] = pair[1]; 479 } 480})(); 481 482WebInspector.loaded = function() 483{ 484 if ("page" in WebInspector.queryParamsObject) { 485 WebInspector.socket = new WebSocket("ws://" + window.location.host + "/devtools/page/" + WebInspector.queryParamsObject.page); 486 WebInspector.socket.onmessage = function(message) { InspectorBackend.dispatch(message.data); } 487 WebInspector.socket.onerror = function(error) { console.error(error); } 488 WebInspector.socket.onopen = function() { 489 InspectorFrontendHost.sendMessageToBackend = WebInspector.socket.send.bind(WebInspector.socket); 490 InspectorFrontendHost.loaded = WebInspector.socket.send.bind(WebInspector.socket, "loaded"); 491 WebInspector.doLoadedDone(); 492 } 493 return; 494 } 495 WebInspector.doLoadedDone(); 496} 497 498WebInspector.doLoadedDone = function() 499{ 500 InspectorFrontendHost.loaded(); 501 502 var platform = WebInspector.platform; 503 document.body.addStyleClass("platform-" + platform); 504 var flavor = WebInspector.platformFlavor; 505 if (flavor) 506 document.body.addStyleClass("platform-" + flavor); 507 var port = WebInspector.port; 508 document.body.addStyleClass("port-" + port); 509 510 WebInspector.settings = new WebInspector.Settings(); 511 512 this._registerShortcuts(); 513 514 // set order of some sections explicitly 515 WebInspector.shortcutsHelp.section(WebInspector.UIString("Console")); 516 WebInspector.shortcutsHelp.section(WebInspector.UIString("Elements Panel")); 517 518 this.drawer = new WebInspector.Drawer(); 519 this.console = new WebInspector.ConsoleView(this.drawer); 520 // TODO: Uncomment when enabling the Changes Panel 521 // this.changes = new WebInspector.ChangesView(this.drawer); 522 // TODO: Remove class="hidden" from inspector.html on button#changes-status-bar-item 523 this.drawer.visibleView = this.console; 524 this.resourceTreeModel = new WebInspector.ResourceTreeModel(); 525 this.networkManager = new WebInspector.NetworkManager(this.resourceTreeModel); 526 this.domAgent = new WebInspector.DOMAgent(); 527 528 InspectorBackend.registerDomainDispatcher("Inspector", this); 529 530 this.resourceCategories = { 531 documents: new WebInspector.ResourceCategory("documents", WebInspector.UIString("Documents"), "rgb(47,102,236)"), 532 stylesheets: new WebInspector.ResourceCategory("stylesheets", WebInspector.UIString("Stylesheets"), "rgb(157,231,119)"), 533 images: new WebInspector.ResourceCategory("images", WebInspector.UIString("Images"), "rgb(164,60,255)"), 534 scripts: new WebInspector.ResourceCategory("scripts", WebInspector.UIString("Scripts"), "rgb(255,121,0)"), 535 xhr: new WebInspector.ResourceCategory("xhr", WebInspector.UIString("XHR"), "rgb(231,231,10)"), 536 fonts: new WebInspector.ResourceCategory("fonts", WebInspector.UIString("Fonts"), "rgb(255,82,62)"), 537 websockets: new WebInspector.ResourceCategory("websockets", WebInspector.UIString("WebSocket"), "rgb(186,186,186)"), // FIXME: Decide the color. 538 other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)") 539 }; 540 541 this.cssModel = new WebInspector.CSSStyleModel(); 542 this.debuggerModel = new WebInspector.DebuggerModel(); 543 544 this.breakpointManager = new WebInspector.BreakpointManager(); 545 546 this.panels = {}; 547 this._createPanels(); 548 this._panelHistory = new WebInspector.PanelHistory(); 549 550 var toolbarElement = document.getElementById("toolbar"); 551 var previousToolbarItem = toolbarElement.children[0]; 552 553 this.panelOrder = []; 554 for (var panelName in this.panels) 555 previousToolbarItem = WebInspector.addPanelToolbarIcon(toolbarElement, this.panels[panelName], previousToolbarItem); 556 557 this.Tips = { 558 ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")} 559 }; 560 561 this.Warnings = { 562 IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")} 563 }; 564 565 this.addMainEventListeners(document); 566 567 window.addEventListener("resize", this.windowResize.bind(this), true); 568 569 document.addEventListener("focus", this.focusChanged.bind(this), true); 570 document.addEventListener("keydown", this.documentKeyDown.bind(this), false); 571 document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true); 572 document.addEventListener("copy", this.documentCopy.bind(this), true); 573 document.addEventListener("contextmenu", this.contextMenuEventFired.bind(this), true); 574 575 var dockToggleButton = document.getElementById("dock-status-bar-item"); 576 dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false); 577 578 if (this.attached) 579 dockToggleButton.title = WebInspector.UIString("Undock into separate window."); 580 else 581 dockToggleButton.title = WebInspector.UIString("Dock to main window."); 582 583 var errorWarningCount = document.getElementById("error-warning-count"); 584 errorWarningCount.addEventListener("click", this.showConsole.bind(this), false); 585 this._updateErrorAndWarningCounts(); 586 587 this.styleChanges = 0; 588 // TODO: Uncomment when enabling the Changes Panel 589 // var changesElement = document.getElementById("changes-count"); 590 // changesElement.addEventListener("click", this.showChanges.bind(this), false); 591 // this._updateErrorAndWarningCounts(); 592 593 var searchField = document.getElementById("search"); 594 searchField.addEventListener("search", this.performSearch.bind(this), false); // when the search is emptied 595 searchField.addEventListener("mousedown", this._searchFieldManualFocus.bind(this), false); // when the search field is manually selected 596 searchField.addEventListener("keydown", this._searchKeyDown.bind(this), true); 597 598 toolbarElement.addEventListener("mousedown", this.toolbarDragStart, true); 599 document.getElementById("close-button-left").addEventListener("click", this.close, true); 600 document.getElementById("close-button-right").addEventListener("click", this.close, true); 601 602 this.extensionServer.initExtensions(); 603 604 function populateInspectorState(inspectorState) 605 { 606 WebInspector.monitoringXHREnabled = inspectorState.monitoringXHREnabled; 607 if ("pauseOnExceptionsState" in inspectorState) 608 WebInspector.panels.scripts.updatePauseOnExceptionsState(inspectorState.pauseOnExceptionsState); 609 } 610 InspectorBackend.getInspectorState(populateInspectorState); 611 612 function onPopulateScriptObjects() 613 { 614 if (!WebInspector.currentPanel) 615 WebInspector.showPanel(WebInspector.settings.lastActivePanel); 616 } 617 InspectorBackend.populateScriptObjects(onPopulateScriptObjects); 618 619 InspectorBackend.setConsoleMessagesEnabled(true); 620 621 function propertyNamesCallback(names) 622 { 623 WebInspector.cssNameCompletions = new WebInspector.CSSCompletions(names); 624 } 625 // As a DOMAgent method, this needs to happen after the frontend has loaded and the agent is available. 626 InspectorBackend.getSupportedCSSProperties(propertyNamesCallback); 627} 628 629WebInspector.addPanelToolbarIcon = function(toolbarElement, panel, previousToolbarItem) 630{ 631 var panelToolbarItem = panel.toolbarItem; 632 this.panelOrder.push(panel); 633 panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this)); 634 if (previousToolbarItem) 635 toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling); 636 else 637 toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild); 638 return panelToolbarItem; 639} 640 641var windowLoaded = function() 642{ 643 var localizedStringsURL = InspectorFrontendHost.localizedStringsURL(); 644 if (localizedStringsURL) { 645 var localizedStringsScriptElement = document.createElement("script"); 646 localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false); 647 localizedStringsScriptElement.type = "text/javascript"; 648 localizedStringsScriptElement.src = localizedStringsURL; 649 document.head.appendChild(localizedStringsScriptElement); 650 } else 651 WebInspector.loaded(); 652 653 window.removeEventListener("DOMContentLoaded", windowLoaded, false); 654 delete windowLoaded; 655}; 656 657window.addEventListener("DOMContentLoaded", windowLoaded, false); 658 659WebInspector.dispatch = function(message) { 660 // We'd like to enforce asynchronous interaction between the inspector controller and the frontend. 661 // This is important to LayoutTests. 662 function delayDispatch() 663 { 664 InspectorBackend.dispatch(message); 665 WebInspector.pendingDispatches--; 666 } 667 WebInspector.pendingDispatches++; 668 setTimeout(delayDispatch, 0); 669} 670 671WebInspector.dispatchMessageFromBackend = function(messageObject) 672{ 673 WebInspector.dispatch(messageObject); 674} 675 676WebInspector.windowResize = function(event) 677{ 678 if (this.currentPanel) 679 this.currentPanel.resize(); 680 this.drawer.resize(); 681} 682 683WebInspector.windowFocused = function(event) 684{ 685 // Fires after blur, so when focusing on either the main inspector 686 // or an <iframe> within the inspector we should always remove the 687 // "inactive" class. 688 if (event.target.document.nodeType === Node.DOCUMENT_NODE) 689 document.body.removeStyleClass("inactive"); 690} 691 692WebInspector.windowBlurred = function(event) 693{ 694 // Leaving the main inspector or an <iframe> within the inspector. 695 // We can add "inactive" now, and if we are moving the focus to another 696 // part of the inspector then windowFocused will correct this. 697 if (event.target.document.nodeType === Node.DOCUMENT_NODE) 698 document.body.addStyleClass("inactive"); 699} 700 701WebInspector.focusChanged = function(event) 702{ 703 this.currentFocusElement = event.target; 704} 705 706WebInspector.setAttachedWindow = function(attached) 707{ 708 this.attached = attached; 709} 710 711WebInspector.close = function(event) 712{ 713 if (this._isClosing) 714 return; 715 this._isClosing = true; 716 InspectorFrontendHost.closeWindow(); 717} 718 719WebInspector.disconnectFromBackend = function() 720{ 721 InspectorFrontendHost.disconnectFromBackend(); 722} 723 724WebInspector.documentClick = function(event) 725{ 726 var anchor = event.target.enclosingNodeOrSelfWithNodeName("a"); 727 if (!anchor || anchor.target === "_blank") 728 return; 729 730 // Prevent the link from navigating, since we don't do any navigation by following links normally. 731 event.preventDefault(); 732 event.stopPropagation(); 733 734 function followLink() 735 { 736 // FIXME: support webkit-html-external-link links here. 737 if (WebInspector.canShowSourceLine(anchor.href, anchor.getAttribute("line_number"), anchor.getAttribute("preferred_panel"))) { 738 if (anchor.hasStyleClass("webkit-html-external-link")) { 739 anchor.removeStyleClass("webkit-html-external-link"); 740 anchor.addStyleClass("webkit-html-resource-link"); 741 } 742 743 WebInspector.showSourceLine(anchor.href, anchor.getAttribute("line_number"), anchor.getAttribute("preferred_panel")); 744 return; 745 } 746 747 const profileMatch = WebInspector.ProfileType.URLRegExp.exec(anchor.href); 748 if (profileMatch) { 749 WebInspector.showProfileForURL(anchor.href); 750 return; 751 } 752 753 var parsedURL = anchor.href.asParsedURL(); 754 if (parsedURL && parsedURL.scheme === "webkit-link-action") { 755 if (parsedURL.host === "show-panel") { 756 var panel = parsedURL.path.substring(1); 757 if (WebInspector.panels[panel]) 758 WebInspector.showPanel(panel); 759 } 760 return; 761 } 762 763 WebInspector.showPanel("resources"); 764 } 765 766 if (WebInspector.followLinkTimeout) 767 clearTimeout(WebInspector.followLinkTimeout); 768 769 if (anchor.preventFollowOnDoubleClick) { 770 // Start a timeout if this is the first click, if the timeout is canceled 771 // before it fires, then a double clicked happened or another link was clicked. 772 if (event.detail === 1) 773 WebInspector.followLinkTimeout = setTimeout(followLink, 333); 774 return; 775 } 776 777 followLink(); 778} 779 780WebInspector.openResource = function(resourceURL, inResourcesPanel) 781{ 782 var resource = WebInspector.resourceForURL(resourceURL); 783 if (inResourcesPanel && resource) { 784 WebInspector.panels.resources.showResource(resource); 785 WebInspector.showPanel("resources"); 786 } else 787 InspectorBackend.openInInspectedWindow(resource ? resource.url : resourceURL); 788} 789 790WebInspector._registerShortcuts = function() 791{ 792 var shortcut = WebInspector.KeyboardShortcut; 793 var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("All Panels")); 794 var keys = [ 795 shortcut.shortcutToString("]", shortcut.Modifiers.CtrlOrMeta), 796 shortcut.shortcutToString("[", shortcut.Modifiers.CtrlOrMeta) 797 ]; 798 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous panel")); 799 section.addKey(shortcut.shortcutToString(shortcut.Keys.Esc), WebInspector.UIString("Toggle console")); 800 section.addKey(shortcut.shortcutToString("f", shortcut.Modifiers.CtrlOrMeta), WebInspector.UIString("Search")); 801 if (WebInspector.isMac()) { 802 keys = [ 803 shortcut.shortcutToString("g", shortcut.Modifiers.Meta), 804 shortcut.shortcutToString("g", shortcut.Modifiers.Meta | shortcut.Modifiers.Shift) 805 ]; 806 section.addRelatedKeys(keys, WebInspector.UIString("Find next/previous")); 807 } 808} 809 810WebInspector.documentKeyDown = function(event) 811{ 812 var isInputElement = event.target.nodeName === "INPUT"; 813 var isInEditMode = event.target.enclosingNodeOrSelfWithClass("text-prompt") || WebInspector.isEditingAnyField(); 814 const helpKey = WebInspector.isMac() ? "U+003F" : "U+00BF"; // "?" for both platforms 815 816 if (event.keyIdentifier === "F1" || 817 (event.keyIdentifier === helpKey && event.shiftKey && (!isInEditMode && !isInputElement || event.metaKey))) { 818 WebInspector.shortcutsHelp.show(); 819 event.stopPropagation(); 820 event.preventDefault(); 821 return; 822 } 823 824 if (WebInspector.isEditingAnyField()) 825 return; 826 827 if (this.currentFocusElement && this.currentFocusElement.handleKeyEvent) { 828 this.currentFocusElement.handleKeyEvent(event); 829 if (event.handled) { 830 event.preventDefault(); 831 return; 832 } 833 } 834 835 if (this.currentPanel && this.currentPanel.handleShortcut) { 836 this.currentPanel.handleShortcut(event); 837 if (event.handled) { 838 event.preventDefault(); 839 return; 840 } 841 } 842 843 var isMac = WebInspector.isMac(); 844 switch (event.keyIdentifier) { 845 case "Left": 846 var isBackKey = !isInEditMode && (isMac ? event.metaKey : event.ctrlKey); 847 if (isBackKey && this._panelHistory.canGoBack()) { 848 this._panelHistory.goBack(); 849 event.preventDefault(); 850 } 851 break; 852 853 case "Right": 854 var isForwardKey = !isInEditMode && (isMac ? event.metaKey : event.ctrlKey); 855 if (isForwardKey && this._panelHistory.canGoForward()) { 856 this._panelHistory.goForward(); 857 event.preventDefault(); 858 } 859 break; 860 861 case "U+001B": // Escape key 862 event.preventDefault(); 863 if (this.drawer.fullPanel) 864 return; 865 866 this.drawer.visible = !this.drawer.visible; 867 break; 868 869 case "U+0046": // F key 870 if (isMac) 871 var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey; 872 else 873 var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey; 874 875 if (isFindKey) { 876 WebInspector.focusSearchField(); 877 event.preventDefault(); 878 } 879 break; 880 881 case "F3": 882 if (!isMac) { 883 WebInspector.focusSearchField(); 884 event.preventDefault(); 885 } 886 break; 887 888 case "U+0047": // G key 889 if (isMac && event.metaKey && !event.ctrlKey && !event.altKey) { 890 if (event.shiftKey) { 891 if (this.currentPanel.jumpToPreviousSearchResult) 892 this.currentPanel.jumpToPreviousSearchResult(); 893 } else if (this.currentPanel.jumpToNextSearchResult) 894 this.currentPanel.jumpToNextSearchResult(); 895 event.preventDefault(); 896 } 897 break; 898 899 // Windows and Mac have two different definitions of [, so accept both. 900 case "U+005B": 901 case "U+00DB": // [ key 902 if (isMac) 903 var isRotateLeft = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey; 904 else 905 var isRotateLeft = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey; 906 907 if (isRotateLeft) { 908 var index = this.panelOrder.indexOf(this.currentPanel); 909 index = (index === 0) ? this.panelOrder.length - 1 : index - 1; 910 this.panelOrder[index].toolbarItem.click(); 911 event.preventDefault(); 912 } 913 914 break; 915 916 // Windows and Mac have two different definitions of ], so accept both. 917 case "U+005D": 918 case "U+00DD": // ] key 919 if (isMac) 920 var isRotateRight = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey; 921 else 922 var isRotateRight = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey; 923 924 if (isRotateRight) { 925 var index = this.panelOrder.indexOf(this.currentPanel); 926 index = (index + 1) % this.panelOrder.length; 927 this.panelOrder[index].toolbarItem.click(); 928 event.preventDefault(); 929 } 930 931 break; 932 933 case "U+0052": // R key 934 if ((event.metaKey && isMac) || (event.ctrlKey && !isMac)) { 935 InspectorBackend.reloadPage(); 936 event.preventDefault(); 937 } 938 break; 939 case "F5": 940 if (!isMac) 941 InspectorBackend.reloadPage(); 942 break; 943 } 944} 945 946WebInspector.documentCanCopy = function(event) 947{ 948 if (this.currentPanel && this.currentPanel.handleCopyEvent) 949 event.preventDefault(); 950} 951 952WebInspector.documentCopy = function(event) 953{ 954 if (this.currentPanel && this.currentPanel.handleCopyEvent) 955 this.currentPanel.handleCopyEvent(event); 956} 957 958WebInspector.contextMenuEventFired = function(event) 959{ 960 if (event.handled || event.target.hasStyleClass("popup-glasspane")) 961 event.preventDefault(); 962} 963 964WebInspector.animateStyle = function(animations, duration, callback) 965{ 966 var interval; 967 var complete = 0; 968 969 const intervalDuration = (1000 / 30); // 30 frames per second. 970 const animationsLength = animations.length; 971 const propertyUnit = {opacity: ""}; 972 const defaultUnit = "px"; 973 974 function cubicInOut(t, b, c, d) 975 { 976 if ((t/=d/2) < 1) return c/2*t*t*t + b; 977 return c/2*((t-=2)*t*t + 2) + b; 978 } 979 980 // Pre-process animations. 981 for (var i = 0; i < animationsLength; ++i) { 982 var animation = animations[i]; 983 var element = null, start = null, end = null, key = null; 984 for (key in animation) { 985 if (key === "element") 986 element = animation[key]; 987 else if (key === "start") 988 start = animation[key]; 989 else if (key === "end") 990 end = animation[key]; 991 } 992 993 if (!element || !end) 994 continue; 995 996 if (!start) { 997 var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element); 998 start = {}; 999 for (key in end) 1000 start[key] = parseInt(computedStyle.getPropertyValue(key)); 1001 animation.start = start; 1002 } else 1003 for (key in start) 1004 element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); 1005 } 1006 1007 function animateLoop() 1008 { 1009 // Advance forward. 1010 complete += intervalDuration; 1011 var next = complete + intervalDuration; 1012 1013 // Make style changes. 1014 for (var i = 0; i < animationsLength; ++i) { 1015 var animation = animations[i]; 1016 var element = animation.element; 1017 var start = animation.start; 1018 var end = animation.end; 1019 if (!element || !end) 1020 continue; 1021 1022 var style = element.style; 1023 for (key in end) { 1024 var endValue = end[key]; 1025 if (next < duration) { 1026 var startValue = start[key]; 1027 var newValue = cubicInOut(complete, startValue, endValue - startValue, duration); 1028 style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); 1029 } else 1030 style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); 1031 } 1032 } 1033 1034 // End condition. 1035 if (complete >= duration) { 1036 clearInterval(interval); 1037 if (callback) 1038 callback(); 1039 } 1040 } 1041 1042 interval = setInterval(animateLoop, intervalDuration); 1043 return interval; 1044} 1045 1046WebInspector.updateSearchLabel = function() 1047{ 1048 if (!this.currentPanel) 1049 return; 1050 1051 var newLabel = WebInspector.UIString("Search %s", this.currentPanel.toolbarItemLabel); 1052 if (this.attached) 1053 document.getElementById("search").setAttribute("placeholder", newLabel); 1054 else { 1055 document.getElementById("search").removeAttribute("placeholder"); 1056 document.getElementById("search-toolbar-label").textContent = newLabel; 1057 } 1058} 1059 1060WebInspector.focusSearchField = function() 1061{ 1062 var searchField = document.getElementById("search"); 1063 searchField.focus(); 1064 searchField.select(); 1065} 1066 1067WebInspector.toggleAttach = function() 1068{ 1069 if (!this.attached) 1070 InspectorFrontendHost.requestAttachWindow(); 1071 else 1072 InspectorFrontendHost.requestDetachWindow(); 1073} 1074 1075WebInspector.toolbarDragStart = function(event) 1076{ 1077 if ((!WebInspector.attached && WebInspector.platformFlavor !== WebInspector.PlatformFlavor.MacLeopard && WebInspector.platformFlavor !== WebInspector.PlatformFlavor.MacSnowLeopard) || WebInspector.port == "qt") 1078 return; 1079 1080 var target = event.target; 1081 if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable")) 1082 return; 1083 1084 var toolbar = document.getElementById("toolbar"); 1085 if (target !== toolbar && !target.hasStyleClass("toolbar-item")) 1086 return; 1087 1088 toolbar.lastScreenX = event.screenX; 1089 toolbar.lastScreenY = event.screenY; 1090 1091 WebInspector.elementDragStart(toolbar, WebInspector.toolbarDrag, WebInspector.toolbarDragEnd, event, (WebInspector.attached ? "row-resize" : "default")); 1092} 1093 1094WebInspector.toolbarDragEnd = function(event) 1095{ 1096 var toolbar = document.getElementById("toolbar"); 1097 1098 WebInspector.elementDragEnd(event); 1099 1100 delete toolbar.lastScreenX; 1101 delete toolbar.lastScreenY; 1102} 1103 1104WebInspector.toolbarDrag = function(event) 1105{ 1106 var toolbar = document.getElementById("toolbar"); 1107 1108 if (WebInspector.attached) { 1109 var height = window.innerHeight - (event.screenY - toolbar.lastScreenY); 1110 1111 InspectorFrontendHost.setAttachedWindowHeight(height); 1112 } else { 1113 var x = event.screenX - toolbar.lastScreenX; 1114 var y = event.screenY - toolbar.lastScreenY; 1115 1116 // We cannot call window.moveBy here because it restricts the movement 1117 // of the window at the edges. 1118 InspectorFrontendHost.moveWindowBy(x, y); 1119 } 1120 1121 toolbar.lastScreenX = event.screenX; 1122 toolbar.lastScreenY = event.screenY; 1123 1124 event.preventDefault(); 1125} 1126 1127WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor) 1128{ 1129 if (this._elementDraggingEventListener || this._elementEndDraggingEventListener) 1130 this.elementDragEnd(event); 1131 1132 this._elementDraggingEventListener = dividerDrag; 1133 this._elementEndDraggingEventListener = elementDragEnd; 1134 1135 document.addEventListener("mousemove", dividerDrag, true); 1136 document.addEventListener("mouseup", elementDragEnd, true); 1137 1138 document.body.style.cursor = cursor; 1139 1140 event.preventDefault(); 1141} 1142 1143WebInspector.elementDragEnd = function(event) 1144{ 1145 document.removeEventListener("mousemove", this._elementDraggingEventListener, true); 1146 document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true); 1147 1148 document.body.style.removeProperty("cursor"); 1149 1150 delete this._elementDraggingEventListener; 1151 delete this._elementEndDraggingEventListener; 1152 1153 event.preventDefault(); 1154} 1155 1156WebInspector.toggleSearchingForNode = function() 1157{ 1158 if (this.panels.elements) { 1159 this.showPanel("elements"); 1160 this.panels.elements.toggleSearchingForNode(); 1161 } 1162} 1163 1164WebInspector.showConsole = function() 1165{ 1166 this.drawer.showView(this.console); 1167} 1168 1169WebInspector.showChanges = function() 1170{ 1171 this.drawer.showView(this.changes); 1172} 1173 1174WebInspector.showPanel = function(panel) 1175{ 1176 if (!(panel in this.panels)) 1177 panel = "elements"; 1178 this.currentPanel = this.panels[panel]; 1179} 1180 1181WebInspector.domContentEventFired = function(time) 1182{ 1183 this.panels.audits.mainResourceDOMContentTime = time; 1184 if (this.panels.network) 1185 this.panels.network.mainResourceDOMContentTime = time; 1186 this.extensionServer.notifyPageDOMContentLoaded((time - WebInspector.mainResource.startTime) * 1000); 1187 this.mainResourceDOMContentTime = time; 1188} 1189 1190WebInspector.loadEventFired = function(time) 1191{ 1192 this.panels.audits.mainResourceLoadTime = time; 1193 if (this.panels.network) 1194 this.panels.network.mainResourceLoadTime = time; 1195 this.extensionServer.notifyPageLoaded((time - WebInspector.mainResource.startTime) * 1000); 1196 this.mainResourceLoadTime = time; 1197} 1198 1199WebInspector.searchingForNodeWasEnabled = function() 1200{ 1201 this.panels.elements.searchingForNodeWasEnabled(); 1202} 1203 1204WebInspector.searchingForNodeWasDisabled = function() 1205{ 1206 this.panels.elements.searchingForNodeWasDisabled(); 1207} 1208 1209WebInspector.reset = function() 1210{ 1211 this.debuggerModel.reset(); 1212 1213 for (var panelName in this.panels) { 1214 var panel = this.panels[panelName]; 1215 if ("reset" in panel) 1216 panel.reset(); 1217 } 1218 1219 this.resources = {}; 1220 this.highlightDOMNode(0); 1221 1222 this.console.clearMessages(); 1223 this.extensionServer.notifyInspectorReset(); 1224} 1225 1226WebInspector.bringToFront = function() 1227{ 1228 InspectorFrontendHost.bringToFront(); 1229} 1230 1231WebInspector.inspectedURLChanged = function(url) 1232{ 1233 InspectorFrontendHost.inspectedURLChanged(url); 1234 this.settings.inspectedURLChanged(url); 1235 this.extensionServer.notifyInspectedURLChanged(); 1236} 1237 1238WebInspector.log = function(message, messageLevel) 1239{ 1240 // remember 'this' for setInterval() callback 1241 var self = this; 1242 1243 // return indication if we can actually log a message 1244 function isLogAvailable() 1245 { 1246 return WebInspector.ConsoleMessage && WebInspector.RemoteObject && self.console; 1247 } 1248 1249 // flush the queue of pending messages 1250 function flushQueue() 1251 { 1252 var queued = WebInspector.log.queued; 1253 if (!queued) 1254 return; 1255 1256 for (var i = 0; i < queued.length; ++i) 1257 logMessage(queued[i]); 1258 1259 delete WebInspector.log.queued; 1260 } 1261 1262 // flush the queue if it console is available 1263 // - this function is run on an interval 1264 function flushQueueIfAvailable() 1265 { 1266 if (!isLogAvailable()) 1267 return; 1268 1269 clearInterval(WebInspector.log.interval); 1270 delete WebInspector.log.interval; 1271 1272 flushQueue(); 1273 } 1274 1275 // actually log the message 1276 function logMessage(message) 1277 { 1278 var repeatCount = 1; 1279 if (message == WebInspector.log.lastMessage) 1280 repeatCount = WebInspector.log.repeatCount + 1; 1281 1282 WebInspector.log.lastMessage = message; 1283 WebInspector.log.repeatCount = repeatCount; 1284 1285 // ConsoleMessage expects a proxy object 1286 message = new WebInspector.RemoteObject.fromPrimitiveValue(message); 1287 1288 // post the message 1289 var msg = new WebInspector.ConsoleMessage( 1290 WebInspector.ConsoleMessage.MessageSource.Other, 1291 WebInspector.ConsoleMessage.MessageType.Log, 1292 messageLevel || WebInspector.ConsoleMessage.MessageLevel.Debug, 1293 -1, 1294 null, 1295 repeatCount, 1296 null, 1297 [message], 1298 null); 1299 1300 self.console.addMessage(msg); 1301 } 1302 1303 // if we can't log the message, queue it 1304 if (!isLogAvailable()) { 1305 if (!WebInspector.log.queued) 1306 WebInspector.log.queued = []; 1307 1308 WebInspector.log.queued.push(message); 1309 1310 if (!WebInspector.log.interval) 1311 WebInspector.log.interval = setInterval(flushQueueIfAvailable, 1000); 1312 1313 return; 1314 } 1315 1316 // flush the pending queue if any 1317 flushQueue(); 1318 1319 // log the message 1320 logMessage(message); 1321} 1322 1323WebInspector.drawLoadingPieChart = function(canvas, percent) { 1324 var g = canvas.getContext("2d"); 1325 var darkColor = "rgb(122, 168, 218)"; 1326 var lightColor = "rgb(228, 241, 251)"; 1327 var cx = 8; 1328 var cy = 8; 1329 var r = 7; 1330 1331 g.beginPath(); 1332 g.arc(cx, cy, r, 0, Math.PI * 2, false); 1333 g.closePath(); 1334 1335 g.lineWidth = 1; 1336 g.strokeStyle = darkColor; 1337 g.fillStyle = lightColor; 1338 g.fill(); 1339 g.stroke(); 1340 1341 var startangle = -Math.PI / 2; 1342 var endangle = startangle + (percent * Math.PI * 2); 1343 1344 g.beginPath(); 1345 g.moveTo(cx, cy); 1346 g.arc(cx, cy, r, startangle, endangle, false); 1347 g.closePath(); 1348 1349 g.fillStyle = darkColor; 1350 g.fill(); 1351} 1352 1353WebInspector.updateFocusedNode = function(nodeId) 1354{ 1355 this._updateFocusedNode(nodeId); 1356 this.highlightDOMNodeForTwoSeconds(nodeId); 1357} 1358 1359WebInspector.displayNameForURL = function(url) 1360{ 1361 if (!url) 1362 return ""; 1363 1364 var resource = this.resourceForURL(url); 1365 if (resource) 1366 return resource.displayName; 1367 1368 if (!WebInspector.mainResource) 1369 return url.trimURL(""); 1370 1371 var lastPathComponent = WebInspector.mainResource.lastPathComponent; 1372 var index = WebInspector.mainResource.url.indexOf(lastPathComponent); 1373 if (index !== -1 && index + lastPathComponent.length === WebInspector.mainResource.url.length) { 1374 var baseURL = WebInspector.mainResource.url.substring(0, index); 1375 if (url.indexOf(baseURL) === 0) 1376 return url.substring(index); 1377 } 1378 1379 return url.trimURL(WebInspector.mainResource.domain); 1380} 1381 1382WebInspector._choosePanelToShowSourceLine = function(url, line, preferredPanel) 1383{ 1384 preferredPanel = preferredPanel || "resources"; 1385 1386 var panel = this.panels[preferredPanel]; 1387 if (panel && panel.canShowSourceLine(url, line)) 1388 return panel; 1389 panel = this.panels.resources; 1390 return panel.canShowSourceLine(url, line) ? panel : null; 1391} 1392 1393WebInspector.canShowSourceLine = function(url, line, preferredPanel) 1394{ 1395 return !!this._choosePanelToShowSourceLine(url, line, preferredPanel); 1396} 1397 1398WebInspector.showSourceLine = function(url, line, preferredPanel) 1399{ 1400 this.currentPanel = this._choosePanelToShowSourceLine(url, line, preferredPanel); 1401 if (!this.currentPanel) 1402 return false; 1403 this.currentPanel.showSourceLine(url, line); 1404 return true; 1405} 1406 1407WebInspector.linkifyStringAsFragment = function(string) 1408{ 1409 var container = document.createDocumentFragment(); 1410 var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/; 1411 var lineColumnRegEx = /:(\d+)(:(\d+))?$/; 1412 1413 while (string) { 1414 var linkString = linkStringRegEx.exec(string); 1415 if (!linkString) 1416 break; 1417 1418 linkString = linkString[0]; 1419 var title = linkString; 1420 var linkIndex = string.indexOf(linkString); 1421 var nonLink = string.substring(0, linkIndex); 1422 container.appendChild(document.createTextNode(nonLink)); 1423 1424 var profileStringMatches = WebInspector.ProfileType.URLRegExp.exec(title); 1425 if (profileStringMatches) 1426 title = WebInspector.panels.profiles.displayTitleForProfileLink(profileStringMatches[2], profileStringMatches[1]); 1427 1428 var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString); 1429 var lineColumnMatch = lineColumnRegEx.exec(realURL); 1430 if (lineColumnMatch) 1431 realURL = realURL.substring(0, realURL.length - lineColumnMatch[0].length); 1432 1433 var hasResourceWithURL = !!WebInspector.resourceForURL(realURL); 1434 var urlNode = WebInspector.linkifyURLAsNode(realURL, title, null, hasResourceWithURL); 1435 container.appendChild(urlNode); 1436 if (lineColumnMatch) { 1437 urlNode.setAttribute("line_number", lineColumnMatch[1]); 1438 urlNode.setAttribute("preferred_panel", "scripts"); 1439 } 1440 string = string.substring(linkIndex + linkString.length, string.length); 1441 } 1442 1443 if (string) 1444 container.appendChild(document.createTextNode(string)); 1445 1446 return container; 1447} 1448 1449WebInspector.showProfileForURL = function(url) 1450{ 1451 WebInspector.showPanel("profiles"); 1452 WebInspector.panels.profiles.showProfileForURL(url); 1453} 1454 1455WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal, tooltipText) 1456{ 1457 if (!linkText) 1458 linkText = url; 1459 classes = (classes ? classes + " " : ""); 1460 classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link"; 1461 1462 var a = document.createElement("a"); 1463 a.href = url; 1464 a.className = classes; 1465 if (typeof tooltipText === "undefined") 1466 a.title = url; 1467 else if (typeof tooltipText !== "string" || tooltipText.length) 1468 a.title = tooltipText; 1469 a.textContent = linkText; 1470 1471 return a; 1472} 1473 1474WebInspector.linkifyURL = function(url, linkText, classes, isExternal, tooltipText) 1475{ 1476 // Use the DOM version of this function so as to avoid needing to escape attributes. 1477 // FIXME: Get rid of linkifyURL entirely. 1478 return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal, tooltipText).outerHTML; 1479} 1480 1481WebInspector.linkifyResourceAsNode = function(url, preferredPanel, lineNumber, classes, tooltipText) 1482{ 1483 var linkText = WebInspector.displayNameForURL(url); 1484 if (lineNumber) 1485 linkText += ":" + lineNumber; 1486 var node = WebInspector.linkifyURLAsNode(url, linkText, classes, false, tooltipText); 1487 node.setAttribute("line_number", lineNumber); 1488 node.setAttribute("preferred_panel", preferredPanel); 1489 return node; 1490} 1491 1492WebInspector.resourceURLForRelatedNode = function(node, url) 1493{ 1494 if (!url || url.indexOf("://") > 0) 1495 return url; 1496 1497 for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) { 1498 if (frameOwnerCandidate.documentURL) { 1499 var result = WebInspector.completeURL(frameOwnerCandidate.documentURL, url); 1500 if (result) 1501 return result; 1502 break; 1503 } 1504 } 1505 1506 // documentURL not found or has bad value 1507 var resourceURL = url; 1508 function callback(resource) 1509 { 1510 if (resource.path === url) { 1511 resourceURL = resource.url; 1512 return true; 1513 } 1514 } 1515 WebInspector.forAllResources(callback); 1516 return resourceURL; 1517} 1518 1519WebInspector.completeURL = function(baseURL, href) 1520{ 1521 if (href) { 1522 // Return absolute URLs as-is. 1523 var parsedHref = href.asParsedURL(); 1524 if (parsedHref && parsedHref.scheme) 1525 return href; 1526 } 1527 1528 var parsedURL = baseURL.asParsedURL(); 1529 if (parsedURL) { 1530 var path = href; 1531 if (path.charAt(0) !== "/") { 1532 var basePath = parsedURL.path; 1533 path = basePath.substring(0, basePath.lastIndexOf("/")) + "/" + path; 1534 } else if (path.length > 1 && path.charAt(1) === "/") { 1535 // href starts with "//" which is a full URL with the protocol dropped (use the baseURL protocol). 1536 return parsedURL.scheme + ":" + path; 1537 } 1538 return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + path; 1539 } 1540 return null; 1541} 1542 1543WebInspector.addMainEventListeners = function(doc) 1544{ 1545 doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), false); 1546 doc.defaultView.addEventListener("blur", this.windowBlurred.bind(this), false); 1547 doc.addEventListener("click", this.documentClick.bind(this), true); 1548} 1549 1550WebInspector._searchFieldManualFocus = function(event) 1551{ 1552 this.currentFocusElement = event.target; 1553 this._previousFocusElement = event.target; 1554} 1555 1556WebInspector._searchKeyDown = function(event) 1557{ 1558 // Escape Key will clear the field and clear the search results 1559 if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) { 1560 // If focus belongs here and text is empty - nothing to do, return unhandled. 1561 if (event.target.value === "" && this.currentFocusElement === this.previousFocusElement) 1562 return; 1563 event.preventDefault(); 1564 event.stopPropagation(); 1565 // When search was selected manually and is currently blank, we'd like Esc stay unhandled 1566 // and hit console drawer handler. 1567 event.target.value = ""; 1568 1569 this.performSearch(event); 1570 this.currentFocusElement = this.previousFocusElement; 1571 if (this.currentFocusElement === event.target) 1572 this.currentFocusElement.select(); 1573 return false; 1574 } 1575 1576 if (!isEnterKey(event)) 1577 return false; 1578 1579 // Select all of the text so the user can easily type an entirely new query. 1580 event.target.select(); 1581 1582 // Only call performSearch if the Enter key was pressed. Otherwise the search 1583 // performance is poor because of searching on every key. The search field has 1584 // the incremental attribute set, so we still get incremental searches. 1585 this.performSearch(event); 1586 1587 // Call preventDefault since this was the Enter key. This prevents a "search" event 1588 // from firing for key down. This stops performSearch from being called twice in a row. 1589 event.preventDefault(); 1590} 1591 1592WebInspector.performSearch = function(event) 1593{ 1594 var forceSearch = event.keyIdentifier === "Enter"; 1595 this.doPerformSearch(event.target.value, forceSearch, event.shiftKey, false); 1596} 1597 1598WebInspector.doPerformSearch = function(query, forceSearch, isBackwardSearch, repeatSearch) 1599{ 1600 var isShortSearch = (query.length < 3); 1601 1602 // Clear a leftover short search flag due to a non-conflicting forced search. 1603 if (isShortSearch && this.shortSearchWasForcedByKeyEvent && this.currentQuery !== query) 1604 delete this.shortSearchWasForcedByKeyEvent; 1605 1606 // Indicate this was a forced search on a short query. 1607 if (isShortSearch && forceSearch) 1608 this.shortSearchWasForcedByKeyEvent = true; 1609 1610 if (!query || !query.length || (!forceSearch && isShortSearch)) { 1611 // Prevent clobbering a short search forced by the user. 1612 if (this.shortSearchWasForcedByKeyEvent) { 1613 delete this.shortSearchWasForcedByKeyEvent; 1614 return; 1615 } 1616 1617 delete this.currentQuery; 1618 1619 for (var panelName in this.panels) { 1620 var panel = this.panels[panelName]; 1621 var hadCurrentQuery = !!panel.currentQuery; 1622 delete panel.currentQuery; 1623 if (hadCurrentQuery && panel.searchCanceled) 1624 panel.searchCanceled(); 1625 } 1626 1627 this.updateSearchMatchesCount(); 1628 1629 return; 1630 } 1631 1632 if (!repeatSearch && query === this.currentPanel.currentQuery && this.currentPanel.currentQuery === this.currentQuery) { 1633 // When this is the same query and a forced search, jump to the next 1634 // search result for a good user experience. 1635 if (forceSearch) { 1636 if (!isBackwardSearch && this.currentPanel.jumpToNextSearchResult) 1637 this.currentPanel.jumpToNextSearchResult(); 1638 else if (isBackwardSearch && this.currentPanel.jumpToPreviousSearchResult) 1639 this.currentPanel.jumpToPreviousSearchResult(); 1640 } 1641 return; 1642 } 1643 1644 this.currentQuery = query; 1645 1646 this.updateSearchMatchesCount(); 1647 1648 if (!this.currentPanel.performSearch) 1649 return; 1650 1651 this.currentPanel.currentQuery = query; 1652 this.currentPanel.performSearch(query); 1653} 1654 1655WebInspector.frontendReused = function() 1656{ 1657 this.networkManager.reset(); 1658 this.reset(); 1659} 1660 1661WebInspector.addNodesToSearchResult = function(nodeIds) 1662{ 1663 WebInspector.panels.elements.addNodesToSearchResult(nodeIds); 1664} 1665 1666WebInspector.updateSearchMatchesCount = function(matches, panel) 1667{ 1668 if (!panel) 1669 panel = this.currentPanel; 1670 1671 panel.currentSearchMatches = matches; 1672 1673 if (panel !== this.currentPanel) 1674 return; 1675 1676 if (!this.currentPanel.currentQuery) { 1677 document.getElementById("search-results-matches").addStyleClass("hidden"); 1678 return; 1679 } 1680 1681 if (matches) { 1682 if (matches === 1) 1683 var matchesString = WebInspector.UIString("1 match"); 1684 else 1685 var matchesString = WebInspector.UIString("%d matches", matches); 1686 } else 1687 var matchesString = WebInspector.UIString("Not Found"); 1688 1689 var matchesToolbarElement = document.getElementById("search-results-matches"); 1690 matchesToolbarElement.removeStyleClass("hidden"); 1691 matchesToolbarElement.textContent = matchesString; 1692} 1693 1694WebInspector.UIString = function(string) 1695{ 1696 if (window.localizedStrings && string in window.localizedStrings) 1697 string = window.localizedStrings[string]; 1698 else { 1699 if (!(string in WebInspector.missingLocalizedStrings)) { 1700 if (!WebInspector.InspectorBackendStub) 1701 console.error("Localized string \"" + string + "\" not found."); 1702 WebInspector.missingLocalizedStrings[string] = true; 1703 } 1704 1705 if (Preferences.showMissingLocalizedStrings) 1706 string += " (not localized)"; 1707 } 1708 1709 return String.vsprintf(string, Array.prototype.slice.call(arguments, 1)); 1710} 1711 1712WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append) 1713{ 1714 return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append); 1715} 1716 1717WebInspector.isMac = function() 1718{ 1719 if (!("_isMac" in this)) 1720 this._isMac = WebInspector.platform === "mac"; 1721 1722 return this._isMac; 1723} 1724 1725WebInspector.isBeingEdited = function(element) 1726{ 1727 return element.__editing; 1728} 1729 1730WebInspector.isEditingAnyField = function() 1731{ 1732 return this.__editing; 1733} 1734 1735// Available config fields (all optional): 1736// context: Object - an arbitrary context object to be passed to the commit and cancel handlers 1737// commitHandler: Function - handles editing "commit" outcome 1738// cancelHandler: Function - handles editing "cancel" outcome 1739// customFinishHandler: Function - custom finish handler for the editing session (invoked on keydown) 1740// pasteHandler: Function - handles the "paste" event, return values are the same as those for customFinishHandler 1741// multiline: Boolean - whether the edited element is multiline 1742WebInspector.startEditing = function(element, config) 1743{ 1744 if (element.__editing) 1745 return; 1746 element.__editing = true; 1747 WebInspector.__editing = true; 1748 1749 config = config || {}; 1750 var committedCallback = config.commitHandler; 1751 var cancelledCallback = config.cancelHandler; 1752 var pasteCallback = config.pasteHandler; 1753 var context = config.context; 1754 var oldText = getContent(element); 1755 var moveDirection = ""; 1756 1757 element.addStyleClass("editing"); 1758 1759 var oldTabIndex = element.tabIndex; 1760 if (element.tabIndex < 0) 1761 element.tabIndex = 0; 1762 1763 function blurEventListener() { 1764 editingCommitted.call(element); 1765 } 1766 1767 function getContent(element) { 1768 if (element.tagName === "INPUT" && element.type === "text") 1769 return element.value; 1770 else 1771 return element.textContent; 1772 } 1773 1774 function cleanUpAfterEditing() { 1775 delete this.__editing; 1776 delete WebInspector.__editing; 1777 1778 this.removeStyleClass("editing"); 1779 this.tabIndex = oldTabIndex; 1780 this.scrollTop = 0; 1781 this.scrollLeft = 0; 1782 1783 element.removeEventListener("blur", blurEventListener, false); 1784 element.removeEventListener("keydown", keyDownEventListener, true); 1785 if (pasteCallback) 1786 element.removeEventListener("paste", pasteEventListener, true); 1787 1788 if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement)) 1789 WebInspector.currentFocusElement = WebInspector.previousFocusElement; 1790 } 1791 1792 function editingCancelled() { 1793 if (this.tagName === "INPUT" && this.type === "text") 1794 this.value = oldText; 1795 else 1796 this.textContent = oldText; 1797 1798 cleanUpAfterEditing.call(this); 1799 1800 if (cancelledCallback) 1801 cancelledCallback(this, context); 1802 } 1803 1804 function editingCommitted() { 1805 cleanUpAfterEditing.call(this); 1806 1807 if (committedCallback) 1808 committedCallback(this, getContent(this), oldText, context, moveDirection); 1809 } 1810 1811 function defaultFinishHandler(event) 1812 { 1813 var isMetaOrCtrl = WebInspector.isMac() ? 1814 event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey : 1815 event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey; 1816 if (isEnterKey(event) && (!config.multiline || isMetaOrCtrl)) 1817 return "commit"; 1818 else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) 1819 return "cancel"; 1820 else if (event.keyIdentifier === "U+0009") // Tab key 1821 return "move-" + (event.shiftKey ? "backward" : "forward"); 1822 } 1823 1824 function handleEditingResult(result, event) 1825 { 1826 if (result === "commit") { 1827 editingCommitted.call(element); 1828 event.preventDefault(); 1829 event.stopPropagation(); 1830 } else if (result === "cancel") { 1831 editingCancelled.call(element); 1832 event.preventDefault(); 1833 event.stopPropagation(); 1834 } else if (result && result.indexOf("move-") === 0) { 1835 moveDirection = result.substring(5); 1836 if (event.keyIdentifier !== "U+0009") 1837 blurEventListener(); 1838 } 1839 } 1840 1841 function pasteEventListener(event) 1842 { 1843 var result = pasteCallback(event); 1844 handleEditingResult(result, event); 1845 } 1846 1847 function keyDownEventListener(event) 1848 { 1849 var handler = config.customFinishHandler || defaultFinishHandler; 1850 var result = handler(event); 1851 handleEditingResult(result, event); 1852 } 1853 1854 element.addEventListener("blur", blurEventListener, false); 1855 element.addEventListener("keydown", keyDownEventListener, true); 1856 if (pasteCallback) 1857 element.addEventListener("paste", pasteEventListener, true); 1858 1859 WebInspector.currentFocusElement = element; 1860 return { 1861 cancel: editingCancelled.bind(element), 1862 commit: editingCommitted.bind(element) 1863 }; 1864} 1865 1866WebInspector._toolbarItemClicked = function(event) 1867{ 1868 var toolbarItem = event.currentTarget; 1869 this.currentPanel = toolbarItem.panel; 1870} 1871 1872// This table maps MIME types to the Resource.Types which are valid for them. 1873// The following line: 1874// "text/html": {0: 1}, 1875// means that text/html is a valid MIME type for resources that have type 1876// WebInspector.Resource.Type.Document (which has a value of 0). 1877WebInspector.MIMETypes = { 1878 "text/html": {0: true}, 1879 "text/xml": {0: true}, 1880 "text/plain": {0: true}, 1881 "application/xhtml+xml": {0: true}, 1882 "text/css": {1: true}, 1883 "text/xsl": {1: true}, 1884 "image/jpeg": {2: true}, 1885 "image/png": {2: true}, 1886 "image/gif": {2: true}, 1887 "image/bmp": {2: true}, 1888 "image/vnd.microsoft.icon": {2: true}, 1889 "image/x-icon": {2: true}, 1890 "image/x-xbitmap": {2: true}, 1891 "font/ttf": {3: true}, 1892 "font/opentype": {3: true}, 1893 "application/x-font-type1": {3: true}, 1894 "application/x-font-ttf": {3: true}, 1895 "application/x-truetype-font": {3: true}, 1896 "text/javascript": {4: true}, 1897 "text/ecmascript": {4: true}, 1898 "application/javascript": {4: true}, 1899 "application/ecmascript": {4: true}, 1900 "application/x-javascript": {4: true}, 1901 "text/javascript1.1": {4: true}, 1902 "text/javascript1.2": {4: true}, 1903 "text/javascript1.3": {4: true}, 1904 "text/jscript": {4: true}, 1905 "text/livescript": {4: true}, 1906} 1907 1908WebInspector.PanelHistory = function() 1909{ 1910 this._history = []; 1911 this._historyIterator = -1; 1912} 1913 1914WebInspector.PanelHistory.prototype = { 1915 canGoBack: function() 1916 { 1917 return this._historyIterator > 0; 1918 }, 1919 1920 goBack: function() 1921 { 1922 this._inHistory = true; 1923 WebInspector.currentPanel = WebInspector.panels[this._history[--this._historyIterator]]; 1924 delete this._inHistory; 1925 }, 1926 1927 canGoForward: function() 1928 { 1929 return this._historyIterator < this._history.length - 1; 1930 }, 1931 1932 goForward: function() 1933 { 1934 this._inHistory = true; 1935 WebInspector.currentPanel = WebInspector.panels[this._history[++this._historyIterator]]; 1936 delete this._inHistory; 1937 }, 1938 1939 setPanel: function(panelName) 1940 { 1941 if (this._inHistory) 1942 return; 1943 1944 this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1); 1945 if (!this._history.length || this._history[this._history.length - 1] !== panelName) 1946 this._history.push(panelName); 1947 this._historyIterator = this._history.length - 1; 1948 } 1949} 1950