2 * Copyright (C) 2010 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 WebInspector.BreakpointManager = function()
33 this._stickyBreakpoints = {};
34 var breakpoints = WebInspector.settings.findSettingForAllProjects("nativeBreakpoints");
35 for (var projectId in breakpoints)
36 this._stickyBreakpoints[projectId] = this._validateBreakpoints(breakpoints[projectId]);
38 this._breakpoints = {};
39 this._domBreakpointsRestored = false;
41 WebInspector.settings.addEventListener(WebInspector.Settings.Events.ProjectChanged, this._projectChanged, this);
42 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, this._debuggerPaused, this);
43 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this);
46 WebInspector.BreakpointManager.BreakpointTypes = {
48 EventListener: "EventListener",
52 WebInspector.BreakpointManager.Events = {
53 DOMBreakpointAdded: "dom-breakpoint-added",
54 EventListenerBreakpointAdded: "event-listener-breakpoint-added",
55 XHRBreakpointAdded: "xhr-breakpoint-added",
56 ProjectChanged: "project-changed"
59 WebInspector.BreakpointManager.prototype = {
60 createDOMBreakpoint: function(nodeId, type)
62 this._createDOMBreakpoint(nodeId, type, true, false);
65 _createDOMBreakpoint: function(nodeId, type, enabled, restored)
67 var node = WebInspector.domAgent.nodeForId(nodeId);
71 var breakpointId = this._createDOMBreakpointId(nodeId, type);
72 if (breakpointId in this._breakpoints)
75 var breakpoint = new WebInspector.DOMBreakpoint(node, type);
76 this._setBreakpoint(breakpointId, breakpoint, enabled, restored);
77 if (enabled && restored)
80 breakpoint.view = new WebInspector.DOMBreakpointView(this, breakpointId, enabled, node, type);
81 this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.DOMBreakpointAdded, breakpoint.view);
84 createEventListenerBreakpoint: function(eventName)
86 this._createEventListenerBreakpoint(eventName, true, false);
89 _createEventListenerBreakpoint: function(eventName, enabled, restored)
91 var breakpointId = this._createEventListenerBreakpointId(eventName);
92 if (breakpointId in this._breakpoints)
95 var breakpoint = new WebInspector.EventListenerBreakpoint(eventName);
96 this._setBreakpoint(breakpointId, breakpoint, enabled, restored);
98 breakpoint.view = new WebInspector.EventListenerBreakpointView(this, breakpointId, enabled, eventName);
99 this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.EventListenerBreakpointAdded, breakpoint.view);
102 createXHRBreakpoint: function(url)
104 this._createXHRBreakpoint(url, true, false);
107 _createXHRBreakpoint: function(url, enabled, restored)
109 var breakpointId = this._createXHRBreakpointId(url);
110 if (breakpointId in this._breakpoints)
113 var breakpoint = new WebInspector.XHRBreakpoint(url);
114 this._setBreakpoint(breakpointId, breakpoint, enabled, restored);
116 breakpoint.view = new WebInspector.XHRBreakpointView(this, breakpointId, enabled, url);
117 this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.XHRBreakpointAdded, breakpoint.view);
120 _setBreakpoint: function(breakpointId, breakpoint, enabled, restored)
122 this._breakpoints[breakpointId] = breakpoint;
123 breakpoint.enabled = enabled;
127 breakpoint._enable();
128 this._saveBreakpoints();
131 _setBreakpointEnabled: function(breakpointId, enabled)
133 var breakpoint = this._breakpoints[breakpointId];
134 if (breakpoint.enabled === enabled)
137 breakpoint._enable();
139 breakpoint._disable();
140 breakpoint.enabled = enabled;
141 this._saveBreakpoints();
144 _removeBreakpoint: function(breakpointId)
146 var breakpoint = this._breakpoints[breakpointId];
147 if (breakpoint.enabled)
148 breakpoint._disable();
149 delete this._breakpoints[breakpointId];
150 this._saveBreakpoints();
153 breakpointViewForEventData: function(eventData)
156 if (eventData.breakpointType === WebInspector.BreakpointManager.BreakpointTypes.DOM)
157 breakpointId = this._createDOMBreakpointId(eventData.nodeId, eventData.type);
158 else if (eventData.breakpointType === WebInspector.BreakpointManager.BreakpointTypes.EventListener)
159 breakpointId = this._createEventListenerBreakpointId(eventData.eventName);
160 else if (eventData.breakpointType === WebInspector.BreakpointManager.BreakpointTypes.XHR)
161 breakpointId = this._createXHRBreakpointId(eventData.breakpointURL);
165 var breakpoint = this._breakpoints[breakpointId];
167 return breakpoint.view;
170 _debuggerPaused: function(event)
172 var eventType = event.data.eventType;
173 var eventData = event.data.eventData;
175 if (eventType !== WebInspector.DebuggerEventTypes.NativeBreakpoint)
178 var breakpointView = this.breakpointViewForEventData(eventData);
182 breakpointView.hit = true;
183 this._lastHitBreakpointView = breakpointView;
186 _debuggerResumed: function(event)
188 if (!this._lastHitBreakpointView)
190 this._lastHitBreakpointView.hit = false;
191 delete this._lastHitBreakpointView;
194 _projectChanged: function(event)
196 this._breakpoints = {};
197 this._domBreakpointsRestored = false;
198 this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.ProjectChanged);
200 var breakpoints = this._stickyBreakpoints[WebInspector.settings.projectId] || [];
201 for (var i = 0; i < breakpoints.length; ++i) {
202 var breakpoint = breakpoints[i];
203 if (breakpoint.type === WebInspector.BreakpointManager.BreakpointTypes.EventListener)
204 this._createEventListenerBreakpoint(breakpoint.condition.eventName, breakpoint.enabled, true);
205 else if (breakpoint.type === WebInspector.BreakpointManager.BreakpointTypes.XHR)
206 this._createXHRBreakpoint(breakpoint.condition.url, breakpoint.enabled, true);
209 if (!this._breakpointsPushedToFrontend) {
210 BrowserDebuggerAgent.setAllBrowserBreakpoints(this._stickyBreakpoints);
211 this._breakpointsPushedToFrontend = true;
215 restoreDOMBreakpoints: function()
217 function didPushNodeByPathToFrontend(path, nodeId)
222 pathToNodeId[path] = nodeId;
226 for (var i = 0; i < breakpoints.length; ++i) {
227 var breakpoint = breakpoints[i];
228 if (breakpoint.type !== WebInspector.BreakpointManager.BreakpointTypes.DOM)
230 var nodeId = pathToNodeId[breakpoint.condition.path];
232 this._createDOMBreakpoint(nodeId, breakpoint.condition.type, breakpoint.enabled, true);
234 this._domBreakpointsRestored = true;
235 this._saveBreakpoints();
238 var breakpoints = this._stickyBreakpoints[WebInspector.settings.projectId] || [];
239 var pathToNodeId = {};
240 var pendingCalls = 0;
241 for (var i = 0; i < breakpoints.length; ++i) {
242 if (breakpoints[i].type !== WebInspector.BreakpointManager.BreakpointTypes.DOM)
244 var path = breakpoints[i].condition.path;
245 if (path in pathToNodeId)
247 pathToNodeId[path] = 0;
249 WebInspector.domAgent.pushNodeByPathToFrontend(path, didPushNodeByPathToFrontend.bind(this, path));
252 this._domBreakpointsRestored = true;
255 _saveBreakpoints: function()
257 var breakpoints = [];
258 for (var breakpointId in this._breakpoints) {
259 var breakpoint = this._breakpoints[breakpointId];
260 var persistentBreakpoint = breakpoint._serializeToJSON();
261 persistentBreakpoint.enabled = breakpoint.enabled;
262 breakpoints.push(persistentBreakpoint);
264 if (!this._domBreakpointsRestored) {
265 var stickyBreakpoints = this._stickyBreakpoints[WebInspector.settings.projectId] || [];
266 for (var i = 0; i < stickyBreakpoints.length; ++i) {
267 if (stickyBreakpoints[i].type === WebInspector.BreakpointManager.BreakpointTypes.DOM)
268 breakpoints.push(stickyBreakpoints[i]);
271 WebInspector.settings.nativeBreakpoints = breakpoints;
273 this._stickyBreakpoints[WebInspector.settings.projectId] = breakpoints;
274 BrowserDebuggerAgent.setAllBrowserBreakpoints(this._stickyBreakpoints);
277 _validateBreakpoints: function(persistentBreakpoints)
279 var breakpoints = [];
280 var breakpointsSet = {};
281 for (var i = 0; i < persistentBreakpoints.length; ++i) {
282 var breakpoint = persistentBreakpoints[i];
283 if (!("type" in breakpoint && "enabled" in breakpoint && "condition" in breakpoint))
285 var id = breakpoint.type + ":";
286 var condition = breakpoint.condition;
287 if (breakpoint.type === WebInspector.BreakpointManager.BreakpointTypes.DOM) {
288 if (typeof condition.path !== "string" || typeof condition.type !== "number")
290 id += condition.path + ":" + condition.type;
291 } else if (breakpoint.type === WebInspector.BreakpointManager.BreakpointTypes.EventListener) {
292 if (typeof condition.eventName !== "string")
294 id += condition.eventName;
295 } else if (breakpoint.type === WebInspector.BreakpointManager.BreakpointTypes.XHR) {
296 if (typeof condition.url !== "string")
301 if (id in breakpointsSet)
303 breakpointsSet[id] = true;
304 breakpoints.push(breakpoint);
309 _createDOMBreakpointId: function(nodeId, type)
311 return "dom:" + nodeId + ":" + type;
314 _createEventListenerBreakpointId: function(eventName)
316 return "eventListner:" + eventName;
319 _createXHRBreakpointId: function(url)
325 WebInspector.BreakpointManager.prototype.__proto__ = WebInspector.Object.prototype;
327 WebInspector.DOMBreakpoint = function(node, type)
329 this._nodeId = node.id;
330 this._path = node.path();
334 WebInspector.DOMBreakpoint.prototype = {
337 BrowserDebuggerAgent.setDOMBreakpoint(this._nodeId, this._type);
342 BrowserDebuggerAgent.removeDOMBreakpoint(this._nodeId, this._type);
345 _serializeToJSON: function()
347 var type = WebInspector.BreakpointManager.BreakpointTypes.DOM;
348 return { type: type, condition: { path: this._path, type: this._type } };
352 WebInspector.EventListenerBreakpoint = function(eventName)
354 this._eventName = eventName;
357 WebInspector.EventListenerBreakpoint.prototype = {
360 BrowserDebuggerAgent.setEventListenerBreakpoint(this._eventName);
365 BrowserDebuggerAgent.removeEventListenerBreakpoint(this._eventName);
368 _serializeToJSON: function()
370 var type = WebInspector.BreakpointManager.BreakpointTypes.EventListener;
371 return { type: type, condition: { eventName: this._eventName } };
375 WebInspector.XHRBreakpoint = function(url)
380 WebInspector.XHRBreakpoint.prototype = {
383 BrowserDebuggerAgent.setXHRBreakpoint(this._url);
388 BrowserDebuggerAgent.removeXHRBreakpoint(this._url);
391 _serializeToJSON: function()
393 var type = WebInspector.BreakpointManager.BreakpointTypes.XHR;
394 return { type: type, condition: { url: this._url } };
400 WebInspector.NativeBreakpointView = function(manager, id, enabled)
402 this._manager = manager;
404 this._enabled = enabled;
408 WebInspector.NativeBreakpointView.prototype = {
411 return this._enabled;
416 this._manager._setBreakpointEnabled(this._id, enabled);
417 this._enabled = enabled;
418 this.dispatchEventToListeners("enable-changed");
429 this.dispatchEventToListeners("hit-state-changed");
434 this._manager._removeBreakpoint(this._id);
436 this.dispatchEventToListeners("removed");
439 _compare: function(x, y)
442 return x < y ? -1 : 1;
446 _onRemove: function()
451 WebInspector.NativeBreakpointView.prototype.__proto__ = WebInspector.Object.prototype;
453 WebInspector.DOMBreakpointView = function(manager, id, enabled, node, type)
455 WebInspector.NativeBreakpointView.call(this, manager, id, enabled);
457 this._nodeId = node.id;
459 node.breakpoints[this._type] = this;
462 WebInspector.DOMBreakpointView.prototype = {
463 compareTo: function(other)
465 return this._compare(this._type, other._type);
468 populateLabelElement: function(element)
470 // FIXME: this should belong to the view, not the manager.
471 var linkifiedNode = WebInspector.panels.elements.linkifyNodeById(this._nodeId);
472 linkifiedNode.addStyleClass("monospace");
473 element.appendChild(linkifiedNode);
474 var description = document.createElement("div");
475 description.className = "source-text";
476 description.textContent = WebInspector.domBreakpointTypeLabel(this._type);
477 element.appendChild(description);
480 populateStatusMessageElement: function(element, eventData)
482 if (this._type === WebInspector.DOMBreakpointTypes.SubtreeModified) {
483 var targetNodeObject = WebInspector.RemoteObject.fromPayload(eventData.targetNode);
484 targetNodeObject.pushNodeToFrontend(decorateNode.bind(this));
485 function decorateNode(targetNodeId)
490 eventData.targetNode.release();
491 var targetNode = WebInspector.panels.elements.linkifyNodeById(targetNodeId);
492 if (eventData.insertion) {
493 if (targetNodeId !== this._nodeId)
494 this._format(element, "Paused on a \"%s\" breakpoint set on %s, because a new child was added to its descendant %s.", targetNode);
496 this._format(element, "Paused on a \"%s\" breakpoint set on %s, because a new child was added to that node.");
498 this._format(element, "Paused on a \"%s\" breakpoint set on %s, because its descendant %s was removed.", targetNode);
501 this._format(element, "Paused on a \"%s\" breakpoint set on %s.");
504 _format: function(element, message, extraSubstitution)
506 var substitutions = [WebInspector.domBreakpointTypeLabel(this._type), WebInspector.panels.elements.linkifyNodeById(this._nodeId)];
507 if (extraSubstitution)
508 substitutions.push(extraSubstitution);
511 s: function(substitution)
516 function append(a, b)
518 if (typeof b === "string")
519 b = document.createTextNode(b);
520 element.appendChild(b);
522 WebInspector.formatLocalized(message, substitutions, formatters, "", append);
525 _onRemove: function()
527 delete this._node.breakpoints[this._type];
531 WebInspector.DOMBreakpointView.prototype.__proto__ = WebInspector.NativeBreakpointView.prototype;
533 WebInspector.EventListenerBreakpointView = function(manager, id, enabled, eventName)
535 WebInspector.NativeBreakpointView.call(this, manager, id, enabled);
536 this._eventName = eventName;
539 WebInspector.EventListenerBreakpointView.eventNameForUI = function(eventName)
541 if (!WebInspector.EventListenerBreakpointView._eventNamesForUI) {
542 WebInspector.EventListenerBreakpointView._eventNamesForUI = {
543 "instrumentation:setTimer": WebInspector.UIString("Set Timer"),
544 "instrumentation:clearTimer": WebInspector.UIString("Clear Timer"),
545 "instrumentation:timerFired": WebInspector.UIString("Timer Fired")
548 return WebInspector.EventListenerBreakpointView._eventNamesForUI[eventName] || eventName.substring(eventName.indexOf(":") + 1);
551 WebInspector.EventListenerBreakpointView.prototype = {
554 return this._eventName;
557 compareTo: function(other)
559 return this._compare(this._eventName, other._eventName);
562 populateLabelElement: function(element)
564 element.appendChild(document.createTextNode(this._uiEventName()));
567 populateStatusMessageElement: function(element, eventData)
569 var status = WebInspector.UIString("Paused on a \"%s\" Event Listener.", this._uiEventName());
570 element.appendChild(document.createTextNode(status));
573 _uiEventName: function()
575 return WebInspector.EventListenerBreakpointView.eventNameForUI(this._eventName);
579 WebInspector.EventListenerBreakpointView.prototype.__proto__ = WebInspector.NativeBreakpointView.prototype;
581 WebInspector.XHRBreakpointView = function(manager, id, enabled, url)
583 WebInspector.NativeBreakpointView.call(this, manager, id, enabled);
587 WebInspector.XHRBreakpointView.prototype = {
588 compareTo: function(other)
590 return this._compare(this._url, other._url);
593 populateEditElement: function(element)
595 element.textContent = this._url;
598 populateLabelElement: function(element)
601 if (!this._url.length)
602 label = WebInspector.UIString("Any XHR");
604 label = WebInspector.UIString("URL contains \"%s\"", this._url);
605 element.appendChild(document.createTextNode(label));
606 element.addStyleClass("cursor-auto");
609 populateStatusMessageElement: function(element)
611 var status = WebInspector.UIString("Paused on a XMLHttpRequest.");
612 element.appendChild(document.createTextNode(status));
616 WebInspector.XHRBreakpointView.prototype.__proto__ = WebInspector.NativeBreakpointView.prototype;
618 WebInspector.DOMBreakpointTypes = {
620 AttributeModified: 1,
624 WebInspector.domBreakpointTypeLabel = function(type)
626 if (!WebInspector._DOMBreakpointTypeLabels) {
627 WebInspector._DOMBreakpointTypeLabels = {};
628 WebInspector._DOMBreakpointTypeLabels[WebInspector.DOMBreakpointTypes.SubtreeModified] = WebInspector.UIString("Subtree Modified");
629 WebInspector._DOMBreakpointTypeLabels[WebInspector.DOMBreakpointTypes.AttributeModified] = WebInspector.UIString("Attribute Modified");
630 WebInspector._DOMBreakpointTypeLabels[WebInspector.DOMBreakpointTypes.NodeRemoved] = WebInspector.UIString("Node Removed");
632 return WebInspector._DOMBreakpointTypeLabels[type];
635 WebInspector.domBreakpointTypeContextMenuLabel = function(type)
637 if (!WebInspector._DOMBreakpointTypeContextMenuLabels) {
638 WebInspector._DOMBreakpointTypeContextMenuLabels = {};
639 WebInspector._DOMBreakpointTypeContextMenuLabels[WebInspector.DOMBreakpointTypes.SubtreeModified] = WebInspector.UIString("Break on Subtree Modifications");
640 WebInspector._DOMBreakpointTypeContextMenuLabels[WebInspector.DOMBreakpointTypes.AttributeModified] = WebInspector.UIString("Break on Attributes Modifications");
641 WebInspector._DOMBreakpointTypeContextMenuLabels[WebInspector.DOMBreakpointTypes.NodeRemoved] = WebInspector.UIString("Break on Node Removal");
643 return WebInspector._DOMBreakpointTypeContextMenuLabels[type];