| /* |
| * Copyright (C) 2013-2016 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.QuickConsole = class QuickConsole extends WI.View |
| { |
| constructor(element) |
| { |
| super(element); |
| |
| this._toggleOrFocusKeyboardShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Escape, this._toggleOrFocus.bind(this)); |
| this._toggleOrFocusKeyboardShortcut.implicitlyPreventsDefault = false; |
| this._keyboardShortcutDisabled = false; |
| |
| this._useExecutionContextOfInspectedNode = this._canUseExecutionContextOfInspectedNode(); |
| this._restoreSelectedExecutionContextForFrame = null; |
| |
| this.element.classList.add("quick-console"); |
| this.element.addEventListener("mousedown", this._handleMouseDown.bind(this)); |
| this.element.addEventListener("dragover", this._handleDragOver.bind(this)); |
| this.element.addEventListener("drop", this._handleDrop.bind(this), true); // Ensure that dropping a DOM node doesn't copy text. |
| |
| this.prompt = new WI.ConsolePrompt(null, "text/javascript"); |
| this.addSubview(this.prompt); |
| |
| // FIXME: CodeMirror 4 has a default "Esc" key handler that always prevents default. |
| // Our keyboard shortcut above will respect the default prevented and ignore the event |
| // and not toggle the console. Install our own Escape key handler that will trigger |
| // when the ConsolePrompt is empty, to restore toggling behavior. A better solution |
| // would be for CodeMirror's event handler to pass if it doesn't do anything. |
| this.prompt.escapeKeyHandlerWhenEmpty = function() { WI.toggleSplitConsole(); }; |
| |
| const navigationbarElement = null; |
| this._navigationBar = new WI.NavigationBar(navigationbarElement, {sizesToFit: true}); |
| this.addSubview(this._navigationBar); |
| |
| this._activeExecutionContextNavigationItemDivider = new WI.DividerNavigationItem; |
| this._navigationBar.addNavigationItem(this._activeExecutionContextNavigationItemDivider); |
| |
| this._activeExecutionContextNavigationItem = new WI.NavigationItem("active-execution-context"); |
| WI.addMouseDownContextMenuHandlers(this._activeExecutionContextNavigationItem.element, this._populateActiveExecutionContextNavigationItemContextMenu.bind(this)); |
| this._navigationBar.addNavigationItem(this._activeExecutionContextNavigationItem); |
| |
| this._updateActiveExecutionContextDisplay(); |
| |
| WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, this._handleConsoleSavedResultAliasSettingChanged, this); |
| WI.settings.engineeringShowInternalExecutionContexts.addEventListener(WI.Setting.Event.Changed, this._handleEngineeringShowInternalExecutionContextsSettingChanged, this); |
| |
| WI.Frame.addEventListener(WI.Frame.Event.PageExecutionContextChanged, this._handleFramePageExecutionContextChanged, this); |
| WI.Frame.addEventListener(WI.Frame.Event.ExecutionContextsCleared, this._handleFrameExecutionContextsCleared, this); |
| WI.Frame.addEventListener(WI.Frame.Event.ExecutionContextAdded, this._handleFrameExecutionContextAdded, this); |
| |
| WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this._handleDebuggerActiveCallFrameDidChange, this); |
| |
| WI.runtimeManager.addEventListener(WI.RuntimeManager.Event.ActiveExecutionContextChanged, this._handleActiveExecutionContextChanged, this); |
| |
| WI.notifications.addEventListener(WI.Notification.TransitionPageTarget, this._handleTransitionPageTarget, this); |
| |
| WI.targetManager.addEventListener(WI.TargetManager.Event.TargetRemoved, this._handleTargetRemoved, this); |
| |
| WI.domManager.addEventListener(WI.DOMManager.Event.InspectedNodeChanged, this._handleInspectedNodeChanged, this); |
| |
| WI.consoleDrawer.toggleButtonShortcutTooltip(this._toggleOrFocusKeyboardShortcut); |
| WI.consoleDrawer.addEventListener(WI.ConsoleDrawer.Event.CollapsedStateChanged, this._updateStyles, this); |
| WI.TabBrowser.addEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._updateStyles, this); |
| |
| WI.whenTargetsAvailable().then(() => { |
| this._updateActiveExecutionContextDisplay(); |
| }); |
| } |
| |
| // Public |
| |
| set keyboardShortcutDisabled(disabled) |
| { |
| this._keyboardShortcutDisabled = disabled; |
| } |
| |
| closed() |
| { |
| WI.settings.consoleSavedResultAlias.removeEventListener(WI.Setting.Event.Changed, this._handleConsoleSavedResultAliasSettingChanged, this); |
| WI.settings.engineeringShowInternalExecutionContexts.removeEventListener(WI.Setting.Event.Changed, this._handleEngineeringShowInternalExecutionContextsSettingChanged, this); |
| |
| WI.Frame.removeEventListener(WI.Frame.Event.PageExecutionContextChanged, this._handleFramePageExecutionContextChanged, this); |
| WI.Frame.removeEventListener(WI.Frame.Event.ExecutionContextsCleared, this._handleFrameExecutionContextsCleared, this); |
| |
| WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this._handleDebuggerActiveCallFrameDidChange, this); |
| |
| WI.runtimeManager.removeEventListener(WI.RuntimeManager.Event.ActiveExecutionContextChanged, this._handleActiveExecutionContextChanged, this); |
| |
| WI.notifications.removeEventListener(WI.Notification.TransitionPageTarget, this._handleTransitionPageTarget, this); |
| |
| WI.targetManager.removeEventListener(WI.TargetManager.Event.TargetRemoved, this._handleTargetRemoved, this); |
| |
| WI.domManager.removeEventListener(WI.DOMManager.Event.InspectedNodeChanged, this._handleInspectedNodeChanged, this); |
| |
| WI.consoleDrawer.removeEventListener(WI.ConsoleDrawer.Event.CollapsedStateChanged, this._updateStyles, this); |
| WI.TabBrowser.removeEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._updateStyles, this); |
| |
| super.closed(); |
| } |
| |
| // Private |
| |
| _displayNameForExecutionContext(context, maxLength = Infinity) |
| { |
| function truncate(string, length) { |
| if (!Number.isFinite(maxLength)) |
| return string; |
| return string.trim().truncateMiddle(length); |
| } |
| |
| if (context.type === WI.ExecutionContext.Type.Internal) |
| return WI.unlocalizedString("[Internal] ") + context.name; |
| |
| if (context.type === WI.ExecutionContext.Type.User) { |
| let extensionName = WI.browserManager.extensionNameForExecutionContext(context); |
| if (extensionName) |
| return truncate(extensionName, maxLength); |
| } |
| |
| let target = context.target; |
| if (target.type === WI.TargetType.Worker) |
| return truncate(target.displayName, maxLength); |
| |
| let frame = context.frame; |
| if (frame) { |
| if (context === frame.executionContextList.pageExecutionContext) { |
| let resourceName = frame.mainResource.displayName; |
| let frameName = frame.name; |
| if (frameName) { |
| // Attempt to show all of the frame name, but ensure that at least 20 characters |
| // of the resource name are shown as well. |
| let frameNameMaxLength = Math.max(maxLength - resourceName.length, 20); |
| return WI.UIString("%s (%s)").format(truncate(frameName, frameNameMaxLength), truncate(resourceName, maxLength - frameNameMaxLength)); |
| } |
| return truncate(resourceName, maxLength); |
| } |
| } |
| |
| return truncate(context.name, maxLength); |
| } |
| |
| _resolveDesiredActiveExecutionContext(forceInspectedNode) |
| { |
| let executionContext = null; |
| |
| if (this._useExecutionContextOfInspectedNode || forceInspectedNode) { |
| let inspectedNode = WI.domManager.inspectedNode; |
| if (inspectedNode) { |
| let frame = inspectedNode.frame; |
| if (frame) { |
| let pageExecutionContext = frame.pageExecutionContext; |
| if (pageExecutionContext) |
| executionContext = pageExecutionContext; |
| } |
| } |
| } |
| |
| if (!executionContext && WI.networkManager.mainFrame) |
| executionContext = WI.networkManager.mainFrame.pageExecutionContext; |
| |
| return executionContext || WI.mainTarget.executionContext; |
| } |
| |
| _setActiveExecutionContext(context) |
| { |
| let wasActive = WI.runtimeManager.activeExecutionContext === context; |
| |
| WI.runtimeManager.activeExecutionContext = context; |
| |
| if (wasActive) |
| this._updateActiveExecutionContextDisplay(); |
| } |
| |
| _updateActiveExecutionContextDisplay() |
| { |
| let toggleHidden = (hidden) => { |
| this._activeExecutionContextNavigationItemDivider.hidden = hidden; |
| this._activeExecutionContextNavigationItem.hidden = hidden; |
| }; |
| |
| if (WI.debuggerManager.activeCallFrame) { |
| toggleHidden(true); |
| return; |
| } |
| |
| if (!WI.runtimeManager.activeExecutionContext || !WI.networkManager.mainFrame) { |
| toggleHidden(true); |
| return; |
| } |
| |
| if (WI.networkManager.frames.length === 1 && !WI.targetManager.workerTargets.length) { |
| let mainFrameContexts = WI.networkManager.mainFrame.executionContextList.contexts; |
| let contextsToShow = mainFrameContexts.filter((context) => context.type !== WI.ExecutionContext.Type.Internal || WI.settings.engineeringShowInternalExecutionContexts.value); |
| if (contextsToShow.length <= 1) { |
| toggleHidden(true); |
| return; |
| } |
| } |
| |
| const maxLength = 40; |
| |
| if (this._useExecutionContextOfInspectedNode) { |
| this._activeExecutionContextNavigationItem.element.classList.add("automatic"); |
| this._activeExecutionContextNavigationItem.element.textContent = WI.UIString("Auto \u2014 %s").format(this._displayNameForExecutionContext(WI.runtimeManager.activeExecutionContext, maxLength)); |
| this._activeExecutionContextNavigationItem.tooltip = WI.UIString("Execution context for %s").format(WI.RuntimeManager.preferredSavedResultPrefix() + "0"); |
| } else { |
| this._activeExecutionContextNavigationItem.element.classList.remove("automatic"); |
| this._activeExecutionContextNavigationItem.element.textContent = this._displayNameForExecutionContext(WI.runtimeManager.activeExecutionContext, maxLength); |
| this._activeExecutionContextNavigationItem.tooltip = this._displayNameForExecutionContext(WI.runtimeManager.activeExecutionContext); |
| } |
| |
| this._activeExecutionContextNavigationItem.element.appendChild(WI.ImageUtilities.useSVGSymbol("Images/UpDownArrows.svg", "selector-arrows")); |
| |
| toggleHidden(false); |
| } |
| |
| _populateActiveExecutionContextNavigationItemContextMenu(contextMenu) |
| { |
| const maxLength = 120; |
| |
| let activeExecutionContext = WI.runtimeManager.activeExecutionContext; |
| |
| if (this._canUseExecutionContextOfInspectedNode()) { |
| let executionContextForInspectedNode = this._resolveDesiredActiveExecutionContext(true); |
| contextMenu.appendCheckboxItem(WI.UIString("Auto \u2014 %s").format(this._displayNameForExecutionContext(executionContextForInspectedNode, maxLength)), () => { |
| this._useExecutionContextOfInspectedNode = true; |
| this._setActiveExecutionContext(executionContextForInspectedNode); |
| }, this._useExecutionContextOfInspectedNode); |
| |
| contextMenu.appendSeparator(); |
| } |
| |
| let indent = 0; |
| let addExecutionContext = (context) => { |
| if (context.type === WI.ExecutionContext.Type.Internal && !WI.settings.engineeringShowInternalExecutionContexts.value) |
| return; |
| |
| let additionalIndent = (context.frame && context !== context.frame.executionContextList.pageExecutionContext) || context.type !== WI.ExecutionContext.Type.Normal; |
| |
| // Mimic macOS `-[NSMenuItem setIndentationLevel]`. |
| contextMenu.appendCheckboxItem(" ".repeat(indent + additionalIndent) + this._displayNameForExecutionContext(context, maxLength), () => { |
| this._useExecutionContextOfInspectedNode = false; |
| this._setActiveExecutionContext(context); |
| }, activeExecutionContext === context); |
| }; |
| |
| let addExecutionContextsForFrame = (frame) => { |
| let pageExecutionContext = frame.executionContextList.pageExecutionContext; |
| |
| let contexts = frame.executionContextList.contexts.sort((a, b) => { |
| if (a === pageExecutionContext) |
| return -1; |
| if (b === pageExecutionContext) |
| return 1; |
| |
| const executionContextTypeRanking = [ |
| WI.ExecutionContext.Type.Normal, |
| WI.ExecutionContext.Type.User, |
| WI.ExecutionContext.Type.Internal, |
| ]; |
| return executionContextTypeRanking.indexOf(a.type) - executionContextTypeRanking.indexOf(b.type); |
| }); |
| for (let context of contexts) |
| addExecutionContext(context); |
| }; |
| |
| let mainFrame = WI.networkManager.mainFrame; |
| addExecutionContextsForFrame(mainFrame); |
| |
| indent = 1; |
| |
| let otherFrames = WI.networkManager.frames.filter((frame) => frame !== mainFrame && frame.executionContextList.pageExecutionContext); |
| if (otherFrames.length) { |
| contextMenu.appendHeader(WI.UIString("Frames", "Frames @ Execution Context Picker", "Title for list of HTML subframe JavaScript execution contexts")); |
| |
| for (let frame of otherFrames) |
| addExecutionContextsForFrame(frame); |
| } |
| |
| let workerTargets = WI.targetManager.workerTargets; |
| if (workerTargets.length) { |
| contextMenu.appendHeader(WI.UIString("Workers", "Workers @ Execution Context Picker", "Title for list of JavaScript web worker execution contexts")); |
| |
| for (let target of workerTargets) |
| addExecutionContext(target.executionContext); |
| } |
| } |
| |
| _handleMouseDown(event) |
| { |
| if (event.target !== this.element) |
| return; |
| |
| event.preventDefault(); |
| this.prompt.focus(); |
| } |
| |
| _handleDragOver(event) |
| { |
| if (event.dataTransfer.types.includes(WI.DOMTreeOutline.DOMNodeIdDragType)) { |
| event.preventDefault(); |
| event.dataTransfer.dropEffect = "copy"; |
| } |
| } |
| |
| _handleDrop(event) |
| { |
| let domNodeId = event.dataTransfer.getData(WI.DOMTreeOutline.DOMNodeIdDragType); |
| if (domNodeId) { |
| event.preventDefault(); |
| |
| let domNode = WI.domManager.nodeForId(domNodeId); |
| WI.RemoteObject.resolveNode(domNode, WI.RuntimeManager.ConsoleObjectGroup) |
| .then((remoteObject) => { |
| let text = domNode.nodeType() === Node.ELEMENT_NODE ? WI.UIString("Dropped Element") : WI.UIString("Dropped Node"); |
| const addSpecialUserLogClass = true; |
| WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, addSpecialUserLogClass); |
| |
| this.prompt.focus(); |
| }); |
| } |
| } |
| |
| _handleConsoleSavedResultAliasSettingChanged() |
| { |
| this._updateActiveExecutionContextDisplay(); |
| } |
| |
| _handleEngineeringShowInternalExecutionContextsSettingChanged(event) |
| { |
| this._updateActiveExecutionContextDisplay(); |
| |
| if (WI.runtimeManager.activeExecutionContext.type !== WI.ExecutionContext.Type.Internal) |
| return; |
| |
| this._useExecutionContextOfInspectedNode = this._canUseExecutionContextOfInspectedNode(); |
| this._setActiveExecutionContext(this._resolveDesiredActiveExecutionContext()); |
| } |
| |
| _handleFramePageExecutionContextChanged(event) |
| { |
| let frame = event.target; |
| |
| if (this._restoreSelectedExecutionContextForFrame !== frame) |
| return; |
| |
| this._restoreSelectedExecutionContextForFrame = null; |
| |
| this._useExecutionContextOfInspectedNode = false; |
| this._setActiveExecutionContext(frame.pageExecutionContext); |
| } |
| |
| _handleFrameExecutionContextAdded(event) |
| { |
| this._updateActiveExecutionContextDisplay(); |
| } |
| |
| _handleFrameExecutionContextsCleared(event) |
| { |
| let {committingProvisionalLoad, contexts} = event.data; |
| |
| let hasActiveExecutionContext = contexts.some((context) => context === WI.runtimeManager.activeExecutionContext); |
| if (!hasActiveExecutionContext) { |
| this._updateActiveExecutionContextDisplay(); |
| return; |
| } |
| |
| // If this frame is navigating and it is selected in the UI we want to reselect its new item after navigation, |
| // however when `_useExecutionContextOfInspectedNode` is true, we should keep the execution context set to `Auto`. |
| if (committingProvisionalLoad && !this._restoreSelectedExecutionContextForFrame && !this._useExecutionContextOfInspectedNode) { |
| this._restoreSelectedExecutionContextForFrame = event.target; |
| |
| // As a fail safe, if the frame never gets an execution context, clear the restore value. |
| setTimeout(() => { |
| if (!this._restoreSelectedExecutionContextForFrame) |
| return; |
| this._restoreSelectedExecutionContextForFrame = null; |
| |
| this._useExecutionContextOfInspectedNode = this._canUseExecutionContextOfInspectedNode(); |
| this._setActiveExecutionContext(this._resolveDesiredActiveExecutionContext()); |
| }, 100); |
| return; |
| } |
| |
| this._useExecutionContextOfInspectedNode = this._canUseExecutionContextOfInspectedNode(); |
| this._setActiveExecutionContext(this._resolveDesiredActiveExecutionContext()); |
| } |
| |
| _handleDebuggerActiveCallFrameDidChange(event) |
| { |
| this._updateActiveExecutionContextDisplay(); |
| } |
| |
| _handleActiveExecutionContextChanged(event) |
| { |
| this._updateActiveExecutionContextDisplay(); |
| } |
| |
| _handleTransitionPageTarget() |
| { |
| this._updateActiveExecutionContextDisplay(); |
| } |
| |
| _handleTargetRemoved(event) |
| { |
| let {target} = event.data; |
| if (target !== WI.runtimeManager.activeExecutionContext) { |
| this._updateActiveExecutionContextDisplay(); |
| return; |
| } |
| |
| this._useExecutionContextOfInspectedNode = this._canUseExecutionContextOfInspectedNode(); |
| this._setActiveExecutionContext(this._resolveDesiredActiveExecutionContext()); |
| } |
| |
| _handleInspectedNodeChanged(event) |
| { |
| if (!this._useExecutionContextOfInspectedNode) |
| return; |
| |
| this._setActiveExecutionContext(this._resolveDesiredActiveExecutionContext()); |
| } |
| |
| _canUseExecutionContextOfInspectedNode() |
| { |
| return InspectorBackend.hasDomain("DOM"); |
| } |
| |
| _toggleOrFocus(event) |
| { |
| if (this._keyboardShortcutDisabled) |
| return; |
| |
| if (this.prompt.focused) { |
| WI.toggleSplitConsole(); |
| event.preventDefault(); |
| } else if (!WI.isEditingAnyField() && !WI.isEventTargetAnEditableField(event)) { |
| this.prompt.focus(); |
| event.preventDefault(); |
| } |
| } |
| |
| _updateStyles() |
| { |
| this.element.classList.toggle("showing-log", WI.isShowingConsoleTab() || WI.isShowingSplitConsole()); |
| } |
| }; |