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