Home | History | Annotate | Download | only in settings
      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 importScript("EditFileSystemDialog.js");
     32 
     33 /**
     34  * @constructor
     35  * @param {!function()} onHide
     36  * @extends {WebInspector.HelpScreen}
     37  */
     38 WebInspector.SettingsScreen = function(onHide)
     39 {
     40     WebInspector.HelpScreen.call(this);
     41     this.element.id = "settings-screen";
     42 
     43     /** @type {function()} */
     44     this._onHide = onHide;
     45 
     46     this._tabbedPane = new WebInspector.TabbedPane();
     47     this._tabbedPane.element.classList.add("help-window-main");
     48     var settingsLabelElement = document.createElement("div");
     49     settingsLabelElement.className = "help-window-label";
     50     settingsLabelElement.createTextChild(WebInspector.UIString("Settings"));
     51     this._tabbedPane.element.insertBefore(settingsLabelElement, this._tabbedPane.element.firstChild);
     52     this._tabbedPane.element.appendChild(this._createCloseButton());
     53     this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.General, WebInspector.UIString("General"), new WebInspector.GenericSettingsTab());
     54     this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Workspace, WebInspector.UIString("Workspace"), new WebInspector.WorkspaceSettingsTab());
     55     if (WebInspector.experimentsSettings.experimentsEnabled)
     56         this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Experiments, WebInspector.UIString("Experiments"), new WebInspector.ExperimentsSettingsTab());
     57     this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Shortcuts, WebInspector.UIString("Shortcuts"), WebInspector.shortcutsScreen.createShortcutsTabView());
     58     this._tabbedPane.shrinkableTabs = false;
     59     this._tabbedPane.verticalTabLayout = true;
     60 
     61     this._lastSelectedTabSetting = WebInspector.settings.createSetting("lastSelectedSettingsTab", WebInspector.SettingsScreen.Tabs.General);
     62     this.selectTab(this._lastSelectedTabSetting.get());
     63     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
     64     this.element.addEventListener("keydown", this._keyDown.bind(this), false);
     65     this._developerModeCounter = 0;
     66 }
     67 
     68 /**
     69  * @param {number} min
     70  * @param {number} max
     71  * @param {string} text
     72  * @return {?string}
     73  */
     74 WebInspector.SettingsScreen.integerValidator = function(min, max, text)
     75 {
     76     var value = Number(text);
     77     if (isNaN(value))
     78         return WebInspector.UIString("Invalid number format");
     79     if (value < min || value > max)
     80         return WebInspector.UIString("Value is out of range [%d, %d]", min, max);
     81     return null;
     82 }
     83 
     84 WebInspector.SettingsScreen.Tabs = {
     85     General: "general",
     86     Overrides: "overrides",
     87     Workspace: "workspace",
     88     Experiments: "experiments",
     89     Shortcuts: "shortcuts"
     90 }
     91 
     92 WebInspector.SettingsScreen.prototype = {
     93     /**
     94      * @param {string} tabId
     95      */
     96     selectTab: function(tabId)
     97     {
     98         this._tabbedPane.selectTab(tabId);
     99     },
    100 
    101     /**
    102      * @param {!WebInspector.Event} event
    103      */
    104     _tabSelected: function(event)
    105     {
    106         this._lastSelectedTabSetting.set(this._tabbedPane.selectedTabId);
    107     },
    108 
    109     /**
    110      * @override
    111      */
    112     wasShown: function()
    113     {
    114         this._tabbedPane.show(this.element);
    115         WebInspector.HelpScreen.prototype.wasShown.call(this);
    116     },
    117 
    118     /**
    119      * @override
    120      * @return {boolean}
    121      */
    122     isClosingKey: function(keyCode)
    123     {
    124         return [
    125             WebInspector.KeyboardShortcut.Keys.Enter.code,
    126             WebInspector.KeyboardShortcut.Keys.Esc.code,
    127         ].indexOf(keyCode) >= 0;
    128     },
    129 
    130     /**
    131      * @override
    132      */
    133     willHide: function()
    134     {
    135         this._onHide();
    136         WebInspector.HelpScreen.prototype.willHide.call(this);
    137     },
    138 
    139     /**
    140      * @param {?Event} event
    141      */
    142     _keyDown: function(event)
    143     {
    144         var shiftKeyCode = 16;
    145         if (event.keyCode === shiftKeyCode && ++this._developerModeCounter > 5)
    146             this.element.classList.add("settings-developer-mode");
    147     },
    148 
    149     __proto__: WebInspector.HelpScreen.prototype
    150 }
    151 
    152 /**
    153  * @constructor
    154  * @extends {WebInspector.VBox}
    155  * @param {string} name
    156  * @param {string=} id
    157  */
    158 WebInspector.SettingsTab = function(name, id)
    159 {
    160     WebInspector.VBox.call(this);
    161     this.element.classList.add("settings-tab-container");
    162     if (id)
    163         this.element.id = id;
    164     var header = this.element.createChild("header");
    165     header.createChild("h3").appendChild(document.createTextNode(name));
    166     this.containerElement = this.element.createChild("div", "help-container-wrapper").createChild("div", "settings-tab help-content help-container");
    167 }
    168 
    169 WebInspector.SettingsTab.prototype = {
    170     /**
    171      *  @param {string=} name
    172      *  @return {!Element}
    173      */
    174     _appendSection: function(name)
    175     {
    176         var block = this.containerElement.createChild("div", "help-block");
    177         if (name)
    178             block.createChild("div", "help-section-title").textContent = name;
    179         return block;
    180     },
    181 
    182     _createSelectSetting: function(name, options, setting)
    183     {
    184         var p = document.createElement("p");
    185         var labelElement = p.createChild("label");
    186         labelElement.textContent = name;
    187 
    188         var select = p.createChild("select");
    189         var settingValue = setting.get();
    190 
    191         for (var i = 0; i < options.length; ++i) {
    192             var option = options[i];
    193             select.add(new Option(option[0], option[1]));
    194             if (settingValue === option[1])
    195                 select.selectedIndex = i;
    196         }
    197 
    198         function changeListener(e)
    199         {
    200             // Don't use e.target.value to avoid conversion of the value to string.
    201             setting.set(options[select.selectedIndex][1]);
    202         }
    203 
    204         select.addEventListener("change", changeListener, false);
    205         return p;
    206     },
    207 
    208     __proto__: WebInspector.VBox.prototype
    209 }
    210 
    211 /**
    212  * @constructor
    213  * @extends {WebInspector.SettingsTab}
    214  */
    215 WebInspector.GenericSettingsTab = function()
    216 {
    217     WebInspector.SettingsTab.call(this, WebInspector.UIString("General"), "general-tab-content");
    218 
    219     this._populateSectionsFromExtensions();
    220 
    221     var restoreDefaults = this._appendSection().createChild("input", "settings-tab-text-button");
    222     restoreDefaults.type = "button";
    223     restoreDefaults.value = WebInspector.UIString("Restore defaults and reload");
    224     restoreDefaults.addEventListener("click", restoreAndReload, false);
    225 
    226     function restoreAndReload()
    227     {
    228         if (window.localStorage)
    229             window.localStorage.clear();
    230         WebInspector.reload();
    231     }
    232 }
    233 
    234 WebInspector.GenericSettingsTab.prototype = {
    235     _populateSectionsFromExtensions: function()
    236     {
    237         /** @const */
    238         var explicitSectionOrder = ["", "Appearance", "Elements", "Sources", "Profiler", "Console", "Extensions"];
    239 
    240         var allExtensions = WebInspector.moduleManager.extensions("ui-setting");
    241 
    242         /** @type {!StringMultimap.<!WebInspector.ModuleManager.Extension>} */
    243         var extensionsBySectionId = new StringMultimap();
    244         /** @type {!StringMultimap.<!WebInspector.ModuleManager.Extension>} */
    245         var childSettingExtensionsByParentName = new StringMultimap();
    246 
    247         allExtensions.forEach(function(extension) {
    248             var descriptor = extension.descriptor();
    249             var sectionName = descriptor["section"] || "";
    250             if (!sectionName && descriptor["parentSettingName"]) {
    251                 childSettingExtensionsByParentName.put(descriptor["parentSettingName"], extension);
    252                 return;
    253             }
    254             extensionsBySectionId.put(sectionName, extension);
    255         });
    256 
    257         var sectionIds = extensionsBySectionId.keys();
    258         var explicitlyOrderedSections = {};
    259         for (var i = 0; i < explicitSectionOrder.length; ++i) {
    260             explicitlyOrderedSections[explicitSectionOrder[i]] = true;
    261             var extensions = extensionsBySectionId.get(explicitSectionOrder[i]);
    262             if (!extensions.size())
    263                 continue;
    264             this._addSectionWithExtensionProvidedSettings(explicitSectionOrder[i], extensions.values(), childSettingExtensionsByParentName);
    265         }
    266         for (var i = 0; i < sectionIds.length; ++i) {
    267             if (explicitlyOrderedSections[sectionIds[i]])
    268                 continue;
    269             this._addSectionWithExtensionProvidedSettings(sectionIds[i], extensionsBySectionId.get(sectionIds[i]).values(), childSettingExtensionsByParentName);
    270         }
    271     },
    272 
    273     /**
    274      * @param {string} sectionName
    275      * @param {!Array.<!WebInspector.ModuleManager.Extension>} extensions
    276      * @param {!StringMultimap.<!WebInspector.ModuleManager.Extension>} childSettingExtensionsByParentName
    277      */
    278     _addSectionWithExtensionProvidedSettings: function(sectionName, extensions, childSettingExtensionsByParentName)
    279     {
    280         var uiSectionName = sectionName && WebInspector.UIString(sectionName);
    281         var sectionElement = this._appendSection(uiSectionName);
    282         extensions.forEach(processSetting.bind(this, null));
    283 
    284         /**
    285          * @param {?Element} parentFieldset
    286          * @param {!WebInspector.ModuleManager.Extension} extension
    287          * @this {WebInspector.GenericSettingsTab}
    288          */
    289         function processSetting(parentFieldset, extension)
    290         {
    291             var descriptor = extension.descriptor();
    292             var experimentName = descriptor["experiment"];
    293             if (experimentName && (!WebInspector.experimentsSettings[experimentName] || !WebInspector.experimentsSettings[experimentName].isEnabled()))
    294                 return;
    295 
    296             var settingName = descriptor["settingName"];
    297             var setting = WebInspector.settings[settingName];
    298             var instance = extension.instance();
    299             var settingControl;
    300             if (instance && descriptor["settingType"] === "custom") {
    301                 settingControl = instance.settingElement();
    302                 if (!settingControl)
    303                     return;
    304             }
    305             if (!settingControl) {
    306                 var uiTitle = WebInspector.UIString(descriptor["title"]);
    307                 settingControl = createSettingControl.call(this, uiTitle, setting, descriptor, instance);
    308             }
    309             if (settingName) {
    310                 var childSettings = childSettingExtensionsByParentName.get(settingName);
    311                 if (childSettings.size()) {
    312                     var fieldSet = WebInspector.SettingsUI.createSettingFieldset(setting);
    313                     settingControl.appendChild(fieldSet);
    314                     childSettings.values().forEach(function(item) { processSetting.call(this, fieldSet, item); }, this);
    315                 }
    316             }
    317             var containerElement = parentFieldset || sectionElement;
    318             containerElement.appendChild(settingControl);
    319         }
    320 
    321         /**
    322          * @param {string} uiTitle
    323          * @param {!WebInspector.Setting} setting
    324          * @param {!Object} descriptor
    325          * @param {?Object} instance
    326          * @return {!Element}
    327          * @this {WebInspector.GenericSettingsTab}
    328          */
    329         function createSettingControl(uiTitle, setting, descriptor, instance)
    330         {
    331             switch (descriptor["settingType"]) {
    332             case "checkbox":
    333                 return WebInspector.SettingsUI.createSettingCheckbox(uiTitle, setting);
    334             case "select":
    335                 var descriptorOptions = descriptor["options"]
    336                 var options = new Array(descriptorOptions.length);
    337                 for (var i = 0; i < options.length; ++i) {
    338                     // The third array item flags that the option name is "raw" (non-i18n-izable).
    339                     var optionName = descriptorOptions[i][2] ? descriptorOptions[i][0] : WebInspector.UIString(descriptorOptions[i][0]);
    340                     options[i] = [WebInspector.UIString(descriptorOptions[i][0]), descriptorOptions[i][1]];
    341                 }
    342                 return this._createSelectSetting(uiTitle, options, setting);
    343             default:
    344                 throw "Invalid setting type: " + descriptor["settingType"];
    345             }
    346         }
    347     },
    348 
    349     /**
    350      * @param {?Element} p
    351      */
    352     _appendDrawerNote: function(p)
    353     {
    354         var noteElement = p.createChild("div", "help-field-note");
    355         noteElement.createTextChild("Hit ");
    356         noteElement.createChild("span", "help-key").textContent = "Esc";
    357         noteElement.createTextChild(WebInspector.UIString(" or click the"));
    358         noteElement.appendChild(new WebInspector.StatusBarButton(WebInspector.UIString("Drawer"), "console-status-bar-item").element);
    359         noteElement.createTextChild(WebInspector.UIString("toolbar item"));
    360     },
    361 
    362     __proto__: WebInspector.SettingsTab.prototype
    363 }
    364 
    365 /**
    366  * @constructor
    367  * @extends {WebInspector.SettingsTab}
    368  */
    369 WebInspector.WorkspaceSettingsTab = function()
    370 {
    371     WebInspector.SettingsTab.call(this, WebInspector.UIString("Workspace"), "workspace-tab-content");
    372     WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemAdded, this._fileSystemAdded, this);
    373     WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemRemoved, this._fileSystemRemoved, this);
    374 
    375     this._commonSection = this._appendSection(WebInspector.UIString("Common"));
    376     var folderExcludePatternInput = WebInspector.SettingsUI.createSettingInputField(WebInspector.UIString("Folder exclude pattern"), WebInspector.settings.workspaceFolderExcludePattern, false, 0, "270px", WebInspector.SettingsUI.regexValidator);
    377     this._commonSection.appendChild(folderExcludePatternInput);
    378 
    379     this._fileSystemsSection = this._appendSection(WebInspector.UIString("Folders"));
    380     this._fileSystemsListContainer = this._fileSystemsSection.createChild("p", "settings-list-container");
    381 
    382     this._addFileSystemRowElement = this._fileSystemsSection.createChild("div");
    383     var addFileSystemButton = this._addFileSystemRowElement.createChild("input", "settings-tab-text-button");
    384     addFileSystemButton.type = "button";
    385     addFileSystemButton.value = WebInspector.UIString("Add folder\u2026");
    386     addFileSystemButton.addEventListener("click", this._addFileSystemClicked.bind(this), false);
    387 
    388     this._editFileSystemButton = this._addFileSystemRowElement.createChild("input", "settings-tab-text-button");
    389     this._editFileSystemButton.type = "button";
    390     this._editFileSystemButton.value = WebInspector.UIString("Edit\u2026");
    391     this._editFileSystemButton.addEventListener("click", this._editFileSystemClicked.bind(this), false);
    392     this._updateEditFileSystemButtonState();
    393 
    394     this._reset();
    395 }
    396 
    397 WebInspector.WorkspaceSettingsTab.prototype = {
    398     wasShown: function()
    399     {
    400         WebInspector.SettingsTab.prototype.wasShown.call(this);
    401         this._reset();
    402     },
    403 
    404     _reset: function()
    405     {
    406         this._resetFileSystems();
    407     },
    408 
    409     _resetFileSystems: function()
    410     {
    411         this._fileSystemsListContainer.removeChildren();
    412         var fileSystemPaths = WebInspector.isolatedFileSystemManager.mapping().fileSystemPaths();
    413         delete this._fileSystemsList;
    414 
    415         if (!fileSystemPaths.length) {
    416             var noFileSystemsMessageElement = this._fileSystemsListContainer.createChild("div", "no-file-systems-message");
    417             noFileSystemsMessageElement.textContent = WebInspector.UIString("You have no file systems added.");
    418             return;
    419         }
    420 
    421         this._fileSystemsList = new WebInspector.SettingsList(["path"], this._renderFileSystem.bind(this));
    422         this._fileSystemsList.element.classList.add("file-systems-list");
    423         this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Selected, this._fileSystemSelected.bind(this));
    424         this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Removed, this._fileSystemRemovedfromList.bind(this));
    425         this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.DoubleClicked, this._fileSystemDoubleClicked.bind(this));
    426         this._fileSystemsListContainer.appendChild(this._fileSystemsList.element);
    427         for (var i = 0; i < fileSystemPaths.length; ++i)
    428             this._fileSystemsList.addItem(fileSystemPaths[i]);
    429         this._updateEditFileSystemButtonState();
    430     },
    431 
    432     _updateEditFileSystemButtonState: function()
    433     {
    434         this._editFileSystemButton.disabled = !this._selectedFileSystemPath();
    435     },
    436 
    437     /**
    438      * @param {!WebInspector.Event} event
    439      */
    440     _fileSystemSelected: function(event)
    441     {
    442         this._updateEditFileSystemButtonState();
    443     },
    444 
    445     /**
    446      * @param {!WebInspector.Event} event
    447      */
    448     _fileSystemDoubleClicked: function(event)
    449     {
    450         var id = /** @type{?string} */ (event.data);
    451         this._editFileSystem(id);
    452     },
    453 
    454     _editFileSystemClicked: function()
    455     {
    456         this._editFileSystem(this._selectedFileSystemPath());
    457     },
    458 
    459     /**
    460      * @param {?string} id
    461      */
    462     _editFileSystem: function(id)
    463     {
    464         WebInspector.EditFileSystemDialog.show(WebInspector.inspectorView.element, id);
    465     },
    466 
    467     /**
    468      * @param {function(?Event)} handler
    469      * @return {!Element}
    470      */
    471     _createRemoveButton: function(handler)
    472     {
    473         var removeButton = document.createElement("button");
    474         removeButton.classList.add("button");
    475         removeButton.classList.add("remove-item-button");
    476         removeButton.value = WebInspector.UIString("Remove");
    477         if (handler)
    478             removeButton.addEventListener("click", handler, false);
    479         else
    480             removeButton.disabled = true;
    481         return removeButton;
    482     },
    483 
    484     /**
    485      * @param {!Element} columnElement
    486      * @param {string} column
    487      * @param {?string} id
    488      */
    489     _renderFileSystem: function(columnElement, column, id)
    490     {
    491         if (!id)
    492             return "";
    493         var fileSystemPath = id;
    494         var textElement = columnElement.createChild("span", "list-column-text");
    495         var pathElement = textElement.createChild("span", "file-system-path");
    496         pathElement.title = fileSystemPath;
    497 
    498         const maxTotalPathLength = 55;
    499         const maxFolderNameLength = 30;
    500 
    501         var lastIndexOfSlash = fileSystemPath.lastIndexOf(WebInspector.isWin() ? "\\" : "/");
    502         var folderName = fileSystemPath.substr(lastIndexOfSlash + 1);
    503         var folderPath = fileSystemPath.substr(0, lastIndexOfSlash + 1);
    504         folderPath = folderPath.trimMiddle(maxTotalPathLength - Math.min(maxFolderNameLength, folderName.length));
    505         folderName = folderName.trimMiddle(maxFolderNameLength);
    506 
    507         var folderPathElement = pathElement.createChild("span");
    508         folderPathElement.textContent = folderPath;
    509 
    510         var nameElement = pathElement.createChild("span", "file-system-path-name");
    511         nameElement.textContent = folderName;
    512     },
    513 
    514     /**
    515      * @param {!WebInspector.Event} event
    516      */
    517     _fileSystemRemovedfromList: function(event)
    518     {
    519         var id = /** @type{?string} */ (event.data);
    520         if (!id)
    521             return;
    522         WebInspector.isolatedFileSystemManager.removeFileSystem(id);
    523     },
    524 
    525     _addFileSystemClicked: function()
    526     {
    527         WebInspector.isolatedFileSystemManager.addFileSystem();
    528     },
    529 
    530     _fileSystemAdded: function(event)
    531     {
    532         var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
    533         if (!this._fileSystemsList)
    534             this._reset();
    535         else
    536             this._fileSystemsList.addItem(fileSystem.path());
    537     },
    538 
    539     _fileSystemRemoved: function(event)
    540     {
    541         var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
    542         var selectedFileSystemPath = this._selectedFileSystemPath();
    543         if (this._fileSystemsList.itemForId(fileSystem.path()))
    544             this._fileSystemsList.removeItem(fileSystem.path());
    545         if (!this._fileSystemsList.itemIds().length)
    546             this._reset();
    547         this._updateEditFileSystemButtonState();
    548     },
    549 
    550     _selectedFileSystemPath: function()
    551     {
    552         return this._fileSystemsList ? this._fileSystemsList.selectedId() : null;
    553     },
    554 
    555     __proto__: WebInspector.SettingsTab.prototype
    556 }
    557 
    558 
    559 /**
    560  * @constructor
    561  * @extends {WebInspector.SettingsTab}
    562  */
    563 WebInspector.ExperimentsSettingsTab = function()
    564 {
    565     WebInspector.SettingsTab.call(this, WebInspector.UIString("Experiments"), "experiments-tab-content");
    566 
    567     var experiments = WebInspector.experimentsSettings.experiments;
    568     if (experiments.length) {
    569         var experimentsSection = this._appendSection();
    570         experimentsSection.appendChild(this._createExperimentsWarningSubsection());
    571         for (var i = 0; i < experiments.length; ++i)
    572             experimentsSection.appendChild(this._createExperimentCheckbox(experiments[i]));
    573     }
    574 }
    575 
    576 WebInspector.ExperimentsSettingsTab.prototype = {
    577     /**
    578      * @return {!Element} element
    579      */
    580     _createExperimentsWarningSubsection: function()
    581     {
    582         var subsection = document.createElement("div");
    583         var warning = subsection.createChild("span", "settings-experiments-warning-subsection-warning");
    584         warning.textContent = WebInspector.UIString("WARNING:");
    585         subsection.appendChild(document.createTextNode(" "));
    586         var message = subsection.createChild("span", "settings-experiments-warning-subsection-message");
    587         message.textContent = WebInspector.UIString("These experiments could be dangerous and may require restart.");
    588         return subsection;
    589     },
    590 
    591     _createExperimentCheckbox: function(experiment)
    592     {
    593         var input = document.createElement("input");
    594         input.type = "checkbox";
    595         input.name = experiment.name;
    596         input.checked = experiment.isEnabled();
    597         function listener()
    598         {
    599             experiment.setEnabled(input.checked);
    600         }
    601         input.addEventListener("click", listener, false);
    602 
    603         var p = document.createElement("p");
    604         p.className = experiment.hidden && !experiment.isEnabled() ? "settings-experiment-hidden" : "";
    605         var label = p.createChild("label");
    606         label.appendChild(input);
    607         label.appendChild(document.createTextNode(WebInspector.UIString(experiment.title)));
    608         p.appendChild(label);
    609         return p;
    610     },
    611 
    612     __proto__: WebInspector.SettingsTab.prototype
    613 }
    614 
    615 /**
    616  * @constructor
    617  */
    618 WebInspector.SettingsController = function()
    619 {
    620     /** @type {?WebInspector.SettingsScreen} */
    621     this._settingsScreen;
    622 
    623     window.addEventListener("resize", this._resize.bind(this), true);
    624 }
    625 
    626 WebInspector.SettingsController.prototype =
    627 {
    628     _onHideSettingsScreen: function()
    629     {
    630         delete this._settingsScreenVisible;
    631     },
    632 
    633     /**
    634      * @param {string=} tabId
    635      */
    636     showSettingsScreen: function(tabId)
    637     {
    638         if (!this._settingsScreen)
    639             this._settingsScreen = new WebInspector.SettingsScreen(this._onHideSettingsScreen.bind(this));
    640 
    641         if (tabId)
    642             this._settingsScreen.selectTab(tabId);
    643 
    644         this._settingsScreen.showModal();
    645         this._settingsScreenVisible = true;
    646     },
    647 
    648     _resize: function()
    649     {
    650         if (this._settingsScreen && this._settingsScreen.isShowing())
    651             this._settingsScreen.doResize();
    652     }
    653 }
    654 
    655 /**
    656  * @constructor
    657  * @implements {WebInspector.ActionDelegate}
    658  */
    659 WebInspector.SettingsController.SettingsScreenActionDelegate = function() { }
    660 
    661 WebInspector.SettingsController.SettingsScreenActionDelegate.prototype = {
    662     /**
    663      * @return {boolean}
    664      */
    665     handleAction: function()
    666     {
    667         WebInspector._settingsController.showSettingsScreen(WebInspector.SettingsScreen.Tabs.General);
    668         return true;
    669     }
    670 }
    671 
    672 /**
    673  * @constructor
    674  * @extends {WebInspector.Object}
    675  * @param {!Array.<string>} columns
    676  * @param {function(!Element, string, ?string)} itemRenderer
    677  */
    678 WebInspector.SettingsList = function(columns, itemRenderer)
    679 {
    680     this.element = document.createElement("div");
    681     this.element.classList.add("settings-list");
    682     this.element.tabIndex = -1;
    683     this._itemRenderer = itemRenderer;
    684     this._listItems = {};
    685     this._ids = [];
    686     this._columns = columns;
    687 }
    688 
    689 WebInspector.SettingsList.Events = {
    690     Selected:  "Selected",
    691     Removed:  "Removed",
    692     DoubleClicked:  "DoubleClicked",
    693 }
    694 
    695 WebInspector.SettingsList.prototype = {
    696     /**
    697      * @param {?string} itemId
    698      * @param {?string=} beforeId
    699      * @return {!Element}
    700      */
    701     addItem: function(itemId, beforeId)
    702     {
    703         var listItem = document.createElement("div");
    704         listItem._id = itemId;
    705         listItem.classList.add("settings-list-item");
    706         if (typeof beforeId !== undefined)
    707             this.element.insertBefore(listItem, this._listItems[beforeId]);
    708         else
    709             this.element.appendChild(listItem);
    710 
    711         var listItemContents = listItem.createChild("div", "settings-list-item-contents");
    712         var listItemColumnsElement = listItemContents.createChild("div", "settings-list-item-columns");
    713 
    714         listItem.columnElements = {};
    715         for (var i = 0; i < this._columns.length; ++i) {
    716             var columnElement = listItemColumnsElement.createChild("div", "list-column");
    717             var columnId = this._columns[i];
    718             listItem.columnElements[columnId] = columnElement;
    719             this._itemRenderer(columnElement, columnId, itemId);
    720         }
    721         var removeItemButton = this._createRemoveButton(removeItemClicked.bind(this));
    722         listItemContents.addEventListener("click", this.selectItem.bind(this, itemId), false);
    723         listItemContents.addEventListener("dblclick", this._onDoubleClick.bind(this, itemId), false);
    724         listItemContents.appendChild(removeItemButton);
    725 
    726         this._listItems[itemId] = listItem;
    727         if (typeof beforeId !== undefined)
    728             this._ids.splice(this._ids.indexOf(beforeId), 0, itemId);
    729         else
    730             this._ids.push(itemId);
    731 
    732         /**
    733          * @param {?Event} event
    734          * @this {WebInspector.SettingsList}
    735          */
    736         function removeItemClicked(event)
    737         {
    738             removeItemButton.disabled = true;
    739             this.removeItem(itemId);
    740             this.dispatchEventToListeners(WebInspector.SettingsList.Events.Removed, itemId);
    741             event.consume();
    742         }
    743 
    744         return listItem;
    745     },
    746 
    747     /**
    748      * @param {?string} id
    749      */
    750     removeItem: function(id)
    751     {
    752         this._listItems[id].remove();
    753         delete this._listItems[id];
    754         this._ids.remove(id);
    755         if (id === this._selectedId) {
    756             delete this._selectedId;
    757             if (this._ids.length)
    758                 this.selectItem(this._ids[0]);
    759         }
    760     },
    761 
    762     /**
    763      * @return {!Array.<?string>}
    764      */
    765     itemIds: function()
    766     {
    767         return this._ids.slice();
    768     },
    769 
    770     /**
    771      * @return {!Array.<string>}
    772      */
    773     columns: function()
    774     {
    775         return this._columns.slice();
    776     },
    777 
    778     /**
    779      * @return {?string}
    780      */
    781     selectedId: function()
    782     {
    783         return this._selectedId;
    784     },
    785 
    786     /**
    787      * @return {!Element}
    788      */
    789     selectedItem: function()
    790     {
    791         return this._selectedId ? this._listItems[this._selectedId] : null;
    792     },
    793 
    794     /**
    795      * @param {string} itemId
    796      * @return {!Element}
    797      */
    798     itemForId: function(itemId)
    799     {
    800         return this._listItems[itemId];
    801     },
    802 
    803     /**
    804      * @param {?string} id
    805      * @param {?Event=} event
    806      */
    807     _onDoubleClick: function(id, event)
    808     {
    809         this.dispatchEventToListeners(WebInspector.SettingsList.Events.DoubleClicked, id);
    810     },
    811 
    812     /**
    813      * @param {?string} id
    814      * @param {?Event=} event
    815      */
    816     selectItem: function(id, event)
    817     {
    818         if (typeof this._selectedId !== "undefined") {
    819             this._listItems[this._selectedId].classList.remove("selected");
    820         }
    821 
    822         this._selectedId = id;
    823         if (typeof this._selectedId !== "undefined") {
    824             this._listItems[this._selectedId].classList.add("selected");
    825         }
    826         this.dispatchEventToListeners(WebInspector.SettingsList.Events.Selected, id);
    827         if (event)
    828             event.consume();
    829     },
    830 
    831     /**
    832      * @param {function(?Event)} handler
    833      * @return {!Element}
    834      */
    835     _createRemoveButton: function(handler)
    836     {
    837         var removeButton = document.createElement("button");
    838         removeButton.classList.add("remove-item-button");
    839         removeButton.value = WebInspector.UIString("Remove");
    840         removeButton.addEventListener("click", handler, false);
    841         return removeButton;
    842     },
    843 
    844     __proto__: WebInspector.Object.prototype
    845 }
    846 
    847 /**
    848  * @constructor
    849  * @extends {WebInspector.SettingsList}
    850  * @param {!Array.<string>} columns
    851  * @param {function(string, string):string} valuesProvider
    852  * @param {function(?string, !Object)} validateHandler
    853  * @param {function(?string, !Object)} editHandler
    854  */
    855 WebInspector.EditableSettingsList = function(columns, valuesProvider, validateHandler, editHandler)
    856 {
    857     WebInspector.SettingsList.call(this, columns, this._renderColumn.bind(this));
    858     this._validateHandler = validateHandler;
    859     this._editHandler = editHandler;
    860     this._valuesProvider = valuesProvider;
    861     /** @type {!Object.<string, !HTMLInputElement>} */
    862     this._addInputElements = {};
    863     /** @type {!Object.<string, !Object.<string, !HTMLInputElement>>} */
    864     this._editInputElements = {};
    865     /** @type {!Object.<string, !Object.<string, !HTMLSpanElement>>} */
    866     this._textElements = {};
    867 
    868     this._addMappingItem = this.addItem(null);
    869     this._addMappingItem.classList.add("item-editing");
    870     this._addMappingItem.classList.add("add-list-item");
    871 }
    872 
    873 WebInspector.EditableSettingsList.prototype = {
    874     /**
    875      * @param {?string} itemId
    876      * @param {?string=} beforeId
    877      * @return {!Element}
    878      */
    879     addItem: function(itemId, beforeId)
    880     {
    881         var listItem = WebInspector.SettingsList.prototype.addItem.call(this, itemId, beforeId);
    882         listItem.classList.add("editable");
    883         return listItem;
    884     },
    885 
    886     /**
    887      * @param {!Element} columnElement
    888      * @param {string} columnId
    889      * @param {?string} itemId
    890      */
    891     _renderColumn: function(columnElement, columnId, itemId)
    892     {
    893         columnElement.classList.add("settings-list-column-" + columnId);
    894         var placeholder = (columnId === "url") ? WebInspector.UIString("URL prefix") : WebInspector.UIString("Folder path");
    895         if (itemId === null) {
    896             var inputElement = /** @type {!HTMLInputElement} */ (columnElement.createChild("input", "list-column-editor"));
    897             inputElement.placeholder = placeholder;
    898             inputElement.addEventListener("blur", this._onAddMappingInputBlur.bind(this), false);
    899             inputElement.addEventListener("input", this._validateEdit.bind(this, itemId), false);
    900             this._addInputElements[columnId] = inputElement;
    901             return;
    902         }
    903         var validItemId = itemId;
    904 
    905         if (!this._editInputElements[itemId])
    906             this._editInputElements[itemId] = {};
    907         if (!this._textElements[itemId])
    908             this._textElements[itemId] = {};
    909 
    910         var value = this._valuesProvider(itemId, columnId);
    911 
    912         var textElement = /** @type {!HTMLSpanElement} */ (columnElement.createChild("span", "list-column-text"));
    913         textElement.textContent = value;
    914         textElement.title = value;
    915         columnElement.addEventListener("click", rowClicked.bind(this), false);
    916         this._textElements[itemId][columnId] = textElement;
    917 
    918         var inputElement = /** @type {!HTMLInputElement} */ (columnElement.createChild("input", "list-column-editor"));
    919         inputElement.value = value;
    920         inputElement.addEventListener("blur", this._editMappingBlur.bind(this, itemId), false);
    921         inputElement.addEventListener("input", this._validateEdit.bind(this, itemId), false);
    922         columnElement.inputElement = inputElement;
    923         this._editInputElements[itemId][columnId] = inputElement;
    924 
    925         /**
    926          * @param {?Event} event
    927          * @this {WebInspector.EditableSettingsList}
    928          */
    929         function rowClicked(event)
    930         {
    931             if (itemId === this._editingId)
    932                 return;
    933             event.consume();
    934             console.assert(!this._editingId);
    935             this._editingId = validItemId;
    936             var listItem = this.itemForId(validItemId);
    937             listItem.classList.add("item-editing");
    938             var inputElement = event.target.inputElement || this._editInputElements[validItemId][this.columns()[0]];
    939             inputElement.focus();
    940             inputElement.select();
    941         }
    942     },
    943 
    944     /**
    945      * @param {?string} itemId
    946      * @return {!Object}
    947      */
    948     _data: function(itemId)
    949     {
    950         var inputElements = this._inputElements(itemId);
    951         var data = {};
    952         var columns = this.columns();
    953         for (var i = 0; i < columns.length; ++i)
    954             data[columns[i]] = inputElements[columns[i]].value;
    955         return data;
    956     },
    957 
    958     /**
    959      * @param {?string} itemId
    960      * @return {?Object.<string, !HTMLInputElement>}
    961      */
    962     _inputElements: function(itemId)
    963     {
    964         if (!itemId)
    965             return this._addInputElements;
    966         return this._editInputElements[itemId] || null;
    967     },
    968 
    969     /**
    970      * @param {?string} itemId
    971      * @return {boolean}
    972      */
    973     _validateEdit: function(itemId)
    974     {
    975         var errorColumns = this._validateHandler(itemId, this._data(itemId));
    976         var hasChanges = this._hasChanges(itemId);
    977         var columns = this.columns();
    978         for (var i = 0; i < columns.length; ++i) {
    979             var columnId = columns[i];
    980             var inputElement = this._inputElements(itemId)[columnId];
    981             if (hasChanges && errorColumns.indexOf(columnId) !== -1)
    982                 inputElement.classList.add("editable-item-error");
    983             else
    984                 inputElement.classList.remove("editable-item-error");
    985         }
    986         return !errorColumns.length;
    987     },
    988 
    989     /**
    990      * @param {?string} itemId
    991      * @return {boolean}
    992      */
    993     _hasChanges: function(itemId)
    994     {
    995         var hasChanges = false;
    996         var columns = this.columns();
    997         for (var i = 0; i < columns.length; ++i) {
    998             var columnId = columns[i];
    999             var oldValue = itemId ? this._textElements[itemId][columnId].textContent : "";
   1000             var newValue = this._inputElements(itemId)[columnId].value;
   1001             if (oldValue !== newValue) {
   1002                 hasChanges = true;
   1003                 break;
   1004             }
   1005         }
   1006         return hasChanges;
   1007     },
   1008 
   1009     /**
   1010      * @param {string} itemId
   1011      * @param {?Event} event
   1012      */
   1013     _editMappingBlur: function(itemId, event)
   1014     {
   1015         var inputElements = Object.values(this._editInputElements[itemId]);
   1016         if (inputElements.indexOf(event.relatedTarget) !== -1)
   1017             return;
   1018 
   1019         var listItem = this.itemForId(itemId);
   1020         listItem.classList.remove("item-editing");
   1021         delete this._editingId;
   1022 
   1023         if (!this._hasChanges(itemId))
   1024             return;
   1025 
   1026         if (!this._validateEdit(itemId)) {
   1027             var columns = this.columns();
   1028             for (var i = 0; i < columns.length; ++i) {
   1029                 var columnId = columns[i];
   1030                 var inputElement = this._editInputElements[itemId][columnId];
   1031                 inputElement.value = this._textElements[itemId][columnId].textContent;
   1032                 inputElement.classList.remove("editable-item-error");
   1033             }
   1034             return;
   1035         }
   1036         this._editHandler(itemId, this._data(itemId));
   1037     },
   1038 
   1039     _onAddMappingInputBlur: function(event)
   1040     {
   1041         var inputElements = Object.values(this._addInputElements);
   1042         if (inputElements.indexOf(event.relatedTarget) !== -1)
   1043             return;
   1044 
   1045         if (!this._hasChanges(null))
   1046             return;
   1047 
   1048         if (!this._validateEdit(null))
   1049             return;
   1050 
   1051         this._editHandler(null, this._data(null));
   1052         var columns = this.columns();
   1053         for (var i = 0; i < columns.length; ++i) {
   1054             var columnId = columns[i];
   1055             var inputElement = this._addInputElements[columnId];
   1056             inputElement.value = "";
   1057         }
   1058     },
   1059 
   1060     __proto__: WebInspector.SettingsList.prototype
   1061 }
   1062 
   1063 WebInspector._settingsController = new WebInspector.SettingsController();
   1064