blob: 781524274b4f29e9178c50c6a4c614d5669e63b8 [file] [log] [blame]
/*
* Copyright (C) 2013 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.
*/
WebInspector.QuickConsole = function(element)
{
// FIXME: Convert this to a WebInspector.Object subclass, and call super().
// WebInspector.Object.call(this);
this._toggleOrFocusKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Escape, this._toggleOrFocus.bind(this));
var mainFrameExecutionContext = new WebInspector.ExecutionContext(WebInspector.QuickConsole.MainFrameContextExecutionIdentifier, WebInspector.UIString("Main Frame"), true, null);
this._mainFrameExecutionContextPathComponent = this._createExecutionContextPathComponent(mainFrameExecutionContext.name, mainFrameExecutionContext.identifier);
this._selectedExecutionContextPathComponent = this._mainFrameExecutionContextPathComponent;
this._otherExecutionContextPathComponents = [];
this._frameIdentifierToExecutionContextPathComponentMap = {};
this._element = element || document.createElement("div");
this._element.classList.add(WebInspector.QuickConsole.StyleClassName);
this.prompt = new WebInspector.ConsolePrompt(null, "text/javascript");
this.prompt.element.classList.add(WebInspector.QuickConsole.TextPromptStyleClassName);
this._element.appendChild(this.prompt.element);
// 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() { WebInspector.toggleSplitConsole(); };
this.prompt.shown();
this._navigationBar = new WebInspector.QuickConsoleNavigationBar;
this._element.appendChild(this._navigationBar.element);
this._executionContextSelectorItem = new WebInspector.HierarchicalPathNavigationItem;
this._executionContextSelectorItem.showSelectorArrows = true;
this._navigationBar.addNavigationItem(this._executionContextSelectorItem);
this._executionContextSelectorDivider = new WebInspector.DividerNavigationItem;
this._navigationBar.addNavigationItem(this._executionContextSelectorDivider);
this._rebuildExecutionContextPathComponents();
// COMPATIBILITY (iOS 6): Execution contexts did not exist, evaluation worked with frame ids.
if (WebInspector.ExecutionContext.supported()) {
WebInspector.Frame.addEventListener(WebInspector.Frame.Event.PageExecutionContextChanged, this._framePageExecutionContextsChanged, this);
WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ExecutionContextsCleared, this._frameExecutionContextsCleared, this);
} else {
WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.FrameWasAdded, this._frameAdded, this);
WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.FrameWasRemoved, this._frameRemoved, this);
WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._frameMainResourceChanged, this);
}
WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._debuggerActiveCallFrameDidChange, this);
};
WebInspector.QuickConsole.StyleClassName = "quick-console";
WebInspector.QuickConsole.ShowingLogClassName = "showing-log";
WebInspector.QuickConsole.NavigationBarContainerStyleClassName = "navigation-bar-container";
WebInspector.QuickConsole.NavigationBarSpacerStyleClassName = "navigation-bar-spacer";
WebInspector.QuickConsole.TextPromptStyleClassName = "text-prompt";
WebInspector.QuickConsole.ToolbarSingleLineHeight = 21;
WebInspector.QuickConsole.ToolbarPromptPadding = 4;
WebInspector.QuickConsole.ToolbarTopBorder = 1;
WebInspector.QuickConsole.MainFrameContextExecutionIdentifier = undefined;
WebInspector.QuickConsole.Event = {
DidResize: "quick-console-did-resize"
};
WebInspector.QuickConsole.prototype = {
constructor: WebInspector.QuickConsole,
// Public
get element()
{
return this._element;
},
get navigationBar()
{
return this._navigationBar;
},
get executionContextIdentifier()
{
return this._selectedExecutionContextPathComponent._executionContextIdentifier;
},
updateLayout: function()
{
// A hard maximum size of 33% of the window.
const maximumAllowedHeight = Math.round(window.innerHeight * 0.33);
this.prompt.element.style.maxHeight = maximumAllowedHeight + "px";
},
consoleLogVisibilityChanged: function(visible)
{
if (visible)
this.element.classList.add(WebInspector.QuickConsole.ShowingLogClassName);
else
this.element.classList.remove(WebInspector.QuickConsole.ShowingLogClassName);
this.dispatchEventToListeners(WebInspector.QuickConsole.Event.DidResize);
},
// Private
_executionContextPathComponentsToDisplay: function()
{
// If we are in the debugger the console will use the active call frame, don't show the selector.
if (WebInspector.debuggerManager.activeCallFrame)
return [];
// If there is only the Main Frame, don't show the selector.
if (!this._otherExecutionContextPathComponents.length)
return [];
return [this._selectedExecutionContextPathComponent];
},
_rebuildExecutionContextPathComponents: function()
{
var components = this._executionContextPathComponentsToDisplay();
var isEmpty = !components.length;
this._executionContextSelectorItem.components = components;
this._executionContextSelectorItem.hidden = isEmpty;
this._executionContextSelectorDivider.hidden = isEmpty;
},
_framePageExecutionContextsChanged: function(event)
{
var frame = event.target;
var shouldAutomaticallySelect = this._restoreSelectedExecutionContextForFrame === frame;
var newExecutionContextPathComponent = this._insertExecutionContextPathComponentForFrame(frame, shouldAutomaticallySelect);
if (shouldAutomaticallySelect) {
delete this._restoreSelectedExecutionContextForFrame;
this._selectedExecutionContextPathComponent = newExecutionContextPathComponent;
this._rebuildExecutionContextPathComponents();
}
},
_frameExecutionContextsCleared: function(event)
{
var 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) {
var executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
if (this._selectedExecutionContextPathComponent === executionContextPathComponent) {
this._restoreSelectedExecutionContextForFrame = frame;
// As a fail safe, if the frame never gets an execution context, clear the restore value.
setTimeout(function() { delete this._restoreSelectedExecutionContextForFrame; }.bind(this), 10);
}
}
this._removeExecutionContextPathComponentForFrame(frame);
},
_frameAdded: function(event)
{
var frame = event.data.frame;
this._insertExecutionContextPathComponentForFrame(frame);
},
_frameRemoved: function(event)
{
var frame = event.data.frame;
this._removeExecutionContextPathComponentForFrame(frame);
},
_frameMainResourceChanged: function(event)
{
var frame = event.target;
this._updateExecutionContextPathComponentForFrame(frame);
},
_createExecutionContextPathComponent: function(name, identifier)
{
var pathComponent = new WebInspector.HierarchicalPathComponent(name, "execution-context", identifier, true, true);
pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this);
pathComponent.truncatedDisplayNameLength = 50;
pathComponent._executionContextIdentifier = identifier;
return pathComponent;
},
_createExecutionContextPathComponentFromFrame: function(frame)
{
var name = frame.name ? frame.name + " \u2014 " + frame.mainResource.displayName : frame.mainResource.displayName;
var identifier = WebInspector.ExecutionContext.supported() ? frame.pageExecutionContext.id : frame.id;
var pathComponent = this._createExecutionContextPathComponent(name, identifier);
pathComponent._frame = frame;
return pathComponent;
},
_compareExecutionContextPathComponents: function(a, b)
{
// "Main Frame" always on top.
if (!a._frame)
return -1;
if (!b._frame)
return 1;
// Frames with a name above frames without a name.
if (a._frame.name && !b._frame.name)
return -1;
if (!a._frame.name && b._frame.name)
return 1;
return a.displayName.localeCompare(b.displayName);
},
_insertExecutionContextPathComponentForFrame: function(frame, skipRebuild)
{
if (frame.isMainFrame())
return this._mainFrameExecutionContextPathComponent;
console.assert(!this._frameIdentifierToExecutionContextPathComponentMap[frame.id]);
if (this._frameIdentifierToExecutionContextPathComponentMap[frame.id])
return null;
var executionContextPathComponent = this._createExecutionContextPathComponentFromFrame(frame);
var index = insertionIndexForObjectInListSortedByFunction(executionContextPathComponent, this._otherExecutionContextPathComponents, this._compareExecutionContextPathComponents);
var prev = index > 0 ? this._otherExecutionContextPathComponents[index - 1] : this._mainFrameExecutionContextPathComponent;
var 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._frameIdentifierToExecutionContextPathComponentMap[frame.id] = executionContextPathComponent;
if (!skipRebuild)
this._rebuildExecutionContextPathComponents();
return executionContextPathComponent;
},
_removeExecutionContextPathComponentForFrame: function(frame, skipRebuild)
{
if (frame.isMainFrame())
return;
var executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
console.assert(executionContextPathComponent);
if (!executionContextPathComponent)
return;
executionContextPathComponent.removeEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
executionContextPathComponent.removeEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this);
var prev = executionContextPathComponent.previousSibling;
var next = executionContextPathComponent.nextSibling;
if (prev)
prev.nextSibling = next;
if (next)
next.previousSibling = prev;
if (this._selectedExecutionContextPathComponent === executionContextPathComponent)
this._selectedExecutionContextPathComponent = this._mainFrameExecutionContextPathComponent;
this._otherExecutionContextPathComponents.remove(executionContextPathComponent, true);
delete this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
if (!skipRebuild)
this._rebuildExecutionContextPathComponents();
},
_updateExecutionContextPathComponentForFrame: function(frame)
{
if (frame.isMainFrame())
return;
var executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
if (!executionContextPathComponent)
return;
var wasSelected = this._selectedExecutionContextPathComponent === executionContextPathComponent;
this._removeExecutionContextPathComponentForFrame(frame, true);
var newExecutionContextPathComponent = this._insertExecutionContextPathComponentForFrame(frame, true);
if (wasSelected)
this._selectedExecutionContextPathComponent = newExecutionContextPathComponent;
this._rebuildExecutionContextPathComponents();
},
_pathComponentSelected: function(event)
{
if (event.data.pathComponent === this._selectedExecutionContextPathComponent)
return;
this._selectedExecutionContextPathComponent = event.data.pathComponent;
this._rebuildExecutionContextPathComponents();
},
_pathComponentClicked: function(event)
{
this.prompt.focus();
},
_debuggerActiveCallFrameDidChange: function(event)
{
this._rebuildExecutionContextPathComponents();
},
_toggleOrFocus: function(event)
{
if (this.prompt.focused)
WebInspector.toggleSplitConsole();
else if (!WebInspector.isEditingAnyField() && !WebInspector.isEventTargetAnEditableField(event))
this.prompt.focus();
}
};
WebInspector.QuickConsole.prototype.__proto__ = WebInspector.Object.prototype;