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