| /* |
| * Copyright (C) 2015 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. |
| */ |
| |
| WI.BreakpointPopover = class BreakpointPopover extends WI.Popover |
| { |
| constructor(delegate, breakpoint) |
| { |
| console.assert(!breakpoint || breakpoint instanceof WI.Breakpoint, breakpoint); |
| |
| super(delegate); |
| |
| this._breakpoint = breakpoint || null; |
| |
| console.assert(this._breakpoint?.constructor.ReferencePage || this.constructor.ReferencePage, "Should have a link to a reference page."); |
| |
| this._contentElement = null; |
| this._conditionCodeMirror = null; |
| this._ignoreCountInputElement = null; |
| this._actionsContainerElement = null; |
| this._actionViews = []; |
| this._optionsRowElement = null; |
| this._autoContinueCheckboxElement = null; |
| |
| this._targetElement = null; |
| |
| this.windowResizeHandler = this._presentOverTargetElement.bind(this); |
| } |
| |
| // Static |
| |
| static show(breakpoint, targetElement) |
| { |
| const delegate = null; |
| let popover; |
| if (breakpoint instanceof WI.EventBreakpoint) |
| popover = new WI.EventBreakpointPopover(delegate, breakpoint); |
| else if (breakpoint instanceof WI.URLBreakpoint) |
| popover = new WI.URLBreakpointPopover(delegate, breakpoint); |
| else |
| popover = new WI.BreakpointPopover(delegate, breakpoint); |
| popover.show(targetElement); |
| } |
| |
| static appendContextMenuItems(contextMenu, breakpoint, targetElement) |
| { |
| if (breakpoint.editable && targetElement) { |
| contextMenu.appendItem(WI.UIString("Edit Breakpoint\u2026"), () => { |
| WI.BreakpointPopover.show(breakpoint, targetElement); |
| }); |
| } |
| |
| if (!breakpoint.disabled) { |
| contextMenu.appendItem(WI.UIString("Disable Breakpoint"), () => { |
| breakpoint.disabled = !breakpoint.disabled; |
| }); |
| |
| if (breakpoint.editable && breakpoint.autoContinue) { |
| contextMenu.appendItem(WI.UIString("Cancel Automatic Continue"), () => { |
| breakpoint.autoContinue = !breakpoint.autoContinue; |
| }); |
| } |
| } else { |
| contextMenu.appendItem(WI.UIString("Enable Breakpoint"), () => { |
| breakpoint.disabled = !breakpoint.disabled; |
| }); |
| } |
| |
| if (breakpoint.editable && !breakpoint.autoContinue && !breakpoint.disabled && breakpoint.actions.length) { |
| contextMenu.appendItem(WI.UIString("Set to Automatically Continue"), () => { |
| breakpoint.autoContinue = !breakpoint.autoContinue; |
| }); |
| } |
| |
| if (breakpoint.removable) { |
| contextMenu.appendItem(WI.UIString("Delete Breakpoint"), () => { |
| breakpoint.remove(); |
| }); |
| } else { |
| contextMenu.appendItem(WI.UIString("Reset Breakpoint", "Reset Breakpoint @ Breakpoint Context Menu", "Context menu action for resetting the breakpoint to its initial configuration."), () => { |
| breakpoint.reset(); |
| }); |
| } |
| |
| if (breakpoint instanceof WI.JavaScriptBreakpoint && breakpoint.sourceCodeLocation.hasMappedLocation()) { |
| contextMenu.appendSeparator(); |
| contextMenu.appendItem(WI.UIString("Reveal in Original Resource"), () => { |
| const options = { |
| ignoreNetworkTab: true, |
| ignoreSearchTab: true, |
| }; |
| WI.showOriginalOrFormattedSourceCodeLocation(breakpoint.sourceCodeLocation, options); |
| }); |
| } |
| } |
| |
| // Public |
| |
| get breakpoint() { return this._breakpoint; } |
| |
| show(targetElement) |
| { |
| this._targetElement = targetElement; |
| |
| this._contentElement = document.createElement("div"); |
| this._contentElement.classList.add("edit-breakpoint-popover-content"); |
| |
| if (this._breakpoint) { |
| let toggleLabelElement = this._contentElement.appendChild(document.createElement("label")); |
| toggleLabelElement.className = "toggle"; |
| |
| let toggleCheckboxElement = toggleLabelElement.appendChild(document.createElement("input")); |
| toggleCheckboxElement.type = "checkbox"; |
| toggleCheckboxElement.checked = !this._breakpoint.disabled; |
| toggleCheckboxElement.addEventListener("change", this._handleEnabledCheckboxChange.bind(this)); |
| |
| toggleLabelElement.appendChild(document.createTextNode(this._breakpoint.displayName)); |
| } |
| |
| this._tableElement = this._contentElement.appendChild(document.createElement("table")); |
| |
| if (!this._breakpoint) |
| this.populateContent(); |
| |
| if (this._breakpoint?.editable || this.constructor.supportsEditing) { |
| let conditionLabelElement = document.createElement("label"); |
| conditionLabelElement.textContent = WI.UIString("Condition"); |
| |
| let conditionEditorElement = document.createElement("div"); |
| conditionEditorElement.classList.add("editor", WI.SyntaxHighlightedStyleClassName); |
| |
| this._conditionCodeMirror = WI.CodeMirrorEditor.create(conditionEditorElement, { |
| extraKeys: {Tab: false}, |
| lineWrapping: false, |
| mode: "text/javascript", |
| matchBrackets: true, |
| placeholder: WI.UIString("Conditional expression"), |
| scrollbarStyle: null, |
| value: this._breakpoint?.condition || "", |
| }); |
| |
| let conditionCodeMirrorInputField = this._conditionCodeMirror.getInputField(); |
| conditionCodeMirrorInputField.id = "edit-breakpoint-popover-content-condition"; |
| conditionLabelElement.setAttribute("for", conditionCodeMirrorInputField.id); |
| |
| this.addRow("condition", conditionLabelElement, conditionEditorElement); |
| |
| this._conditionCodeMirror.addKeyMap({ |
| "Enter": () => { this.dismiss(); }, |
| "Esc": () => { this.dismiss(); }, |
| }); |
| |
| this._conditionCodeMirror.on("beforeChange", this._handleConditionCodeMirrorBeforeChange.bind(this)); |
| if (this._breakpoint) |
| this._conditionCodeMirror.on("change", this._handleConditionCodeMirrorChange.bind(this)); |
| |
| let completionController = new WI.CodeMirrorCompletionController(this.codeMirrorCompletionControllerMode, this._conditionCodeMirror, this); |
| completionController.addExtendedCompletionProvider("javascript", WI.javaScriptRuntimeCompletionProvider); |
| |
| let ignoreCountLabelElement = document.createElement("label"); |
| ignoreCountLabelElement.textContent = WI.UIString("Ignore"); |
| |
| let ignoreCountContentFragment = document.createDocumentFragment(); |
| |
| this._ignoreCountInputElement = ignoreCountContentFragment.appendChild(document.createElement("input")); |
| this._ignoreCountInputElement.id = "edit-breakpoint-popover-ignore-count"; |
| this._ignoreCountInputElement.type = "number"; |
| this._ignoreCountInputElement.min = 0; |
| this._ignoreCountInputElement.value = this._breakpoint?.ignoreCount || 0; |
| this._ignoreCountInputElement.addEventListener("change", this._handleIgnoreCountInputChange.bind(this)); |
| |
| this._ignoreCountText = ignoreCountContentFragment.appendChild(document.createElement("label")); |
| this._updateIgnoreCountText(); |
| |
| ignoreCountLabelElement.setAttribute("for", this._ignoreCountInputElement.id); |
| this._ignoreCountText.setAttribute("for", this._ignoreCountInputElement.id); |
| |
| this.addRow("ignore-count", ignoreCountLabelElement, ignoreCountContentFragment); |
| |
| let actionsLabelElement = document.createElement("label"); |
| actionsLabelElement.textContent = WI.UIString("Action"); |
| |
| this._actionsContainerElement = document.createElement("div"); |
| |
| if (!this._breakpoint?.actions.length) |
| this._createAddActionButton(); |
| else { |
| this._contentElement.classList.add("wide"); |
| |
| for (let i = 0; i < this._breakpoint.actions.length; ++i) { |
| let breakpointActionView = new WI.BreakpointActionView(this._breakpoint.actions[i], this, {omitFocus: true}); |
| this._insertBreakpointActionView(breakpointActionView); |
| } |
| } |
| |
| this.addRow("actions", actionsLabelElement, this._actionsContainerElement); |
| |
| let optionsLabelElement = document.createElement("label"); |
| optionsLabelElement.textContent = WI.UIString("Options"); |
| |
| let optionsDocumentFragment = document.createDocumentFragment(); |
| |
| this._autoContinueCheckboxElement = optionsDocumentFragment.appendChild(document.createElement("input")); |
| this._autoContinueCheckboxElement.id = "edit-breakpoint-popover-auto-continue"; |
| this._autoContinueCheckboxElement.type = "checkbox"; |
| this._autoContinueCheckboxElement.checked = this._breakpoint?.autoContinue || false; |
| this._autoContinueCheckboxElement.addEventListener("change", this._handleAutoContinueCheckboxChange.bind(this)); |
| |
| let optionsCheckboxLabel = optionsDocumentFragment.appendChild(document.createElement("label")); |
| optionsCheckboxLabel.textContent = WI.UIString("Automatically continue after evaluating"); |
| |
| optionsLabelElement.setAttribute("for", this._autoContinueCheckboxElement.id); |
| optionsCheckboxLabel.setAttribute("for", this._autoContinueCheckboxElement.id); |
| |
| this._optionsRowElement = this.addRow("options", optionsLabelElement, optionsDocumentFragment); |
| if (!this._breakpoint?.actions.length) |
| this._optionsRowElement.classList.add("hidden"); |
| |
| // CodeMirror needs to refresh after the popover is shown as otherwise it doesn't appear. |
| setTimeout(() => { |
| this._conditionCodeMirror.refresh(); |
| if (this._breakpoint) |
| this._conditionCodeMirror.focus(); |
| |
| this.update(); |
| }); |
| } |
| |
| let referencePage = this._breakpoint?.constructor.ReferencePage || this.constructor.ReferencePage; |
| if (this._breakpoint) |
| referencePage = referencePage.Configuration; |
| this._contentElement.appendChild(referencePage.createLinkElement()); |
| |
| this.content = this._contentElement; |
| |
| this._presentOverTargetElement(); |
| } |
| |
| dismiss() |
| { |
| this._breakpoint ??= this.createBreakpoint({ |
| condition: this._conditionCodeMirror ? this._conditionCodeMirror.getValue().trim() : "", |
| actions: this._actionViews.map((breakpointActionView) => breakpointActionView.action), |
| ignoreCount: this._ignoreCountInputElement ? this._parseIgnoreCountNumber() : 0, |
| autoContinue: this._autoContinueCheckboxElement ? this._autoContinueCheckboxElement.checked : false, |
| }); |
| |
| // Remove Evaluate and Probe actions that have no data. |
| let emptyActions = this._breakpoint?.actions.filter(function(action) { |
| if (action.type === WI.BreakpointAction.Type.Sound) |
| return false; |
| return !action.data?.trim(); |
| }) || []; |
| for (let action of emptyActions) |
| this._breakpoint.removeAction(action); |
| |
| super.dismiss(); |
| } |
| |
| // CodeMirrorCompletionController delegate |
| |
| completionControllerShouldAllowEscapeCompletion() |
| { |
| return false; |
| } |
| |
| // BreakpointActionView delegate |
| |
| breakpointActionViewCodeMirrorCompletionControllerMode(breakpointActionView, codeMirror) |
| { |
| return this.codeMirrorCompletionControllerMode; |
| } |
| |
| breakpointActionViewAppendActionView(breakpointActionView, newBreakpointAction) |
| { |
| this._breakpoint?.addAction(newBreakpointAction, {precedingAction: breakpointActionView.action}); |
| |
| let newBreakpointActionView = new WI.BreakpointActionView(newBreakpointAction, this); |
| |
| let index = this._actionViews.indexOf(breakpointActionView) + 1; |
| this._insertBreakpointActionView(newBreakpointActionView, index); |
| this._optionsRowElement.classList.remove("hidden"); |
| |
| this.update(); |
| } |
| |
| breakpointActionViewRemoveActionView(breakpointActionView) |
| { |
| this._breakpoint?.removeAction(breakpointActionView.action); |
| |
| breakpointActionView.element.remove(); |
| this._actionViews.remove(breakpointActionView); |
| |
| if (!this._actionViews.length) { |
| this._createAddActionButton(); |
| this._optionsRowElement.classList.add("hidden"); |
| this._autoContinueCheckboxElement.checked = false; |
| } |
| |
| this.update(); |
| } |
| |
| breakpointActionViewResized(breakpointActionView) |
| { |
| this.update(); |
| } |
| |
| // Protected |
| |
| get codeMirrorCompletionControllerMode() |
| { |
| // Overridden by subclasses if needed. |
| |
| if (this._breakpoint === WI.debuggerManager.allExceptionsBreakpoint || this._breakpoint === WI.debuggerManager.uncaughtExceptionsBreakpoint) |
| return WI.CodeMirrorCompletionController.Mode.ExceptionBreakpoint; |
| |
| return WI.CodeMirrorCompletionController.Mode.Basic; |
| } |
| |
| populateContent() |
| { |
| throw WI.NotImplementedError.subclassMustOverride(); |
| } |
| |
| addRow(className, label, content) |
| { |
| let rowElement = this._tableElement.appendChild(document.createElement("tr")); |
| rowElement.className = className; |
| |
| let headerElement = rowElement.appendChild(document.createElement("th")); |
| headerElement.append(label); |
| |
| let dataElement = rowElement.appendChild(document.createElement("td")); |
| dataElement.append(content); |
| |
| return rowElement; |
| } |
| |
| createBreakpoint(options = {}) |
| { |
| throw WI.NotImplementedError.subclassMustOverride(); |
| } |
| |
| // Private |
| |
| _presentOverTargetElement() |
| { |
| if (!this._targetElement) |
| return; |
| |
| let targetFrame = WI.Rect.rectFromClientRect(this._targetElement.getBoundingClientRect()); |
| this.present(targetFrame.pad(2), [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_X]); |
| } |
| |
| _parseIgnoreCountNumber() |
| { |
| let ignoreCount = 0; |
| if (this._ignoreCountInputElement.value) { |
| ignoreCount = parseInt(this._ignoreCountInputElement.value, 10); |
| if (isNaN(ignoreCount) || ignoreCount < 0) |
| ignoreCount = 0; |
| } |
| return ignoreCount; |
| } |
| |
| _updateIgnoreCountText() |
| { |
| if (this._parseIgnoreCountNumber() === 1) |
| this._ignoreCountText.textContent = WI.UIString("time before stopping"); |
| else |
| this._ignoreCountText.textContent = WI.UIString("times before stopping"); |
| } |
| |
| _createAddActionButton() |
| { |
| this._contentElement.classList.remove("wide"); |
| this._actionsContainerElement.removeChildren(); |
| |
| let addActionButton = this._actionsContainerElement.appendChild(document.createElement("button")); |
| addActionButton.textContent = WI.UIString("Add Action"); |
| addActionButton.addEventListener("click", this._handleAddActionButtonClick.bind(this)); |
| } |
| |
| _insertBreakpointActionView(breakpointActionView, index = this._actionViews.length) |
| { |
| if (index >= this._actionViews.length) { |
| this._actionsContainerElement.appendChild(breakpointActionView.element); |
| this._actionViews.push(breakpointActionView); |
| } else { |
| this._actionsContainerElement.insertBefore(breakpointActionView.element, this._actionViews[index].element); |
| this._actionViews.splice(index, 0, breakpointActionView) |
| } |
| } |
| |
| _handleEnabledCheckboxChange(event) |
| { |
| this._breakpoint.disabled = !event.target.checked; |
| } |
| |
| _handleConditionCodeMirrorBeforeChange(codeMirror, change) |
| { |
| if (change.update) { |
| let newText = change.text.join("").replace(/\n/g, ""); |
| change.update(change.from, change.to, [newText]); |
| } |
| |
| return true; |
| } |
| |
| _handleConditionCodeMirrorChange(codeMirror, change) |
| { |
| this._breakpoint.condition = this._conditionCodeMirror.getValue().trim(); |
| } |
| |
| _handleIgnoreCountInputChange(event) |
| { |
| let ignoreCount = this._parseIgnoreCountNumber(); |
| this._ignoreCountInputElement.value = ignoreCount; |
| |
| if (this._breakpoint) |
| this._breakpoint.ignoreCount = ignoreCount; |
| |
| this._updateIgnoreCountText(); |
| } |
| |
| _handleAddActionButtonClick(event) |
| { |
| this._contentElement.classList.add("wide"); |
| |
| this._actionsContainerElement.removeChildren(); |
| |
| let action = new WI.BreakpointAction(WI.BreakpointAction.Type.Evaluate); |
| this._breakpoint?.addAction(action); |
| |
| let breakpointActionView = new WI.BreakpointActionView(action, this); |
| this._insertBreakpointActionView(breakpointActionView); |
| |
| this._optionsRowElement.classList.remove("hidden"); |
| |
| this.update(); |
| } |
| |
| _handleAutoContinueCheckboxChange(event) |
| { |
| if (this._breakpoint) |
| this._breakpoint.autoContinue = this._autoContinueCheckboxElement.checked; |
| } |
| }; |