Home | History | Annotate | Download | only in front_end
      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 : "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 "Invalid number format";
     89     if (value < min || value > max)
     90         return "Value is out of range [" + 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      */
    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  */
    157 WebInspector.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  */
    177 WebInspector.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  */
    212 WebInspector.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  */
    221 WebInspector.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 
    234 WebInspector.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  */
    334 WebInspector.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 
    417 WebInspector.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  */
    478 WebInspector.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 
    506 WebInspector.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  */
    675 WebInspector.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 
    688 WebInspector.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  */
    729 WebInspector.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 
    738 WebInspector.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  */
    791 WebInspector.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 
    802 WebInspector.SettingsList.Events = {
    803     Selected:  "Selected",
    804     Removed:  "Removed",
    805     DoubleClicked:  "DoubleClicked",
    806 }
    807 
    808 WebInspector.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  */
    966 WebInspector.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 
    984 WebInspector.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