In a following patch, all DevTools moz.build files will use DevToolsModules to install JS modules at a path that corresponds directly to their source tree location. Here we rewrite all require and import calls to match the new location that these files are installed to.
184 lines
6.0 KiB
JavaScript
184 lines
6.0 KiB
JavaScript
const EventEmitter = require("devtools/shared/event-emitter");
|
|
const { Services } = require("resource://gre/modules/Services.jsm");
|
|
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
|
|
// it's 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);
|
|
}
|
|
};
|