Add MSE logging configuration
[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                 mediasource: WI.UIString("MSE Logging:"),
231                 webrtc: WI.UIString("WebRTC Logging:"),
232             };
233
234             let channels = WI.consoleManager.customLoggingChannels;
235             for (let channel of channels) {
236                 let logEditor = generalSettingsView.addGroupWithCustomSetting(editorLabels[channel.source], WI.SettingEditor.Type.Select, {values: logLevels});
237                 logEditor.value = channel.level;
238                 logEditor.addEventListener(WI.SettingEditor.Event.ValueDidChange, () => {
239                     for (let target of WI.targets)
240                         target.ConsoleAgent.setLoggingChannelLevel(channel.source, logEditor.value);
241                 });
242             }
243         }
244
245         this.addSettingsView(generalSettingsView);
246     }
247
248     _createExperimentalSettingsView()
249     {
250         if (!(window.CanvasAgent || window.NetworkAgent || window.LayerTreeAgent))
251             return;
252
253         let experimentalSettingsView = new WI.SettingsView("experimental", WI.UIString("Experimental"));
254
255         let initialValues = new Map;
256
257         if (window.LayerTreeAgent) {
258             experimentalSettingsView.addSetting(WI.UIString("Layers:"), WI.settings.experimentalEnableLayersTab, WI.UIString("Enable Layers Tab"));
259             experimentalSettingsView.addSeparator();
260         }
261
262         experimentalSettingsView.addSetting(WI.UIString("User Interface:"), WI.settings.experimentalEnableNewTabBar, WI.UIString("Enable New Tab Bar"));
263         experimentalSettingsView.addSeparator();
264
265         experimentalSettingsView.addSetting(WI.unlocalizedString("CPU Usage:"), WI.settings.experimentalEnableCPUUsageEnhancements, WI.unlocalizedString("Enhancements"));
266         experimentalSettingsView.addSeparator();
267
268         let reloadInspectorButton = document.createElement("button");
269         reloadInspectorButton.textContent = WI.UIString("Reload Web Inspector");
270         reloadInspectorButton.addEventListener("click", (event) => {
271             // Force a copy so that WI.Setting sees it as a new value.
272             let newTabs = WI._openTabsSetting.value.slice();
273             if (!initialValues.get(WI.settings.experimentalEnableLayersTab) && window.LayerTreeAgent && WI.settings.experimentalEnableLayersTab.value)
274                 newTabs.push(WI.LayersTabContentView.Type);
275             WI._openTabsSetting.value = newTabs;
276
277             InspectorFrontendHost.reopen();
278         });
279
280         let reloadInspectorContainerElement = experimentalSettingsView.addCenteredContainer(reloadInspectorButton, WI.UIString("for changes to take effect"));
281         reloadInspectorContainerElement.classList.add("hidden");
282
283         function listenForChange(setting) {
284             initialValues.set(setting, setting.value);
285             setting.addEventListener(WI.Setting.Event.Changed, () => {
286                 reloadInspectorContainerElement.classList.toggle("hidden", Array.from(initialValues).every(([setting, initialValue]) => setting.value === initialValue));
287             });
288         }
289
290         listenForChange(WI.settings.experimentalEnableLayersTab);
291         listenForChange(WI.settings.experimentalEnableNewTabBar);
292         listenForChange(WI.settings.experimentalEnableCPUUsageEnhancements);
293
294         this.addSettingsView(experimentalSettingsView);
295     }
296
297     _createDebugSettingsView()
298     {
299         if (this._debugSettingsView)
300             return;
301
302         // These settings are only ever shown in engineering builds, so the strings are unlocalized.
303
304         this._debugSettingsView = new WI.SettingsView("debug", WI.unlocalizedString("Debug"));
305
306         let protocolMessagesGroup = this._debugSettingsView.addGroup(WI.unlocalizedString("Protocol Logging:"));
307
308         let autoLogProtocolMessagesEditor = protocolMessagesGroup.addSetting(WI.settings.autoLogProtocolMessages, WI.unlocalizedString("Messages"));
309         WI.settings.autoLogProtocolMessages.addEventListener(WI.Setting.Event.Changed, () => {
310             autoLogProtocolMessagesEditor.value = InspectorBackend.dumpInspectorProtocolMessages;
311         });
312
313         protocolMessagesGroup.addSetting(WI.settings.autoLogTimeStats, WI.unlocalizedString("Time Stats"));
314
315         this._debugSettingsView.addSeparator();
316
317         this._debugSettingsView.addSetting(WI.unlocalizedString("Layout Flashing:"), WI.settings.enableLayoutFlashing, WI.unlocalizedString("Draw borders when a view performs a layout"));
318
319         this._debugSettingsView.addSeparator();
320
321         this._debugSettingsView.addSetting(WI.unlocalizedString("Styles:"), WI.settings.enableStyleEditingDebugMode, WI.unlocalizedString("Enable style editing debug mode"));
322
323         this._debugSettingsView.addSeparator();
324
325         this._debugSettingsView.addSetting(WI.unlocalizedString("Heap Snapshot:"), WI.settings.debugShowInternalObjectsInHeapSnapshot, WI.unlocalizedString("Show Internal Objects"));
326
327         this._debugSettingsView.addSeparator();
328
329         this._debugSettingsView.addSetting(WI.unlocalizedString("Debugging:"), WI.settings.pauseForInternalScripts, WI.unlocalizedString("Pause in WebKit-internal scripts"));
330
331         this._debugSettingsView.addSetting(WI.unlocalizedString("Uncaught Exception Reporter:"), WI.settings.enableUncaughtExceptionReporter, WI.unlocalizedString("Enabled"));
332
333         this._debugSettingsView.addSeparator();
334
335         const layoutDirectionValues = [
336             [WI.LayoutDirection.System, WI.unlocalizedString("System Default")],
337             [WI.LayoutDirection.LTR, WI.unlocalizedString("Left to Right (LTR)")],
338             [WI.LayoutDirection.RTL, WI.unlocalizedString("Right to Left (RTL)")],
339         ];
340
341         let layoutDirectionEditor = this._debugSettingsView.addGroupWithCustomSetting(WI.unlocalizedString("Layout Direction:"), WI.SettingEditor.Type.Select, {values: layoutDirectionValues});
342         layoutDirectionEditor.value = WI.settings.layoutDirection.value;
343         layoutDirectionEditor.addEventListener(WI.SettingEditor.Event.ValueDidChange, () => { WI.setLayoutDirection(layoutDirectionEditor.value); });
344
345         this.addSettingsView(this._debugSettingsView);
346     }
347
348     _updateNavigationBarVisibility()
349     {
350         let visibleItems = 0;
351         for (let item of this._navigationBar.navigationItems) {
352             if (!item.hidden && ++visibleItems > 1) {
353                 this._navigationBar.element.classList.remove("invisible");
354                 return;
355             }
356         }
357
358         this._navigationBar.element.classList.add("invisible");
359     }
360
361     _navigationItemSelected(event)
362     {
363         let navigationItem = event.target.selectedNavigationItem;
364         if (!navigationItem)
365             return;
366
367         let settingsView = this._settingsViews.find((view) => view.identifier === navigationItem.identifier);
368         console.assert(settingsView, "Missing SettingsView for identifier " + navigationItem.identifier);
369         if (!settingsView)
370             return;
371
372         this.selectedSettingsView = settingsView;
373     }
374
375     _updateDebugSettingsViewVisibility()
376     {
377         // Only create the Debug view if the debug UI is enabled.
378         if (WI.isDebugUIEnabled())
379             this._createDebugSettingsView();
380
381         if (!this._debugSettingsView)
382             return;
383
384         this.setSettingsViewVisible(this._debugSettingsView, WI.isDebugUIEnabled());
385
386         this.needsLayout();
387     }
388 };
389
390 WI.SettingsTabContentView.Type = "settings";