123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- "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;
- let { 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 () {
- let { 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);
- let observerConfig = { attributes: true, attributeFilter: ["checked"]};
- // Sets observers and default options for all options
- for (let $el of this.$$("menuitem", this.menupopup)) {
- let 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) {
- let $el = this.$(`menuitem[data-pref="${prefName}"]`, this.menupopup);
- let 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) {
- let { target } = mutations[0];
- let prefName = target.getAttribute("data-pref");
- let 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) {
- let fullName = this.branchName + prefName;
- return Preferences.get(fullName);
- },
- /**
- * Sets `prefName`'s `value`. Does not require the branch name.
- */
- set: function (prefName, value) {
- let fullName = this.branchName + prefName;
- Preferences.set(fullName, value);
- },
- register: function () {
- this.branch.addObserver("", this, false);
- },
- unregister: function () {
- this.branch.removeObserver("", this);
- },
- observe: function (subject, topic, prefName) {
- this.emit(PREF_CHANGE_EVENT, prefName);
- }
- };
|