Web Inspector: Remove redundant tooltips
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / QuickConsole.js
1 /*
2  * Copyright (C) 2013-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 WI.QuickConsole = class QuickConsole extends WI.View
27 {
28     constructor(element)
29     {
30         super(element);
31
32         this._toggleOrFocusKeyboardShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Escape, this._toggleOrFocus.bind(this));
33         this._toggleOrFocusKeyboardShortcut.implicitlyPreventsDefault = false;
34
35         this._mainExecutionContextPathComponent = this._createExecutionContextPathComponent(WI.mainTarget.executionContext);
36
37         this._otherExecutionContextPathComponents = [];
38         this._frameToPathComponent = new Map;
39         this._targetToPathComponent = new Map;
40
41         this._restoreSelectedExecutionContextForFrame = false;
42
43         this.element.classList.add("quick-console");
44         this.element.addEventListener("mousedown", this._handleMouseDown.bind(this));
45
46         this.prompt = new WI.ConsolePrompt(null, "text/javascript");
47         this.prompt.element.classList.add("text-prompt");
48         this.addSubview(this.prompt);
49
50         // FIXME: CodeMirror 4 has a default "Esc" key handler that always prevents default.
51         // Our keyboard shortcut above will respect the default prevented and ignore the event
52         // and not toggle the console. Install our own Escape key handler that will trigger
53         // when the ConsolePrompt is empty, to restore toggling behavior. A better solution
54         // would be for CodeMirror's event handler to pass if it doesn't do anything.
55         this.prompt.escapeKeyHandlerWhenEmpty = function() { WI.toggleSplitConsole(); };
56
57         this._navigationBar = new WI.QuickConsoleNavigationBar;
58         this.addSubview(this._navigationBar);
59
60         this._executionContextSelectorItem = new WI.HierarchicalPathNavigationItem;
61         this._executionContextSelectorItem.showSelectorArrows = true;
62         this._navigationBar.addNavigationItem(this._executionContextSelectorItem);
63
64         this._executionContextSelectorDivider = new WI.DividerNavigationItem;
65         this._navigationBar.addNavigationItem(this._executionContextSelectorDivider);
66
67         this._rebuildExecutionContextPathComponents();
68
69         WI.consoleDrawer.toggleButtonShortcutTooltip(this._toggleOrFocusKeyboardShortcut);
70         WI.consoleDrawer.addEventListener(WI.ConsoleDrawer.Event.CollapsedStateChanged, this._updateStyles, this);
71
72         WI.Frame.addEventListener(WI.Frame.Event.PageExecutionContextChanged, this._framePageExecutionContextsChanged, this);
73         WI.Frame.addEventListener(WI.Frame.Event.ExecutionContextsCleared, this._frameExecutionContextsCleared, this);
74
75         WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this._debuggerActiveCallFrameDidChange, this);
76
77         WI.runtimeManager.addEventListener(WI.RuntimeManager.Event.ActiveExecutionContextChanged, this._activeExecutionContextChanged, this);
78
79         WI.targetManager.addEventListener(WI.TargetManager.Event.TargetAdded, this._targetAdded, this);
80         WI.targetManager.addEventListener(WI.TargetManager.Event.TargetRemoved, this._targetRemoved, this);
81
82         WI.TabBrowser.addEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._updateStyles, this);
83     }
84
85     // Public
86
87     get navigationBar()
88     {
89         return this._navigationBar;
90     }
91
92     get selectedExecutionContext()
93     {
94         return WI.runtimeManager.activeExecutionContext;
95     }
96
97     set selectedExecutionContext(executionContext)
98     {
99         WI.runtimeManager.activeExecutionContext = executionContext;
100     }
101
102     closed()
103     {
104         WI.Frame.removeEventListener(null, null, this);
105         WI.debuggerManager.removeEventListener(null, null, this);
106         WI.runtimeManager.removeEventListener(null, null, this);
107         WI.targetManager.removeEventListener(null, null, this);
108         WI.consoleDrawer.removeEventListener(null, null, this);
109         WI.TabBrowser.removeEventListener(null, null, this);
110
111         super.closed();
112     }
113
114     // Protected
115
116     layout()
117     {
118         // A hard maximum size of 33% of the window.
119         let maximumAllowedHeight = Math.round(window.innerHeight * 0.33);
120         this.prompt.element.style.maxHeight = maximumAllowedHeight + "px";
121     }
122
123     // Private
124
125     _handleMouseDown(event)
126     {
127         if (event.target !== this.element)
128             return;
129
130         event.preventDefault();
131         this.prompt.focus();
132     }
133
134     _executionContextPathComponentsToDisplay()
135     {
136         // If we are in the debugger the console will use the active call frame, don't show the selector.
137         if (WI.debuggerManager.activeCallFrame)
138             return [];
139
140         // If there is only the Main ExecutionContext, don't show the selector.
141         if (!this._otherExecutionContextPathComponents.length)
142             return [];
143
144         if (this.selectedExecutionContext === WI.mainTarget.executionContext)
145             return [this._mainExecutionContextPathComponent];
146
147         return this._otherExecutionContextPathComponents.filter((component) => component.representedObject === this.selectedExecutionContext);
148     }
149
150     _rebuildExecutionContextPathComponents()
151     {
152         let components = this._executionContextPathComponentsToDisplay();
153         let isEmpty = !components.length;
154
155         this._executionContextSelectorItem.components = components;
156
157         this._executionContextSelectorItem.hidden = isEmpty;
158         this._executionContextSelectorDivider.hidden = isEmpty;
159     }
160
161     _framePageExecutionContextsChanged(event)
162     {
163         let frame = event.target;
164
165         let shouldAutomaticallySelect = this._restoreSelectedExecutionContextForFrame === frame;
166
167         let newExecutionContextPathComponent = this._insertExecutionContextPathComponentForFrame(frame, shouldAutomaticallySelect);
168
169         if (shouldAutomaticallySelect) {
170             this._restoreSelectedExecutionContextForFrame = null;
171             this.selectedExecutionContext = newExecutionContextPathComponent.representedObject;
172         }
173     }
174
175     _frameExecutionContextsCleared(event)
176     {
177         let frame = event.target;
178
179         // If this frame is navigating and it is selected in the UI we want to reselect its new item after navigation.
180         if (event.data.committingProvisionalLoad && !this._restoreSelectedExecutionContextForFrame) {
181             let executionContextPathComponent = this._frameToPathComponent.get(frame);
182             if (executionContextPathComponent && executionContextPathComponent.representedObject === this.selectedExecutionContext) {
183                 this._restoreSelectedExecutionContextForFrame = frame;
184                 // As a fail safe, if the frame never gets an execution context, clear the restore value.
185                 setTimeout(() => { this._restoreSelectedExecutionContextForFrame = false; }, 10);
186             }
187         }
188
189         this._removeExecutionContextPathComponentForFrame(frame);
190     }
191
192     _activeExecutionContextChanged(event)
193     {
194         this._rebuildExecutionContextPathComponents();
195
196         this._executionContextSelectorItem.element.classList.toggle("non-default-execution-context", this.selectedExecutionContext !== WI.mainTarget.executionContext);
197     }
198
199     _createExecutionContextPathComponent(executionContext, preferredName)
200     {
201         console.assert(executionContext instanceof WI.ExecutionContext);
202
203         let pathComponent = new WI.HierarchicalPathComponent(preferredName || executionContext.name, "execution-context", executionContext, true, true);
204         pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
205         pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this);
206         pathComponent.truncatedDisplayNameLength = 50;
207         return pathComponent;
208     }
209
210     _createExecutionContextPathComponentFromFrame(frame)
211     {
212         let preferredName = frame.name ? frame.name + " \u2014 " + frame.mainResource.displayName : frame.mainResource.displayName;
213         return this._createExecutionContextPathComponent(frame.pageExecutionContext, preferredName);
214     }
215
216     _compareExecutionContextPathComponents(a, b)
217     {
218         let aExecutionContext = a.representedObject;
219         let bExecutionContext = b.representedObject;
220
221         // "Targets" (workers) at the top.
222         let aNonMainTarget = aExecutionContext.target !== WI.mainTarget;
223         let bNonMainTarget = bExecutionContext.target !== WI.mainTarget;
224         if (aNonMainTarget && !bNonMainTarget)
225             return -1;
226         if (bNonMainTarget && !aNonMainTarget)
227             return 1;
228         if (aNonMainTarget && bNonMainTarget)
229             return a.displayName.extendedLocaleCompare(b.displayName);
230
231         // "Main Frame" follows.
232         if (aExecutionContext === WI.mainTarget.executionContext)
233             return -1;
234         if (bExecutionContext === WI.mainTarget.executionContext)
235             return 1;
236
237         // Only Frame contexts remain.
238         console.assert(aExecutionContext.frame);
239         console.assert(bExecutionContext.frame);
240
241         // Frames with a name above frames without a name.
242         if (aExecutionContext.frame.name && !bExecutionContext.frame.name)
243             return -1;
244         if (!aExecutionContext.frame.name && bExecutionContext.frame.name)
245             return 1;
246
247         return a.displayName.extendedLocaleCompare(b.displayName);
248     }
249
250     _insertOtherExecutionContextPathComponent(executionContextPathComponent, skipRebuild)
251     {
252         let index = insertionIndexForObjectInListSortedByFunction(executionContextPathComponent, this._otherExecutionContextPathComponents, this._compareExecutionContextPathComponents);
253
254         let prev = index > 0 ? this._otherExecutionContextPathComponents[index - 1] : this._mainExecutionContextPathComponent;
255         let next = this._otherExecutionContextPathComponents[index] || null;
256         if (prev) {
257             prev.nextSibling = executionContextPathComponent;
258             executionContextPathComponent.previousSibling = prev;
259         }
260         if (next) {
261             next.previousSibling = executionContextPathComponent;
262             executionContextPathComponent.nextSibling = next;
263         }
264
265         this._otherExecutionContextPathComponents.splice(index, 0, executionContextPathComponent);
266
267         if (!skipRebuild)
268             this._rebuildExecutionContextPathComponents();
269     }
270
271     _removeOtherExecutionContextPathComponent(executionContextPathComponent, skipRebuild)
272     {
273         executionContextPathComponent.removeEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
274         executionContextPathComponent.removeEventListener(WI.HierarchicalPathComponent.Event.Clicked, this._pathComponentClicked, this);
275
276         let prev = executionContextPathComponent.previousSibling;
277         let next = executionContextPathComponent.nextSibling;
278         if (prev)
279             prev.nextSibling = next;
280         if (next)
281             next.previousSibling = prev;
282
283         this._otherExecutionContextPathComponents.remove(executionContextPathComponent, true);
284
285         if (!skipRebuild)
286             this._rebuildExecutionContextPathComponents();
287     }
288
289     _insertExecutionContextPathComponentForFrame(frame, skipRebuild)
290     {
291         if (frame.isMainFrame())
292             return this._mainExecutionContextPathComponent;
293
294         let executionContextPathComponent = this._createExecutionContextPathComponentFromFrame(frame);
295         this._insertOtherExecutionContextPathComponent(executionContextPathComponent, skipRebuild);
296         this._frameToPathComponent.set(frame, executionContextPathComponent);
297
298         return executionContextPathComponent;
299     }
300
301     _removeExecutionContextPathComponentForFrame(frame, skipRebuild)
302     {
303         if (frame.isMainFrame())
304             return;
305
306         let executionContextPathComponent = this._frameToPathComponent.take(frame);
307         this._removeOtherExecutionContextPathComponent(executionContextPathComponent, skipRebuild);
308     }
309
310     _targetAdded(event)
311     {
312         let target = event.data.target;
313         console.assert(target.type === WI.Target.Type.Worker);
314         let preferredName = WI.UIString("Worker \u2014 %s").format(target.displayName);
315         let executionContextPathComponent = this._createExecutionContextPathComponent(target.executionContext, preferredName);
316
317         this._targetToPathComponent.set(target, executionContextPathComponent);
318         this._insertOtherExecutionContextPathComponent(executionContextPathComponent);
319     }
320
321     _targetRemoved(event)
322     {
323         let target = event.data.target;
324         let executionContextPathComponent = this._targetToPathComponent.take(target);
325         this._removeOtherExecutionContextPathComponent(executionContextPathComponent);
326
327         if (this.selectedExecutionContext === executionContextPathComponent.representedObject)
328             this.selectedExecutionContext = WI.mainTarget.executionContext;
329     }
330
331     _pathComponentSelected(event)
332     {
333         let executionContext = event.data.pathComponent.representedObject;
334         this.selectedExecutionContext = executionContext;
335     }
336
337     _pathComponentClicked(event)
338     {
339         this.prompt.focus();
340     }
341
342     _debuggerActiveCallFrameDidChange(event)
343     {
344         this._rebuildExecutionContextPathComponents();
345     }
346
347     _toggleOrFocus(event)
348     {
349         if (this.prompt.focused) {
350             WI.toggleSplitConsole();
351             event.preventDefault();
352         } else if (!WI.isEditingAnyField() && !WI.isEventTargetAnEditableField(event)) {
353             this.prompt.focus();
354             event.preventDefault();
355         }
356     }
357
358     _updateStyles()
359     {
360         this.element.classList.toggle("showing-log", WI.isShowingConsoleTab() || WI.isShowingSplitConsole());
361     }
362 };