/* * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). * Copyright (C) 2009 Joseph Pecoraro * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // Keep this ; so that concatenated version of the script worked. ;(function preloadImages() { (new Image()).src = "Images/clearConsoleButtonGlyph.png"; (new Image()).src = "Images/consoleButtonGlyph.png"; (new Image()).src = "Images/dockButtonGlyph.png"; (new Image()).src = "Images/enableOutlineButtonGlyph.png"; (new Image()).src = "Images/enableSolidButtonGlyph.png"; (new Image()).src = "Images/excludeButtonGlyph.png"; (new Image()).src = "Images/focusButtonGlyph.png"; (new Image()).src = "Images/largerResourcesButtonGlyph.png"; (new Image()).src = "Images/nodeSearchButtonGlyph.png"; (new Image()).src = "Images/pauseOnExceptionButtonGlyph.png"; (new Image()).src = "Images/percentButtonGlyph.png"; (new Image()).src = "Images/recordButtonGlyph.png"; (new Image()).src = "Images/recordToggledButtonGlyph.png"; (new Image()).src = "Images/reloadButtonGlyph.png"; (new Image()).src = "Images/undockButtonGlyph.png"; })(); var WebInspector = { resources: {}, missingLocalizedStrings: {}, pendingDispatches: 0, get platform() { if (!("_platform" in this)) this._platform = InspectorFrontendHost.platform(); return this._platform; }, get platformFlavor() { if (!("_platformFlavor" in this)) this._platformFlavor = this._detectPlatformFlavor(); return this._platformFlavor; }, _detectPlatformFlavor: function() { const userAgent = navigator.userAgent; if (this.platform === "windows") { var match = userAgent.match(/Windows NT (\d+)\.(?:\d+)/); if (match && match[1] >= 6) return WebInspector.PlatformFlavor.WindowsVista; return null; } else if (this.platform === "mac") { var match = userAgent.match(/Mac OS X\s*(?:(\d+)_(\d+))?/); if (!match || match[1] != 10) return WebInspector.PlatformFlavor.MacSnowLeopard; switch (Number(match[2])) { case 4: return WebInspector.PlatformFlavor.MacTiger; case 5: return WebInspector.PlatformFlavor.MacLeopard; case 6: default: return WebInspector.PlatformFlavor.MacSnowLeopard; } } return null; }, get port() { if (!("_port" in this)) this._port = InspectorFrontendHost.port(); return this._port; }, get previousFocusElement() { return this._previousFocusElement; }, get currentFocusElement() { return this._currentFocusElement; }, set currentFocusElement(x) { if (this._currentFocusElement !== x) this._previousFocusElement = this._currentFocusElement; this._currentFocusElement = x; if (this._currentFocusElement) { this._currentFocusElement.focus(); // Make a caret selection inside the new element if there isn't a range selection and // there isn't already a caret selection inside. var selection = window.getSelection(); if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) { var selectionRange = this._currentFocusElement.ownerDocument.createRange(); selectionRange.setStart(this._currentFocusElement, 0); selectionRange.setEnd(this._currentFocusElement, 0); selection.removeAllRanges(); selection.addRange(selectionRange); } } else if (this._previousFocusElement) this._previousFocusElement.blur(); }, resetFocusElement: function() { this.currentFocusElement = null; this._previousFocusElement = null; }, get currentPanel() { return this._currentPanel; }, set currentPanel(x) { if (this._currentPanel === x) return; if (this._currentPanel) this._currentPanel.hide(); this._currentPanel = x; if (x) { x.show(); WebInspector.searchController.activePanelChanged(); } for (var panelName in WebInspector.panels) { if (WebInspector.panels[panelName] === x) { WebInspector.settings.lastActivePanel = panelName; this._panelHistory.setPanel(panelName); } } }, _createPanels: function() { var hiddenPanels = (InspectorFrontendHost.hiddenPanels() || "").split(','); if (hiddenPanels.indexOf("elements") === -1) this.panels.elements = new WebInspector.ElementsPanel(); if (hiddenPanels.indexOf("resources") === -1) this.panels.resources = new WebInspector.ResourcesPanel(); if (hiddenPanels.indexOf("network") === -1) this.panels.network = new WebInspector.NetworkPanel(); if (hiddenPanels.indexOf("scripts") === -1) this.panels.scripts = new WebInspector.ScriptsPanel(); if (hiddenPanels.indexOf("timeline") === -1) this.panels.timeline = new WebInspector.TimelinePanel(); if (hiddenPanels.indexOf("profiles") === -1) this.panels.profiles = new WebInspector.ProfilesPanel(); if (hiddenPanels.indexOf("audits") === -1) this.panels.audits = new WebInspector.AuditsPanel(); if (hiddenPanels.indexOf("console") === -1) this.panels.console = new WebInspector.ConsolePanel(); }, get attached() { return this._attached; }, set attached(x) { if (this._attached === x) return; this._attached = x; var dockToggleButton = document.getElementById("dock-status-bar-item"); var body = document.body; if (x) { body.removeStyleClass("detached"); body.addStyleClass("attached"); dockToggleButton.title = WebInspector.UIString("Undock into separate window."); } else { body.removeStyleClass("attached"); body.addStyleClass("detached"); dockToggleButton.title = WebInspector.UIString("Dock to main window."); } // This may be called before onLoadedDone, hence the bulk of inspector objects may // not be created yet. if (WebInspector.searchController) WebInspector.searchController.updateSearchLabel(); }, get errors() { return this._errors || 0; }, set errors(x) { x = Math.max(x, 0); if (this._errors === x) return; this._errors = x; this._updateErrorAndWarningCounts(); }, get warnings() { return this._warnings || 0; }, set warnings(x) { x = Math.max(x, 0); if (this._warnings === x) return; this._warnings = x; this._updateErrorAndWarningCounts(); }, _updateErrorAndWarningCounts: function() { var errorWarningElement = document.getElementById("error-warning-count"); if (!errorWarningElement) return; if (!this.errors && !this.warnings) { errorWarningElement.addStyleClass("hidden"); return; } errorWarningElement.removeStyleClass("hidden"); errorWarningElement.removeChildren(); if (this.errors) { var errorElement = document.createElement("span"); errorElement.id = "error-count"; errorElement.textContent = this.errors; errorWarningElement.appendChild(errorElement); } if (this.warnings) { var warningsElement = document.createElement("span"); warningsElement.id = "warning-count"; warningsElement.textContent = this.warnings; errorWarningElement.appendChild(warningsElement); } if (this.errors) { if (this.warnings) { if (this.errors == 1) { if (this.warnings == 1) errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings); else errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings); } else if (this.warnings == 1) errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings); else errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings); } else if (this.errors == 1) errorWarningElement.title = WebInspector.UIString("%d error", this.errors); else errorWarningElement.title = WebInspector.UIString("%d errors", this.errors); } else if (this.warnings == 1) errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings); else if (this.warnings) errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings); else errorWarningElement.title = null; }, highlightDOMNode: function(nodeId) { if ("_hideDOMNodeHighlightTimeout" in this) { clearTimeout(this._hideDOMNodeHighlightTimeout); delete this._hideDOMNodeHighlightTimeout; } if (this._highlightedDOMNodeId === nodeId) return; this._highlightedDOMNodeId = nodeId; if (nodeId) DOMAgent.highlightDOMNode(nodeId); else DOMAgent.hideDOMNodeHighlight(); }, highlightDOMNodeForTwoSeconds: function(nodeId) { this.highlightDOMNode(nodeId); this._hideDOMNodeHighlightTimeout = setTimeout(this.highlightDOMNode.bind(this, 0), 2000); }, wireElementWithDOMNode: function(element, nodeId) { element.addEventListener("click", this._updateFocusedNode.bind(this, nodeId), false); element.addEventListener("mouseover", this.highlightDOMNode.bind(this, nodeId), false); element.addEventListener("mouseout", this.highlightDOMNode.bind(this, 0), false); }, _updateFocusedNode: function(nodeId) { this.currentPanel = this.panels.elements; this.panels.elements.updateFocusedNode(nodeId); }, get networkResources() { return this.panels.network.resources; }, networkResourceById: function(id) { return this.panels.network.resourceById(id); }, forAllResources: function(callback) { WebInspector.resourceTreeModel.forAllResources(callback); }, resourceForURL: function(url) { return this.resourceTreeModel.resourceForURL(url); }, openLinkExternallyLabel: function() { return WebInspector.UIString("Open Link in New Window"); } } WebInspector.PlatformFlavor = { WindowsVista: "windows-vista", MacTiger: "mac-tiger", MacLeopard: "mac-leopard", MacSnowLeopard: "mac-snowleopard" }; (function parseQueryParameters() { WebInspector.queryParamsObject = {}; var queryParams = window.location.search; if (!queryParams) return; var params = queryParams.substring(1).split("&"); for (var i = 0; i < params.length; ++i) { var pair = params[i].split("="); WebInspector.queryParamsObject[pair[0]] = pair[1]; } })(); WebInspector.loaded = function() { if ("page" in WebInspector.queryParamsObject) { var page = WebInspector.queryParamsObject.page; var host = "host" in WebInspector.queryParamsObject ? WebInspector.queryParamsObject.host : window.location.host; WebInspector.socket = new WebSocket("ws://" + host + "/devtools/page/" + page); WebInspector.socket.onmessage = function(message) { InspectorBackend.dispatch(message.data); } WebInspector.socket.onerror = function(error) { console.error(error); } WebInspector.socket.onopen = function() { InspectorFrontendHost.sendMessageToBackend = WebInspector.socket.send.bind(WebInspector.socket); InspectorFrontendHost.loaded = WebInspector.socket.send.bind(WebInspector.socket, "loaded"); WebInspector.doLoadedDone(); } return; } WebInspector.doLoadedDone(); } WebInspector.doLoadedDone = function() { InspectorFrontendHost.loaded(); var platform = WebInspector.platform; document.body.addStyleClass("platform-" + platform); var flavor = WebInspector.platformFlavor; if (flavor) document.body.addStyleClass("platform-" + flavor); var port = WebInspector.port; document.body.addStyleClass("port-" + port); if (WebInspector.socket) document.body.addStyleClass("remote"); WebInspector.settings = new WebInspector.Settings(); this._registerShortcuts(); // set order of some sections explicitly WebInspector.shortcutsHelp.section(WebInspector.UIString("Console")); WebInspector.shortcutsHelp.section(WebInspector.UIString("Elements Panel")); this.drawer = new WebInspector.Drawer(); this.console = new WebInspector.ConsoleView(this.drawer); this.drawer.visibleView = this.console; this.networkManager = new WebInspector.NetworkManager(); this.resourceTreeModel = new WebInspector.ResourceTreeModel(); this.domAgent = new WebInspector.DOMAgent(); InspectorBackend.registerDomainDispatcher("Inspector", this); InspectorBackend.registerDomainDispatcher("Page", this); this.resourceCategories = { documents: new WebInspector.ResourceCategory("documents", WebInspector.UIString("Documents"), "rgb(47,102,236)"), stylesheets: new WebInspector.ResourceCategory("stylesheets", WebInspector.UIString("Stylesheets"), "rgb(157,231,119)"), images: new WebInspector.ResourceCategory("images", WebInspector.UIString("Images"), "rgb(164,60,255)"), scripts: new WebInspector.ResourceCategory("scripts", WebInspector.UIString("Scripts"), "rgb(255,121,0)"), xhr: new WebInspector.ResourceCategory("xhr", WebInspector.UIString("XHR"), "rgb(231,231,10)"), fonts: new WebInspector.ResourceCategory("fonts", WebInspector.UIString("Fonts"), "rgb(255,82,62)"), websockets: new WebInspector.ResourceCategory("websockets", WebInspector.UIString("WebSockets"), "rgb(186,186,186)"), // FIXME: Decide the color. other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)") }; this.cssModel = new WebInspector.CSSStyleModel(); this.debuggerModel = new WebInspector.DebuggerModel(); this.searchController = new WebInspector.SearchController(); this.domBreakpointsSidebarPane = new WebInspector.DOMBreakpointsSidebarPane(); this.panels = {}; this._createPanels(); this._panelHistory = new WebInspector.PanelHistory(); this.toolbar = new WebInspector.Toolbar(); this.panelOrder = []; for (var panelName in this.panels) this.addPanel(this.panels[panelName]); this.Tips = { ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")} }; this.Warnings = { IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")} }; this.addMainEventListeners(document); window.addEventListener("resize", this.windowResize.bind(this), true); document.addEventListener("focus", this.focusChanged.bind(this), true); document.addEventListener("keydown", this.documentKeyDown.bind(this), false); document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true); document.addEventListener("copy", this.documentCopy.bind(this), true); document.addEventListener("contextmenu", this.contextMenuEventFired.bind(this), true); var dockToggleButton = document.getElementById("dock-status-bar-item"); dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false); if (this.attached) dockToggleButton.title = WebInspector.UIString("Undock into separate window."); else dockToggleButton.title = WebInspector.UIString("Dock to main window."); var errorWarningCount = document.getElementById("error-warning-count"); errorWarningCount.addEventListener("click", this.showConsole.bind(this), false); this._updateErrorAndWarningCounts(); this.extensionServer.initExtensions(); if (WebInspector.settings.monitoringXHREnabled) ConsoleAgent.setMonitoringXHREnabled(true); ConsoleAgent.enable(this.console.setConsoleMessageExpiredCount.bind(this.console)); DatabaseAgent.enable(); WebInspector.showPanel(WebInspector.settings.lastActivePanel); function propertyNamesCallback(error, names) { if (!error) WebInspector.cssNameCompletions = new WebInspector.CSSCompletions(names); } // As a DOMAgent method, this needs to happen after the frontend has loaded and the agent is available. CSSAgent.getSupportedCSSProperties(propertyNamesCallback); } WebInspector.addPanel = function(panel) { this.panelOrder.push(panel); this.toolbar.addPanel(panel); } var windowLoaded = function() { var localizedStringsURL = InspectorFrontendHost.localizedStringsURL(); if (localizedStringsURL) { var localizedStringsScriptElement = document.createElement("script"); localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false); localizedStringsScriptElement.type = "text/javascript"; localizedStringsScriptElement.src = localizedStringsURL; document.head.appendChild(localizedStringsScriptElement); } else WebInspector.loaded(); window.removeEventListener("DOMContentLoaded", windowLoaded, false); delete windowLoaded; }; window.addEventListener("DOMContentLoaded", windowLoaded, false); // We'd like to enforce asynchronous interaction between the inspector controller and the frontend. // It is needed to prevent re-entering the backend code. // Also, native dispatches do not guarantee setTimeouts to be serialized, so we // enforce serialization using 'messagesToDispatch' queue. It is also important that JSC debugger // tests require that each command was dispatch within individual timeout callback, so we don't batch them. var messagesToDispatch = []; WebInspector.dispatch = function(message) { messagesToDispatch.push(message); setTimeout(function() { InspectorBackend.dispatch(messagesToDispatch.shift()); }, 0); } WebInspector.dispatchMessageFromBackend = function(messageObject) { WebInspector.dispatch(messageObject); } WebInspector.windowResize = function(event) { if (this.currentPanel) this.currentPanel.resize(); this.drawer.resize(); this.toolbar.resize(); } WebInspector.windowFocused = function(event) { // Fires after blur, so when focusing on either the main inspector // or an