summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/toolbox-options.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/framework/toolbox-options.js')
-rw-r--r--devtools/client/framework/toolbox-options.js431
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);
+ }
+}