| /* |
| * 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.BreakpointPopoverController = class BreakpointPopoverController extends WI.Object |
| { |
| constructor() |
| { |
| super(); |
| |
| this._breakpoint = null; |
| this._popover = null; |
| this._popoverContentElement = null; |
| } |
| |
| // Public |
| |
| appendContextMenuItems(contextMenu, breakpoint, breakpointDisplayElement) |
| { |
| console.assert(document.body.contains(breakpointDisplayElement), "Breakpoint popover display element must be in the DOM."); |
| |
| const editBreakpoint = () => { |
| console.assert(!this._popover, "Breakpoint popover already exists."); |
| if (this._popover) |
| return; |
| |
| this._createPopoverContent(breakpoint); |
| this._popover = new WI.Popover(this); |
| this._popover.content = this._popoverContentElement; |
| |
| let bounds = WI.Rect.rectFromClientRect(breakpointDisplayElement.getBoundingClientRect()); |
| bounds.origin.x -= 1; // Move the anchor left one pixel so it looks more centered. |
| this._popover.present(bounds.pad(2), [WI.RectEdge.MAX_Y]); |
| }; |
| |
| const removeBreakpoint = () => { |
| WI.debuggerManager.removeBreakpoint(breakpoint); |
| }; |
| |
| const toggleBreakpoint = () => { |
| breakpoint.disabled = !breakpoint.disabled; |
| }; |
| |
| const toggleAutoContinue = () => { |
| breakpoint.autoContinue = !breakpoint.autoContinue; |
| }; |
| |
| const revealOriginalSourceCodeLocation = () => { |
| const options = { |
| ignoreNetworkTab: true, |
| ignoreSearchTab: true, |
| }; |
| WI.showOriginalOrFormattedSourceCodeLocation(breakpoint.sourceCodeLocation, options); |
| }; |
| |
| if (WI.debuggerManager.isBreakpointEditable(breakpoint)) |
| contextMenu.appendItem(WI.UIString("Edit Breakpoint\u2026"), editBreakpoint); |
| |
| if (breakpoint.autoContinue && !breakpoint.disabled) { |
| contextMenu.appendItem(WI.UIString("Disable Breakpoint"), toggleBreakpoint); |
| contextMenu.appendItem(WI.UIString("Cancel Automatic Continue"), toggleAutoContinue); |
| } else if (!breakpoint.disabled) |
| contextMenu.appendItem(WI.UIString("Disable Breakpoint"), toggleBreakpoint); |
| else |
| contextMenu.appendItem(WI.UIString("Enable Breakpoint"), toggleBreakpoint); |
| |
| if (!breakpoint.autoContinue && !breakpoint.disabled && breakpoint.actions.length) |
| contextMenu.appendItem(WI.UIString("Set to Automatically Continue"), toggleAutoContinue); |
| |
| if (WI.debuggerManager.isBreakpointRemovable(breakpoint)) |
| contextMenu.appendItem(WI.UIString("Delete Breakpoint"), removeBreakpoint); |
| |
| if (breakpoint._sourceCodeLocation.hasMappedLocation()) { |
| contextMenu.appendSeparator(); |
| contextMenu.appendItem(WI.UIString("Reveal in Original Resource"), revealOriginalSourceCodeLocation); |
| } |
| } |
| |
| // CodeMirrorCompletionController delegate |
| |
| completionControllerShouldAllowEscapeCompletion() |
| { |
| return false; |
| } |
| |
| // Private |
| |
| _createPopoverContent(breakpoint) |
| { |
| console.assert(!this._popoverContentElement, "Popover content element already exists."); |
| if (this._popoverContentElement) |
| return; |
| |
| this._breakpoint = breakpoint; |
| this._popoverContentElement = document.createElement("div"); |
| this._popoverContentElement.className = "edit-breakpoint-popover-content"; |
| |
| let checkboxElement = document.createElement("input"); |
| checkboxElement.type = "checkbox"; |
| checkboxElement.checked = !this._breakpoint.disabled; |
| checkboxElement.addEventListener("change", this._popoverToggleEnabledCheckboxChanged.bind(this)); |
| |
| let checkboxLabel = document.createElement("label"); |
| checkboxLabel.className = "toggle"; |
| checkboxLabel.appendChild(checkboxElement); |
| checkboxLabel.append(this._breakpoint.sourceCodeLocation.displayLocationString()); |
| |
| let table = document.createElement("table"); |
| |
| let conditionRow = table.appendChild(document.createElement("tr")); |
| let conditionHeader = conditionRow.appendChild(document.createElement("th")); |
| let conditionData = conditionRow.appendChild(document.createElement("td")); |
| let conditionLabel = conditionHeader.appendChild(document.createElement("label")); |
| conditionLabel.textContent = WI.UIString("Condition"); |
| let conditionEditorElement = conditionData.appendChild(document.createElement("div")); |
| conditionEditorElement.classList.add("edit-breakpoint-popover-condition", 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 = "codemirror-condition-input-field"; |
| conditionLabel.setAttribute("for", conditionCodeMirrorInputField.id); |
| |
| this._conditionCodeMirrorEscapeOrEnterKeyHandler = this._conditionCodeMirrorEscapeOrEnterKey.bind(this); |
| this._conditionCodeMirror.addKeyMap({ |
| "Esc": this._conditionCodeMirrorEscapeOrEnterKeyHandler, |
| "Enter": this._conditionCodeMirrorEscapeOrEnterKeyHandler, |
| }); |
| |
| this._conditionCodeMirror.on("change", this._conditionCodeMirrorChanged.bind(this)); |
| this._conditionCodeMirror.on("beforeChange", this._conditionCodeMirrorBeforeChange.bind(this)); |
| |
| let completionController = new WI.CodeMirrorCompletionController(this._conditionCodeMirror, this); |
| completionController.addExtendedCompletionProvider("javascript", WI.javaScriptRuntimeCompletionProvider); |
| |
| // CodeMirror needs a refresh after the popover displays, to layout, otherwise it doesn't appear. |
| setTimeout(() => { |
| this._conditionCodeMirror.refresh(); |
| this._conditionCodeMirror.focus(); |
| }, 0); |
| |
| // COMPATIBILITY (iOS 9): Legacy backends don't support breakpoint ignore count. |
| // Since support can't be tested directly, check for Runtime.run (iOS 9.3) or Console.heapSnapshot (iOS 10.0+). |
| // FIXME: Use explicit version checking once https://webkit.org/b/148680 is fixed. |
| if (InspectorBackend.hasCommand("Console.heapSnapshot") || InspectorBackend.hasCommand("Runtime.run")) { |
| let ignoreCountRow = table.appendChild(document.createElement("tr")); |
| let ignoreCountHeader = ignoreCountRow.appendChild(document.createElement("th")); |
| let ignoreCountLabel = ignoreCountHeader.appendChild(document.createElement("label")); |
| let ignoreCountData = ignoreCountRow.appendChild(document.createElement("td")); |
| this._ignoreCountInput = ignoreCountData.appendChild(document.createElement("input")); |
| this._ignoreCountInput.id = "edit-breakpoint-popover-ignore"; |
| this._ignoreCountInput.type = "number"; |
| this._ignoreCountInput.min = 0; |
| this._ignoreCountInput.value = 0; |
| this._ignoreCountInput.addEventListener("change", this._popoverIgnoreInputChanged.bind(this)); |
| |
| ignoreCountLabel.setAttribute("for", this._ignoreCountInput.id); |
| ignoreCountLabel.textContent = WI.UIString("Ignore"); |
| |
| this._ignoreCountText = ignoreCountData.appendChild(document.createElement("span")); |
| this._updateIgnoreCountText(); |
| } |
| |
| let actionRow = table.appendChild(document.createElement("tr")); |
| let actionHeader = actionRow.appendChild(document.createElement("th")); |
| let actionData = this._actionsContainer = actionRow.appendChild(document.createElement("td")); |
| let actionLabel = actionHeader.appendChild(document.createElement("label")); |
| actionLabel.textContent = WI.UIString("Action"); |
| |
| if (!this._breakpoint.actions.length) |
| this._popoverActionsCreateAddActionButton(); |
| else { |
| this._popoverContentElement.classList.add(WI.BreakpointPopoverController.WidePopoverClassName); |
| for (let i = 0; i < this._breakpoint.actions.length; ++i) { |
| let breakpointActionView = new WI.BreakpointActionView(this._breakpoint.actions[i], this, true); |
| this._popoverActionsInsertBreakpointActionView(breakpointActionView, i); |
| } |
| } |
| |
| let optionsRow = this._popoverOptionsRowElement = table.appendChild(document.createElement("tr")); |
| if (!this._breakpoint.actions.length) |
| optionsRow.classList.add(WI.BreakpointPopoverController.HiddenStyleClassName); |
| let optionsHeader = optionsRow.appendChild(document.createElement("th")); |
| let optionsData = optionsRow.appendChild(document.createElement("td")); |
| let optionsLabel = optionsHeader.appendChild(document.createElement("label")); |
| let optionsCheckbox = this._popoverOptionsCheckboxElement = optionsData.appendChild(document.createElement("input")); |
| let optionsCheckboxLabel = optionsData.appendChild(document.createElement("label")); |
| optionsCheckbox.id = "edit-breakpoint-popover-auto-continue"; |
| optionsCheckbox.type = "checkbox"; |
| optionsCheckbox.checked = this._breakpoint.autoContinue; |
| optionsCheckbox.addEventListener("change", this._popoverToggleAutoContinueCheckboxChanged.bind(this)); |
| optionsLabel.textContent = WI.UIString("Options"); |
| optionsCheckboxLabel.setAttribute("for", optionsCheckbox.id); |
| optionsCheckboxLabel.textContent = WI.UIString("Automatically continue after evaluating"); |
| |
| this._popoverContentElement.appendChild(checkboxLabel); |
| this._popoverContentElement.appendChild(table); |
| } |
| |
| _popoverToggleEnabledCheckboxChanged(event) |
| { |
| this._breakpoint.disabled = !event.target.checked; |
| } |
| |
| _conditionCodeMirrorChanged(codeMirror, change) |
| { |
| this._breakpoint.condition = (codeMirror.getValue() || "").trim(); |
| } |
| |
| _conditionCodeMirrorBeforeChange(codeMirror, change) |
| { |
| if (change.update) { |
| let newText = change.text.join("").replace(/\n/g, ""); |
| change.update(change.from, change.to, [newText]); |
| } |
| |
| return true; |
| } |
| |
| _conditionCodeMirrorEscapeOrEnterKey() |
| { |
| if (!this._popover) |
| return; |
| |
| this._popover.dismiss(); |
| } |
| |
| _popoverIgnoreInputChanged(event) |
| { |
| let ignoreCount = 0; |
| if (event.target.value) { |
| ignoreCount = parseInt(event.target.value, 10); |
| if (isNaN(ignoreCount) || ignoreCount < 0) |
| ignoreCount = 0; |
| } |
| |
| this._ignoreCountInput.value = ignoreCount; |
| this._breakpoint.ignoreCount = ignoreCount; |
| |
| this._updateIgnoreCountText(); |
| } |
| |
| _popoverToggleAutoContinueCheckboxChanged(event) |
| { |
| this._breakpoint.autoContinue = event.target.checked; |
| } |
| |
| _popoverActionsCreateAddActionButton() |
| { |
| this._popoverContentElement.classList.remove(WI.BreakpointPopoverController.WidePopoverClassName); |
| this._actionsContainer.removeChildren(); |
| |
| let addActionButton = this._actionsContainer.appendChild(document.createElement("button")); |
| addActionButton.textContent = WI.UIString("Add Action"); |
| addActionButton.addEventListener("click", this._popoverActionsAddActionButtonClicked.bind(this)); |
| } |
| |
| _popoverActionsAddActionButtonClicked(event) |
| { |
| this._popoverContentElement.classList.add(WI.BreakpointPopoverController.WidePopoverClassName); |
| this._actionsContainer.removeChildren(); |
| |
| let newAction = this._breakpoint.createAction(WI.BreakpointAction.Type.Log); |
| let newBreakpointActionView = new WI.BreakpointActionView(newAction, this); |
| this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, -1); |
| this._popoverOptionsRowElement.classList.remove(WI.BreakpointPopoverController.HiddenStyleClassName); |
| this._popover.update(); |
| } |
| |
| _popoverActionsInsertBreakpointActionView(breakpointActionView, index) |
| { |
| if (index === -1) |
| this._actionsContainer.appendChild(breakpointActionView.element); |
| else { |
| let nextElement = this._actionsContainer.children[index + 1] || null; |
| this._actionsContainer.insertBefore(breakpointActionView.element, nextElement); |
| } |
| } |
| |
| _updateIgnoreCountText() |
| { |
| if (this._breakpoint.ignoreCount === 1) |
| this._ignoreCountText.textContent = WI.UIString("time before stopping"); |
| else |
| this._ignoreCountText.textContent = WI.UIString("times before stopping"); |
| } |
| |
| breakpointActionViewAppendActionView(breakpointActionView, newAction) |
| { |
| let newBreakpointActionView = new WI.BreakpointActionView(newAction, this); |
| |
| let index = 0; |
| let children = this._actionsContainer.children; |
| for (let i = 0; children.length; ++i) { |
| if (children[i] === breakpointActionView.element) { |
| index = i; |
| break; |
| } |
| } |
| |
| this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, index); |
| this._popoverOptionsRowElement.classList.remove(WI.BreakpointPopoverController.HiddenStyleClassName); |
| |
| this._popover.update(); |
| } |
| |
| breakpointActionViewRemoveActionView(breakpointActionView) |
| { |
| breakpointActionView.element.remove(); |
| |
| if (!this._actionsContainer.children.length) { |
| this._popoverActionsCreateAddActionButton(); |
| this._popoverOptionsRowElement.classList.add(WI.BreakpointPopoverController.HiddenStyleClassName); |
| this._popoverOptionsCheckboxElement.checked = false; |
| } |
| |
| this._popover.update(); |
| } |
| |
| breakpointActionViewResized(breakpointActionView) |
| { |
| this._popover.update(); |
| } |
| |
| willDismissPopover(popover) |
| { |
| console.assert(this._popover === popover); |
| this._popoverContentElement = null; |
| this._popoverOptionsRowElement = null; |
| this._popoverOptionsCheckboxElement = null; |
| this._actionsContainer = null; |
| this._popover = null; |
| } |
| |
| didDismissPopover(popover) |
| { |
| // Remove Evaluate and Probe actions that have no data. |
| let emptyActions = this._breakpoint.actions.filter(function(action) { |
| if (action.type !== WI.BreakpointAction.Type.Evaluate && action.type !== WI.BreakpointAction.Type.Probe) |
| return false; |
| return !(action.data && action.data.trim()); |
| }); |
| |
| for (let action of emptyActions) |
| this._breakpoint.removeAction(action); |
| |
| this._breakpoint = null; |
| } |
| }; |
| |
| WI.BreakpointPopoverController.WidePopoverClassName = "wide"; |
| WI.BreakpointPopoverController.HiddenStyleClassName = "hidden"; |