/* * Copyright (C) 2013 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 = function(sourceCodeLocationOrInfo, disabled, condition) { WebInspector.Object.call(this); 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); }; WebInspector.Object.addConstructorFunctions(WebInspector.Breakpoint); 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", }; WebInspector.Breakpoint.prototype = { constructor: WebInspector.Breakpoint, // 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 && WebInspector.debuggerManager.breakpointsEnabled; }, set resolved(resolved) { if (this._resolved === resolved) return; 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 }; }, cycleToNextMode: function() { 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: function(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: function(type, precedingAction) { var newAction = new WebInspector.BreakpointAction(this, type, 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: function(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: function(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); }, saveIdentityToCookie: function(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: function(action) { var index = this._actions.indexOf(action); console.assert(index !== -1); if (index === -1) return; this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); }, // Private _serializableActions: function() { var actions = []; for (var i = 0; i < this._actions.length; ++i) actions.push(this._actions[i].info); return actions; }, _popoverToggleEnabledCheckboxChanged: function(event) { this.disabled = !event.target.checked; }, _popoverConditionInputChanged: function(event) { this.condition = event.target.value; }, _popoverToggleAutoContinueCheckboxChanged: function(event) { this.autoContinue = event.target.checked; }, _popoverConditionInputKeyDown: function(event) { if (this._keyboardShortcutEsc.matchesEvent(event) || this._keyboardShortcutEnter.matchesEvent(event)) { this._popover.dismiss(); event.stopPropagation(); event.preventDefault(); } }, _editBreakpointPopoverContentElement: function() { 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: function() { 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: function(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: function(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: function(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: function(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: function(breakpointActionView) { this._popover.update(); }, willDismissPopover: function(popover) { console.assert(this._popover === popover); delete this._popoverContentElement; delete this._popoverOptionsRowElement; delete this._popoverOptionsCheckboxElement; delete this._actionsContainer; delete this._popover; }, _showEditBreakpointPopover: function(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: function(event) { this.dispatchEventToListeners(WebInspector.Breakpoint.Event.LocationDidChange, event.data); }, _sourceCodeLocationDisplayLocationChanged: function(event) { this.dispatchEventToListeners(WebInspector.Breakpoint.Event.DisplayLocationDidChange, event.data); } }; WebInspector.Breakpoint.prototype.__proto__ = WebInspector.Object.prototype;