/* * Copyright (C) 2010 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER OR 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. */ WebInspector.BreakpointManager = function() { this._stickyBreakpoints = {}; var breakpoints = WebInspector.settings.findSettingForAllProjects("nativeBreakpoints"); for (var projectId in breakpoints) this._stickyBreakpoints[projectId] = this._validateBreakpoints(breakpoints[projectId]); this._breakpoints = {}; this._domBreakpointsRestored = false; WebInspector.settings.addEventListener(WebInspector.Settings.Events.ProjectChanged, this._projectChanged, this); WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, this._debuggerPaused, this); WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this); } WebInspector.BreakpointManager.BreakpointTypes = { DOM: "DOM", EventListener: "EventListener", XHR: "XHR" } WebInspector.BreakpointManager.Events = { DOMBreakpointAdded: "dom-breakpoint-added", EventListenerBreakpointAdded: "event-listener-breakpoint-added", XHRBreakpointAdded: "xhr-breakpoint-added", ProjectChanged: "project-changed" } WebInspector.BreakpointManager.prototype = { createDOMBreakpoint: function(nodeId, type) { this._createDOMBreakpoint(nodeId, type, true, false); }, _createDOMBreakpoint: function(nodeId, type, enabled, restored) { var node = WebInspector.domAgent.nodeForId(nodeId); if (!node) return; var breakpointId = this._createDOMBreakpointId(nodeId, type); if (breakpointId in this._breakpoints) return; var breakpoint = new WebInspector.DOMBreakpoint(node, type); this._setBreakpoint(breakpointId, breakpoint, enabled, restored); if (enabled && restored) breakpoint._enable(); breakpoint.view = new WebInspector.DOMBreakpointView(this, breakpointId, enabled, node, type); this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.DOMBreakpointAdded, breakpoint.view); }, createEventListenerBreakpoint: function(eventName) { this._createEventListenerBreakpoint(eventName, true, false); }, _createEventListenerBreakpoint: function(eventName, enabled, restored) { var breakpointId = this._createEventListenerBreakpointId(eventName); if (breakpointId in this._breakpoints) return; var breakpoint = new WebInspector.EventListenerBreakpoint(eventName); this._setBreakpoint(breakpointId, breakpoint, enabled, restored); breakpoint.view = new WebInspector.EventListenerBreakpointView(this, breakpointId, enabled, eventName); this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.EventListenerBreakpointAdded, breakpoint.view); }, createXHRBreakpoint: function(url) { this._createXHRBreakpoint(url, true, false); }, _createXHRBreakpoint: function(url, enabled, restored) { var breakpointId = this._createXHRBreakpointId(url); if (breakpointId in this._breakpoints) return; var breakpoint = new WebInspector.XHRBreakpoint(url); this._setBreakpoint(breakpointId, breakpoint, enabled, restored); breakpoint.view = new WebInspector.XHRBreakpointView(this, breakpointId, enabled, url); this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.XHRBreakpointAdded, breakpoint.view); }, _setBreakpoint: function(breakpointId, breakpoint, enabled, restored) { this._breakpoints[breakpointId] = breakpoint; breakpoint.enabled = enabled; if (restored) return; if (enabled) breakpoint._enable(); this._saveBreakpoints(); }, _setBreakpointEnabled: function(breakpointId, enabled) { var breakpoint = this._breakpoints[breakpointId]; if (breakpoint.enabled === enabled) return; if (enabled) breakpoint._enable(); else breakpoint._disable(); breakpoint.enabled = enabled; this._saveBreakpoints(); }, _removeBreakpoint: function(breakpointId) { var breakpoint = this._breakpoints[breakpointId]; if (breakpoint.enabled) breakpoint._disable(); delete this._breakpoints[breakpointId]; this._saveBreakpoints(); }, breakpointViewForEventData: function(eventData) { var breakpointId; if (eventData.breakpointType === WebInspector.BreakpointManager.BreakpointTypes.DOM) breakpointId = this._createDOMBreakpointId(eventData.nodeId, eventData.type); else if (eventData.breakpointType === WebInspector.BreakpointManager.BreakpointTypes.EventListener) breakpointId = this._createEventListenerBreakpointId(eventData.eventName); else if (eventData.breakpointType === WebInspector.BreakpointManager.BreakpointTypes.XHR) breakpointId = this._createXHRBreakpointId(eventData.breakpointURL); else return; var breakpoint = this._breakpoints[breakpointId]; if (breakpoint) return breakpoint.view; }, _debuggerPaused: function(event) { var eventType = event.data.eventType; var eventData = event.data.eventData; if (eventType !== WebInspector.DebuggerEventTypes.NativeBreakpoint) return; var breakpointView = this.breakpointViewForEventData(eventData); if (!breakpointView) return; breakpointView.hit = true; this._lastHitBreakpointView = breakpointView; }, _debuggerResumed: function(event) { if (!this._lastHitBreakpointView) return; this._lastHitBreakpointView.hit = false; delete this._lastHitBreakpointView; }, _projectChanged: function(event) { this._breakpoints = {}; this._domBreakpointsRestored = false; this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.ProjectChanged); var breakpoints = this._stickyBreakpoints[WebInspector.settings.projectId] || []; for (var i = 0; i < breakpoints.length; ++i) { var breakpoint = breakpoints[i]; if (breakpoint.type === WebInspector.BreakpointManager.BreakpointTypes.EventListener) this._createEventListenerBreakpoint(breakpoint.condition.eventName, breakpoint.enabled, true); else if (breakpoint.type === WebInspector.BreakpointManager.BreakpointTypes.XHR) this._createXHRBreakpoint(breakpoint.condition.url, breakpoint.enabled, true); } if (!this._breakpointsPushedToFrontend) { InspectorBackend.setAllBrowserBreakpoints(this._stickyBreakpoints); this._breakpointsPushedToFrontend = true; } }, restoreDOMBreakpoints: function() { function didPushNodeByPathToFrontend(path, nodeId) { pathToNodeId[path] = nodeId; pendingCalls -= 1; if (pendingCalls) return; for (var i = 0; i < breakpoints.length; ++i) { var breakpoint = breakpoints[i]; if (breakpoint.type !== WebInspector.BreakpointManager.BreakpointTypes.DOM) continue; var nodeId = pathToNodeId[breakpoint.condition.path]; if (nodeId) this._createDOMBreakpoint(nodeId, breakpoint.condition.type, breakpoint.enabled, true); } this._domBreakpointsRestored = true; this._saveBreakpoints(); } var breakpoints = this._stickyBreakpoints[WebInspector.settings.projectId] || []; var pathToNodeId = {}; var pendingCalls = 0; for (var i = 0; i < breakpoints.length; ++i) { if (breakpoints[i].type !== WebInspector.BreakpointManager.BreakpointTypes.DOM) continue; var path = breakpoints[i].condition.path; if (path in pathToNodeId) continue; pathToNodeId[path] = 0; pendingCalls += 1; InspectorBackend.pushNodeByPathToFrontend(path, didPushNodeByPathToFrontend.bind(this, path)); } if (!pendingCalls) this._domBreakpointsRestored = true; }, _saveBreakpoints: function() { var breakpoints = []; for (var breakpointId in this._breakpoints) { var breakpoint = this._breakpoints[breakpointId]; var persistentBreakpoint = breakpoint._serializeToJSON(); persistentBreakpoint.enabled = breakpoint.enabled; breakpoints.push(persistentBreakpoint); } if (!this._domBreakpointsRestored) { var stickyBreakpoints = this._stickyBreakpoints[WebInspector.settings.projectId] || []; for (var i = 0; i < stickyBreakpoints.length; ++i) { if (stickyBreakpoints[i].type === WebInspector.BreakpointManager.BreakpointTypes.DOM) breakpoints.push(stickyBreakpoints[i]); } } WebInspector.settings.nativeBreakpoints = breakpoints; this._stickyBreakpoints[WebInspector.settings.projectId] = breakpoints; InspectorBackend.setAllBrowserBreakpoints(this._stickyBreakpoints); }, _validateBreakpoints: function(persistentBreakpoints) { var breakpoints = []; var breakpointsSet = {}; for (var i = 0; i < persistentBreakpoints.length; ++i) { var breakpoint = persistentBreakpoints[i]; if (!("type" in breakpoint && "enabled" in breakpoint && "condition" in breakpoint)) continue; var id = breakpoint.type + ":"; var condition = breakpoint.condition; if (breakpoint.type === WebInspector.BreakpointManager.BreakpointTypes.DOM) { if (typeof condition.path !== "string" || typeof condition.type !== "number") continue; id += condition.path + ":" + condition.type; } else if (breakpoint.type === WebInspector.BreakpointManager.BreakpointTypes.EventListener) { if (typeof condition.eventName !== "string") continue; id += condition.eventName; } else if (breakpoint.type === WebInspector.BreakpointManager.BreakpointTypes.XHR) { if (typeof condition.url !== "string") continue; id += condition.url; } else continue; if (id in breakpointsSet) continue; breakpointsSet[id] = true; breakpoints.push(breakpoint); } return breakpoints; }, _createDOMBreakpointId: function(nodeId, type) { return "dom:" + nodeId + ":" + type; }, _createEventListenerBreakpointId: function(eventName) { return "eventListner:" + eventName; }, _createXHRBreakpointId: function(url) { return "xhr:" + url; } } WebInspector.BreakpointManager.prototype.__proto__ = WebInspector.Object.prototype; WebInspector.DOMBreakpoint = function(node, type) { this._nodeId = node.id; this._path = node.path(); this._type = type; } WebInspector.DOMBreakpoint.prototype = { _enable: function() { InspectorBackend.setDOMBreakpoint(this._nodeId, this._type); }, _disable: function() { InspectorBackend.removeDOMBreakpoint(this._nodeId, this._type); }, _serializeToJSON: function() { var type = WebInspector.BreakpointManager.BreakpointTypes.DOM; return { type: type, condition: { path: this._path, type: this._type } }; } } WebInspector.EventListenerBreakpoint = function(eventName) { this._eventName = eventName; } WebInspector.EventListenerBreakpoint.prototype = { _enable: function() { InspectorBackend.setEventListenerBreakpoint(this._eventName); }, _disable: function() { InspectorBackend.removeEventListenerBreakpoint(this._eventName); }, _serializeToJSON: function() { var type = WebInspector.BreakpointManager.BreakpointTypes.EventListener; return { type: type, condition: { eventName: this._eventName } }; } } WebInspector.XHRBreakpoint = function(url) { this._url = url; } WebInspector.XHRBreakpoint.prototype = { _enable: function() { InspectorBackend.setXHRBreakpoint(this._url); }, _disable: function() { InspectorBackend.removeXHRBreakpoint(this._url); }, _serializeToJSON: function() { var type = WebInspector.BreakpointManager.BreakpointTypes.XHR; return { type: type, condition: { url: this._url } }; } } WebInspector.NativeBreakpointView = function(manager, id, enabled) { this._manager = manager; this._id = id; this._enabled = enabled; this._hit = false; } WebInspector.NativeBreakpointView.prototype = { get enabled() { return this._enabled; }, set enabled(enabled) { this._manager._setBreakpointEnabled(this._id, enabled); this._enabled = enabled; this.dispatchEventToListeners("enable-changed"); }, get hit() { return this._hit; }, set hit(hit) { this._hit = hit; this.dispatchEventToListeners("hit-state-changed"); }, remove: function() { this._manager._removeBreakpoint(this._id); this._onRemove(); this.dispatchEventToListeners("removed"); }, _compare: function(x, y) { if (x !== y) return x < y ? -1 : 1; return 0; }, _onRemove: function() { } } WebInspector.NativeBreakpointView.prototype.__proto__ = WebInspector.Object.prototype; WebInspector.DOMBreakpointView = function(manager, id, enabled, node, type) { WebInspector.NativeBreakpointView.call(this, manager, id, enabled); this._node = node; this._nodeId = node.id; this._type = type; node.breakpoints[this._type] = this; } WebInspector.DOMBreakpointView.prototype = { compareTo: function(other) { return this._compare(this._type, other._type); }, populateLabelElement: function(element) { // FIXME: this should belong to the view, not the manager. var linkifiedNode = WebInspector.panels.elements.linkifyNodeById(this._nodeId); linkifiedNode.addStyleClass("monospace"); element.appendChild(linkifiedNode); var description = document.createElement("div"); description.className = "source-text"; description.textContent = WebInspector.domBreakpointTypeLabel(this._type); element.appendChild(description); }, populateStatusMessageElement: function(element, eventData) { var substitutions = [WebInspector.domBreakpointTypeLabel(this._type), WebInspector.panels.elements.linkifyNodeById(this._nodeId)]; var formatters = { s: function(substitution) { return substitution; } }; function append(a, b) { if (typeof b === "string") b = document.createTextNode(b); element.appendChild(b); } if (this._type === WebInspector.DOMBreakpointTypes.SubtreeModified) { var targetNode = WebInspector.panels.elements.linkifyNodeById(eventData.targetNodeId); if (eventData.insertion) { if (eventData.targetNodeId !== this._nodeId) WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s, because a new child was added to its descendant %s.", substitutions.concat(targetNode), formatters, "", append); else WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s, because a new child was added to that node.", substitutions, formatters, "", append); } else WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s, because its descendant %s was removed.", substitutions.concat(targetNode), formatters, "", append); } else WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s.", substitutions, formatters, "", append); }, _onRemove: function() { delete this._node.breakpoints[this._type]; } } WebInspector.DOMBreakpointView.prototype.__proto__ = WebInspector.NativeBreakpointView.prototype; WebInspector.EventListenerBreakpointView = function(manager, id, enabled, eventName) { WebInspector.NativeBreakpointView.call(this, manager, id, enabled); this._eventName = eventName; } WebInspector.EventListenerBreakpointView.eventNameForUI = function(eventName) { if (!WebInspector.EventListenerBreakpointView._eventNamesForUI) { WebInspector.EventListenerBreakpointView._eventNamesForUI = { "instrumentation:setTimer": WebInspector.UIString("Set Timer"), "instrumentation:clearTimer": WebInspector.UIString("Clear Timer"), "instrumentation:timerFired": WebInspector.UIString("Timer Fired") }; } return WebInspector.EventListenerBreakpointView._eventNamesForUI[eventName] || eventName.substring(eventName.indexOf(":") + 1); } WebInspector.EventListenerBreakpointView.prototype = { get eventName() { return this._eventName; }, compareTo: function(other) { return this._compare(this._eventName, other._eventName); }, populateLabelElement: function(element) { element.appendChild(document.createTextNode(this._uiEventName())); }, populateStatusMessageElement: function(element, eventData) { var status = WebInspector.UIString("Paused on a \"%s\" Event Listener.", this._uiEventName()); element.appendChild(document.createTextNode(status)); }, _uiEventName: function() { return WebInspector.EventListenerBreakpointView.eventNameForUI(this._eventName); } } WebInspector.EventListenerBreakpointView.prototype.__proto__ = WebInspector.NativeBreakpointView.prototype; WebInspector.XHRBreakpointView = function(manager, id, enabled, url) { WebInspector.NativeBreakpointView.call(this, manager, id, enabled); this._url = url; } WebInspector.XHRBreakpointView.prototype = { compareTo: function(other) { return this._compare(this._url, other._url); }, populateEditElement: function(element) { element.textContent = this._url; }, populateLabelElement: function(element) { var label; if (!this._url.length) label = WebInspector.UIString("Any XHR"); else label = WebInspector.UIString("URL contains \"%s\"", this._url); element.appendChild(document.createTextNode(label)); element.addStyleClass("cursor-auto"); }, populateStatusMessageElement: function(element) { var status = WebInspector.UIString("Paused on a XMLHttpRequest."); element.appendChild(document.createTextNode(status)); } } WebInspector.XHRBreakpointView.prototype.__proto__ = WebInspector.NativeBreakpointView.prototype; WebInspector.DOMBreakpointTypes = { SubtreeModified: 0, AttributeModified: 1, NodeRemoved: 2 }; WebInspector.domBreakpointTypeLabel = function(type) { if (!WebInspector._DOMBreakpointTypeLabels) { WebInspector._DOMBreakpointTypeLabels = {}; WebInspector._DOMBreakpointTypeLabels[WebInspector.DOMBreakpointTypes.SubtreeModified] = WebInspector.UIString("Subtree Modified"); WebInspector._DOMBreakpointTypeLabels[WebInspector.DOMBreakpointTypes.AttributeModified] = WebInspector.UIString("Attribute Modified"); WebInspector._DOMBreakpointTypeLabels[WebInspector.DOMBreakpointTypes.NodeRemoved] = WebInspector.UIString("Node Removed"); } return WebInspector._DOMBreakpointTypeLabels[type]; } WebInspector.domBreakpointTypeContextMenuLabel = function(type) { if (!WebInspector._DOMBreakpointTypeContextMenuLabels) { WebInspector._DOMBreakpointTypeContextMenuLabels = {}; WebInspector._DOMBreakpointTypeContextMenuLabels[WebInspector.DOMBreakpointTypes.SubtreeModified] = WebInspector.UIString("Break on Subtree Modifications"); WebInspector._DOMBreakpointTypeContextMenuLabels[WebInspector.DOMBreakpointTypes.AttributeModified] = WebInspector.UIString("Break on Attributes Modifications"); WebInspector._DOMBreakpointTypeContextMenuLabels[WebInspector.DOMBreakpointTypes.NodeRemoved] = WebInspector.UIString("Break on Node Removal"); } return WebInspector._DOMBreakpointTypeContextMenuLabels[type]; }