зеркало из https://github.com/mozilla/gecko-dev.git
191 строка
6.2 KiB
JavaScript
191 строка
6.2 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 EventEmitter = require("devtools/shared/event-emitter");
|
|
const Services = require("Services");
|
|
const { Preferences } = require("resource://gre/modules/Preferences.jsm");
|
|
const OPTIONS_SHOWN_EVENT = "options-shown";
|
|
const OPTIONS_HIDDEN_EVENT = "options-hidden";
|
|
const PREF_CHANGE_EVENT = "pref-changed";
|
|
|
|
/**
|
|
* OptionsView constructor. Takes several options, all required:
|
|
* - branchName: The name of the prefs branch, like "devtools.debugger."
|
|
* - menupopup: The XUL `menupopup` item that contains the pref buttons.
|
|
*
|
|
* Fires an event, PREF_CHANGE_EVENT, with the preference name that changed as
|
|
* the second argument. Fires events on opening/closing the XUL panel
|
|
* (OPTIONS_SHOW_EVENT, OPTIONS_HIDDEN_EVENT) as the second argument in the
|
|
* listener, used for tests mostly.
|
|
*/
|
|
const OptionsView = function(options = {}) {
|
|
this.branchName = options.branchName;
|
|
this.menupopup = options.menupopup;
|
|
this.window = this.menupopup.ownerDocument.defaultView;
|
|
const { document } = this.window;
|
|
this.$ = document.querySelector.bind(document);
|
|
this.$$ = (selector, parent = document) => parent.querySelectorAll(selector);
|
|
// Get the corresponding button that opens the popup by looking
|
|
// for an element with a `popup` attribute matching the menu's ID
|
|
this.button = this.$(`[popup=${this.menupopup.getAttribute("id")}]`);
|
|
|
|
this.prefObserver = new PrefObserver(this.branchName);
|
|
|
|
EventEmitter.decorate(this);
|
|
};
|
|
exports.OptionsView = OptionsView;
|
|
|
|
OptionsView.prototype = {
|
|
/**
|
|
* Binds the events and observers for the OptionsView.
|
|
*/
|
|
initialize: function() {
|
|
const { MutationObserver } = this.window;
|
|
this._onPrefChange = this._onPrefChange.bind(this);
|
|
this._onOptionChange = this._onOptionChange.bind(this);
|
|
this._onPopupShown = this._onPopupShown.bind(this);
|
|
this._onPopupHidden = this._onPopupHidden.bind(this);
|
|
|
|
// We use a mutation observer instead of a click handler
|
|
// because the click handler is fired before the XUL menuitem updates its
|
|
// checked status, which cascades incorrectly with the Preference observer.
|
|
this.mutationObserver = new MutationObserver(this._onOptionChange);
|
|
const observerConfig = { attributes: true, attributeFilter: ["checked"] };
|
|
|
|
// Sets observers and default options for all options
|
|
for (const $el of this.$$("menuitem", this.menupopup)) {
|
|
const prefName = $el.getAttribute("data-pref");
|
|
|
|
if (this.prefObserver.get(prefName)) {
|
|
$el.setAttribute("checked", "true");
|
|
} else {
|
|
$el.removeAttribute("checked");
|
|
}
|
|
this.mutationObserver.observe($el, observerConfig);
|
|
}
|
|
|
|
// Listen to any preference change in the specified branch
|
|
this.prefObserver.register();
|
|
this.prefObserver.on(PREF_CHANGE_EVENT, this._onPrefChange);
|
|
|
|
// Bind to menupopup's open and close event
|
|
this.menupopup.addEventListener("popupshown", this._onPopupShown);
|
|
this.menupopup.addEventListener("popuphidden", this._onPopupHidden);
|
|
},
|
|
|
|
/**
|
|
* Removes event handlers for all of the option buttons and
|
|
* preference observer.
|
|
*/
|
|
destroy: function() {
|
|
this.mutationObserver.disconnect();
|
|
this.prefObserver.off(PREF_CHANGE_EVENT, this._onPrefChange);
|
|
this.menupopup.removeEventListener("popupshown", this._onPopupShown);
|
|
this.menupopup.removeEventListener("popuphidden", this._onPopupHidden);
|
|
},
|
|
|
|
/**
|
|
* Returns the value for the specified `prefName`
|
|
*/
|
|
getPref: function(prefName) {
|
|
return this.prefObserver.get(prefName);
|
|
},
|
|
|
|
/**
|
|
* Called when a preference is changed (either via clicking an option
|
|
* button or by changing it in about:config). Updates the checked status
|
|
* of the corresponding button.
|
|
*/
|
|
_onPrefChange: function(prefName) {
|
|
const $el = this.$(`menuitem[data-pref="${prefName}"]`, this.menupopup);
|
|
const value = this.prefObserver.get(prefName);
|
|
|
|
// If options panel does not contain a menuitem for the
|
|
// pref, emit an event and do nothing.
|
|
if (!$el) {
|
|
this.emit(PREF_CHANGE_EVENT, prefName);
|
|
return;
|
|
}
|
|
|
|
if (value) {
|
|
$el.setAttribute("checked", value);
|
|
} else {
|
|
$el.removeAttribute("checked");
|
|
}
|
|
|
|
this.emit(PREF_CHANGE_EVENT, prefName);
|
|
},
|
|
|
|
/**
|
|
* Mutation handler for handling a change on an options button.
|
|
* Sets the preference accordingly.
|
|
*/
|
|
_onOptionChange: function(mutations) {
|
|
const { target } = mutations[0];
|
|
const prefName = target.getAttribute("data-pref");
|
|
const value = target.getAttribute("checked") === "true";
|
|
|
|
this.prefObserver.set(prefName, value);
|
|
},
|
|
|
|
/**
|
|
* Fired when the `menupopup` is opened, bound via XUL.
|
|
* Fires an event used in tests.
|
|
*/
|
|
_onPopupShown: function() {
|
|
this.button.setAttribute("open", true);
|
|
this.emit(OPTIONS_SHOWN_EVENT);
|
|
},
|
|
|
|
/**
|
|
* Fired when the `menupopup` is closed, bound via XUL.
|
|
* Fires an event used in tests.
|
|
*/
|
|
_onPopupHidden: function() {
|
|
this.button.removeAttribute("open");
|
|
this.emit(OPTIONS_HIDDEN_EVENT);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Constructor for PrefObserver. Small helper for observing changes
|
|
* on a preference branch. Takes a `branchName`, like "devtools.debugger."
|
|
*
|
|
* Fires an event of PREF_CHANGE_EVENT with the preference name that changed
|
|
* as the second argument in the listener.
|
|
*/
|
|
const PrefObserver = function(branchName) {
|
|
this.branchName = branchName;
|
|
this.branch = Services.prefs.getBranch(branchName);
|
|
EventEmitter.decorate(this);
|
|
};
|
|
|
|
PrefObserver.prototype = {
|
|
/**
|
|
* Returns `prefName`'s value. Does not require the branch name.
|
|
*/
|
|
get: function(prefName) {
|
|
const fullName = this.branchName + prefName;
|
|
return Preferences.get(fullName);
|
|
},
|
|
/**
|
|
* Sets `prefName`'s `value`. Does not require the branch name.
|
|
*/
|
|
set: function(prefName, value) {
|
|
const fullName = this.branchName + prefName;
|
|
Preferences.set(fullName, value);
|
|
},
|
|
register: function() {
|
|
this.branch.addObserver("", this);
|
|
},
|
|
unregister: function() {
|
|
this.branch.removeObserver("", this);
|
|
},
|
|
observe: function(subject, topic, prefName) {
|
|
this.emit(PREF_CHANGE_EVENT, prefName);
|
|
},
|
|
};
|