Revision control

Copy as Markdown

Other Tools

<?xml version="1.0"?>
<!DOCTYPE bindings [
<!ENTITY % preferencesDTD SYSTEM "chrome://communicator/locale/pref/preferences.dtd">
%preferencesDTD;
<!ENTITY % globalKeysDTD SYSTEM "chrome://global/locale/globalKeys.dtd">
%globalKeysDTD;
]>
<bindings id="preferencesBindings"
#
# = Preferences Window Framework
#
# The syntax for use looks something like:
#
# <prefwindow>
# <prefpane id="prefPaneA">
# <preferences>
# <preference id="preference1" name="app.preference1" type="bool" onchange="foo();"/>
# <preference id="preference2" name="app.preference2" type="bool" useDefault="true"/>
# </preferences>
# <checkbox label="Preference" preference="preference1"/>
# </prefpane>
# </prefwindow>
#
<binding id="preferences">
<implementation implements="nsIObserver">
<method name="_constructAfterChildren">
<body>
<![CDATA[
// This method will be called after the last of the child
// <preference> elements is constructed. Its purpose is to propagate
// the values to the associated form elements. Sometimes the code for
// some <preference> initializers depend on other <preference> elements
// being initialized so we wait and call updateElements on all of them
// once the last one has been constructed. See bugs 997570 and 992185.
var elements = this.getElementsByTagName("preference");
for (let element of elements) {
element.updateElements();
}
this._constructAfterChildrenCalled = true;
]]>
</body>
</method>
<method name="observe">
<parameter name="aSubject"/>
<parameter name="aTopic"/>
<parameter name="aData"/>
<body>
<![CDATA[
for (var i = 0; i < this.childNodes.length; ++i) {
var preference = this.childNodes[i];
if (preference.name == aData) {
preference.value = preference.valueFromPreferences;
}
}
]]>
</body>
</method>
<method name="fireChangedEvent">
<parameter name="aPreference"/>
<body>
<![CDATA[
// Value changed, synthesize an event
try {
var event = document.createEvent("Events");
event.initEvent("change", true, true);
aPreference.dispatchEvent(event);
} catch (e) {
Cu.reportError(e);
}
]]>
</body>
</method>
<field name="service">
Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
</field>
<field name="rootBranch">
Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
</field>
<field name="defaultBranch">
this.service.getDefaultBranch("");
</field>
<field name="rootBranchInternal">
Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
</field>
<property name="type" readonly="true">
<getter>
<![CDATA[
return document.documentElement.type || "";
]]>
</getter>
</property>
<property name="instantApply" readonly="true">
<getter>
<![CDATA[
var doc = document.documentElement;
return this.type == "child" ? doc.instantApply
: doc.instantApply || this.rootBranch.getBoolPref("browser.preferences.instantApply");
]]>
</getter>
</property>
<!-- We want to call _constructAfterChildren after all child
<preference> elements have been constructed. To do this, we get
and store the node list of all child <preference> elements in the
constructor, and maintain a count which is incremented in the
constructor of <preference>. _constructAfterChildren is called
when the count matches the length of the list. -->
<field name="_constructedChildrenCount">0</field>
<field name="_preferenceChildren">null</field>
<!-- Some <preference> elements are added dynamically after
_constructAfterChildren has already been called - we want to
avoid looping over all of them again in this case so we remember
if we already called it. -->
<field name="_constructAfterChildrenCalled">false</field>
<constructor>
<![CDATA[
this._preferenceChildren = this.getElementsByTagName("preference");
]]>
</constructor>
</implementation>
</binding>
<binding id="preference">
<implementation>
<constructor>
<![CDATA[
// if the element has been inserted without the name attribute set,
// we have nothing to do here
if (!this.name)
return;
this.preferences.rootBranchInternal
.addObserver(this.name, this.preferences);
// In non-instant apply mode, we must try and use the last saved state
// from any previous opens of a child dialog instead of the value from
// preferences, to pick up any edits a user may have made.
var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager);
if (this.preferences.type == "child" &&
!this.instantApply && window.opener &&
secMan.isSystemPrincipal(window.opener.document.nodePrincipal)) {
var pdoc = window.opener.document;
// Try to find a preference element for the same preference.
var preference = null;
var parentPreferences = pdoc.getElementsByTagName("preferences");
for (var k = 0; (k < parentPreferences.length && !preference); ++k) {
var parentPrefs = parentPreferences[k]
.getElementsByAttribute("name", this.name);
for (var l = 0; (l < parentPrefs.length && !preference); ++l) {
if (parentPrefs[l].localName == "preference")
preference = parentPrefs[l];
}
}
// Don't use the value setter here, we don't want updateElements to be prematurely fired.
this._value = preference ? preference.value : this.valueFromPreferences;
} else {
this._value = this.valueFromPreferences;
}
if (this.preferences._constructAfterChildrenCalled) {
// This <preference> was added after _constructAfterChildren() was already called.
// We can directly call updateElements().
this.updateElements();
} else {
this.preferences._constructedChildrenCount++;
if (this.preferences._constructedChildrenCount ==
this.preferences._preferenceChildren.length) {
// This is the last <preference>, time to updateElements() on all of them.
this.preferences._constructAfterChildren();
}
}
this.dispatchEvent(new CustomEvent("bindingattached", { bubbles: false }));
]]>
</constructor>
<destructor>
this.preferences.rootBranchInternal
.removeObserver(this.name, this.preferences);
</destructor>
<field name="_constructed">false</field>
<property name="instantApply">
<getter>
if (this.getAttribute("instantApply") == "false")
return false;
return this.getAttribute("instantApply") == "true" || this.preferences.instantApply;
</getter>
</property>
<property name="preferences" onget="return this.parentNode"/>
<property name="name" onget="return this.getAttribute('name');">
<setter>
if (val == this.name)
return val;
this.preferences.rootBranchInternal
.removeObserver(this.name, this.preferences);
this.setAttribute("name", val);
this.preferences.rootBranchInternal
.addObserver(val, this.preferences);
return val;
</setter>
</property>
<property name="type" onget="return this.getAttribute('type');"
onset="this.setAttribute('type', val); return val;"/>
<property name="inverted" onget="return this.getAttribute('inverted') == 'true';"
onset="this.setAttribute('inverted', val); return val;"/>
<property name="readonly" onget="return this.getAttribute('readonly') == 'true';"
onset="this.setAttribute('readonly', val); return val;"/>
<field name="_value">null</field>
<method name="_setValue">
<parameter name="aValue"/>
<body>
<![CDATA[
if (this.value !== aValue) {
this._value = aValue;
if (this.instantApply)
this.valueFromPreferences = aValue;
this.preferences.fireChangedEvent(this);
}
return aValue;
]]>
</body>
</method>
<property name="value" onget="return this._value" onset="return this._setValue(val);"/>
<property name="locked">
<getter>
return this.preferences.rootBranch.prefIsLocked(this.name);
</getter>
</property>
<property name="disabled">
<getter>
return this.getAttribute("disabled") == "true";
</getter>
<setter>
<![CDATA[
if (val)
this.setAttribute("disabled", "true");
else
this.removeAttribute("disabled");
if (!this.id)
return val;
var elements = document.getElementsByAttribute("preference", this.id);
for (var i = 0; i < elements.length; ++i) {
elements[i].disabled = val;
var labels = document.getElementsByAttribute("control", elements[i].id);
for (var j = 0; j < labels.length; ++j)
labels[j].disabled = val;
}
return val;
]]>
</setter>
</property>
<property name="tabIndex">
<getter>
return parseInt(this.getAttribute("tabindex"));
</getter>
<setter>
<![CDATA[
if (val)
this.setAttribute("tabindex", val);
else
this.removeAttribute("tabindex");
if (!this.id)
return val;
var elements = document.getElementsByAttribute("preference", this.id);
for (var i = 0; i < elements.length; ++i) {
elements[i].tabIndex = val;
var labels = document.getElementsByAttribute("control", elements[i].id);
for (var j = 0; j < labels.length; ++j)
labels[j].tabIndex = val;
}
return val;
]]>
</setter>
</property>
<property name="hasUserValue">
<getter>
<![CDATA[
return this.preferences.rootBranch.prefHasUserValue(this.name) &&
this.value !== undefined;
]]>
</getter>
</property>
<method name="reset">
<body>
// defer reset until preference update
this.value = undefined;
</body>
</method>
<field name="_useDefault">false</field>
<property name="defaultValue">
<getter>
<![CDATA[
this._useDefault = true;
var val = this.valueFromPreferences;
this._useDefault = false;
return val;
]]>
</getter>
</property>
<property name="_branch">
<getter>
return this._useDefault ? this.preferences.defaultBranch : this.preferences.rootBranch;
</getter>
</property>
<field name="batching">false</field>
<method name="_reportUnknownType">
<body>
<![CDATA[
var consoleService = Cc["@mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService);
var msg = "<preference> with id='" + this.id + "' and name='" +
this.name + "' has unknown type '" + this.type + "'.";
consoleService.logStringMessage(msg);
]]>
</body>
</method>
<property name="valueFromPreferences">
<getter>
<![CDATA[
try {
// Force a resync of value with preferences.
switch (this.type) {
case "int":
return this._branch.getIntPref(this.name);
case "bool":
var val = this._branch.getBoolPref(this.name);
return this.inverted ? !val : val;
case "wstring":
return this._branch
.getComplexValue(this.name, Ci.nsIPrefLocalizedString)
.data;
case "string":
case "unichar":
return this._branch.getStringPref(this.name);
case "fontname":
var family = this._branch.getStringPref(this.name);
var fontEnumerator = Cc["@mozilla.org/gfx/fontenumerator;1"]
.createInstance(Ci.nsIFontEnumerator);
return fontEnumerator.getStandardFamilyName(family);
case "file":
var f = this._branch
.getComplexValue(this.name, Ci.nsIFile);
return f;
default:
this._reportUnknownType();
}
} catch (e) { }
return null;
]]>
</getter>
<setter>
<![CDATA[
// Exit early if nothing to do.
if (this.readonly || this.valueFromPreferences == val)
return val;
// The special value undefined means 'reset preference to default'.
if (val === undefined) {
this.preferences.rootBranch.clearUserPref(this.name);
return val;
}
// Force a resync of preferences with value.
switch (this.type) {
case "int":
this.preferences.rootBranch.setIntPref(this.name, val);
break;
case "bool":
this.preferences.rootBranch.setBoolPref(this.name, this.inverted ? !val : val);
break;
case "wstring":
var pls = Cc["@mozilla.org/pref-localizedstring;1"]
.createInstance(Ci.nsIPrefLocalizedString);
pls.data = val;
this.preferences.rootBranch
.setComplexValue(this.name, Ci.nsIPrefLocalizedString, pls);
break;
case "string":
case "unichar":
case "fontname":
this.preferences.rootBranch.setStringPref(this.name, val);
break;
case "file":
var lf;
if (typeof(val) == "string") {
lf = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsIFile);
lf.persistentDescriptor = val;
if (!lf.exists())
lf.initWithPath(val);
} else {
lf = val.QueryInterface(Ci.nsIFile);
}
this.preferences.rootBranch
.setComplexValue(this.name, Ci.nsIFile, lf);
break;
default:
this._reportUnknownType();
}
if (!this.batching)
this.preferences.service.savePrefFile(null);
return val;
]]>
</setter>
</property>
<method name="setElementValue">
<parameter name="aElement"/>
<body>
<![CDATA[
if (this.locked)
aElement.disabled = true;
if (!this.isElementEditable(aElement))
return;
var rv = undefined;
if (aElement.hasAttribute("onsyncfrompreference")) {
// Value changed, synthesize an event
try {
var event = document.createEvent("Events");
event.initEvent("syncfrompreference", true, true);
var f = new Function("event",
aElement.getAttribute("onsyncfrompreference"));
rv = f.call(aElement, event);
} catch (e) {
Cu.reportError(e);
}
}
var val = rv;
if (val === undefined)
val = this.instantApply ? this.valueFromPreferences : this.value;
// if the preference is marked for reset, show default value in UI
if (val === undefined)
val = this.defaultValue;
/**
* Initialize a UI element property with a value. Handles the case
* where an element has not yet had a XBL binding attached for it and
* the property setter does not yet exist by setting the same attribute
* on the XUL element using DOM apis and assuming the element's
* constructor or property getters appropriately handle this state.
*/
function setValue(element, attribute, value) {
if (attribute in element)
element[attribute] = value;
else
element.setAttribute(attribute, value);
}
if (aElement.localName == "checkbox") {
setValue(aElement, "checked", val);
} else if (aElement.localName == "colorpicker") {
setValue(aElement, "color", val);
} else if (aElement.localName == "textbox") {
// XXXmano Bug 303998: Avoid a caret placement issue if either the
// preference observer or its setter calls updateElements as a result
// of the input event handler.
if (aElement.value !== val)
setValue(aElement, "value", val);
} else {
setValue(aElement, "value", val);
}
]]>
</body>
</method>
<method name="getElementValue">
<parameter name="aElement"/>
<body>
<![CDATA[
if (aElement.hasAttribute("onsynctopreference")) {
// Value changed, synthesize an event
try {
var event = document.createEvent("Events");
event.initEvent("synctopreference", true, true);
var f = new Function("event",
aElement.getAttribute("onsynctopreference"));
var rv = f.call(aElement, event);
if (rv !== undefined)
return rv;
} catch (e) {
Cu.reportError(e);
}
}
/**
* Read the value of an attribute from an element, assuming the
* attribute is a property on the element's node API. If the property
* is not present in the API, then assume its value is contained in
* an attribute, as is the case before a binding has been attached.
*/
function getValue(element, attribute) {
if (attribute in element)
return element[attribute];
return element.getAttribute(attribute);
}
if (aElement.localName == "checkbox")
var value = getValue(aElement, "checked");
else if (aElement.localName == "colorpicker")
value = getValue(aElement, "color");
else
value = getValue(aElement, "value");
switch (this.type) {
case "int":
return parseInt(value, 10) || 0;
case "bool":
return typeof(value) == "boolean" ? value : value == "true";
}
return value;
]]>
</body>
</method>
<method name="isElementEditable">
<parameter name="aElement"/>
<body>
<![CDATA[
switch (aElement.localName) {
case "checkbox":
case "colorpicker":
case "radiogroup":
case "textbox":
case "richlistitem":
case "richlistbox":
case "menulist":
return true;
}
return aElement.getAttribute("preference-editable") == "true";
]]>
</body>
</method>
<method name="updateElements">
<body>
<![CDATA[
if (!this.id)
return;
// This "change" event handler tracks changes made to preferences by
// sources other than the user in this window.
var elements = document.getElementsByAttribute("preference", this.id);
for (var i = 0; i < elements.length; ++i)
this.setElementValue(elements[i]);
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="change">
this.updateElements();
</handler>
</handlers>
</binding>
<binding id="prefpane">
<implementation>
<method name="writePreferences">
<parameter name="aFlushToDisk"/>
<body>
<![CDATA[
// Write all values to preferences.
if (this._deferredValueUpdateElements.size) {
this._finalizeDeferredElements();
}
var preferences = this.preferences;
for (var i = 0; i < preferences.length; ++i) {
var preference = preferences[i];
preference.batching = true;
preference.valueFromPreferences = preference.value;
preference.batching = false;
}
if (aFlushToDisk) {
var psvc = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService);
psvc.savePrefFile(null);
}
]]>
</body>
</method>
<property name="src"
onget="return this.getAttribute('src');"
onset="this.setAttribute('src', val); return val;"/>
<property name="selected"
onget="return this.getAttribute('selected') == 'true';"
onset="this.setAttribute('selected', val); return val;"/>
<property name="image"
onget="return this.getAttribute('image');"
onset="this.setAttribute('image', val); return val;"/>
<property name="label"
onget="return this.getAttribute('label');"
onset="this.setAttribute('label', val); return val;"/>
<property name="preferenceElements"
onget="return this.getElementsByAttribute('preference', '*');"/>
<property name="preferences"
onget="return this.getElementsByTagName('preference');"/>
<property name="helpTopic">
<getter>
<![CDATA[
// if there are tabs, and the selected tab provides a helpTopic, return that
var box = this.getElementsByTagName("tabbox");
if (box[0]) {
var tab = box[0].selectedTab;
if (tab && tab.hasAttribute("helpTopic"))
return tab.getAttribute("helpTopic");
}
// otherwise, return the helpTopic of the current panel
return this.getAttribute("helpTopic");
]]>
</getter>
</property>
<field name="_loaded">false</field>
<property name="loaded"
onget="return !this.src ? true : this._loaded;"
onset="this._loaded = val; return val;"/>
<method name="preferenceForElement">
<parameter name="aElement"/>
<body>
return document.getElementById(aElement.getAttribute("preference"));
</body>
</method>
<method name="getPreferenceElement">
<parameter name="aStartElement"/>
<body>
<![CDATA[
var temp = aStartElement;
while (temp && temp.nodeType == Node.ELEMENT_NODE &&
!temp.hasAttribute("preference"))
temp = temp.parentNode;
return temp && temp.nodeType == Node.ELEMENT_NODE ?
temp : aStartElement;
]]>
</body>
</method>
<property name="DeferredTask" readonly="true">
<getter><![CDATA[
let module = {};
ChromeUtils.import("resource://gre/modules/DeferredTask.jsm", module);
Object.defineProperty(this, "DeferredTask", {
configurable: true,
enumerable: true,
writable: true,
value: module.DeferredTask,
});
return module.DeferredTask;
]]></getter>
</property>
<method name="_deferredValueUpdate">
<parameter name="aElement"/>
<body>
<![CDATA[
delete aElement._deferredValueUpdateTask;
let preference = document.getElementById(aElement.getAttribute("preference"));
let prefVal = preference.getElementValue(aElement);
preference.value = prefVal;
this._deferredValueUpdateElements.delete(aElement);
]]>
</body>
</method>
<field name="_deferredValueUpdateElements">
new Set();
</field>
<method name="_finalizeDeferredElements">
<body>
<![CDATA[
for (let el of this._deferredValueUpdateElements) {
if (el._deferredValueUpdateTask) {
el._deferredValueUpdateTask.finalize();
}
}
]]>
</body>
</method>
<method name="userChangedValue">
<parameter name="aElement"/>
<body>
<![CDATA[
let element = this.getPreferenceElement(aElement);
if (element.hasAttribute("preference")) {
if (element.getAttribute("delayprefsave") != "true") {
var preference = document.getElementById(element.getAttribute("preference"));
var prefVal = preference.getElementValue(element);
preference.value = prefVal;
} else {
if (!element._deferredValueUpdateTask) {
element._deferredValueUpdateTask = new this.DeferredTask(this._deferredValueUpdate.bind(this, element), 1000);
this._deferredValueUpdateElements.add(element);
} else {
// Each time the preference is changed, restart the delay.
element._deferredValueUpdateTask.disarm();
}
element._deferredValueUpdateTask.arm();
}
}
]]>
</body>
</method>
<property name="contentHeight">
<getter>
var targetHeight = parseInt(window.getComputedStyle(this).height);
targetHeight += parseInt(window.getComputedStyle(this).marginTop);
targetHeight += parseInt(window.getComputedStyle(this).marginBottom);
return targetHeight;
</getter>
</property>
</implementation>
<handlers>
<handler event="command">
// This "command" event handler tracks changes made to preferences by
// the user in this window.
if (event.sourceEvent)
event = event.sourceEvent;
this.userChangedValue(event.target);
</handler>
<handler event="select">
// This "select" event handler tracks changes made to colorpicker
// preferences by the user in this window.
if (event.target.localName == "colorpicker")
this.userChangedValue(event.target);
</handler>
<handler event="change">
// This "change" event handler tracks changes made to preferences by
// the user in this window.
this.userChangedValue(event.target);
</handler>
<handler event="input">
// This "input" event handler tracks changes made to preferences by
// the user in this window.
this.userChangedValue(event.target);
</handler>
<handler event="paneload">
<![CDATA[
// Initialize all values from preferences.
var elements = this.preferenceElements;
for (var i = 0; i < elements.length; ++i) {
try {
var preference = this.preferenceForElement(elements[i]);
preference.setElementValue(elements[i]);
} catch (e) {
dump("*** No preference found for " + elements[i].getAttribute("preference") + "\n");
}
}
]]>
</handler>
</handlers>
</binding>
<binding id="panebutton" role="listitem"
<content>
<xul:image class="paneButtonIcon" xbl:inherits="src"/>
<xul:label class="paneButtonLabel" xbl:inherits="value=label"/>
</content>
</binding>
</bindings>
# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
# 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/.
#
# This is PrefWindow 6. The Code Could Well Be Ready, Are You?
#
# Historical References:
# PrefWindow V (February 1, 2003)
# PrefWindow IV (April 24, 2000)
# PrefWindow III (January 6, 2000)
# PrefWindow II (???)
# PrefWindow I (June 4, 1999)
#