52299bb09097273cf7d688054900850937efc636
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / SettingsTabContentView.js
1 /*
2  * Copyright (C) 2015-2017 Apple Inc. All rights reserved.
3  * Copyright (C) 2016 Devin Rousso <webkit@devinrousso.com>. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 WI.SettingsTabContentView = class SettingsTabContentView extends WI.TabContentView
28 {
29     constructor(identifier)
30     {
31         let tabBarItem = WI.PinnedTabBarItem.fromTabInfo(WI.SettingsTabContentView.tabInfo());
32
33         super(identifier || "settings", "settings", tabBarItem);
34
35         // Ensures that the Settings tab is displayable from a pinned tab bar item.
36         tabBarItem.representedObject = this;
37
38         this._selectedSettingsView = null;
39         this._settingsViews = [];
40     }
41
42     static tabInfo()
43     {
44         return {
45             image: "Images/Gear.svg",
46             title: WI.UIString("Settings"),
47             isEphemeral: true,
48         };
49     }
50
51     static shouldSaveTab()
52     {
53         return false;
54     }
55
56     // Public
57
58     get type() { return WI.SettingsTabContentView.Type; }
59
60     get supportsSplitContentBrowser()
61     {
62         return false;
63     }
64
65     get selectedSettingsView()
66     {
67         return this._selectedSettingsView;
68     }
69
70     set selectedSettingsView(settingsView)
71     {
72         if (this._selectedSettingsView === settingsView)
73             return;
74
75         if (this._selectedSettingsView)
76             this.replaceSubview(this._selectedSettingsView, settingsView);
77         else
78             this.addSubview(settingsView);
79
80         this._selectedSettingsView = settingsView;
81         this._selectedSettingsView.updateLayout();
82
83         let navigationItem = this._navigationBar.findNavigationItem(settingsView.identifier);
84         console.assert(navigationItem, "Missing navigation item for settings view.", settingsView);
85         if (!navigationItem)
86             return;
87
88         this._navigationBar.selectedNavigationItem = navigationItem;
89     }
90
91     addSettingsView(settingsView)
92     {
93         if (this._settingsViews.includes(settingsView)) {
94             console.assert(false, "SettingsView already exists.", settingsView);
95             return;
96         }
97
98         this._settingsViews.push(settingsView);
99         this._navigationBar.addNavigationItem(new WI.RadioButtonNavigationItem(settingsView.identifier, settingsView.displayName));
100
101         this._updateNavigationBarVisibility();
102     }
103
104     setSettingsViewVisible(settingsView, visible)
105     {
106         let navigationItem = this._navigationBar.findNavigationItem(settingsView.identifier);
107         console.assert(navigationItem, "Missing NavigationItem for identifier: " + settingsView.identifier);
108         if (!navigationItem)
109             return;
110
111         if (navigationItem.hidden === !visible)
112             return;
113
114         navigationItem.hidden = !visible;
115         settingsView.element.classList.toggle("hidden", !visible);
116
117         this._updateNavigationBarVisibility();
118
119         if (!this.selectedSettingsView) {
120             if (visible)
121                 this.selectedSettingsView = settingsView;
122             return;
123         }
124
125         if (this.selectedSettingsView !== settingsView)
126             return;
127
128         let index = this._settingsViews.indexOf(settingsView);
129         console.assert(index !== -1, "SettingsView not found.", settingsView);
130         if (index === -1)
131             return;
132
133         let previousIndex = index;
134         while (--previousIndex >= 0) {
135             let previousNavigationItem = this._navigationBar.navigationItems[previousIndex];
136             console.assert(previousNavigationItem);
137             if (!previousNavigationItem || previousNavigationItem.hidden)
138                 continue;
139
140             this.selectedSettingsView = this._settingsViews[previousIndex];
141             return;
142         }
143
144         let nextIndex = index;
145         while (++nextIndex < this._settingsViews.length) {
146             let nextNavigationItem = this._navigationBar.navigationItems[nextIndex];
147             console.assert(nextNavigationItem);
148             if (!nextNavigationItem || nextNavigationItem.hidden)
149                 continue;
150
151             this.selectedSettingsView = this._settingsViews[nextIndex];
152             return;
153         }
154     }
155
156     // Protected
157
158     initialLayout()
159     {
160         this._navigationBar = new WI.NavigationBar;
161         this._navigationBar.addEventListener(WI.NavigationBar.Event.NavigationItemSelected, this._navigationItemSelected, this);
162         this.addSubview(this._navigationBar);
163
164         this._createGeneralSettingsView();
165         this._createExperimentalSettingsView();
166
167         WI.notifications.addEventListener(WI.Notification.DebugUIEnabledDidChange, this._updateDebugSettingsViewVisibility, this);
168         this._updateDebugSettingsViewVisibility();
169
170         this.selectedSettingsView = this._settingsViews[0];
171     }
172
173     // Private
174
175     _createGeneralSettingsView()
176     {
177         let generalSettingsView = new WI.SettingsView("general", WI.UIString("General"));
178
179         const indentValues = [WI.UIString("Tabs"), WI.UIString("Spaces")];
180
181         let indentEditor = generalSettingsView.addGroupWithCustomSetting(WI.UIString("Prefer indent using:"), WI.SettingEditor.Type.Select, {values: indentValues});
182         indentEditor.value = indentValues[WI.settings.indentWithTabs.value ? 0 : 1];
183         indentEditor.addEventListener(WI.SettingEditor.Event.ValueDidChange, () => {
184             WI.settings.indentWithTabs.value = indentEditor.value === indentValues[0];
185         });
186
187         function addSpacesSetting(title, setting) {
188             let editor = generalSettingsView.addSetting(title, setting, WI.UIString("spaces"), {min: 1});
189
190             function updateLabel() {
191                 editor.label = setting.value === 1 ? WI.UIString("space") : WI.UIString("spaces");
192             }
193             setting.addEventListener(WI.Setting.Event.Changed, (event) => {
194                 updateLabel();
195             });
196             updateLabel();
197         }
198         addSpacesSetting(WI.UIString("Tab width:"), WI.settings.tabSize);
199         addSpacesSetting(WI.UIString("Indent width:"), WI.settings.indentUnit);
200
201         generalSettingsView.addSetting(WI.UIString("Line wrapping:"), WI.settings.enableLineWrapping, WI.UIString("Wrap lines to editor width"));
202
203         let showGroup = generalSettingsView.addGroup(WI.UIString("Show:"));
204         showGroup.addSetting(WI.settings.showWhitespaceCharacters, WI.UIString("Whitespace characters"));
205         showGroup.addSetting(WI.settings.showInvalidCharacters, WI.UIString("Invalid characters"));
206
207         generalSettingsView.addSeparator();
208
209         generalSettingsView.addSetting(WI.UIString("Debugger:"), WI.settings.showScopeChainOnPause, WI.UIString("Show Scope Chain on pause"));
210         generalSettingsView.addSetting(WI.UIString("Source maps:"), WI.settings.sourceMapsEnabled, WI.UIString("Enable source maps"));
211
212         generalSettingsView.addSeparator();
213
214         const zoomLevels = [0.6, 0.8, 1, 1.2, 1.4, 1.6, 1.8, 2, 2.2, 2.4];
215         const zoomValues = zoomLevels.map((level) => [level, Number.percentageString(level, 0)]);
216
217         let zoomEditor = generalSettingsView.addGroupWithCustomSetting(WI.UIString("Zoom:"), WI.SettingEditor.Type.Select, {values: zoomValues});
218         zoomEditor.value = WI.getZoomFactor();
219         zoomEditor.addEventListener(WI.SettingEditor.Event.ValueDidChange, () => { WI.setZoomFactor(zoomEditor.value); });
220         WI.settings.zoomFactor.addEventListener(WI.Setting.Event.Changed, () => { zoomEditor.value = WI.getZoomFactor().maxDecimals(2); });
221
222         if (WI.ConsoleManager.supportsLogChannels()) {
223             const logLevels = [
224                 [WI.LoggingChannel.Level.Off, WI.UIString("Off")],
225                 [WI.LoggingChannel.Level.Basic, WI.UIString("Basic")],
226                 [WI.LoggingChannel.Level.Verbose, WI.UIString("Verbose")],
227             ];
228             const editorLabels = {
229                 media: WI.UIString("Media Logging:"),
230                 webrtc: WI.UIString("WebRTC Logging:"),
231             };
232
233             let channels = WI.consoleManager.customLoggingChannels;
234             for (let channel of channels) {
235                 let logEditor = generalSettingsView.addGroupWithCustomSetting(editorLabels[channel.source], WI.SettingEditor.Type.Select, {values: logLevels});
236                 logEditor.value = channel.level;
237                 logEditor.addEventListener(WI.SettingEditor.Event.ValueDidChange, () => {
238                     for (let target of WI.targets)
239                         target.ConsoleAgent.setLoggingChannelLevel(channel.source, logEditor.value);
240                 });
241             }
242         }
243
244         this.addSettingsView(generalSettingsView);
245     }
246
247     _createExperimentalSettingsView()
248     {
249         if (!(window.CanvasAgent || window.NetworkAgent || window.LayerTreeAgent))
250             return;
251
252         let experimentalSettingsView = new WI.SettingsView("experimental", WI.UIString("Experimental"));
253
254         let initialValues = new Map;
255
256         if (window.LayerTreeAgent) {
257             experimentalSettingsView.addSetting(WI.UIString("Layers:"), WI.settings.experimentalEnableLayersTab, WI.UIString("Enable Layers Tab"));
258             experimentalSettingsView.addSeparator();
259         }
260
261         experimentalSettingsView.addSetting(WI.UIString("User Interface:"), WI.settings.experimentalEnableNewTabBar, WI.UIString("Enable New Tab Bar"));
262         experimentalSettingsView.addSeparator();
263
264         experimentalSettingsView.addSetting(WI.unlocalizedString("CPU Usage:"), WI.settings.experimentalEnableCPUUsageEnhancements, WI.unlocalizedString("Enhancements"));
265         experimentalSettingsView.addSeparator();
266
267         let reloadInspectorButton = document.createElement("button");
268         reloadInspectorButton.textContent = WI.UIString("Reload Web Inspector");
269         reloadInspectorButton.addEventListener("click", (event) => {
270             // Force a copy so that WI.Setting sees it as a new value.
271             let newTabs = WI._openTabsSetting.value.slice();
272             if (!initialValues.get(WI.settings.experimentalEnableLayersTab) && window.LayerTreeAgent && WI.settings.experimentalEnableLayersTab.value)
273                 newTabs.push(WI.LayersTabContentView.Type);
274             WI._openTabsSetting.value = newTabs;
275
276             InspectorFrontendHost.reopen();
277         });
278
279         let reloadInspectorContainerElement = experimentalSettingsView.addCenteredContainer(reloadInspectorButton, WI.UIString("for changes to take effect"));
280         reloadInspectorContainerElement.classList.add("hidden");
281
282         function listenForChange(setting) {
283             initialValues.set(setting, setting.value);
284             setting.addEventListener(WI.Setting.Event.Changed, () => {
285                 reloadInspectorContainerElement.classList.toggle("hidden", Array.from(initialValues).every(([setting, initialValue]) => setting.value === initialValue));
286             });
287         }
288
289         listenForChange(WI.settings.experimentalEnableLayersTab);
290         listenForChange(WI.settings.experimentalEnableNewTabBar);
291         listenForChange(WI.settings.experimentalEnableCPUUsageEnhancements);
292
293         this.addSettingsView(experimentalSettingsView);
294     }
295
296     _createDebugSettingsView()
297     {
298         if (this._debugSettingsView)
299             return;
300
301         // These settings are only ever shown in engineering builds, so the strings are unlocalized.
302
303         this._debugSettingsView = new WI.SettingsView("debug", WI.unlocalizedString("Debug"));
304
305         let protocolMessagesGroup = this._debugSettingsView.addGroup(WI.unlocalizedString("Protocol Logging:"));
306
307         let autoLogProtocolMessagesEditor = protocolMessagesGroup.addSetting(WI.settings.autoLogProtocolMessages, WI.unlocalizedString("Messages"));
308         WI.settings.autoLogProtocolMessages.addEventListener(WI.Setting.Event.Changed, () => {
309             autoLogProtocolMessagesEditor.value = InspectorBackend.dumpInspectorProtocolMessages;
310         });
311
312         protocolMessagesGroup.addSetting(WI.settings.autoLogTimeStats, WI.unlocalizedString("Time Stats"));
313
314         this._debugSettingsView.addSeparator();
315
316         this._debugSettingsView.addSetting(WI.unlocalizedString("Layout Flashing:"), WI.settings.enableLayoutFlashing, WI.unlocalizedString("Draw borders when a view performs a layout"));
317
318         this._debugSettingsView.addSeparator();
319
320         this._debugSettingsView.addSetting(WI.unlocalizedString("Styles:"), WI.settings.enableStyleEditingDebugMode, WI.unlocalizedString("Enable style editing debug mode"));
321
322         this._debugSettingsView.addSeparator();
323
324         this._debugSettingsView.addSetting(WI.unlocalizedString("Heap Snapshot:"), WI.settings.debugShowInternalObjectsInHeapSnapshot, WI.unlocalizedString("Show Internal Objects"));
325
326         this._debugSettingsView.addSeparator();
327
328         this._debugSettingsView.addSetting(WI.unlocalizedString("Debugging:"), WI.settings.pauseForInternalScripts, WI.unlocalizedString("Pause in WebKit-internal scripts"));
329
330         this._debugSettingsView.addSetting(WI.unlocalizedString("Uncaught Exception Reporter:"), WI.settings.enableUncaughtExceptionReporter, WI.unlocalizedString("Enabled"));
331
332         this._debugSettingsView.addSeparator();
333
334         const layoutDirectionValues = [
335             [WI.LayoutDirection.System, WI.unlocalizedString("System Default")],
336             [WI.LayoutDirection.LTR, WI.unlocalizedString("Left to Right (LTR)")],
337             [WI.LayoutDirection.RTL, WI.unlocalizedString("Right to Left (RTL)")],
338         ];
339
340         let layoutDirectionEditor = this._debugSettingsView.addGroupWithCustomSetting(WI.unlocalizedString("Layout Direction:"), WI.SettingEditor.Type.Select, {values: layoutDirectionValues});
341         layoutDirectionEditor.value = WI.settings.layoutDirection.value;
342         layoutDirectionEditor.addEventListener(WI.SettingEditor.Event.ValueDidChange, () => { WI.setLayoutDirection(layoutDirectionEditor.value); });
343
344         this.addSettingsView(this._debugSettingsView);
345     }
346
347     _updateNavigationBarVisibility()
348     {
349         let visibleItems = 0;
350         for (let item of this._navigationBar.navigationItems) {
351             if (!item.hidden && ++visibleItems > 1) {
352                 this._navigationBar.element.classList.remove("invisible");
353                 return;
354             }
355         }
356
357         this._navigationBar.element.classList.add("invisible");
358     }
359
360     _navigationItemSelected(event)
361     {
362         let navigationItem = event.target.selectedNavigationItem;
363         if (!navigationItem)
364             return;
365
366         let settingsView = this._settingsViews.find((view) => view.identifier === navigationItem.identifier);
367         console.assert(settingsView, "Missing SettingsView for identifier " + navigationItem.identifier);
368         if (!settingsView)
369             return;
370
371         this.selectedSettingsView = settingsView;
372     }
373
374     _updateDebugSettingsViewVisibility()
375     {
376         // Only create the Debug view if the debug UI is enabled.
377         if (WI.isDebugUIEnabled())
378             this._createDebugSettingsView();
379
380         if (!this._debugSettingsView)
381             return;
382
383         this.setSettingsViewVisible(this._debugSettingsView, WI.isDebugUIEnabled());
384
385         this.needsLayout();
386     }
387 };
388
389 WI.SettingsTabContentView.Type = "settings";