blob: 2821a70f0fb4c8253011550372d8935f30cf0414 [file] [log] [blame]
/*
* Copyright (C) 2013, 2014 Apple 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:
* 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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.
*/
WebInspector.Breakpoint = class Breakpoint extends WebInspector.Object
{
constructor(sourceCodeLocationOrInfo, disabled, condition)
{
super();
if (sourceCodeLocationOrInfo instanceof WebInspector.SourceCodeLocation) {
var sourceCode = sourceCodeLocationOrInfo.sourceCode;
var url = sourceCode ? sourceCode.url : null;
var scriptIdentifier = sourceCode instanceof WebInspector.Script ? sourceCode.id : null;
var location = sourceCodeLocationOrInfo;
} else if (sourceCodeLocationOrInfo && typeof sourceCodeLocationOrInfo === "object") {
var url = sourceCodeLocationOrInfo.url;
var lineNumber = sourceCodeLocationOrInfo.lineNumber || 0;
var columnNumber = sourceCodeLocationOrInfo.columnNumber || 0;
var location = new WebInspector.SourceCodeLocation(null, lineNumber, columnNumber);
var autoContinue = sourceCodeLocationOrInfo.autoContinue || false;
var actions = sourceCodeLocationOrInfo.actions || [];
for (var i = 0; i < actions.length; ++i)
actions[i] = new WebInspector.BreakpointAction(this, actions[i]);
disabled = sourceCodeLocationOrInfo.disabled;
condition = sourceCodeLocationOrInfo.condition;
} else
console.error("Unexpected type passed to WebInspector.Breakpoint", sourceCodeLocationOrInfo);
this._id = null;
this._url = url || null;
this._scriptIdentifier = scriptIdentifier || null;
this._disabled = disabled || false;
this._condition = condition || "";
this._autoContinue = autoContinue || false;
this._actions = actions || [];
this._resolved = false;
this._sourceCodeLocation = location;
this._sourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._sourceCodeLocationLocationChanged, this);
this._sourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, this._sourceCodeLocationDisplayLocationChanged, this);
}
// Public
get id()
{
return this._id;
}
set id(id)
{
this._id = id || null;
}
get url()
{
return this._url;
}
get scriptIdentifier()
{
return this._scriptIdentifier;
}
get sourceCodeLocation()
{
return this._sourceCodeLocation;
}
get resolved()
{
return this._resolved;
}
set resolved(resolved)
{
if (this._resolved === resolved)
return;
function isSpecialBreakpoint()
{
return this._sourceCodeLocation.isEqual(new WebInspector.SourceCodeLocation(null, Infinity, Infinity));
}
console.assert(!resolved || this._sourceCodeLocation.sourceCode || isSpecialBreakpoint.call(this), "Breakpoints must have a SourceCode to be resolved.", this);
this._resolved = resolved || false;
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ResolvedStateDidChange);
}
get disabled()
{
return this._disabled;
}
set disabled(disabled)
{
if (this._disabled === disabled)
return;
this._disabled = disabled || false;
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.DisabledStateDidChange);
}
get condition()
{
return this._condition;
}
set condition(condition)
{
if (this._condition === condition)
return;
this._condition = condition;
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ConditionDidChange);
}
get autoContinue()
{
return this._autoContinue;
}
set autoContinue(cont)
{
if (this._autoContinue === cont)
return;
this._autoContinue = cont;
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.AutoContinueDidChange);
}
get actions()
{
return this._actions;
}
get options()
{
return {
condition: this._condition,
actions: this._serializableActions(),
autoContinue: this._autoContinue
};
}
get info()
{
// The id, scriptIdentifier and resolved state are tied to the current session, so don't include them for serialization.
return {
url: this._url,
lineNumber: this._sourceCodeLocation.lineNumber,
columnNumber: this._sourceCodeLocation.columnNumber,
disabled: this._disabled,
condition: this._condition,
actions: this._serializableActions(),
autoContinue: this._autoContinue
};
}
get probeActions()
{
return this._actions.filter(function(action) {
return action.type === WebInspector.BreakpointAction.Type.Probe;
});
}
cycleToNextMode()
{
if (this.disabled) {
// When cycling, clear auto-continue when going from disabled to enabled.
this.autoContinue = false;
this.disabled = false;
return;
}
if (this.autoContinue) {
this.disabled = true;
return;
}
if (this.actions.length) {
this.autoContinue = true;
return;
}
this.disabled = true;
}
appendContextMenuItems(contextMenu, breakpointDisplayElement)
{
console.assert(document.body.contains(breakpointDisplayElement), "breakpoint popover display element must be in the DOM");
var boundingClientRect = breakpointDisplayElement.getBoundingClientRect();
function editBreakpoint()
{
this._showEditBreakpointPopover(boundingClientRect);
}
function removeBreakpoint()
{
WebInspector.debuggerManager.removeBreakpoint(this);
}
function toggleBreakpoint()
{
this.disabled = !this.disabled;
}
function toggleAutoContinue()
{
this.autoContinue = !this.autoContinue;
}
function revealOriginalSourceCodeLocation()
{
WebInspector.resourceSidebarPanel.showOriginalOrFormattedSourceCodeLocation(this._sourceCodeLocation);
}
if (WebInspector.debuggerManager.isBreakpointEditable(this))
contextMenu.appendItem(WebInspector.UIString("Edit Breakpoint…"), editBreakpoint.bind(this));
if (this.autoContinue && !this.disabled) {
contextMenu.appendItem(WebInspector.UIString("Disable Breakpoint"), toggleBreakpoint.bind(this));
contextMenu.appendItem(WebInspector.UIString("Cancel Automatic Continue"), toggleAutoContinue.bind(this));
} else if (!this.disabled)
contextMenu.appendItem(WebInspector.UIString("Disable Breakpoint"), toggleBreakpoint.bind(this));
else
contextMenu.appendItem(WebInspector.UIString("Enable Breakpoint"), toggleBreakpoint.bind(this));
if (!this.autoContinue && !this.disabled && this.actions.length)
contextMenu.appendItem(WebInspector.UIString("Set to Automatically Continue"), toggleAutoContinue.bind(this));
if (WebInspector.debuggerManager.isBreakpointRemovable(this)) {
contextMenu.appendSeparator();
contextMenu.appendItem(WebInspector.UIString("Delete Breakpoint"), removeBreakpoint.bind(this));
}
if (this._sourceCodeLocation.hasMappedLocation()) {
contextMenu.appendSeparator();
contextMenu.appendItem(WebInspector.UIString("Reveal in Original Resource"), revealOriginalSourceCodeLocation.bind(this));
}
}
createAction(type, precedingAction, data)
{
var newAction = new WebInspector.BreakpointAction(this, type, data || null);
if (!precedingAction)
this._actions.push(newAction);
else {
var index = this._actions.indexOf(precedingAction);
console.assert(index !== -1);
if (index === -1)
this._actions.push(newAction);
else
this._actions.splice(index + 1, 0, newAction);
}
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange);
return newAction;
}
recreateAction(type, actionToReplace)
{
var newAction = new WebInspector.BreakpointAction(this, type, null);
var index = this._actions.indexOf(actionToReplace);
console.assert(index !== -1);
if (index === -1)
return null;
this._actions[index] = newAction;
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange);
return newAction;
}
removeAction(action)
{
var index = this._actions.indexOf(action);
console.assert(index !== -1);
if (index === -1)
return;
this._actions.splice(index, 1);
if (!this._actions.length)
this.autoContinue = false;
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange);
}
clearActions(type)
{
if (!type)
this._actions = [];
else
this._actions = this._actions.filter(function(action) { action.type !== type; });
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange);
}
saveIdentityToCookie(cookie)
{
cookie[WebInspector.Breakpoint.URLCookieKey] = this.url;
cookie[WebInspector.Breakpoint.LineNumberCookieKey] = this.sourceCodeLocation.lineNumber;
cookie[WebInspector.Breakpoint.ColumnNumberCookieKey] = this.sourceCodeLocation.columnNumber;
}
// Protected (Called by BreakpointAction)
breakpointActionDidChange(action)
{
var index = this._actions.indexOf(action);
console.assert(index !== -1);
if (index === -1)
return;
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange);
}
// Private
_serializableActions()
{
var actions = [];
for (var i = 0; i < this._actions.length; ++i)
actions.push(this._actions[i].info);
return actions;
}
_popoverToggleEnabledCheckboxChanged(event)
{
this.disabled = !event.target.checked;
}
_popoverConditionInputChanged(event)
{
this.condition = event.target.value;
}
_popoverToggleAutoContinueCheckboxChanged(event)
{
this.autoContinue = event.target.checked;
}
_popoverConditionInputKeyDown(event)
{
if (this._keyboardShortcutEsc.matchesEvent(event) || this._keyboardShortcutEnter.matchesEvent(event)) {
this._popover.dismiss();
event.stopPropagation();
event.preventDefault();
}
}
_editBreakpointPopoverContentElement()
{
var content = this._popoverContentElement = document.createElement("div");
content.className = WebInspector.Breakpoint.PopoverClassName;
var checkboxElement = document.createElement("input");
checkboxElement.type = "checkbox";
checkboxElement.checked = !this._disabled;
checkboxElement.addEventListener("change", this._popoverToggleEnabledCheckboxChanged.bind(this));
var checkboxLabel = document.createElement("label");
checkboxLabel.className = "toggle";
checkboxLabel.appendChild(checkboxElement);
checkboxLabel.appendChild(document.createTextNode(this._sourceCodeLocation.displayLocationString()));
var table = document.createElement("table");
var conditionRow = table.appendChild(document.createElement("tr"));
var conditionHeader = conditionRow.appendChild(document.createElement("th"));
var conditionData = conditionRow.appendChild(document.createElement("td"));
var conditionLabel = conditionHeader.appendChild(document.createElement("label"));
var conditionInput = conditionData.appendChild(document.createElement("input"));
conditionInput.id = WebInspector.Breakpoint.PopoverConditionInputId;
conditionInput.value = this._condition || "";
conditionInput.spellcheck = false;
conditionInput.addEventListener("change", this._popoverConditionInputChanged.bind(this));
conditionInput.addEventListener("keydown", this._popoverConditionInputKeyDown.bind(this));
conditionInput.placeholder = WebInspector.UIString("Conditional expression");
conditionLabel.setAttribute("for", conditionInput.id);
conditionLabel.textContent = WebInspector.UIString("Condition");
if (DebuggerAgent.setBreakpoint.supports("options")) {
var actionRow = table.appendChild(document.createElement("tr"));
var actionHeader = actionRow.appendChild(document.createElement("th"));
var actionData = this._actionsContainer = actionRow.appendChild(document.createElement("td"));
var actionLabel = actionHeader.appendChild(document.createElement("label"));
actionLabel.textContent = WebInspector.UIString("Action");
if (!this._actions.length)
this._popoverActionsCreateAddActionButton();
else {
this._popoverContentElement.classList.add(WebInspector.Breakpoint.WidePopoverClassName);
for (var i = 0; i < this._actions.length; ++i) {
var breakpointActionView = new WebInspector.BreakpointActionView(this._actions[i], this, true);
this._popoverActionsInsertBreakpointActionView(breakpointActionView, i);
}
}
var optionsRow = this._popoverOptionsRowElement = table.appendChild(document.createElement("tr"));
if (!this._actions.length)
optionsRow.classList.add(WebInspector.Breakpoint.HiddenStyleClassName);
var optionsHeader = optionsRow.appendChild(document.createElement("th"));
var optionsData = optionsRow.appendChild(document.createElement("td"));
var optionsLabel = optionsHeader.appendChild(document.createElement("label"));
var optionsCheckbox = this._popoverOptionsCheckboxElement = optionsData.appendChild(document.createElement("input"));
var optionsCheckboxLabel = optionsData.appendChild(document.createElement("label"));
optionsCheckbox.id = WebInspector.Breakpoint.PopoverOptionsAutoContinueInputId;
optionsCheckbox.type = "checkbox";
optionsCheckbox.checked = this._autoContinue;
optionsCheckbox.addEventListener("change", this._popoverToggleAutoContinueCheckboxChanged.bind(this));
optionsLabel.textContent = WebInspector.UIString("Options");
optionsCheckboxLabel.setAttribute("for", optionsCheckbox.id);
optionsCheckboxLabel.textContent = WebInspector.UIString("Automatically continue after evaluating");
}
content.appendChild(checkboxLabel);
content.appendChild(table);
return content;
}
_popoverActionsCreateAddActionButton()
{
this._popoverContentElement.classList.remove(WebInspector.Breakpoint.WidePopoverClassName);
this._actionsContainer.removeChildren();
var addActionButton = this._actionsContainer.appendChild(document.createElement("button"));
addActionButton.textContent = WebInspector.UIString("Add Action");
addActionButton.addEventListener("click", this._popoverActionsAddActionButtonClicked.bind(this));
}
_popoverActionsAddActionButtonClicked(event)
{
this._popoverContentElement.classList.add(WebInspector.Breakpoint.WidePopoverClassName);
this._actionsContainer.removeChildren();
var newAction = this.createAction(WebInspector.Breakpoint.DefaultBreakpointActionType);
var newBreakpointActionView = new WebInspector.BreakpointActionView(newAction, this);
this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, -1);
this._popoverOptionsRowElement.classList.remove(WebInspector.Breakpoint.HiddenStyleClassName);
this._popover.update();
}
_popoverActionsInsertBreakpointActionView(breakpointActionView, index)
{
if (index === -1)
this._actionsContainer.appendChild(breakpointActionView.element);
else {
var nextElement = this._actionsContainer.children[index + 1] || null;
this._actionsContainer.insertBefore(breakpointActionView.element, nextElement);
}
}
breakpointActionViewAppendActionView(breakpointActionView, newAction)
{
var newBreakpointActionView = new WebInspector.BreakpointActionView(newAction, this);
var index = 0;
var children = this._actionsContainer.children;
for (var i = 0; children.length; ++i) {
if (children[i] === breakpointActionView.element) {
index = i;
break;
}
}
this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, index);
this._popoverOptionsRowElement.classList.remove(WebInspector.Breakpoint.HiddenStyleClassName);
this._popover.update();
}
breakpointActionViewRemoveActionView(breakpointActionView)
{
breakpointActionView.element.remove();
if (!this._actionsContainer.children.length) {
this._popoverActionsCreateAddActionButton();
this._popoverOptionsRowElement.classList.add(WebInspector.Breakpoint.HiddenStyleClassName);
this._popoverOptionsCheckboxElement.checked = false;
}
this._popover.update();
}
breakpointActionViewResized(breakpointActionView)
{
this._popover.update();
}
willDismissPopover(popover)
{
console.assert(this._popover === popover);
delete this._popoverContentElement;
delete this._popoverOptionsRowElement;
delete this._popoverOptionsCheckboxElement;
delete this._actionsContainer;
delete this._popover;
}
_showEditBreakpointPopover(boundingClientRect)
{
var bounds = WebInspector.Rect.rectFromClientRect(boundingClientRect);
bounds.origin.x -= 1; // Move the anchor left one pixel so it looks more centered.
this._popover = this._popover || new WebInspector.Popover(this);
this._popover.content = this._editBreakpointPopoverContentElement();
this._popover.present(bounds.pad(2), [WebInspector.RectEdge.MAX_Y]);
if (!this._keyboardShortcutEsc) {
this._keyboardShortcutEsc = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Escape);
this._keyboardShortcutEnter = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Enter);
}
document.getElementById(WebInspector.Breakpoint.PopoverConditionInputId).select();
}
_sourceCodeLocationLocationChanged(event)
{
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.LocationDidChange, event.data);
}
_sourceCodeLocationDisplayLocationChanged(event)
{
this.dispatchEventToListeners(WebInspector.Breakpoint.Event.DisplayLocationDidChange, event.data);
}
};
WebInspector.Breakpoint.PopoverClassName = "edit-breakpoint-popover-content";
WebInspector.Breakpoint.WidePopoverClassName = "wide";
WebInspector.Breakpoint.PopoverConditionInputId = "edit-breakpoint-popover-condition";
WebInspector.Breakpoint.PopoverOptionsAutoContinueInputId = "edit-breakpoint-popoover-auto-continue";
WebInspector.Breakpoint.HiddenStyleClassName = "hidden";
WebInspector.Breakpoint.DefaultBreakpointActionType = WebInspector.BreakpointAction.Type.Log;
WebInspector.Breakpoint.TypeIdentifier = "breakpoint";
WebInspector.Breakpoint.URLCookieKey = "breakpoint-url";
WebInspector.Breakpoint.LineNumberCookieKey = "breakpoint-line-number";
WebInspector.Breakpoint.ColumnNumberCookieKey = "breakpoint-column-number";
WebInspector.Breakpoint.Event = {
DisabledStateDidChange: "breakpoint-disabled-state-did-change",
ResolvedStateDidChange: "breakpoint-resolved-state-did-change",
ConditionDidChange: "breakpoint-condition-did-change",
ActionsDidChange: "breakpoint-actions-did-change",
AutoContinueDidChange: "breakpoint-auto-continue-did-change",
LocationDidChange: "breakpoint-location-did-change",
DisplayLocationDidChange: "breakpoint-display-location-did-change",
};