/* * 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"); WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, {addSpecialUserLogClass: true}); 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()); } };