| /* |
| * 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._automaticExecutionContextPathComponent = this._createExecutionContextPathComponent(null, WI.UIString("Auto")); |
| this._updateAutomaticExecutionContextPathComponentTooltip(); |
| |
| this._mainExecutionContextPathComponent = null; |
| this._otherExecutionContextPathComponents = []; |
| |
| this._frameToPathComponent = new Map; |
| this._targetToPathComponent = new Map; |
| |
| this._shouldAutomaticallySelectExecutionContext = true; |
| this._restoreSelectedExecutionContextForFrame = false; |
| |
| 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(); }; |
| |
| this._navigationBar = new WI.QuickConsoleNavigationBar; |
| this.addSubview(this._navigationBar); |
| |
| this._executionContextSelectorItem = new WI.HierarchicalPathNavigationItem; |
| this._executionContextSelectorItem.showSelectorArrows = true; |
| this._navigationBar.addNavigationItem(this._executionContextSelectorItem); |
| |
| this._executionContextSelectorDivider = new WI.DividerNavigationItem; |
| this._navigationBar.addNavigationItem(this._executionContextSelectorDivider); |
| |
| this.initializeMainExecutionContextPathComponent(); |
| |
| WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, this._updateAutomaticExecutionContextPathComponentTooltip, this); |
| |
| WI.consoleDrawer.toggleButtonShortcutTooltip(this._toggleOrFocusKeyboardShortcut); |
| WI.consoleDrawer.addEventListener(WI.ConsoleDrawer.Event.CollapsedStateChanged, this._updateStyles, this); |
| |
| WI.Frame.addEventListener(WI.Frame.Event.PageExecutionContextChanged, this._framePageExecutionContextsChanged, this); |
| WI.Frame.addEventListener(WI.Frame.Event.ExecutionContextsCleared, this._frameExecutionContextsCleared, this); |
| |
| WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this._debuggerActiveCallFrameDidChange, this); |
| |
| WI.runtimeManager.addEventListener(WI.RuntimeManager.Event.ActiveExecutionContextChanged, this._activeExecutionContextChanged, this); |
| |
| WI.targetManager.addEventListener(WI.TargetManager.Event.TargetAdded, this._targetAdded, this); |
| WI.targetManager.addEventListener(WI.TargetManager.Event.TargetRemoved, this._targetRemoved, this); |
| |
| WI.domManager.addEventListener(WI.DOMManager.Event.InspectedNodeChanged, this._handleInspectedNodeChanged, this); |
| |
| WI.TabBrowser.addEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._updateStyles, this); |
| } |
| |
| // Public |
| |
| get navigationBar() |
| { |
| return this._navigationBar; |
| } |
| |
| closed() |
| { |
| WI.settings.consoleSavedResultAlias.removeEventListener(null, null, this); |
| WI.Frame.removeEventListener(null, null, this); |
| WI.debuggerManager.removeEventListener(null, null, this); |
| WI.runtimeManager.removeEventListener(null, null, this); |
| WI.targetManager.removeEventListener(null, null, this); |
| WI.consoleDrawer.removeEventListener(null, null, this); |
| WI.TabBrowser.removeEventListener(null, null, this); |
| |
| super.closed(); |
| } |
| |
| initializeMainExecutionContextPathComponent() |
| { |
| if (!WI.mainTarget || WI.mainTarget instanceof WI.MultiplexingBackendTarget) |
| return; |
| |
| this._mainExecutionContextPathComponent = this._createExecutionContextPathComponent(WI.mainTarget.executionContext); |
| this._mainExecutionContextPathComponent.previousSibling = this._automaticExecutionContextPathComponent; |
| |
| this._automaticExecutionContextPathComponent.nextSibling = this._mainExecutionContextPathComponent; |
| |
| this._shouldAutomaticallySelectExecutionContext = true; |
| this._selectExecutionContext(WI.mainTarget.executionContext); |
| this._rebuildExecutionContextPathComponents(); |
| } |
| |
| // Protected |
| |
| layout() |
| { |
| // A hard maximum size of 33% of the window. |
| let maximumAllowedHeight = Math.round(window.innerHeight * 0.33); |
| this.prompt.element.style.maxHeight = maximumAllowedHeight + "px"; |
| } |
| |
| // Private |
| |
| _preferredNameForFrame(frame) |
| { |
| if (frame.name) |
| return WI.UIString("%s (%s)").format(frame.name, frame.mainResource.displayName); |
| return frame.mainResource.displayName; |
| } |
| |
| _selectExecutionContext(executionContext) |
| { |
| let preferredName = null; |
| |
| let inspectedNode = WI.domManager.inspectedNode; |
| if (inspectedNode) { |
| let frame = inspectedNode.frame; |
| if (frame) { |
| if (this._shouldAutomaticallySelectExecutionContext) |
| executionContext = frame.pageExecutionContext; |
| preferredName = this._preferredNameForFrame(frame); |
| } |
| } |
| |
| console.assert(executionContext); |
| if (!executionContext) |
| executionContext = WI.mainTarget.executionContext; |
| |
| this._automaticExecutionContextPathComponent.displayName = WI.UIString("Auto - %s").format(preferredName || executionContext.name); |
| |
| let changed = WI.runtimeManager.activeExecutionContext !== executionContext; |
| if (changed) |
| WI.runtimeManager.activeExecutionContext = executionContext; |
| return changed; |
| } |
| |
| _updateAutomaticExecutionContextPathComponentTooltip() |
| { |
| this._automaticExecutionContextPathComponent.tooltip = WI.UIString("Execution context for %s").format(WI.RuntimeManager.preferredSavedResultPrefix() + "0"); |
| } |
| |
| _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(); |
| }); |
| } |
| } |
| |
| _executionContextPathComponentsToDisplay() |
| { |
| // If we are in the debugger the console will use the active call frame, don't show the selector. |
| if (WI.debuggerManager.activeCallFrame) |
| return []; |
| |
| // If there is only the Main ExecutionContext, don't show the selector. |
| if (!this._otherExecutionContextPathComponents.length) |
| return []; |
| |
| if (this._shouldAutomaticallySelectExecutionContext) |
| return [this._automaticExecutionContextPathComponent]; |
| |
| if (WI.runtimeManager.activeExecutionContext === WI.mainTarget.executionContext) |
| return [this._mainExecutionContextPathComponent]; |
| |
| return this._otherExecutionContextPathComponents.filter((component) => component.representedObject === WI.runtimeManager.activeExecutionContext); |
| } |
| |
| _rebuildExecutionContextPathComponents() |
| { |
| let components = this._executionContextPathComponentsToDisplay(); |
| let isEmpty = !components.length; |
| |
| this._executionContextSelectorItem.element.classList.toggle("automatic-execution-context", this._shouldAutomaticallySelectExecutionContext); |
| this._executionContextSelectorItem.components = components; |
| |
| this._executionContextSelectorItem.hidden = isEmpty; |
| this._executionContextSelectorDivider.hidden = isEmpty; |
| |
| } |
| |
| _framePageExecutionContextsChanged(event) |
| { |
| let frame = event.target; |
| |
| let newExecutionContextPathComponent = this._insertExecutionContextPathComponentForFrame(frame); |
| |
| if (this._restoreSelectedExecutionContextForFrame === frame) { |
| this._restoreSelectedExecutionContextForFrame = null; |
| |
| this._selectExecutionContext(newExecutionContextPathComponent.representedObject); |
| } |
| } |
| |
| _frameExecutionContextsCleared(event) |
| { |
| let frame = event.target; |
| |
| // If this frame is navigating and it is selected in the UI we want to reselect its new item after navigation. |
| if (event.data.committingProvisionalLoad && !this._restoreSelectedExecutionContextForFrame) { |
| let executionContextPathComponent = this._frameToPathComponent.get(frame); |
| if (executionContextPathComponent && executionContextPathComponent.representedObject === WI.runtimeManager.activeExecutionContext) { |
| this._restoreSelectedExecutionContextForFrame = frame; |
| // As a fail safe, if the frame never gets an execution context, clear the restore value. |
| setTimeout(() => { |
| this._restoreSelectedExecutionContextForFrame = false; |
| }, 10); |
| } |
| } |
| |
| this._removeExecutionContextPathComponentForFrame(frame); |
| } |
| |
| _activeExecutionContextChanged(event) |
| { |
| this._rebuildExecutionContextPathComponents(); |
| } |
| |
| _createExecutionContextPathComponent(executionContext, preferredName) |
| { |
| console.assert(!executionContext || executionContext instanceof WI.ExecutionContext); |
| |
| let pathComponent = new WI.HierarchicalPathComponent(preferredName || executionContext.name, "execution-context", executionContext, true, true); |
| pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this); |
| pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this); |
| pathComponent.truncatedDisplayNameLength = 50; |
| return pathComponent; |
| } |
| |
| _compareExecutionContextPathComponents(a, b) |
| { |
| let aExecutionContext = a.representedObject; |
| let bExecutionContext = b.representedObject; |
| |
| // "Targets" (workers) at the top. |
| let aNonMainTarget = aExecutionContext.target !== WI.mainTarget; |
| let bNonMainTarget = bExecutionContext.target !== WI.mainTarget; |
| if (aNonMainTarget && !bNonMainTarget) |
| return -1; |
| if (bNonMainTarget && !aNonMainTarget) |
| return 1; |
| if (aNonMainTarget && bNonMainTarget) |
| return a.displayName.extendedLocaleCompare(b.displayName); |
| |
| // "Main Frame" follows. |
| if (aExecutionContext === WI.mainTarget.executionContext) |
| return -1; |
| if (bExecutionContext === WI.mainTarget.executionContext) |
| return 1; |
| |
| // Only Frame contexts remain. |
| console.assert(aExecutionContext.frame); |
| console.assert(bExecutionContext.frame); |
| |
| // Frames with a name above frames without a name. |
| if (aExecutionContext.frame.name && !bExecutionContext.frame.name) |
| return -1; |
| if (!aExecutionContext.frame.name && bExecutionContext.frame.name) |
| return 1; |
| |
| return a.displayName.extendedLocaleCompare(b.displayName); |
| } |
| |
| _insertOtherExecutionContextPathComponent(executionContextPathComponent) |
| { |
| let index = insertionIndexForObjectInListSortedByFunction(executionContextPathComponent, this._otherExecutionContextPathComponents, this._compareExecutionContextPathComponents); |
| |
| let prev = index > 0 ? this._otherExecutionContextPathComponents[index - 1] : this._mainExecutionContextPathComponent; |
| let next = this._otherExecutionContextPathComponents[index] || null; |
| if (prev) { |
| prev.nextSibling = executionContextPathComponent; |
| executionContextPathComponent.previousSibling = prev; |
| } |
| if (next) { |
| next.previousSibling = executionContextPathComponent; |
| executionContextPathComponent.nextSibling = next; |
| } |
| |
| this._otherExecutionContextPathComponents.splice(index, 0, executionContextPathComponent); |
| |
| this._rebuildExecutionContextPathComponents(); |
| } |
| |
| _removeOtherExecutionContextPathComponent(executionContextPathComponent) |
| { |
| executionContextPathComponent.removeEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this); |
| executionContextPathComponent.removeEventListener(WI.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this); |
| |
| let prev = executionContextPathComponent.previousSibling; |
| let next = executionContextPathComponent.nextSibling; |
| if (prev) |
| prev.nextSibling = next; |
| if (next) |
| next.previousSibling = prev; |
| |
| this._otherExecutionContextPathComponents.remove(executionContextPathComponent, true); |
| |
| this._rebuildExecutionContextPathComponents(); |
| } |
| |
| _insertExecutionContextPathComponentForFrame(frame) |
| { |
| if (frame.isMainFrame()) |
| return this._mainExecutionContextPathComponent; |
| |
| let executionContextPathComponent = this._createExecutionContextPathComponent(frame.pageExecutionContext, this._preferredNameForFrame(frame)); |
| this._insertOtherExecutionContextPathComponent(executionContextPathComponent); |
| this._frameToPathComponent.set(frame, executionContextPathComponent); |
| return executionContextPathComponent; |
| } |
| |
| _removeExecutionContextPathComponentForFrame(frame) |
| { |
| if (frame.isMainFrame()) { |
| this._shouldAutomaticallySelectExecutionContext = true; |
| return; |
| } |
| |
| let executionContextPathComponent = this._frameToPathComponent.take(frame); |
| this._removeOtherExecutionContextPathComponent(executionContextPathComponent); |
| } |
| |
| _targetAdded(event) |
| { |
| let target = event.data.target; |
| if (target.type !== WI.TargetType.Worker) |
| return; |
| |
| console.assert(target.type === WI.TargetType.Worker); |
| let preferredName = WI.UIString("Worker \u2014 %s").format(target.displayName); |
| let executionContextPathComponent = this._createExecutionContextPathComponent(target.executionContext, preferredName); |
| |
| this._targetToPathComponent.set(target, executionContextPathComponent); |
| this._insertOtherExecutionContextPathComponent(executionContextPathComponent); |
| } |
| |
| _targetRemoved(event) |
| { |
| let target = event.data.target; |
| if (target.type !== WI.TargetType.Worker) |
| return; |
| |
| let executionContextPathComponent = this._targetToPathComponent.take(target); |
| |
| if (WI.runtimeManager.activeExecutionContext === executionContextPathComponent.representedObject) { |
| this._shouldAutomaticallySelectExecutionContext = true; |
| this._selectExecutionContext(); |
| } |
| |
| this._removeOtherExecutionContextPathComponent(executionContextPathComponent); |
| } |
| |
| _pathComponentSelected(event) |
| { |
| this._shouldAutomaticallySelectExecutionContext = event.data.pathComponent === this._automaticExecutionContextPathComponent; |
| |
| // Only manually rebuild the execution context path components if the newly selected |
| // execution context matches the previously selected one. |
| if (!this._selectExecutionContext(event.data.pathComponent.representedObject)) |
| this._rebuildExecutionContextPathComponents(); |
| } |
| |
| _pathComponentClicked(event) |
| { |
| this.prompt.focus(); |
| } |
| |
| _debuggerActiveCallFrameDidChange(event) |
| { |
| this._rebuildExecutionContextPathComponents(); |
| } |
| |
| _toggleOrFocus(event) |
| { |
| 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()); |
| } |
| |
| _handleInspectedNodeChanged(event) |
| { |
| this._selectExecutionContext(WI.runtimeManager.activeExecutionContext); |
| } |
| }; |