blob: 94345d5f23810138e549becb650e793fe675cbb9 [file] [log] [blame]
/*
* 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];
}