blob: 5c51a3a4c0bed417351c0c18977548df5f4dbb91 [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._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());
}
};