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