gecko-dev/devtools/client/framework/toolbox-options.js

432 строки
14 KiB
JavaScript

/* -*- 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/locale/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);
}
}