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