blob: 01eea3d828b0a612408158826a4200b70e39e551 [file] [log] [blame]
/*
* 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._automaticExecutionContextPathComponent.tooltip = WI.UIString("Execution context for $0");
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.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.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.executionContext)
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;
WI.runtimeManager.activeExecutionContext = executionContext;
this._automaticExecutionContextPathComponent.displayName = WI.UIString("Auto - %s").format(preferredName || executionContext.name);
}
_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.Target.Type.Worker)
return;
console.assert(target.type === WI.Target.Type.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.Target.Type.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;
this._selectExecutionContext(event.data.pathComponent.representedObject);
}
_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);
}
};