зеркало из https://github.com/mozilla/gecko-dev.git
484 строки
16 KiB
JavaScript
484 строки
16 KiB
JavaScript
/* 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");
|
|
|
|
loader.lazyRequireGetter(this, "system", "devtools/shared/system");
|
|
|
|
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();
|
|
this.setupNightlyOptions();
|
|
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 = GetPref(prefName);
|
|
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: Task.async(function* () {
|
|
// Ensure the toolbox is open, and the buttons are all set up.
|
|
yield this.toolbox.isOpen;
|
|
|
|
let enabledToolbarButtonsBox = this.panelDoc.getElementById(
|
|
"enabled-toolbox-buttons-box");
|
|
|
|
let toolbarButtons = this.toolbox.toolbarButtons;
|
|
|
|
if (!toolbarButtons) {
|
|
console.warn("The command buttons weren't initiated yet.");
|
|
return;
|
|
}
|
|
|
|
let onCheckboxClick = (checkbox) => {
|
|
let commandButton = toolbarButtons.filter(
|
|
toggleableButton => toggleableButton.id === checkbox.id)[0];
|
|
Services.prefs.setBoolPref(
|
|
commandButton.visibilityswitch, checkbox.checked);
|
|
this.toolbox.updateToolboxButtonsVisibility();
|
|
};
|
|
|
|
let createCommandCheckbox = button => {
|
|
let checkboxLabel = this.panelDoc.createElement("label");
|
|
let checkboxSpanLabel = this.panelDoc.createElement("span");
|
|
checkboxSpanLabel.textContent = button.description;
|
|
let checkboxInput = this.panelDoc.createElement("input");
|
|
checkboxInput.setAttribute("type", "checkbox");
|
|
checkboxInput.setAttribute("id", button.id);
|
|
if (button.isVisible) {
|
|
checkboxInput.setAttribute("checked", true);
|
|
}
|
|
checkboxInput.addEventListener("change",
|
|
onCheckboxClick.bind(this, checkboxInput));
|
|
|
|
checkboxLabel.appendChild(checkboxInput);
|
|
checkboxLabel.appendChild(checkboxSpanLabel);
|
|
return checkboxLabel;
|
|
};
|
|
|
|
for (let button of toolbarButtons) {
|
|
if (!button.isTargetSupported(this.toolbox.target)) {
|
|
continue;
|
|
}
|
|
|
|
enabledToolbarButtonsBox.appendChild(createCommandCheckbox(button));
|
|
}
|
|
}),
|
|
|
|
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;
|
|
|
|
const toolbox = this.toolbox;
|
|
|
|
// Signal tool registering/unregistering globally (for the tools registered
|
|
// globally) and per toolbox (for the tools registered to a single toolbox).
|
|
let onCheckboxClick = function (id) {
|
|
let toolDefinition = gDevTools._tools.get(id) || toolbox.getToolDefinition(id);
|
|
// Set the kill switch pref boolean to true
|
|
Services.prefs.setBoolPref(toolDefinition.visibilityswitch, this.checked);
|
|
gDevTools.emit(this.checked ? "tool-registered" : "tool-unregistered", id);
|
|
};
|
|
|
|
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));
|
|
}
|
|
|
|
// Populating the additional toolbox-specific tools list that came
|
|
// from WebExtension add-ons.
|
|
for (let tool of this.toolbox.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) {
|
|
SetPref(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();
|
|
},
|
|
|
|
/**
|
|
* Add common preferences enabled only on Nightly.
|
|
*/
|
|
setupNightlyOptions: function () {
|
|
let isNightly = system.constants.NIGHTLY_BUILD;
|
|
if (!isNightly) {
|
|
return;
|
|
}
|
|
|
|
// Labels for these new buttons are nightly only and mostly intended for working on
|
|
// devtools. They should not be localized.
|
|
let prefDefinitions = [{
|
|
pref: "devtools.webconsole.new-frontend-enabled",
|
|
label: "Enable new console frontend",
|
|
id: "devtools-new-webconsole",
|
|
parentId: "webconsole-options"
|
|
}, {
|
|
pref: "devtools.debugger.new-debugger-frontend",
|
|
label: "Enable new debugger frontend",
|
|
id: "devtools-new-debugger",
|
|
parentId: "debugger-options"
|
|
}, {
|
|
pref: "devtools.layoutview.enabled",
|
|
label: "Enable layout panel",
|
|
id: "devtools-layout-panel",
|
|
parentId: "inspector-options"
|
|
}];
|
|
|
|
let createPreferenceOption = ({pref, label, id}) => {
|
|
let inputLabel = this.panelDoc.createElement("label");
|
|
let checkbox = this.panelDoc.createElement("input");
|
|
checkbox.setAttribute("type", "checkbox");
|
|
if (GetPref(pref)) {
|
|
checkbox.setAttribute("checked", "checked");
|
|
}
|
|
checkbox.setAttribute("id", id);
|
|
checkbox.addEventListener("change", e => {
|
|
SetPref(pref, e.target.checked);
|
|
});
|
|
|
|
let inputSpanLabel = this.panelDoc.createElement("span");
|
|
inputSpanLabel.textContent = label;
|
|
inputLabel.appendChild(checkbox);
|
|
inputLabel.appendChild(inputSpanLabel);
|
|
|
|
return inputLabel;
|
|
};
|
|
|
|
for (let prefDefinition of prefDefinitions) {
|
|
let parent = this.panelDoc.getElementById(prefDefinition.parentId);
|
|
parent.appendChild(createPreferenceOption(prefDefinition));
|
|
}
|
|
},
|
|
|
|
populatePreferences: Task.async(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;
|
|
SetPref(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) {
|
|
SetPref(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;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
prefSelect.addEventListener("change", function (e) {
|
|
let select = e.target;
|
|
SetPref(select.getAttribute("data-pref"),
|
|
select.options[select.selectedIndex].value);
|
|
});
|
|
}
|
|
|
|
if (this.target.activeTab) {
|
|
let [ response ] = yield this.target.client.attachTab(this.target.activeTab._actor);
|
|
this._origJavascriptEnabled = !response.javascriptEnabled;
|
|
this.disableJSNode.checked = this._origJavascriptEnabled;
|
|
this.disableJSNode.addEventListener("click", this._disableJSClicked);
|
|
} else {
|
|
// Hide the checkbox and label
|
|
this.disableJSNode.parentNode.style.display = "none";
|
|
}
|
|
}),
|
|
|
|
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;
|
|
}
|
|
};
|