Web Inspector: QuickConsole should update its selection when RuntimeManager.defaultEx...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / QuickConsole.js
1 /*
2  * Copyright (C) 2013, 2015, 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.QuickConsole = class QuickConsole extends WebInspector.View
27 {
28     constructor(element)
29     {
30         super(element);
31
32         this._toggleOrFocusKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Escape, this._toggleOrFocus.bind(this));
33
34         let mainFrameExecutionContext = new WebInspector.ExecutionContext(WebInspector.RuntimeManager.TopLevelContextExecutionIdentifier, WebInspector.UIString("Main Frame"), true, null);
35         this._mainFrameExecutionContextPathComponent = this._createExecutionContextPathComponent(mainFrameExecutionContext.name, mainFrameExecutionContext.identifier);
36
37         this._otherExecutionContextPathComponents = [];
38         this._frameIdentifierToExecutionContextPathComponentMap = {};
39
40         this.element.classList.add("quick-console");
41         this.element.addEventListener("mousedown", this._handleMouseDown.bind(this));
42
43         this.prompt = new WebInspector.ConsolePrompt(null, "text/javascript");
44         this.prompt.element.classList.add("text-prompt");
45         this.addSubview(this.prompt);
46
47         // FIXME: CodeMirror 4 has a default "Esc" key handler that always prevents default.
48         // Our keyboard shortcut above will respect the default prevented and ignore the event
49         // and not toggle the console. Install our own Escape key handler that will trigger
50         // when the ConsolePrompt is empty, to restore toggling behavior. A better solution
51         // would be for CodeMirror's event handler to pass if it doesn't do anything.
52         this.prompt.escapeKeyHandlerWhenEmpty = function() { WebInspector.toggleSplitConsole(); };
53
54         this.prompt.shown();
55
56         this._navigationBar = new WebInspector.QuickConsoleNavigationBar;
57         this.addSubview(this._navigationBar);
58
59         this._executionContextSelectorItem = new WebInspector.HierarchicalPathNavigationItem;
60         this._executionContextSelectorItem.showSelectorArrows = true;
61         this._navigationBar.addNavigationItem(this._executionContextSelectorItem);
62
63         this._executionContextSelectorDivider = new WebInspector.DividerNavigationItem;
64         this._navigationBar.addNavigationItem(this._executionContextSelectorDivider);
65
66         this._rebuildExecutionContextPathComponents();
67
68         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.PageExecutionContextChanged, this._framePageExecutionContextsChanged, this);
69         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ExecutionContextsCleared, this._frameExecutionContextsCleared, this);
70
71         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._debuggerActiveCallFrameDidChange, this);
72
73         WebInspector.runtimeManager.addEventListener(WebInspector.RuntimeManager.Event.DefaultExecutionContextChanged, this._defaultExecutionContextChanged, this);
74     }
75
76     // Public
77
78     get navigationBar()
79     {
80         return this._navigationBar;
81     }
82
83     get selectedExecutionContextIdentifier()
84     {
85         return WebInspector.runtimeManager.defaultExecutionContextIdentifier;
86     }
87
88     set selectedExecutionContextIdentifier(value)
89     {
90         if (value === this.selectedExecutionContextIdentifier)
91             return;
92
93         WebInspector.runtimeManager.defaultExecutionContextIdentifier = value;
94     }
95
96     consoleLogVisibilityChanged(visible)
97     {
98         if (visible === this.element.classList.contains(WebInspector.QuickConsole.ShowingLogClassName))
99             return;
100
101         this.element.classList.toggle(WebInspector.QuickConsole.ShowingLogClassName, visible);
102
103         this.dispatchEventToListeners(WebInspector.QuickConsole.Event.DidResize);
104     }
105
106     // Protected
107
108     layout()
109     {
110         // A hard maximum size of 33% of the window.
111         let maximumAllowedHeight = Math.round(window.innerHeight * 0.33);
112         this.prompt.element.style.maxHeight = maximumAllowedHeight + "px";
113     }
114
115     // Private
116
117     _handleMouseDown(event)
118     {
119         if (event.target !== this.element)
120             return;
121
122         event.preventDefault();
123         this.prompt.focus();
124     }
125
126     _executionContextPathComponentsToDisplay()
127     {
128         // If we are in the debugger the console will use the active call frame, don't show the selector.
129         if (WebInspector.debuggerManager.activeCallFrame)
130             return [];
131
132         // If there is only the Main Frame, don't show the selector.
133         if (!this._otherExecutionContextPathComponents.length)
134             return [];
135
136         if (this.selectedExecutionContextIdentifier === this._mainFrameExecutionContextPathComponent._executionContextIdentifier)
137             return [this._mainFrameExecutionContextPathComponent];
138
139         return this._otherExecutionContextPathComponents.filter((component) => component._executionContextIdentifier === this.selectedExecutionContextIdentifier);
140     }
141
142     _rebuildExecutionContextPathComponents()
143     {
144         var components = this._executionContextPathComponentsToDisplay();
145         var isEmpty = !components.length;
146
147         this._executionContextSelectorItem.components = components;
148
149         this._executionContextSelectorItem.hidden = isEmpty;
150         this._executionContextSelectorDivider.hidden = isEmpty;
151     }
152
153     _framePageExecutionContextsChanged(event)
154     {
155         var frame = event.target;
156
157         var shouldAutomaticallySelect = this._restoreSelectedExecutionContextForFrame === frame;
158
159         var newExecutionContextPathComponent = this._insertExecutionContextPathComponentForFrame(frame, shouldAutomaticallySelect);
160
161         if (shouldAutomaticallySelect) {
162             this._restoreSelectedExecutionContextForFrame = null;
163             this.selectedExecutionContextIdentifier = newExecutionContextPathComponent._executionContextIdentifier;
164         }
165     }
166
167     _frameExecutionContextsCleared(event)
168     {
169         let frame = event.target;
170
171         // If this frame is navigating and it is selected in the UI we want to reselect its new item after navigation.
172         if (event.data.committingProvisionalLoad && !this._restoreSelectedExecutionContextForFrame) {
173             let executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
174             if (executionContextPathComponent && executionContextPathComponent._executionContextIdentifier === this.selectedExecutionContextIdentifier) {
175                 this._restoreSelectedExecutionContextForFrame = frame;
176                 // As a fail safe, if the frame never gets an execution context, clear the restore value.
177                 setTimeout(() => { this._restoreSelectedExecutionContextForFrame = false; }, 10);
178             }
179         }
180
181         this._removeExecutionContextPathComponentForFrame(frame);
182     }
183
184     _defaultExecutionContextChanged(event)
185     {
186         this._rebuildExecutionContextPathComponents();
187     }
188
189     _createExecutionContextPathComponent(name, identifier)
190     {
191         var pathComponent = new WebInspector.HierarchicalPathComponent(name, "execution-context", identifier, true, true);
192         pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
193         pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this);
194         pathComponent.truncatedDisplayNameLength = 50;
195         pathComponent._executionContextIdentifier = identifier;
196         return pathComponent;
197     }
198
199     _createExecutionContextPathComponentFromFrame(frame)
200     {
201         var name = frame.name ? frame.name + " \u2014 " + frame.mainResource.displayName : frame.mainResource.displayName;
202
203         var pathComponent = this._createExecutionContextPathComponent(name, frame.pageExecutionContext.id);
204         pathComponent._frame = frame;
205
206         return pathComponent;
207     }
208
209     _compareExecutionContextPathComponents(a, b)
210     {
211         // "Main Frame" always on top.
212         if (!a._frame)
213             return -1;
214         if (!b._frame)
215             return 1;
216
217         // Frames with a name above frames without a name.
218         if (a._frame.name && !b._frame.name)
219             return -1;
220         if (!a._frame.name && b._frame.name)
221             return 1;
222
223         return a.displayName.localeCompare(b.displayName);
224     }
225
226     _insertExecutionContextPathComponentForFrame(frame, skipRebuild)
227     {
228         if (frame.isMainFrame())
229             return this._mainFrameExecutionContextPathComponent;
230
231         console.assert(!this._frameIdentifierToExecutionContextPathComponentMap[frame.id]);
232         if (this._frameIdentifierToExecutionContextPathComponentMap[frame.id])
233             return null;
234
235         var executionContextPathComponent = this._createExecutionContextPathComponentFromFrame(frame);
236
237         var index = insertionIndexForObjectInListSortedByFunction(executionContextPathComponent, this._otherExecutionContextPathComponents, this._compareExecutionContextPathComponents);
238
239         var prev = index > 0 ? this._otherExecutionContextPathComponents[index - 1] : this._mainFrameExecutionContextPathComponent;
240         var next = this._otherExecutionContextPathComponents[index] || null;
241         if (prev) {
242             prev.nextSibling = executionContextPathComponent;
243             executionContextPathComponent.previousSibling = prev;
244         }
245         if (next) {
246             next.previousSibling = executionContextPathComponent;
247             executionContextPathComponent.nextSibling = next;
248         }
249
250         this._otherExecutionContextPathComponents.splice(index, 0, executionContextPathComponent);
251         this._frameIdentifierToExecutionContextPathComponentMap[frame.id] = executionContextPathComponent;
252
253         if (!skipRebuild)
254             this._rebuildExecutionContextPathComponents();
255
256         return executionContextPathComponent;
257     }
258
259     _removeExecutionContextPathComponentForFrame(frame, skipRebuild)
260     {
261         if (frame.isMainFrame())
262             return;
263
264         var executionContextPathComponent = this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
265         console.assert(executionContextPathComponent);
266         if (!executionContextPathComponent)
267             return;
268
269         executionContextPathComponent.removeEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
270         executionContextPathComponent.removeEventListener(WebInspector.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this);
271
272         var prev = executionContextPathComponent.previousSibling;
273         var next = executionContextPathComponent.nextSibling;
274         if (prev)
275             prev.nextSibling = next;
276         if (next)
277             next.previousSibling = prev;
278
279         this._otherExecutionContextPathComponents.remove(executionContextPathComponent, true);
280         delete this._frameIdentifierToExecutionContextPathComponentMap[frame.id];
281
282         if (!skipRebuild)
283             this._rebuildExecutionContextPathComponents();
284     }
285
286     _pathComponentSelected(event)
287     {
288         this.selectedExecutionContextIdentifier = event.data.pathComponent._executionContextIdentifier;
289     }
290
291     _pathComponentClicked(event)
292     {
293         this.prompt.focus();
294     }
295
296     _debuggerActiveCallFrameDidChange(event)
297     {
298         this._rebuildExecutionContextPathComponents();
299     }
300
301     _toggleOrFocus(event)
302     {
303         if (this.prompt.focused)
304             WebInspector.toggleSplitConsole();
305         else if (!WebInspector.isEditingAnyField() && !WebInspector.isEventTargetAnEditableField(event))
306             this.prompt.focus();
307     }
308 };
309
310 WebInspector.QuickConsole.ShowingLogClassName = "showing-log";
311
312 WebInspector.QuickConsole.ToolbarSingleLineHeight = 21;
313 WebInspector.QuickConsole.ToolbarPromptPadding = 4;
314 WebInspector.QuickConsole.ToolbarTopBorder = 1;
315
316 WebInspector.QuickConsole.Event = {
317     DidResize: "quick-console-did-resize"
318 };