diff options
Diffstat (limited to 'devtools/client/framework/toolbox-options.js')
-rw-r--r-- | devtools/client/framework/toolbox-options.js | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/devtools/client/framework/toolbox-options.js b/devtools/client/framework/toolbox-options.js new file mode 100644 index 000000000..6362d98dd --- /dev/null +++ b/devtools/client/framework/toolbox-options.js @@ -0,0 +1,431 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Services = require("Services"); +const defer = require("devtools/shared/defer"); +const {Task} = require("devtools/shared/task"); +const {gDevTools} = require("devtools/client/framework/devtools"); + +const {LocalizationHelper} = require("devtools/shared/l10n"); +const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties"); + +exports.OptionsPanel = OptionsPanel; + +function GetPref(name) { + let type = Services.prefs.getPrefType(name); + switch (type) { + case Services.prefs.PREF_STRING: + return Services.prefs.getCharPref(name); + case Services.prefs.PREF_INT: + return Services.prefs.getIntPref(name); + case Services.prefs.PREF_BOOL: + return Services.prefs.getBoolPref(name); + default: + throw new Error("Unknown type"); + } +} + +function SetPref(name, value) { + let type = Services.prefs.getPrefType(name); + switch (type) { + case Services.prefs.PREF_STRING: + return Services.prefs.setCharPref(name, value); + case Services.prefs.PREF_INT: + return Services.prefs.setIntPref(name, value); + case Services.prefs.PREF_BOOL: + return Services.prefs.setBoolPref(name, value); + default: + throw new Error("Unknown type"); + } +} + +function InfallibleGetBoolPref(key) { + try { + return Services.prefs.getBoolPref(key); + } catch (ex) { + return true; + } +} + +/** + * Represents the Options Panel in the Toolbox. + */ +function OptionsPanel(iframeWindow, toolbox) { + this.panelDoc = iframeWindow.document; + this.panelWin = iframeWindow; + + this.toolbox = toolbox; + this.isReady = false; + + this._prefChanged = this._prefChanged.bind(this); + this._themeRegistered = this._themeRegistered.bind(this); + this._themeUnregistered = this._themeUnregistered.bind(this); + this._disableJSClicked = this._disableJSClicked.bind(this); + + this.disableJSNode = this.panelDoc.getElementById( + "devtools-disable-javascript"); + + this._addListeners(); + + const EventEmitter = require("devtools/shared/event-emitter"); + EventEmitter.decorate(this); +} + +OptionsPanel.prototype = { + + get target() { + return this.toolbox.target; + }, + + open: Task.async(function* () { + // For local debugging we need to make the target remote. + if (!this.target.isRemote) { + yield this.target.makeRemote(); + } + + this.setupToolsList(); + this.setupToolbarButtonsList(); + this.setupThemeList(); + yield this.populatePreferences(); + this.isReady = true; + this.emit("ready"); + return this; + }), + + _addListeners: function () { + Services.prefs.addObserver("devtools.cache.disabled", this._prefChanged, false); + Services.prefs.addObserver("devtools.theme", this._prefChanged, false); + gDevTools.on("theme-registered", this._themeRegistered); + gDevTools.on("theme-unregistered", this._themeUnregistered); + }, + + _removeListeners: function () { + Services.prefs.removeObserver("devtools.cache.disabled", this._prefChanged); + Services.prefs.removeObserver("devtools.theme", this._prefChanged); + gDevTools.off("theme-registered", this._themeRegistered); + gDevTools.off("theme-unregistered", this._themeUnregistered); + }, + + _prefChanged: function (subject, topic, prefName) { + if (prefName === "devtools.cache.disabled") { + let cacheDisabled = data.newValue; + let cbx = this.panelDoc.getElementById("devtools-disable-cache"); + + cbx.checked = cacheDisabled; + } else if (prefName === "devtools.theme") { + this.updateCurrentTheme(); + } + }, + + _themeRegistered: function (event, themeId) { + this.setupThemeList(); + }, + + _themeUnregistered: function (event, theme) { + let themeBox = this.panelDoc.getElementById("devtools-theme-box"); + let themeInput = themeBox.querySelector(`[value=${theme.id}]`); + + if (themeInput) { + themeInput.parentNode.remove(); + } + }, + + setupToolbarButtonsList: function () { + let enabledToolbarButtonsBox = this.panelDoc.getElementById( + "enabled-toolbox-buttons-box"); + + let toggleableButtons = this.toolbox.toolboxButtons; + let setToolboxButtonsVisibility = + this.toolbox.setToolboxButtonsVisibility.bind(this.toolbox); + + let onCheckboxClick = (checkbox) => { + let toolDefinition = toggleableButtons.filter( + toggleableButton => toggleableButton.id === checkbox.id)[0]; + Services.prefs.setBoolPref( + toolDefinition.visibilityswitch, checkbox.checked); + setToolboxButtonsVisibility(); + }; + + let createCommandCheckbox = tool => { + let checkboxLabel = this.panelDoc.createElement("label"); + let checkboxSpanLabel = this.panelDoc.createElement("span"); + checkboxSpanLabel.textContent = tool.label; + let checkboxInput = this.panelDoc.createElement("input"); + checkboxInput.setAttribute("type", "checkbox"); + checkboxInput.setAttribute("id", tool.id); + if (InfallibleGetBoolPref(tool.visibilityswitch)) { + checkboxInput.setAttribute("checked", true); + } + checkboxInput.addEventListener("change", + onCheckboxClick.bind(this, checkboxInput)); + + checkboxLabel.appendChild(checkboxInput); + checkboxLabel.appendChild(checkboxSpanLabel); + return checkboxLabel; + }; + + for (let tool of toggleableButtons) { + if (!tool.isTargetSupported(this.toolbox.target)) { + continue; + } + + enabledToolbarButtonsBox.appendChild(createCommandCheckbox(tool)); + } + }, + + setupToolsList: function () { + let defaultToolsBox = this.panelDoc.getElementById("default-tools-box"); + let additionalToolsBox = this.panelDoc.getElementById( + "additional-tools-box"); + let toolsNotSupportedLabel = this.panelDoc.getElementById( + "tools-not-supported-label"); + let atleastOneToolNotSupported = false; + + let onCheckboxClick = function (id) { + let toolDefinition = gDevTools._tools.get(id); + // Set the kill switch pref boolean to true + Services.prefs.setBoolPref(toolDefinition.visibilityswitch, this.checked); + if (this.checked) { + gDevTools.emit("tool-registered", id); + } else { + gDevTools.emit("tool-unregistered", toolDefinition); + } + }; + + let createToolCheckbox = tool => { + let checkboxLabel = this.panelDoc.createElement("label"); + let checkboxInput = this.panelDoc.createElement("input"); + checkboxInput.setAttribute("type", "checkbox"); + checkboxInput.setAttribute("id", tool.id); + checkboxInput.setAttribute("title", tool.tooltip || ""); + + let checkboxSpanLabel = this.panelDoc.createElement("span"); + if (tool.isTargetSupported(this.target)) { + checkboxSpanLabel.textContent = tool.label; + } else { + atleastOneToolNotSupported = true; + checkboxSpanLabel.textContent = + L10N.getFormatStr("options.toolNotSupportedMarker", tool.label); + checkboxInput.setAttribute("data-unsupported", "true"); + checkboxInput.setAttribute("disabled", "true"); + } + + if (InfallibleGetBoolPref(tool.visibilityswitch)) { + checkboxInput.setAttribute("checked", "true"); + } + + checkboxInput.addEventListener("change", + onCheckboxClick.bind(checkboxInput, tool.id)); + + checkboxLabel.appendChild(checkboxInput); + checkboxLabel.appendChild(checkboxSpanLabel); + return checkboxLabel; + }; + + // Populating the default tools lists + let toggleableTools = gDevTools.getDefaultTools().filter(tool => { + return tool.visibilityswitch && !tool.hiddenInOptions; + }); + + for (let tool of toggleableTools) { + defaultToolsBox.appendChild(createToolCheckbox(tool)); + } + + // Populating the additional tools list that came from add-ons. + let atleastOneAddon = false; + for (let tool of gDevTools.getAdditionalTools()) { + atleastOneAddon = true; + additionalToolsBox.appendChild(createToolCheckbox(tool)); + } + + if (!atleastOneAddon) { + additionalToolsBox.style.display = "none"; + } + + if (!atleastOneToolNotSupported) { + toolsNotSupportedLabel.style.display = "none"; + } + + this.panelWin.focus(); + }, + + setupThemeList: function () { + let themeBox = this.panelDoc.getElementById("devtools-theme-box"); + let themeLabels = themeBox.querySelectorAll("label"); + for (let label of themeLabels) { + label.remove(); + } + + let createThemeOption = theme => { + let inputLabel = this.panelDoc.createElement("label"); + let inputRadio = this.panelDoc.createElement("input"); + inputRadio.setAttribute("type", "radio"); + inputRadio.setAttribute("value", theme.id); + inputRadio.setAttribute("name", "devtools-theme-item"); + inputRadio.addEventListener("change", function (e) { + setPrefAndEmit(themeBox.getAttribute("data-pref"), + e.target.value); + }); + + let inputSpanLabel = this.panelDoc.createElement("span"); + inputSpanLabel.textContent = theme.label; + inputLabel.appendChild(inputRadio); + inputLabel.appendChild(inputSpanLabel); + + return inputLabel; + }; + + // Populating the default theme list + let themes = gDevTools.getThemeDefinitionArray(); + for (let theme of themes) { + themeBox.appendChild(createThemeOption(theme)); + } + + this.updateCurrentTheme(); + }, + + populatePreferences: function () { + let prefCheckboxes = this.panelDoc.querySelectorAll( + "input[type=checkbox][data-pref]"); + for (let prefCheckbox of prefCheckboxes) { + if (GetPref(prefCheckbox.getAttribute("data-pref"))) { + prefCheckbox.setAttribute("checked", true); + } + prefCheckbox.addEventListener("change", function (e) { + let checkbox = e.target; + setPrefAndEmit(checkbox.getAttribute("data-pref"), checkbox.checked); + }); + } + // Themes radio inputs are handled in setupThemeList + let prefRadiogroups = this.panelDoc.querySelectorAll( + ".radiogroup[data-pref]:not(#devtools-theme-box)"); + for (let radioGroup of prefRadiogroups) { + let selectedValue = GetPref(radioGroup.getAttribute("data-pref")); + + for (let radioInput of radioGroup.querySelectorAll("input[type=radio]")) { + if (radioInput.getAttribute("value") == selectedValue) { + radioInput.setAttribute("checked", true); + } + + radioInput.addEventListener("change", function (e) { + setPrefAndEmit(radioGroup.getAttribute("data-pref"), + e.target.value); + }); + } + } + let prefSelects = this.panelDoc.querySelectorAll("select[data-pref]"); + for (let prefSelect of prefSelects) { + let pref = GetPref(prefSelect.getAttribute("data-pref")); + let options = [...prefSelect.options]; + options.some(function (option) { + let value = option.value; + // non strict check to allow int values. + if (value == pref) { + prefSelect.selectedIndex = options.indexOf(option); + return true; + } + }); + + prefSelect.addEventListener("change", function (e) { + let select = e.target; + setPrefAndEmit(select.getAttribute("data-pref"), + select.options[select.selectedIndex].value); + }); + } + + if (this.target.activeTab) { + return this.target.client.attachTab(this.target.activeTab._actor) + .then(([response, client]) => { + this._origJavascriptEnabled = !response.javascriptEnabled; + this.disableJSNode.checked = this._origJavascriptEnabled; + this.disableJSNode.addEventListener("click", + this._disableJSClicked, false); + }); + } + this.disableJSNode.hidden = true; + }, + + updateCurrentTheme: function () { + let currentTheme = GetPref("devtools.theme"); + let themeBox = this.panelDoc.getElementById("devtools-theme-box"); + let themeRadioInput = themeBox.querySelector(`[value=${currentTheme}]`); + + if (themeRadioInput) { + themeRadioInput.checked = true; + } else { + // If the current theme does not exist anymore, switch to light theme + let lightThemeInputRadio = themeBox.querySelector("[value=light]"); + lightThemeInputRadio.checked = true; + } + }, + + /** + * Disables JavaScript for the currently loaded tab. We force a page refresh + * here because setting docShell.allowJavascript to true fails to block JS + * execution from event listeners added using addEventListener(), AJAX calls + * and timers. The page refresh prevents these things from being added in the + * first place. + * + * @param {Event} event + * The event sent by checking / unchecking the disable JS checkbox. + */ + _disableJSClicked: function (event) { + let checked = event.target.checked; + + let options = { + "javascriptEnabled": !checked + }; + + this.target.activeTab.reconfigure(options); + }, + + destroy: function () { + if (this.destroyPromise) { + return this.destroyPromise; + } + + let deferred = defer(); + this.destroyPromise = deferred.promise; + + this._removeListeners(); + + if (this.target.activeTab) { + this.disableJSNode.removeEventListener("click", this._disableJSClicked); + // FF41+ automatically cleans up state in actor on disconnect + if (!this.target.activeTab.traits.noTabReconfigureOnClose) { + let options = { + "javascriptEnabled": this._origJavascriptEnabled, + "performReload": false + }; + this.target.activeTab.reconfigure(options, deferred.resolve); + } else { + deferred.resolve(); + } + } else { + deferred.resolve(); + } + + this.panelWin = this.panelDoc = this.disableJSNode = this.toolbox = null; + + return this.destroyPromise; + } +}; + +/* Set a pref and emit the pref-changed event if needed. */ +function setPrefAndEmit(prefName, newValue) { + let data = { + pref: prefName, + newValue: newValue + }; + data.oldValue = GetPref(data.pref); + SetPref(data.pref, data.newValue); + + if (data.newValue != data.oldValue) { + gDevTools.emit("pref-changed", data); + } +} |