Web Inspector: REGRESSION (r244157): Timelines: ruler size appears wrong on first...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / TabBrowser.js
1 /*
2  * Copyright (C) 2015 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.TabBrowser = class TabBrowser extends WI.View
27 {
28     constructor(element, tabBar, navigationSidebar, detailsSidebar)
29     {
30         console.assert(tabBar, "Must provide a TabBar.");
31
32         super(element);
33
34         this.element.classList.add("tab-browser");
35
36         this._tabBar = tabBar;
37         this._navigationSidebar = navigationSidebar || null;
38         this._detailsSidebar = detailsSidebar || null;
39
40         if (this._navigationSidebar) {
41             this._navigationSidebar.addEventListener(WI.Sidebar.Event.CollapsedStateDidChange, this._sidebarCollapsedStateDidChange, this);
42             this._navigationSidebar.addEventListener(WI.Sidebar.Event.WidthDidChange, this._sidebarWidthDidChange, this);
43         }
44
45         if (this._detailsSidebar) {
46             this._detailsSidebar.addEventListener(WI.Sidebar.Event.CollapsedStateDidChange, this._sidebarCollapsedStateDidChange, this);
47             this._detailsSidebar.addEventListener(WI.Sidebar.Event.SidebarPanelSelected, this._sidebarPanelSelected, this);
48             this._detailsSidebar.addEventListener(WI.Sidebar.Event.WidthDidChange, this._sidebarWidthDidChange, this);
49         }
50
51         this._contentViewContainer = new WI.ContentViewContainer;
52         this.addSubview(this._contentViewContainer);
53
54         let showNextTab = () => { this._showNextTab(); };
55         let showPreviousTab = () => { this._showPreviousTab(); };
56
57         let isRTL = WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL;
58
59         let nextKey1 = isRTL ? WI.KeyboardShortcut.Key.LeftCurlyBrace : WI.KeyboardShortcut.Key.RightCurlyBrace;
60         let previousKey1 = isRTL ? WI.KeyboardShortcut.Key.RightCurlyBrace : WI.KeyboardShortcut.Key.LeftCurlyBrace;
61
62         this._showNextTabKeyboardShortcut1 = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, nextKey1, showNextTab);
63         this._showPreviousTabKeyboardShortcut1 = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, previousKey1, showPreviousTab);
64
65         let nextModifier2 = isRTL ? WI.KeyboardShortcut.Modifier.Shift : 0;
66         let previousModifier2 = isRTL ? 0 : WI.KeyboardShortcut.Modifier.Shift;
67
68         this._showNextTabKeyboardShortcut2 = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control | nextModifier2, WI.KeyboardShortcut.Key.Tab, showNextTab);
69         this._showPreviousTabKeyboardShortcut2 = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control | previousModifier2, WI.KeyboardShortcut.Key.Tab, showPreviousTab);
70
71         let previousTabKey = isRTL ? WI.KeyboardShortcut.Key.Right : WI.KeyboardShortcut.Key.Left;
72         let nextTabKey = isRTL ? WI.KeyboardShortcut.Key.Left : WI.KeyboardShortcut.Key.Right;
73         this._previousTabKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, previousTabKey, this._showPreviousTabCheckingForEditableField.bind(this));
74         this._previousTabKeyboardShortcut.implicitlyPreventsDefault = false;
75         this._nextTabKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, nextTabKey, this._showNextTabCheckingForEditableField.bind(this));
76         this._nextTabKeyboardShortcut.implicitlyPreventsDefault = false;
77
78         this._tabBar.addEventListener(WI.TabBar.Event.TabBarItemSelected, this._tabBarItemSelected, this);
79         this._tabBar.addEventListener(WI.TabBar.Event.TabBarItemAdded, this._tabBarItemAdded, this);
80         this._tabBar.addEventListener(WI.TabBar.Event.TabBarItemRemoved, this._tabBarItemRemoved, this);
81
82         this._recentTabContentViews = [];
83         this._closedTabClasses = new Set;
84     }
85
86     // Public
87
88     get tabBar()
89     {
90         return this._tabBar;
91     }
92
93     get navigationSidebar()
94     {
95         return this._navigationSidebar;
96     }
97
98     get detailsSidebar()
99     {
100         return this._detailsSidebar;
101     }
102
103     get selectedTabContentView()
104     {
105         return this._contentViewContainer.currentContentView;
106     }
107
108     bestTabContentViewForClass(constructor)
109     {
110         console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0]);
111
112         for (var tabContentView of this._recentTabContentViews) {
113             if (tabContentView instanceof constructor)
114                 return tabContentView;
115         }
116
117         return null;
118     }
119
120     bestTabContentViewForRepresentedObject(representedObject, options = {})
121     {
122         console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0]);
123
124         let tabContentView = this._recentTabContentViews.find((tabContentView) => tabContentView.type === options.preferredTabType);
125         if (tabContentView && tabContentView.canShowRepresentedObject(representedObject))
126             return tabContentView;
127
128         for (let tabContentView of this._recentTabContentViews) {
129             if (options.ignoreSearchTab && tabContentView instanceof WI.SearchTabContentView)
130                 continue;
131             if (options.ignoreNetworkTab && tabContentView instanceof WI.NetworkTabContentView)
132                 continue;
133
134             if (tabContentView.canShowRepresentedObject(representedObject))
135                 return tabContentView;
136         }
137
138         return null;
139     }
140
141     addTabForContentView(tabContentView, options = {})
142     {
143         console.assert(tabContentView instanceof WI.TabContentView);
144         if (!(tabContentView instanceof WI.TabContentView))
145             return false;
146
147         let tabBarItem = tabContentView.tabBarItem;
148
149         console.assert(tabBarItem instanceof WI.TabBarItem);
150         if (!(tabBarItem instanceof WI.TabBarItem))
151             return false;
152
153         if (tabBarItem.representedObject !== tabContentView)
154             tabBarItem.representedObject = tabContentView;
155
156         if (tabBarItem.parentTabBar === this._tabBar)
157             return true;
158
159         // Add the tab after the first tab content view, since the first
160         // tab content view is the currently selected one.
161         if (this._recentTabContentViews.length && this.selectedTabContentView)
162             this._recentTabContentViews.splice(1, 0, tabContentView);
163         else
164             this._recentTabContentViews.push(tabContentView);
165
166         if (typeof options.insertionIndex === "number")
167             this._tabBar.insertTabBarItem(tabBarItem, options.insertionIndex, options);
168         else
169             this._tabBar.addTabBarItem(tabBarItem, options);
170
171         console.assert(this._recentTabContentViews.length === this._tabBar.saveableTabCount);
172         console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0]);
173
174         return true;
175     }
176
177     showTabForContentView(tabContentView, options = {})
178     {
179         if (!this.addTabForContentView(tabContentView, options))
180             return false;
181
182         this._tabBar.selectedTabBarItem = tabContentView.tabBarItem;
183
184         // FIXME: this is a workaround for <https://webkit.org/b/151876>.
185         // Without this extra call, we might never lay out the child tab
186         // if it has already marked itself as dirty in the same run loop
187         // as it is attached. It will schedule a layout, but when the rAF
188         // fires the parent will abort the layout because the counter is
189         // out of sync.
190         this.needsLayout();
191         return true;
192     }
193
194     closeTabForContentView(tabContentView, options = {})
195     {
196         console.assert(tabContentView instanceof WI.TabContentView);
197         if (!(tabContentView instanceof WI.TabContentView))
198             return false;
199
200         console.assert(tabContentView.tabBarItem instanceof WI.TabBarItem);
201         if (!(tabContentView.tabBarItem instanceof WI.TabBarItem))
202             return false;
203
204         if (tabContentView.tabBarItem.parentTabBar !== this._tabBar)
205             return false;
206
207         this._tabBar.removeTabBarItem(tabContentView.tabBarItem, options);
208
209         console.assert(this._recentTabContentViews.length === this._tabBar.saveableTabCount);
210         console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0]);
211
212         return true;
213     }
214
215     // Protected
216
217     sizeDidChange()
218     {
219         super.sizeDidChange();
220
221         for (let tabContentView of this._recentTabContentViews)
222             tabContentView[WI.TabBrowser.NeedsResizeLayoutSymbol] = tabContentView !== this.selectedTabContentView;
223     }
224
225     // Private
226
227     _tabBarItemSelected(event)
228     {
229         let tabContentView = this._tabBar.selectedTabBarItem ? this._tabBar.selectedTabBarItem.representedObject : null;
230
231         if (tabContentView) {
232             let shouldSaveTab = tabContentView.constructor.shouldSaveTab();
233             if (shouldSaveTab) {
234                 this._recentTabContentViews.remove(tabContentView);
235                 this._recentTabContentViews.unshift(tabContentView);
236             }
237
238             this._contentViewContainer.showContentView(tabContentView);
239
240             console.assert(this.selectedTabContentView);
241             console.assert(this._recentTabContentViews.length === this._tabBar.saveableTabCount);
242             console.assert(this.selectedTabContentView === this._recentTabContentViews[0] || !shouldSaveTab);
243         } else {
244             this._contentViewContainer.closeAllContentViews();
245
246             console.assert(!this.selectedTabContentView);
247         }
248
249         this._showNavigationSidebarPanelForTabContentView(tabContentView);
250         this._showDetailsSidebarPanelsForTabContentView(tabContentView);
251
252         // If the tab browser was resized prior to showing the tab, the new tab needs to perform a resize layout.
253         if (tabContentView && tabContentView[WI.TabBrowser.NeedsResizeLayoutSymbol]) {
254             tabContentView[WI.TabBrowser.NeedsResizeLayoutSymbol] = false;
255             tabContentView.updateLayout(WI.View.LayoutReason.Resize);
256         }
257
258         this.dispatchEventToListeners(WI.TabBrowser.Event.SelectedTabContentViewDidChange);
259     }
260
261     _tabBarItemAdded(event)
262     {
263         let tabContentView = event.data.tabBarItem.representedObject;
264
265         console.assert(tabContentView);
266         if (!tabContentView)
267             return;
268
269         this._closedTabClasses.delete(tabContentView.constructor);
270     }
271
272     _tabBarItemRemoved(event)
273     {
274         let tabContentView = event.data.tabBarItem.representedObject;
275
276         console.assert(tabContentView);
277         if (!tabContentView)
278             return;
279
280         this._recentTabContentViews.remove(tabContentView);
281
282         if (!tabContentView.constructor.tabInfo().isEphemeral)
283             this._closedTabClasses.add(tabContentView.constructor);
284
285         this._contentViewContainer.closeContentView(tabContentView);
286
287         console.assert(this._recentTabContentViews.length === this._tabBar.saveableTabCount);
288         console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0]);
289     }
290
291     _sidebarPanelSelected(event)
292     {
293         if (this._ignoreSidebarEvents)
294             return;
295
296         var tabContentView = this.selectedTabContentView;
297         if (!tabContentView)
298             return;
299
300         console.assert(event.target === this._detailsSidebar);
301
302         if (tabContentView.managesDetailsSidebarPanels)
303             return;
304
305         var selectedSidebarPanel = this._detailsSidebar.selectedSidebarPanel;
306         tabContentView.detailsSidebarSelectedPanelSetting.value = selectedSidebarPanel ? selectedSidebarPanel.identifier : null;
307     }
308
309     _sidebarCollapsedStateDidChange(event)
310     {
311         if (this._ignoreSidebarEvents)
312             return;
313
314         var tabContentView = this.selectedTabContentView;
315         if (!tabContentView)
316             return;
317
318         if (event.target === this._navigationSidebar && !tabContentView.managesNavigationSidebarPanel)
319             tabContentView.navigationSidebarCollapsedSetting.value = this._navigationSidebar.collapsed;
320         else if (event.target === this._detailsSidebar && !tabContentView.managesDetailsSidebarPanels)
321             tabContentView.detailsSidebarCollapsedSetting.value = this._detailsSidebar.collapsed;
322     }
323
324     _sidebarWidthDidChange(event)
325     {
326         if (this._ignoreSidebarEvents || !event.data)
327             return;
328
329         let tabContentView = this.selectedTabContentView;
330         if (!tabContentView)
331             return;
332
333         switch (event.target) {
334         case this._navigationSidebar:
335             tabContentView.navigationSidebarWidthSetting.value = event.data.newWidth;
336             break;
337
338         case this._detailsSidebar:
339             tabContentView.detailsSidebarWidthSetting.value = event.data.newWidth;
340             break;
341         }
342     }
343
344     _showNavigationSidebarPanelForTabContentView(tabContentView)
345     {
346         if (!this._navigationSidebar)
347             return;
348
349         this._ignoreSidebarEvents = true;
350
351         this._navigationSidebar.removeSidebarPanel(0);
352
353         console.assert(!this._navigationSidebar.sidebarPanels.length);
354
355         if (!tabContentView) {
356             this._ignoreSidebarEvents = false;
357             return;
358         }
359
360         if (tabContentView.navigationSidebarWidthSetting.value)
361             this._navigationSidebar.width = tabContentView.navigationSidebarWidthSetting.value;
362
363         var navigationSidebarPanel = tabContentView.navigationSidebarPanel;
364         if (!navigationSidebarPanel) {
365             this._navigationSidebar.collapsed = true;
366             this._ignoreSidebarEvents = false;
367             return;
368         }
369
370         if (tabContentView.managesNavigationSidebarPanel) {
371             tabContentView.showNavigationSidebarPanel();
372             this._ignoreSidebarEvents = false;
373             return;
374         }
375
376         this._navigationSidebar.addSidebarPanel(navigationSidebarPanel);
377         this._navigationSidebar.selectedSidebarPanel = navigationSidebarPanel;
378
379         this._navigationSidebar.collapsed = tabContentView.navigationSidebarCollapsedSetting.value;
380
381         this._ignoreSidebarEvents = false;
382     }
383
384     _showDetailsSidebarPanelsForTabContentView(tabContentView)
385     {
386         if (!this._detailsSidebar)
387             return;
388
389         this._ignoreSidebarEvents = true;
390
391         for (var i = this._detailsSidebar.sidebarPanels.length - 1; i >= 0; --i)
392             this._detailsSidebar.removeSidebarPanel(i);
393
394         console.assert(!this._detailsSidebar.sidebarPanels.length);
395
396         if (!tabContentView) {
397             this._ignoreSidebarEvents = false;
398             return;
399         }
400
401         if (tabContentView.detailsSidebarWidthSetting.value)
402             this._detailsSidebar.width = tabContentView.detailsSidebarWidthSetting.value;
403
404         if (tabContentView.managesDetailsSidebarPanels) {
405             tabContentView.showDetailsSidebarPanels();
406             this._ignoreSidebarEvents = false;
407             return;
408         }
409
410         var detailsSidebarPanels = tabContentView.detailsSidebarPanels;
411         if (!detailsSidebarPanels) {
412             this._detailsSidebar.collapsed = true;
413             this._ignoreSidebarEvents = false;
414             return;
415         }
416
417         for (var detailsSidebarPanel of detailsSidebarPanels)
418             this._detailsSidebar.addSidebarPanel(detailsSidebarPanel);
419
420         this._detailsSidebar.selectedSidebarPanel = tabContentView.detailsSidebarSelectedPanelSetting.value || detailsSidebarPanels[0];
421
422         this._detailsSidebar.collapsed = tabContentView.detailsSidebarCollapsedSetting.value || !detailsSidebarPanels.length;
423
424         this._ignoreSidebarEvents = false;
425     }
426
427     _showPreviousTab(event)
428     {
429         this._tabBar.selectPreviousTab();
430     }
431
432     _showNextTab(event)
433     {
434         this._tabBar.selectNextTab();
435     }
436
437     _showNextTabCheckingForEditableField(event)
438     {
439         if (WI.isEventTargetAnEditableField(event))
440             return;
441
442         this._showNextTab(event);
443
444         event.preventDefault();
445     }
446
447     _showPreviousTabCheckingForEditableField(event)
448     {
449         if (WI.isEventTargetAnEditableField(event))
450             return;
451
452         this._showPreviousTab(event);
453
454         event.preventDefault();
455     }
456 };
457
458 WI.TabBrowser.NeedsResizeLayoutSymbol = Symbol("needs-resize-layout");
459
460 WI.TabBrowser.Event = {
461     SelectedTabContentViewDidChange: "tab-browser-selected-tab-content-view-did-change"
462 };