Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / SettingsScreen.js
1 /*
2  * Copyright (C) 2013 Google 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 are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @constructor
33  * @param {!function()} onHide
34  * @extends {WebInspector.HelpScreen}
35  */
36 WebInspector.SettingsScreen = function(onHide)
37 {
38     WebInspector.HelpScreen.call(this);
39     this.element.id = "settings-screen";
40
41     /** @type {function()} */
42     this._onHide = onHide;
43
44     this._tabbedPane = new WebInspector.TabbedPane();
45     this._tabbedPane.element.classList.add("help-window-main");
46     var settingsLabelElement = document.createElement("div");
47     settingsLabelElement.className = "help-window-label";
48     settingsLabelElement.createTextChild(WebInspector.UIString("Settings"));
49     this._tabbedPane.element.insertBefore(settingsLabelElement, this._tabbedPane.element.firstChild);
50     this._tabbedPane.element.appendChild(this._createCloseButton());
51     this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.General, WebInspector.UIString("General"), new WebInspector.GenericSettingsTab());
52     this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Workspace, WebInspector.UIString("Workspace"), new WebInspector.WorkspaceSettingsTab());
53     if (WebInspector.experimentsSettings.experimentsEnabled)
54         this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Experiments, WebInspector.UIString("Experiments"), new WebInspector.ExperimentsSettingsTab());
55     this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Shortcuts, WebInspector.UIString("Shortcuts"), WebInspector.shortcutsScreen.createShortcutsTabView());
56     this._tabbedPane.shrinkableTabs = false;
57     this._tabbedPane.verticalTabLayout = true;
58
59     this._lastSelectedTabSetting = WebInspector.settings.createSetting("lastSelectedSettingsTab", WebInspector.SettingsScreen.Tabs.General);
60     this.selectTab(this._lastSelectedTabSetting.get());
61     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
62 }
63
64 /**
65  * @param {string} text
66  * @return {?string}
67  */
68 WebInspector.SettingsScreen.regexValidator = function(text)
69 {
70     var regex;
71     try {
72         regex = new RegExp(text);
73     } catch (e) {
74     }
75     return regex ? null : WebInspector.UIString("Invalid pattern");
76 }
77
78 /**
79  * @param {number} min
80  * @param {number} max
81  * @param {string} text
82  * @return {?string}
83  */
84 WebInspector.SettingsScreen.integerValidator = function(min, max, text)
85 {
86     var value = Number(text);
87     if (isNaN(value))
88         return WebInspector.UIString("Invalid number format");
89     if (value < min || value > max)
90         return WebInspector.UIString("Value is out of range [%d, %d]", min, max);
91     return null;
92 }
93
94 WebInspector.SettingsScreen.Tabs = {
95     General: "general",
96     Overrides: "overrides",
97     Workspace: "workspace",
98     Experiments: "experiments",
99     Shortcuts: "shortcuts"
100 }
101
102 WebInspector.SettingsScreen.prototype = {
103     /**
104      * @param {string} tabId
105      */
106     selectTab: function(tabId)
107     {
108         this._tabbedPane.selectTab(tabId);
109     },
110
111     /**
112      * @param {!WebInspector.Event} event
113      */
114     _tabSelected: function(event)
115     {
116         this._lastSelectedTabSetting.set(this._tabbedPane.selectedTabId);
117     },
118
119     /**
120      * @override
121      */
122     wasShown: function()
123     {
124         this._tabbedPane.show(this.element);
125         WebInspector.HelpScreen.prototype.wasShown.call(this);
126     },
127
128     /**
129      * @override
130      * @return {boolean}
131      */
132     isClosingKey: function(keyCode)
133     {
134         return [
135             WebInspector.KeyboardShortcut.Keys.Enter.code,
136             WebInspector.KeyboardShortcut.Keys.Esc.code,
137         ].indexOf(keyCode) >= 0;
138     },
139
140     /**
141      * @override
142      */
143     willHide: function()
144     {
145         this._onHide();
146         WebInspector.HelpScreen.prototype.willHide.call(this);
147     },
148
149     __proto__: WebInspector.HelpScreen.prototype
150 }
151
152 /**
153  * @constructor
154  * @extends {WebInspector.View}
155  * @param {string} name
156  * @param {string=} id
157  */
158 WebInspector.SettingsTab = function(name, id)
159 {
160     WebInspector.View.call(this);
161     this.element.classList.add("settings-tab-container");
162     if (id)
163         this.element.id = id;
164     var header = this.element.createChild("header");
165     header.createChild("h3").appendChild(document.createTextNode(name));
166     this.containerElement = this.element.createChild("div", "help-container-wrapper").createChild("div", "settings-tab help-content help-container");
167 }
168
169 WebInspector.SettingsTab.prototype = {
170     /**
171      *  @param {string=} name
172      *  @return {!Element}
173      */
174     _appendSection: function(name)
175     {
176         var block = this.containerElement.createChild("div", "help-block");
177         if (name)
178             block.createChild("div", "help-section-title").textContent = name;
179         return block;
180     },
181
182     _createSelectSetting: function(name, options, setting)
183     {
184         var p = document.createElement("p");
185         var labelElement = p.createChild("label");
186         labelElement.textContent = name;
187
188         var select = p.createChild("select");
189         var settingValue = setting.get();
190
191         for (var i = 0; i < options.length; ++i) {
192             var option = options[i];
193             select.add(new Option(option[0], option[1]));
194             if (settingValue === option[1])
195                 select.selectedIndex = i;
196         }
197
198         function changeListener(e)
199         {
200             // Don't use e.target.value to avoid conversion of the value to string.
201             setting.set(options[select.selectedIndex][1]);
202         }
203
204         select.addEventListener("change", changeListener, false);
205         return p;
206     },
207
208     /**
209      * @param {string} label
210      * @param {!WebInspector.Setting} setting
211      * @param {boolean} numeric
212      * @param {number=} maxLength
213      * @param {string=} width
214      * @param {function(string):?string=} validatorCallback
215      */
216     _createInputSetting: function(label, setting, numeric, maxLength, width, validatorCallback)
217     {
218         var p = document.createElement("p");
219         var labelElement = p.createChild("label");
220         labelElement.textContent = label;
221         var inputElement = p.createChild("input");
222         inputElement.value = setting.get();
223         inputElement.type = "text";
224         if (numeric)
225             inputElement.className = "numeric";
226         if (maxLength)
227             inputElement.maxLength = maxLength;
228         if (width)
229             inputElement.style.width = width;
230         if (validatorCallback) {
231             var errorMessageLabel = p.createChild("div");
232             errorMessageLabel.classList.add("field-error-message");
233             errorMessageLabel.style.color = "DarkRed";
234             inputElement.oninput = function()
235             {
236                 var error = validatorCallback(inputElement.value);
237                 if (!error)
238                     error = "";
239                 errorMessageLabel.textContent = error;
240             };
241         }
242
243         function onBlur()
244         {
245             setting.set(numeric ? Number(inputElement.value) : inputElement.value);
246         }
247         inputElement.addEventListener("blur", onBlur, false);
248
249         return p;
250     },
251
252     _createCustomSetting: function(name, element)
253     {
254         var p = document.createElement("p");
255         var fieldsetElement = document.createElement("fieldset");
256         fieldsetElement.createChild("label").textContent = name;
257         fieldsetElement.appendChild(element);
258         p.appendChild(fieldsetElement);
259         return p;
260     },
261
262     __proto__: WebInspector.View.prototype
263 }
264
265 /**
266  * @constructor
267  * @extends {WebInspector.SettingsTab}
268  */
269 WebInspector.GenericSettingsTab = function()
270 {
271     WebInspector.SettingsTab.call(this, WebInspector.UIString("General"), "general-tab-content");
272
273     var p = this._appendSection();
274     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Disable cache (while DevTools is open)"), WebInspector.settings.cacheDisabled));
275     var disableJSElement = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Disable JavaScript"), WebInspector.settings.javaScriptDisabled);
276     p.appendChild(disableJSElement);
277     WebInspector.settings.javaScriptDisabled.addChangeListener(this._javaScriptDisabledChanged, this);
278     this._disableJSCheckbox = disableJSElement.getElementsByTagName("input")[0];
279     this._updateScriptDisabledCheckbox();
280
281     p = this._appendSection(WebInspector.UIString("Appearance"));
282     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show 'Emulation' view in console drawer."), WebInspector.settings.showEmulationViewInDrawer));
283     this._appendDrawerNote(p.lastElementChild);
284     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show 'Rendering' view in console drawer."), WebInspector.settings.showRenderingViewInDrawer));
285     this._appendDrawerNote(p.lastElementChild);
286     var splitVerticallyTitle = WebInspector.UIString("Split panels vertically when docked to %s", WebInspector.experimentsSettings.dockToLeft.isEnabled() ? "left or right" : "right");
287     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(splitVerticallyTitle, WebInspector.settings.splitVerticallyWhenDockedToRight));
288     var panelShortcutTitle = WebInspector.UIString("Enable %s + 1-9 shortcut to switch panels", WebInspector.isMac() ? "Cmd" : "Ctrl");
289     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(panelShortcutTitle, WebInspector.settings.shortcutPanelSwitch));
290
291     p = this._appendSection(WebInspector.UIString("Elements"));
292     var colorFormatElement = this._createSelectSetting(WebInspector.UIString("Color format"), [
293             [ WebInspector.UIString("As authored"), WebInspector.Color.Format.Original ],
294             [ "HEX: #DAC0DE", WebInspector.Color.Format.HEX ],
295             [ "RGB: rgb(128, 255, 255)", WebInspector.Color.Format.RGB ],
296             [ "HSL: hsl(300, 80%, 90%)", WebInspector.Color.Format.HSL ]
297         ], WebInspector.settings.colorFormat);
298     p.appendChild(colorFormatElement);
299     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show user agent styles"), WebInspector.settings.showUserAgentStyles));
300     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Word wrap"), WebInspector.settings.domWordWrap));
301     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show Shadow DOM"), WebInspector.settings.showShadowDOM));
302     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show rulers"), WebInspector.settings.showMetricsRulers));
303
304     p = this._appendSection(WebInspector.UIString("Sources"));
305     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Search in content scripts"), WebInspector.settings.searchInContentScripts));
306     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Enable JavaScript source maps"), WebInspector.settings.jsSourceMapsEnabled));
307
308     var checkbox = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Enable CSS source maps"), WebInspector.settings.cssSourceMapsEnabled);
309     p.appendChild(checkbox);
310     var fieldset = WebInspector.SettingsUI.createSettingFieldset(WebInspector.settings.cssSourceMapsEnabled);
311     var autoReloadCSSCheckbox = fieldset.createChild("input");
312     fieldset.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Auto-reload generated CSS"), WebInspector.settings.cssReloadEnabled, false, autoReloadCSSCheckbox));
313     checkbox.appendChild(fieldset);
314
315     var indentationElement = this._createSelectSetting(WebInspector.UIString("Default indentation"), [
316             [ WebInspector.UIString("2 spaces"), WebInspector.TextUtils.Indent.TwoSpaces ],
317             [ WebInspector.UIString("4 spaces"), WebInspector.TextUtils.Indent.FourSpaces ],
318             [ WebInspector.UIString("8 spaces"), WebInspector.TextUtils.Indent.EightSpaces ],
319             [ WebInspector.UIString("Tab character"), WebInspector.TextUtils.Indent.TabCharacter ]
320         ], WebInspector.settings.textEditorIndent);
321     p.appendChild(indentationElement);
322     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Detect indentation"), WebInspector.settings.textEditorAutoDetectIndent));
323     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Autocompletion"), WebInspector.settings.textEditorAutocompletion));
324     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Bracket matching"), WebInspector.settings.textEditorBracketMatching));
325     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show whitespace characters"), WebInspector.settings.showWhitespacesInEditor));
326     if (WebInspector.experimentsSettings.frameworksDebuggingSupport.isEnabled()) {
327         checkbox = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Skip stepping through sources with particular names"), WebInspector.settings.skipStackFramesSwitch);
328         fieldset = WebInspector.SettingsUI.createSettingFieldset(WebInspector.settings.skipStackFramesSwitch);
329         fieldset.appendChild(this._createInputSetting(WebInspector.UIString("Pattern"), WebInspector.settings.skipStackFramesPattern, false, 1000, "100px", WebInspector.SettingsScreen.regexValidator));
330         checkbox.appendChild(fieldset);
331         p.appendChild(checkbox);
332     }
333     WebInspector.settings.skipStackFramesSwitch.addChangeListener(this._skipStackFramesSwitchOrPatternChanged, this);
334     WebInspector.settings.skipStackFramesPattern.addChangeListener(this._skipStackFramesSwitchOrPatternChanged, this);
335
336     p = this._appendSection(WebInspector.UIString("Profiler"));
337     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show advanced heap snapshot properties"), WebInspector.settings.showAdvancedHeapSnapshotProperties));
338     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("High resolution CPU profiling"), WebInspector.settings.highResolutionCpuProfiling));
339
340     p = this._appendSection(WebInspector.UIString("Console"));
341     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Log XMLHttpRequests"), WebInspector.settings.monitoringXHREnabled));
342     p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Preserve log upon navigation"), WebInspector.settings.preserveConsoleLog));
343
344     if (WebInspector.openAnchorLocationRegistry.handlerNames.length > 0) {
345         var handlerSelector = new WebInspector.HandlerSelector(WebInspector.openAnchorLocationRegistry);
346         p = this._appendSection(WebInspector.UIString("Extensions"));
347         p.appendChild(this._createCustomSetting(WebInspector.UIString("Open links in"), handlerSelector.element));
348     }
349
350     p = this._appendSection();
351
352     var restoreDefaults = p.createChild("input", "settings-tab-text-button");
353     restoreDefaults.type = "button";
354     restoreDefaults.value = WebInspector.UIString("Restore defaults and reload");
355     restoreDefaults.addEventListener("click", restoreAndReload);
356
357     function restoreAndReload()
358     {
359         if (window.localStorage)
360             window.localStorage.clear();
361         WebInspector.reload();
362     }
363 }
364
365 WebInspector.GenericSettingsTab.prototype = {
366     _updateScriptDisabledCheckbox: function()
367     {
368         /**
369          * @param {?Protocol.Error} error
370          * @param {string} status
371          * @this {WebInspector.GenericSettingsTab}
372          */
373         function executionStatusCallback(error, status)
374         {
375             if (error || !status)
376                 return;
377
378             switch (status) {
379             case "forbidden":
380                 this._disableJSCheckbox.checked = true;
381                 this._disableJSCheckbox.disabled = true;
382                 break;
383             case "disabled":
384                 this._disableJSCheckbox.checked = true;
385                 break;
386             default:
387                 this._disableJSCheckbox.checked = false;
388                 break;
389             }
390         }
391
392         PageAgent.getScriptExecutionStatus(executionStatusCallback.bind(this));
393     },
394
395     _javaScriptDisabledChanged: function()
396     {
397         // We need to manually update the checkbox state, since enabling JavaScript in the page can actually uncover the "forbidden" state.
398         PageAgent.setScriptExecutionDisabled(WebInspector.settings.javaScriptDisabled.get(), this._updateScriptDisabledCheckbox.bind(this));
399     },
400
401     _skipStackFramesSwitchOrPatternChanged: function()
402     {
403         WebInspector.DebuggerModel.applySkipStackFrameSettings();
404     },
405
406     /**
407      * @param {?Element} p
408      */
409     _appendDrawerNote: function(p)
410     {
411         var noteElement = p.createChild("div", "help-field-note");
412         noteElement.createTextChild("Hit ");
413         noteElement.createChild("span", "help-key").textContent = "Esc";
414         noteElement.createTextChild(WebInspector.UIString(" or click the"));
415         noteElement.appendChild(new WebInspector.StatusBarButton(WebInspector.UIString("Drawer"), "console-status-bar-item").element);
416         noteElement.createTextChild(WebInspector.UIString("toolbar item"));
417     },
418
419     __proto__: WebInspector.SettingsTab.prototype
420 }
421
422 /**
423  * @constructor
424  * @extends {WebInspector.SettingsTab}
425  */
426 WebInspector.WorkspaceSettingsTab = function()
427 {
428     WebInspector.SettingsTab.call(this, WebInspector.UIString("Workspace"), "workspace-tab-content");
429     WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemAdded, this._fileSystemAdded, this);
430     WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemRemoved, this._fileSystemRemoved, this);
431
432     this._commonSection = this._appendSection(WebInspector.UIString("Common"));
433     var folderExcludePatternInput = this._createInputSetting(WebInspector.UIString("Folder exclude pattern"), WebInspector.settings.workspaceFolderExcludePattern, false, 0, "270px", WebInspector.SettingsScreen.regexValidator);
434     this._commonSection.appendChild(folderExcludePatternInput);
435
436     this._fileSystemsSection = this._appendSection(WebInspector.UIString("Folders"));
437     this._fileSystemsListContainer = this._fileSystemsSection.createChild("p", "settings-list-container");
438
439     this._addFileSystemRowElement = this._fileSystemsSection.createChild("div");
440     var addFileSystemButton = this._addFileSystemRowElement.createChild("input", "settings-tab-text-button");
441     addFileSystemButton.type = "button";
442     addFileSystemButton.value = WebInspector.UIString("Add folder\u2026");
443     addFileSystemButton.addEventListener("click", this._addFileSystemClicked.bind(this));
444
445     this._editFileSystemButton = this._addFileSystemRowElement.createChild("input", "settings-tab-text-button");
446     this._editFileSystemButton.type = "button";
447     this._editFileSystemButton.value = WebInspector.UIString("Edit\u2026");
448     this._editFileSystemButton.addEventListener("click", this._editFileSystemClicked.bind(this));
449     this._updateEditFileSystemButtonState();
450
451     this._reset();
452 }
453
454 WebInspector.WorkspaceSettingsTab.prototype = {
455     wasShown: function()
456     {
457         WebInspector.SettingsTab.prototype.wasShown.call(this);
458         this._reset();
459     },
460
461     _reset: function()
462     {
463         this._resetFileSystems();
464     },
465
466     _resetFileSystems: function()
467     {
468         this._fileSystemsListContainer.removeChildren();
469         var fileSystemPaths = WebInspector.isolatedFileSystemManager.mapping().fileSystemPaths();
470         delete this._fileSystemsList;
471
472         if (!fileSystemPaths.length) {
473             var noFileSystemsMessageElement = this._fileSystemsListContainer.createChild("div", "no-file-systems-message");
474             noFileSystemsMessageElement.textContent = WebInspector.UIString("You have no file systems added.");
475             return;
476         }
477
478         this._fileSystemsList = new WebInspector.SettingsList(["path"], this._renderFileSystem.bind(this));
479         this._fileSystemsList.element.classList.add("file-systems-list");
480         this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Selected, this._fileSystemSelected.bind(this));
481         this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Removed, this._fileSystemRemovedfromList.bind(this));
482         this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.DoubleClicked, this._fileSystemDoubleClicked.bind(this));
483         this._fileSystemsListContainer.appendChild(this._fileSystemsList.element);
484         for (var i = 0; i < fileSystemPaths.length; ++i)
485             this._fileSystemsList.addItem(fileSystemPaths[i]);
486         this._updateEditFileSystemButtonState();
487     },
488
489     _updateEditFileSystemButtonState: function()
490     {
491         this._editFileSystemButton.disabled = !this._selectedFileSystemPath();
492     },
493
494     /**
495      * @param {!WebInspector.Event} event
496      */
497     _fileSystemSelected: function(event)
498     {
499         this._updateEditFileSystemButtonState();
500     },
501
502     /**
503      * @param {!WebInspector.Event} event
504      */
505     _fileSystemDoubleClicked: function(event)
506     {
507         var id = /** @type{?string} */ (event.data);
508         this._editFileSystem(id);
509     },
510
511     /**
512      * @param {!WebInspector.Event=} event
513      */
514     _editFileSystemClicked: function(event)
515     {
516         this._editFileSystem(this._selectedFileSystemPath());
517     },
518
519     /**
520      * @param {?string} id
521      */
522     _editFileSystem: function(id)
523     {
524         WebInspector.EditFileSystemDialog.show(WebInspector.inspectorView.devtoolsElement(), id);
525     },
526
527     /**
528      * @param {function(?Event)} handler
529      * @return {!Element}
530      */
531     _createRemoveButton: function(handler)
532     {
533         var removeButton = document.createElement("button");
534         removeButton.classList.add("button");
535         removeButton.classList.add("remove-item-button");
536         removeButton.value = WebInspector.UIString("Remove");
537         if (handler)
538             removeButton.addEventListener("click", handler, false);
539         else
540             removeButton.disabled = true;
541         return removeButton;
542     },
543
544     /**
545      * @param {!Element} columnElement
546      * @param {string} column
547      * @param {?string} id
548      */
549     _renderFileSystem: function(columnElement, column, id)
550     {
551         if (!id)
552             return "";
553         var fileSystemPath = id;
554         var textElement = columnElement.createChild("span", "list-column-text");
555         var pathElement = textElement.createChild("span", "file-system-path");
556         pathElement.title = fileSystemPath;
557
558         const maxTotalPathLength = 55;
559         const maxFolderNameLength = 30;
560
561         var lastIndexOfSlash = fileSystemPath.lastIndexOf(WebInspector.isWin() ? "\\" : "/");
562         var folderName = fileSystemPath.substr(lastIndexOfSlash + 1);
563         var folderPath = fileSystemPath.substr(0, lastIndexOfSlash + 1);
564         folderPath = folderPath.trimMiddle(maxTotalPathLength - Math.min(maxFolderNameLength, folderName.length));
565         folderName = folderName.trimMiddle(maxFolderNameLength);
566
567         var folderPathElement = pathElement.createChild("span");
568         folderPathElement.textContent = folderPath;
569
570         var nameElement = pathElement.createChild("span", "file-system-path-name");
571         nameElement.textContent = folderName;
572     },
573
574     /**
575      * @param {!WebInspector.Event} event
576      */
577     _fileSystemRemovedfromList: function(event)
578     {
579         var id = /** @type{?string} */ (event.data);
580         if (!id)
581             return;
582         WebInspector.isolatedFileSystemManager.removeFileSystem(id);
583     },
584
585     _addFileSystemClicked: function()
586     {
587         WebInspector.isolatedFileSystemManager.addFileSystem();
588     },
589
590     _fileSystemAdded: function(event)
591     {
592         var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
593         if (!this._fileSystemsList)
594             this._reset();
595         else
596             this._fileSystemsList.addItem(fileSystem.path());
597     },
598
599     _fileSystemRemoved: function(event)
600     {
601         var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
602         var selectedFileSystemPath = this._selectedFileSystemPath();
603         if (this._fileSystemsList.itemForId(fileSystem.path()))
604             this._fileSystemsList.removeItem(fileSystem.path());
605         if (!this._fileSystemsList.itemIds().length)
606             this._reset();
607         this._updateEditFileSystemButtonState();
608     },
609
610     _selectedFileSystemPath: function()
611     {
612         return this._fileSystemsList ? this._fileSystemsList.selectedId() : null;
613     },
614
615     __proto__: WebInspector.SettingsTab.prototype
616 }
617
618
619 /**
620  * @constructor
621  * @extends {WebInspector.SettingsTab}
622  */
623 WebInspector.ExperimentsSettingsTab = function()
624 {
625     WebInspector.SettingsTab.call(this, WebInspector.UIString("Experiments"), "experiments-tab-content");
626
627     var experiments = WebInspector.experimentsSettings.experiments;
628     if (experiments.length) {
629         var experimentsSection = this._appendSection();
630         experimentsSection.appendChild(this._createExperimentsWarningSubsection());
631         for (var i = 0; i < experiments.length; ++i)
632             experimentsSection.appendChild(this._createExperimentCheckbox(experiments[i]));
633     }
634 }
635
636 WebInspector.ExperimentsSettingsTab.prototype = {
637     /**
638      * @return {!Element} element
639      */
640     _createExperimentsWarningSubsection: function()
641     {
642         var subsection = document.createElement("div");
643         var warning = subsection.createChild("span", "settings-experiments-warning-subsection-warning");
644         warning.textContent = WebInspector.UIString("WARNING:");
645         subsection.appendChild(document.createTextNode(" "));
646         var message = subsection.createChild("span", "settings-experiments-warning-subsection-message");
647         message.textContent = WebInspector.UIString("These experiments could be dangerous and may require restart.");
648         return subsection;
649     },
650
651     _createExperimentCheckbox: function(experiment)
652     {
653         var input = document.createElement("input");
654         input.type = "checkbox";
655         input.name = experiment.name;
656         input.checked = experiment.isEnabled();
657         function listener()
658         {
659             experiment.setEnabled(input.checked);
660         }
661         input.addEventListener("click", listener, false);
662
663         var p = document.createElement("p");
664         var label = document.createElement("label");
665         label.appendChild(input);
666         label.appendChild(document.createTextNode(WebInspector.UIString(experiment.title)));
667         p.appendChild(label);
668         return p;
669     },
670
671     __proto__: WebInspector.SettingsTab.prototype
672 }
673
674 /**
675  * @constructor
676  */
677 WebInspector.SettingsController = function()
678 {
679     this._statusBarButton = new WebInspector.StatusBarButton(WebInspector.UIString("Settings"), "settings-status-bar-item");
680     this._statusBarButton.element.addEventListener("mouseup", this._mouseUp.bind(this), false);
681
682     /** @type {?WebInspector.SettingsScreen} */
683     this._settingsScreen;
684 }
685
686 WebInspector.SettingsController.prototype =
687 {
688     /**
689      * @return {!Element}
690      */
691     get statusBarItem()
692     {
693         return this._statusBarButton.element;
694     },
695
696     _mouseUp: function()
697     {
698         this.showSettingsScreen();
699     },
700
701     _onHideSettingsScreen: function()
702     {
703         delete this._settingsScreenVisible;
704     },
705
706     /**
707      * @param {string=} tabId
708      */
709     showSettingsScreen: function(tabId)
710     {
711         if (!this._settingsScreen)
712             this._settingsScreen = new WebInspector.SettingsScreen(this._onHideSettingsScreen.bind(this));
713
714         if (tabId)
715             this._settingsScreen.selectTab(tabId);
716
717         this._settingsScreen.showModal();
718         this._settingsScreenVisible = true;
719     },
720
721     _hideSettingsScreen: function()
722     {
723         if (this._settingsScreen)
724             this._settingsScreen.hide();
725     },
726
727     resize: function()
728     {
729         if (this._settingsScreen && this._settingsScreen.isShowing())
730             this._settingsScreen.doResize();
731     }
732 }
733
734 /**
735  * @constructor
736  * @extends {WebInspector.Object}
737  * @param {function(!Element, string, ?string)} itemRenderer
738  */
739 WebInspector.SettingsList = function(columns, itemRenderer)
740 {
741     this.element = document.createElement("div");
742     this.element.classList.add("settings-list");
743     this.element.tabIndex = -1;
744     this._itemRenderer = itemRenderer;
745     this._listItems = {};
746     this._ids = [];
747     this._columns = columns;
748 }
749
750 WebInspector.SettingsList.Events = {
751     Selected:  "Selected",
752     Removed:  "Removed",
753     DoubleClicked:  "DoubleClicked",
754 }
755
756 WebInspector.SettingsList.prototype = {
757     /**
758      * @param {?string} itemId
759      * @param {?string=} beforeId
760      * @return {!Element}
761      */
762     addItem: function(itemId, beforeId)
763     {
764         var listItem = document.createElement("div");
765         listItem._id = itemId;
766         listItem.classList.add("settings-list-item");
767         if (typeof beforeId !== undefined)
768             this.element.insertBefore(listItem, this._listItems[beforeId]);
769         else
770             this.element.appendChild(listItem);
771
772         var listItemContents = listItem.createChild("div", "settings-list-item-contents");
773         var listItemColumnsElement = listItemContents.createChild("div", "settings-list-item-columns");
774
775         listItem.columnElements = {};
776         for (var i = 0; i < this._columns.length; ++i) {
777             var columnElement = listItemColumnsElement.createChild("div", "list-column");
778             var columnId = this._columns[i];
779             listItem.columnElements[columnId] = columnElement;
780             this._itemRenderer(columnElement, columnId, itemId);
781         }
782         var removeItemButton = this._createRemoveButton(removeItemClicked.bind(this));
783         listItemContents.addEventListener("click", this.selectItem.bind(this, itemId), false);
784         listItemContents.addEventListener("dblclick", this._onDoubleClick.bind(this, itemId), false);
785         listItemContents.appendChild(removeItemButton);
786
787         this._listItems[itemId] = listItem;
788         if (typeof beforeId !== undefined)
789             this._ids.splice(this._ids.indexOf(beforeId), 0, itemId);
790         else
791             this._ids.push(itemId);
792
793         /**
794          * @param {?Event} event
795          * @this {WebInspector.SettingsList}
796          */
797         function removeItemClicked(event)
798         {
799             removeItemButton.disabled = true;
800             this.removeItem(itemId);
801             this.dispatchEventToListeners(WebInspector.SettingsList.Events.Removed, itemId);
802             event.consume();
803         }
804
805         return listItem;
806     },
807
808     /**
809      * @param {?string} id
810      */
811     removeItem: function(id)
812     {
813         this._listItems[id].remove();
814         delete this._listItems[id];
815         this._ids.remove(id);
816         if (id === this._selectedId) {
817             delete this._selectedId;
818             if (this._ids.length)
819                 this.selectItem(this._ids[0]);
820         }
821     },
822
823     /**
824      * @return {!Array.<?string>}
825      */
826     itemIds: function()
827     {
828         return this._ids.slice();
829     },
830
831     /**
832      * @return {!Array.<string>}
833      */
834     columns: function()
835     {
836         return this._columns.slice();
837     },
838
839     /**
840      * @return {?string}
841      */
842     selectedId: function()
843     {
844         return this._selectedId;
845     },
846
847     /**
848      * @return {!Element}
849      */
850     selectedItem: function()
851     {
852         return this._selectedId ? this._listItems[this._selectedId] : null;
853     },
854
855     /**
856      * @param {string} itemId
857      * @return {!Element}
858      */
859     itemForId: function(itemId)
860     {
861         return this._listItems[itemId];
862     },
863
864     /**
865      * @param {?string} id
866      * @param {!Event=} event
867      */
868     _onDoubleClick: function(id, event)
869     {
870         this.dispatchEventToListeners(WebInspector.SettingsList.Events.DoubleClicked, id);
871     },
872
873     /**
874      * @param {?string} id
875      * @param {!Event=} event
876      */
877     selectItem: function(id, event)
878     {
879         if (typeof this._selectedId !== "undefined") {
880             this._listItems[this._selectedId].classList.remove("selected");
881         }
882
883         this._selectedId = id;
884         if (typeof this._selectedId !== "undefined") {
885             this._listItems[this._selectedId].classList.add("selected");
886         }
887         this.dispatchEventToListeners(WebInspector.SettingsList.Events.Selected, id);
888         if (event)
889             event.consume();
890     },
891
892     /**
893      * @param {function(?Event)} handler
894      * @return {!Element}
895      */
896     _createRemoveButton: function(handler)
897     {
898         var removeButton = document.createElement("button");
899         removeButton.classList.add("remove-item-button");
900         removeButton.value = WebInspector.UIString("Remove");
901         removeButton.addEventListener("click", handler, false);
902         return removeButton;
903     },
904
905     __proto__: WebInspector.Object.prototype
906 }
907
908 /**
909  * @constructor
910  * @extends {WebInspector.SettingsList}
911  * @param {function(?string, !Object)} validateHandler
912  * @param {function(?string, !Object)} editHandler
913  */
914 WebInspector.EditableSettingsList = function(columns, valuesProvider, validateHandler, editHandler)
915 {
916     WebInspector.SettingsList.call(this, columns, this._renderColumn.bind(this));
917     this._validateHandler = validateHandler;
918     this._editHandler = editHandler;
919     this._valuesProvider = valuesProvider;
920     /** @type {!Object.<string, !HTMLInputElement>} */
921     this._addInputElements = {};
922     /** @type {!Object.<string, !Object.<string, !HTMLInputElement>>} */
923     this._editInputElements = {};
924     /** @type {!Object.<string, !Object.<string, !HTMLSpanElement>>} */
925     this._textElements = {};
926
927     this._addMappingItem = this.addItem(null);
928     this._addMappingItem.classList.add("item-editing");
929     this._addMappingItem.classList.add("add-list-item");
930 }
931
932 WebInspector.EditableSettingsList.prototype = {
933     /**
934      * @param {?string} itemId
935      * @param {?string=} beforeId
936      * @return {!Element}
937      */
938     addItem: function(itemId, beforeId)
939     {
940         var listItem = WebInspector.SettingsList.prototype.addItem.call(this, itemId, beforeId);
941         listItem.classList.add("editable");
942         return listItem;
943     },
944
945     /**
946      * @param {!Element} columnElement
947      * @param {string} columnId
948      * @param {?string} itemId
949      */
950     _renderColumn: function(columnElement, columnId, itemId)
951     {
952         columnElement.classList.add("settings-list-column-" + columnId);
953         var placeholder = (columnId === "url") ? WebInspector.UIString("URL prefix") : WebInspector.UIString("Folder path");
954         if (itemId === null) {
955             var inputElement = columnElement.createChild("input", "list-column-editor");
956             inputElement.placeholder = placeholder;
957             inputElement.addEventListener("blur", this._onAddMappingInputBlur.bind(this));
958             inputElement.addEventListener("input", this._validateEdit.bind(this, itemId));
959             this._addInputElements[columnId] = inputElement;
960             return;
961         }
962         var validItemId = itemId;
963
964         if (!this._editInputElements[itemId])
965             this._editInputElements[itemId] = {};
966         if (!this._textElements[itemId])
967             this._textElements[itemId] = {};
968
969         var value = this._valuesProvider(itemId, columnId);
970
971         var textElement = columnElement.createChild("span", "list-column-text");
972         textElement.textContent = value;
973         textElement.title = value;
974         columnElement.addEventListener("click", rowClicked.bind(this), false);
975         this._textElements[itemId][columnId] = textElement;
976
977         var inputElement = columnElement.createChild("input", "list-column-editor");
978         inputElement.value = value;
979         inputElement.addEventListener("blur", this._editMappingBlur.bind(this, itemId));
980         inputElement.addEventListener("input", this._validateEdit.bind(this, itemId));
981         columnElement.inputElement = inputElement;
982         this._editInputElements[itemId][columnId] = inputElement;
983
984         /**
985          * @param {?Event} event
986          * @this {WebInspector.EditableSettingsList}
987          */
988         function rowClicked(event)
989         {
990             if (itemId === this._editingId)
991                 return;
992             event.consume();
993             console.assert(!this._editingId);
994             this._editingId = validItemId;
995             var listItem = this.itemForId(validItemId);
996             listItem.classList.add("item-editing");
997             var inputElement = event.target.inputElement || this._editInputElements[validItemId][this.columns()[0]];
998             inputElement.focus();
999             inputElement.select();
1000         }
1001     },
1002
1003     /**
1004      * @param {?string} itemId
1005      * @return {!Object}
1006      */
1007     _data: function(itemId)
1008     {
1009         var inputElements = this._inputElements(itemId);
1010         var data = {};
1011         var columns = this.columns();
1012         for (var i = 0; i < columns.length; ++i)
1013             data[columns[i]] = inputElements[columns[i]].value;
1014         return data;
1015     },
1016
1017     /**
1018      * @param {?string} itemId
1019      * @return {?Object.<string, !HTMLInputElement>}
1020      */
1021     _inputElements: function(itemId)
1022     {
1023         if (!itemId)
1024             return this._addInputElements;
1025         return this._editInputElements[itemId] || null;
1026     },
1027
1028     /**
1029      * @param {?string} itemId
1030      * @return {boolean}
1031      */
1032     _validateEdit: function(itemId)
1033     {
1034         var errorColumns = this._validateHandler(itemId, this._data(itemId));
1035         var hasChanges = this._hasChanges(itemId);
1036         var columns = this.columns();
1037         for (var i = 0; i < columns.length; ++i) {
1038             var columnId = columns[i];
1039             var inputElement = this._inputElements(itemId)[columnId];
1040             if (hasChanges && errorColumns.indexOf(columnId) !== -1)
1041                 inputElement.classList.add("editable-item-error");
1042             else
1043                 inputElement.classList.remove("editable-item-error");
1044         }
1045         return !errorColumns.length;
1046     },
1047
1048     /**
1049      * @param {?string} itemId
1050      * @return {boolean}
1051      */
1052     _hasChanges: function(itemId)
1053     {
1054         var hasChanges = false;
1055         var columns = this.columns();
1056         for (var i = 0; i < columns.length; ++i) {
1057             var columnId = columns[i];
1058             var oldValue = itemId ? this._textElements[itemId][columnId].textContent : "";
1059             var newValue = this._inputElements(itemId)[columnId].value;
1060             if (oldValue !== newValue) {
1061                 hasChanges = true;
1062                 break;
1063             }
1064         }
1065         return hasChanges;
1066     },
1067
1068     /**
1069      * @param {string} itemId
1070      */
1071     _editMappingBlur: function(itemId, event)
1072     {
1073         var inputElements = Object.values(this._editInputElements[itemId]);
1074         if (inputElements.indexOf(event.relatedTarget) !== -1)
1075             return;
1076
1077         var listItem = this.itemForId(itemId);
1078         listItem.classList.remove("item-editing");
1079         delete this._editingId;
1080
1081         if (!this._hasChanges(itemId))
1082             return;
1083
1084         if (!this._validateEdit(itemId)) {
1085             var columns = this.columns();
1086             for (var i = 0; i < columns.length; ++i) {
1087                 var columnId = columns[i];
1088                 var inputElement = this._editInputElements[itemId][columnId];
1089                 inputElement.value = this._textElements[itemId][columnId].textContent;
1090                 inputElement.classList.remove("editable-item-error");
1091             }
1092             return;
1093         }
1094         this._editHandler(itemId, this._data(itemId));
1095     },
1096
1097     _onAddMappingInputBlur: function(event)
1098     {
1099         var inputElements = Object.values(this._addInputElements);
1100         if (inputElements.indexOf(event.relatedTarget) !== -1)
1101             return;
1102
1103         if (!this._hasChanges(null))
1104             return;
1105
1106         if (!this._validateEdit(null))
1107             return;
1108
1109         this._editHandler(null, this._data(null));
1110         var columns = this.columns();
1111         for (var i = 0; i < columns.length; ++i) {
1112             var columnId = columns[i];
1113             var inputElement = this._addInputElements[columnId];
1114             inputElement.value = "";
1115         }
1116     },
1117
1118     __proto__: WebInspector.SettingsList.prototype
1119 }
1120
1121 /** @type {!WebInspector.SettingsController} */
1122 WebInspector.settingsController;