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
};
Make the debugger frontend cope with an already connected target (bug 933212); r=jryans,fitzgen * Made the DebuggerClient, which is actually the RootActor front, not consider one of the attached child fronts as "active". Since a single DebuggerClient (or RootFront) is kept around for the App Manager's lifetime, it makes sense to move the notion of "active" tab to the toolbox's target. As each toolbox gets destroyed, the fronts should be detaching from their actors (if they are stateful) so that the app is no longer in a debugging state. Debugging a new app (or reconnecting to a previous one) will create new fronts anyway. * Slightly refactored the TabClient, ThreadClient, SourceClient and TracerClient towards a protocol.js-based architecture, by adding parent-child references and lifecycle management. Now a tab-scoped thread actor for instance has the tab as its parent, while a global-scoped thread actor (chrome debugger) has the DebuggerCLient (RootFront) as its parent. This lets parents reference their children, so that caching in the target object can work. It also allowed me to move some methods from the DebuggerClient to the actual front that should be responsible, like reconfigureTab, reconfigureThread and attachThread. These methods now use DebuggerClient.requester, too. * Added some error handling in the debugger client requester around "before" and "after" callbacks, which exposed some errors in tests that are now fixed. * Fixed the state handling in the thread actor so that merely detaching from a thread doesn't put it in the exited state. This is the part that what was necessary for Firebug's use case. * Properly loading tracer and webgl actors now on b2g.
2014-01-14 19:39:40 +04:00
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);
}
}