<?xml version="1.0"?>

<!DOCTYPE bindings [
  <!ENTITY % preferencesDTD SYSTEM "chrome://global/locale/preferences.dtd">
  %preferencesDTD;
  <!ENTITY % globalKeysDTD SYSTEM "chrome://global/locale/globalKeys.dtd">
  %globalKeysDTD;
]>

<bindings id="preferencesBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">


  <binding id="preferences">
    <implementation implements="nsIObserver">
      <method name="_constructAfterChildren">
      <body>
      <![CDATA[
      // This method will be called after each one of the child
      // <preference> elements is constructed. Its purpose is to propagate
      // the values to the associated form elements

      var elements = this.getElementsByTagName("preference");
      for (let element of elements) {
        if (!element._constructed) {
          return;
        }
      }
      for (let element of elements) {
        element.updateElements();
      }
      ]]>
      </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) {
            Components.utils.reportError(e);
          }
        ]]>
        </body>
      </method>

      <field name="service">
        Components.classes["@mozilla.org/preferences-service;1"]
                  .getService(Components.interfaces.nsIPrefService);
      </field>
      <field name="rootBranch">
        Components.classes["@mozilla.org/preferences-service;1"]
                  .getService(Components.interfaces.nsIPrefBranch);
      </field>
      <field name="defaultBranch">
        this.service.getDefaultBranch("");
      </field>
      <field name="rootBranchInternal">
        Components.classes["@mozilla.org/preferences-service;1"]
                  .getService(Components.interfaces.nsIPrefBranchInternal);
      </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>
    </implementation>
  </binding>

  <binding id="preference">
    <implementation>
      <constructor>
      <![CDATA[
        this._constructed = true;

        // 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, false);
        // 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 = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
                    .getService(Components.interfaces.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;
        this.preferences._constructAfterChildren();
      ]]>
      </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, false);

          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 = Components.classes["@mozilla.org/consoleservice;1"]
                                         .getService(Components.interfaces.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, Components.interfaces.nsIPrefLocalizedString)
                         .data;
            case "string":
            case "unichar":
              return this._branch
                         .getComplexValue(this.name, Components.interfaces.nsISupportsString)
                         .data;
            case "fontname":
              var family = this._branch
                               .getComplexValue(this.name, Components.interfaces.nsISupportsString)
                               .data;
              var fontEnumerator = Components.classes["@mozilla.org/gfx/fontenumerator;1"]
                                             .createInstance(Components.interfaces.nsIFontEnumerator);
              return fontEnumerator.getStandardFamilyName(family);
            case "file":
              var f = this._branch
                          .getComplexValue(this.name, Components.interfaces.nsILocalFile);
              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 = Components.classes["@mozilla.org/pref-localizedstring;1"]
                                .createInstance(Components.interfaces.nsIPrefLocalizedString);
            pls.data = val;
            this.preferences.rootBranch
                .setComplexValue(this.name, Components.interfaces.nsIPrefLocalizedString, pls);
            break;
          case "string":
          case "unichar":
          case "fontname":
            var iss = Components.classes["@mozilla.org/supports-string;1"]
                                .createInstance(Components.interfaces.nsISupportsString);
            iss.data = val;
            this.preferences.rootBranch
                .setComplexValue(this.name, Components.interfaces.nsISupportsString, iss);
            break;
          case "file":
            var lf;
            if (typeof(val) == "string") {
              lf = Components.classes["@mozilla.org/file/local;1"]
                             .createInstance(Components.interfaces.nsILocalFile);
              lf.persistentDescriptor = val;
              if (!lf.exists())
                lf.initWithPath(val);
            }
            else
              lf = val.QueryInterface(Components.interfaces.nsILocalFile);
            this.preferences.rootBranch
                .setComplexValue(this.name, Components.interfaces.nsILocalFile, 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) {
              Components.utils.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" ||
              aElement.localName == "listitem")
            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) {
              Components.utils.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" ||
              aElement.localName == "listitem")
            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 "listitem":
          case "listbox":
          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="prefwindow"
           extends="chrome://global/content/bindings/dialog.xml#dialog">
    <resources>
      <stylesheet src="chrome://global/skin/preferences.css"/>
    </resources>
    <content dlgbuttons="accept,cancel" persist="lastSelected screenX screenY"
             closebuttonlabel="&preferencesCloseButton.label;"
             closebuttonaccesskey="&preferencesCloseButton.accesskey;"
             role="dialog"
             title="&preferencesDefaultTitleMac.title;">
      <xul:windowdragbox orient="vertical">
        <xul:radiogroup anonid="selector" orient="horizontal" class="paneSelector chromeclass-toolbar"
                        role="listbox"/> <!-- Expose to accessibility APIs as a listbox -->
      </xul:windowdragbox>
      <xul:hbox flex="1" class="paneDeckContainer">
        <xul:deck anonid="paneDeck" flex="1">
          <children includes="prefpane"/>
        </xul:deck>
      </xul:hbox>
      <xul:hbox anonid="dlg-buttons" class="prefWindow-dlgbuttons" pack="end">
        <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
        <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/>
        <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
        <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
        <xul:spacer anonid="spacer" flex="1"/>
        <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/>
        <xul:button dlgtype="accept" class="dialog-button" icon="accept"/>
      </xul:hbox>
      <xul:hbox>
        <children/>
      </xul:hbox>
    </content>
    <implementation implements="nsITimerCallback">
      <constructor>
      <![CDATA[
        if (this.type != "child") {
          if (!this._instantApplyInitialized) {
            let psvc = Components.classes["@mozilla.org/preferences-service;1"]
                                 .getService(Components.interfaces.nsIPrefBranch);
            this.instantApply = psvc.getBoolPref("browser.preferences.instantApply");
          }
          if (this.instantApply) {
            var docElt = document.documentElement;
            var acceptButton = docElt.getButton("accept");
            acceptButton.hidden = true;
            var cancelButton  = docElt.getButton("cancel");
            if (/Mac/.test(navigator.platform)) {
              // no buttons on Mac except Help
              cancelButton.hidden = true;
              // Move Help button to the end
              document.getAnonymousElementByAttribute(this, "anonid", "spacer").hidden = true;
              // Also, don't fire onDialogAccept on enter
              acceptButton.disabled = true;
            } else {
              // morph the Cancel button into the Close button
              cancelButton.setAttribute ("icon", "close");
              cancelButton.label = docElt.getAttribute("closebuttonlabel");
              cancelButton.accesskey = docElt.getAttribute("closebuttonaccesskey");
            }
          }
        }
        this.setAttribute("animated", this._shouldAnimate ? "true" : "false");
        var panes = this.preferencePanes;

        var lastPane = null;
        if (this.lastSelected) {
          lastPane = document.getElementById(this.lastSelected);
          if (!lastPane) {
            this.lastSelected = "";
          }
        }

        var paneToLoad;
        if ("arguments" in window && window.arguments[0] && document.getElementById(window.arguments[0]) && document.getElementById(window.arguments[0]).nodeName == "prefpane") {
          paneToLoad = document.getElementById(window.arguments[0]);
          this.lastSelected = paneToLoad.id;
        }
        else if (lastPane)
          paneToLoad = lastPane;
        else
          paneToLoad = panes[0];

        for (var i = 0; i < panes.length; ++i) {
          this._makePaneButton(panes[i]);
          if (panes[i].loaded) {
            // Inline pane content, fire load event to force initialization.
            this._fireEvent("paneload", panes[i]);
          }
        }
        this.showPane(paneToLoad);

        if (panes.length == 1)
          this._selector.setAttribute("collapsed", "true");
      ]]>
      </constructor>

      <destructor>
      <![CDATA[
        // Release timers to avoid reference cycles.
        if (this._animateTimer) {
          this._animateTimer.cancel();
          this._animateTimer = null;
        }
        if (this._fadeTimer) {
          this._fadeTimer.cancel();
          this._fadeTimer = null;
        }
      ]]>
      </destructor>

      <!-- Derived bindings can set this to true to cause us to skip
           reading the browser.preferences.instantApply pref in the constructor.
           Then they can set instantApply to their wished value. -->
      <field name="_instantApplyInitialized">false</field>
      <!-- Controls whether changed pref values take effect immediately. -->
      <field name="instantApply">false</field>

      <property name="preferencePanes"
                onget="return this.getElementsByTagName('prefpane');"/>

      <property name="type" onget="return this.getAttribute('type');"/>
      <property name="_paneDeck"
                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'paneDeck');"/>
      <property name="_paneDeckContainer"
                onget="return document.getAnonymousElementByAttribute(this, 'class', 'paneDeckContainer');"/>
      <property name="_selector"
                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'selector');"/>
      <property name="lastSelected"
                onget="return this.getAttribute('lastSelected');">
        <setter>
          this.setAttribute("lastSelected", val);
          document.persist(this.id, "lastSelected");
          return val;
        </setter>
      </property>
      <property name="currentPane"
                onset="return this._currentPane = val;">
        <getter>
          if (!this._currentPane)
            this._currentPane = this.preferencePanes[0];

          return this._currentPane;
        </getter>
      </property>
      <field name="_currentPane">null</field>


      <method name="_makePaneButton">
        <parameter name="aPaneElement"/>
        <body>
        <![CDATA[
          var radio = document.createElement("radio");
          radio.setAttribute("pane", aPaneElement.id);
          radio.setAttribute("label", aPaneElement.label);
          // Expose preference group choice to accessibility APIs as an unchecked list item
          // The parent group is exposed to accessibility APIs as a list
          if (aPaneElement.image)
            radio.setAttribute("src", aPaneElement.image);
          radio.style.listStyleImage = aPaneElement.style.listStyleImage;
          this._selector.appendChild(radio);
          return radio;
        ]]>
        </body>
      </method>

      <method name="showPane">
        <parameter name="aPaneElement"/>
        <body>
        <![CDATA[
          if (!aPaneElement)
            return;

          this._selector.selectedItem = document.getAnonymousElementByAttribute(this, "pane", aPaneElement.id);
          if (!aPaneElement.loaded) {
            let OverlayLoadObserver = function(aPane)
            {
              this._pane = aPane;
            }
            OverlayLoadObserver.prototype = {
              _outer: this,
              observe: function (aSubject, aTopic, aData)
              {
                this._pane.loaded = true;
                this._outer._fireEvent("paneload", this._pane);
                this._outer._selectPane(this._pane);
              }
            };

            var obs = new OverlayLoadObserver(aPaneElement);
            document.loadOverlay(aPaneElement.src, obs);
          }
          else
            this._selectPane(aPaneElement);
        ]]>
        </body>
      </method>

      <method name="_fireEvent">
        <parameter name="aEventName"/>
        <parameter name="aTarget"/>
        <body>
        <![CDATA[
          // Panel loaded, synthesize a load event.
          try {
            var event = document.createEvent("Events");
            event.initEvent(aEventName, true, true);
            var cancel = !aTarget.dispatchEvent(event);
            if (aTarget.hasAttribute("on" + aEventName)) {
              var fn = new Function ("event", aTarget.getAttribute("on" + aEventName));
              var rv = fn.call(aTarget, event);
              if (rv == false)
                cancel = true;
            }
            return !cancel;
          }
          catch (e) {
            Components.utils.reportError(e);
          }
          return false;
        ]]>
        </body>
      </method>

      <field name="_initialized">false</field>
      <method name="_selectPane">
        <parameter name="aPaneElement"/>
        <body>
        <![CDATA[
          if (/Mac/.test(navigator.platform)) {
            var paneTitle = aPaneElement.label;
            if (paneTitle != "")
              document.title = paneTitle;
          }
          var helpButton = document.documentElement.getButton("help");
          if (aPaneElement.helpTopic)
            helpButton.hidden = false;
          else
            helpButton.hidden = true;

          // Find this pane's index in the deck and set the deck's
          // selectedIndex to that value to switch to it.
          var prefpanes = this.preferencePanes;
          for (var i = 0; i < prefpanes.length; ++i) {
            if (prefpanes[i] == aPaneElement) {
              this._paneDeck.selectedIndex = i;

              if (this.type != "child") {
                if (aPaneElement.hasAttribute("flex") && this._shouldAnimate &&
                    prefpanes.length > 1)
                  aPaneElement.removeAttribute("flex");
                // Calling sizeToContent after the first prefpane is loaded
                // will size the windows contents so style information is
                // available to calculate correct sizing.
                if (!this._initialized && prefpanes.length > 1) {
                  if (this._shouldAnimate)
                    this.style.minHeight = 0;
                  window.sizeToContent();
                }

                var oldPane = this.lastSelected ? document.getElementById(this.lastSelected) : this.preferencePanes[0];
                oldPane.selected = !(aPaneElement.selected = true);
                this.lastSelected = aPaneElement.id;
                this.currentPane = aPaneElement;
                this._initialized = true;

                // Only animate if we've switched between prefpanes
                if (this._shouldAnimate && oldPane.id != aPaneElement.id) {
                  aPaneElement.style.opacity = 0.0;
                  this.animate(oldPane, aPaneElement);
                }
                else if (!this._shouldAnimate && prefpanes.length > 1) {
                  var targetHeight = parseInt(window.getComputedStyle(this._paneDeckContainer, "").height);
                  var verticalPadding = parseInt(window.getComputedStyle(aPaneElement, "").paddingTop);
                  verticalPadding += parseInt(window.getComputedStyle(aPaneElement, "").paddingBottom);
                  if (aPaneElement.contentHeight > targetHeight - verticalPadding) {
                    // To workaround the bottom border of a groupbox from being
                    // cutoff an hbox with a class of bottomBox may enclose it.
                    // This needs to include its padding to resize properly.
                    // See bug 394433
                    var bottomPadding = 0;
                    var bottomBox = aPaneElement.getElementsByAttribute("class", "bottomBox")[0];
                    if (bottomBox)
                      bottomPadding = parseInt(window.getComputedStyle(bottomBox, "").paddingBottom);
                    window.innerHeight += bottomPadding + verticalPadding + aPaneElement.contentHeight - targetHeight;
                  }

                  // XXX rstrong - extend the contents of the prefpane to
                  // prevent elements from being cutoff (see bug 349098).
                  if (aPaneElement.contentHeight + verticalPadding < targetHeight)
                    aPaneElement._content.style.height = targetHeight - verticalPadding + "px";
                }
              }
              break;
            }
          }
        ]]>
        </body>
      </method>

      <property name="_shouldAnimate">
        <getter>
        <![CDATA[
          var psvc = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefBranch);
          var animate = /Mac/.test(navigator.platform);
          try {
            animate = psvc.getBoolPref("browser.preferences.animateFadeIn");
          }
          catch (e) { }
          return animate;
        ]]>
        </getter>
      </property>

      <method name="animate">
        <parameter name="aOldPane"/>
        <parameter name="aNewPane"/>
        <body>
        <![CDATA[
          // if we are already resizing, use currentHeight
          var oldHeight = this._currentHeight ? this._currentHeight : aOldPane.contentHeight;

          this._multiplier = aNewPane.contentHeight > oldHeight ? 1 : -1;
          var sizeDelta = Math.abs(oldHeight - aNewPane.contentHeight);
          this._animateRemainder = sizeDelta % this._animateIncrement;

          this._setUpAnimationTimer(oldHeight);
        ]]>
        </body>
      </method>

      <property name="_sizeIncrement">
        <getter>
        <![CDATA[
          var lastSelectedPane = document.getElementById(this.lastSelected);
          var increment = this._animateIncrement * this._multiplier;
          var newHeight = this._currentHeight + increment;
          if ((this._multiplier > 0 && this._currentHeight >= lastSelectedPane.contentHeight) ||
              (this._multiplier < 0 && this._currentHeight <= lastSelectedPane.contentHeight))
            return 0;

          if ((this._multiplier > 0 && newHeight > lastSelectedPane.contentHeight) ||
              (this._multiplier < 0 && newHeight < lastSelectedPane.contentHeight))
            increment = this._animateRemainder * this._multiplier;
          return increment;
        ]]>
        </getter>
      </property>

      <method name="notify">
        <parameter name="aTimer"/>
        <body>
        <![CDATA[
          if (!document)
            aTimer.cancel();

          if (aTimer == this._animateTimer) {
            var increment = this._sizeIncrement;
            if (increment != 0) {
              window.innerHeight += increment;
              this._currentHeight += increment;
            }
            else {
              aTimer.cancel();
              this._setUpFadeTimer();
            }
          } else if (aTimer == this._fadeTimer) {
            var elt = document.getElementById(this.lastSelected);
            var newOpacity = parseFloat(window.getComputedStyle(elt, "").opacity) + this._fadeIncrement;
            if (newOpacity < 1.0)
              elt.style.opacity = newOpacity;
            else {
              aTimer.cancel();
              elt.style.opacity = 1.0;
            }
          }
        ]]>
        </body>
      </method>

      <method name="_setUpAnimationTimer">
        <parameter name="aStartHeight"/>
        <body>
        <![CDATA[
          if (!this._animateTimer)
            this._animateTimer = Components.classes["@mozilla.org/timer;1"]
                                           .createInstance(Components.interfaces.nsITimer);
          else
            this._animateTimer.cancel();
          this._currentHeight = aStartHeight;

          this._animateTimer.initWithCallback(this, this._animateDelay,
                                              Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
        ]]>
        </body>
      </method>

      <method name="_setUpFadeTimer">
        <body>
        <![CDATA[
          if (!this._fadeTimer)
            this._fadeTimer = Components.classes["@mozilla.org/timer;1"]
                                        .createInstance(Components.interfaces.nsITimer);
          else
            this._fadeTimer.cancel();

          this._fadeTimer.initWithCallback(this, this._fadeDelay,
                                           Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
        ]]>
        </body>
      </method>

      <field name="_animateTimer">null</field>
      <field name="_fadeTimer">null</field>
      <field name="_animateDelay">15</field>
      <field name="_animateIncrement">40</field>
      <field name="_fadeDelay">5</field>
      <field name="_fadeIncrement">0.40</field>
      <field name="_animateRemainder">0</field>
      <field name="_currentHeight">0</field>
      <field name="_multiplier">0</field>

      <method name="addPane">
        <parameter name="aPaneElement"/>
        <body>
        <![CDATA[
          this.appendChild(aPaneElement);

          // Set up pane button
          this._makePaneButton(aPaneElement);
        ]]>
        </body>
      </method>

      <method name="openSubDialog">
        <parameter name="aURL"/>
        <parameter name="aFeatures"/>
        <parameter name="aParams"/>
        <body>
          return openDialog(aURL, "", "modal,centerscreen,resizable=no" + (aFeatures != "" ? ("," + aFeatures) : ""), aParams);
        </body>
      </method>

      <method name="openWindow">
        <parameter name="aWindowType"/>
        <parameter name="aURL"/>
        <parameter name="aFeatures"/>
        <parameter name="aParams"/>
        <body>
        <![CDATA[
          var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                             .getService(Components.interfaces.nsIWindowMediator);
          var win = aWindowType ? wm.getMostRecentWindow(aWindowType) : null;
          if (win) {
            if ("initWithParams" in win)
              win.initWithParams(aParams);
            win.focus();
          }
          else {
            var features = "resizable,dialog=no,centerscreen" + (aFeatures != "" ? ("," + aFeatures) : "");
            var parentWindow = (this.instantApply || !window.opener || window.opener.closed) ? window : window.opener;
            win = parentWindow.openDialog(aURL, "_blank", features, aParams);
          }
          return win;
        ]]>
        </body>
      </method>
    </implementation>
    <handlers>
      <handler event="dialogaccept">
      <![CDATA[
        if (!this._fireEvent("beforeaccept", this)) {
          return false;
        }

        var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
                    .getService(Components.interfaces.nsIScriptSecurityManager);
        if (this.type == "child" && window.opener &&
            secMan.isSystemPrincipal(window.opener.document.nodePrincipal)) {
          let psvc = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefBranch);
          var pdocEl = window.opener.document.documentElement;
          if (pdocEl.instantApply) {
            let panes = this.preferencePanes;
            for (let i = 0; i < panes.length; ++i)
              panes[i].writePreferences(true);
          }
          else {
            // Clone all the preferences elements from the child document and
            // insert them into the pane collection of the parent.
            var pdoc = window.opener.document;
            if (pdoc.documentElement.localName == "prefwindow") {
              var currentPane = pdoc.documentElement.currentPane;
              var id = window.location.href + "#childprefs";
              var childPrefs = pdoc.getElementById(id);
              if (!childPrefs) {
                childPrefs = pdoc.createElement("preferences");
                currentPane.appendChild(childPrefs);
                childPrefs.id = id;
              }
              let panes = this.preferencePanes;
              for (let i = 0; i < panes.length; ++i) {
                var preferences = panes[i].preferences;
                for (var j = 0; j < preferences.length; ++j) {
                  // 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", preferences[j].name);
                    for (var l = 0; (l < parentPrefs.length && !preference); ++l) {
                      if (parentPrefs[l].localName == "preference")
                        preference = parentPrefs[l];
                    }
                  }
                  if (!preference) {
                    // No matching preference in the parent window.
                    preference = pdoc.createElement("preference");
                    childPrefs.appendChild(preference);
                    preference.name     = preferences[j].name;
                    preference.type     = preferences[j].type;
                    preference.inverted = preferences[j].inverted;
                    preference.readonly = preferences[j].readonly;
                    preference.disabled = preferences[j].disabled;
                  }
                  preference.value = preferences[j].value;
                }
              }
            }
          }
        }
        else {
          let panes = this.preferencePanes;
          for (var i = 0; i < panes.length; ++i)
            panes[i].writePreferences(false);

          let psvc = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefService);
          psvc.savePrefFile(null);
        }

        return true;
      ]]>
      </handler>
      <handler event="command">
        if (event.originalTarget.hasAttribute("pane")) {
          var pane = document.getElementById(event.originalTarget.getAttribute("pane"));
          this.showPane(pane);
        }
      </handler>

      <handler event="keypress" key="&windowClose.key;" modifiers="accel" phase="capturing">
      <![CDATA[
        if (this.instantApply)
          window.close();
        event.stopPropagation();
        event.preventDefault();
      ]]>
      </handler>

      <handler event="keypress"
               keycode="&openHelp.commandkey;"
               phase="capturing">
      <![CDATA[
        var helpButton = this.getButton("help");
        if (helpButton.disabled || helpButton.hidden)
          return;
        this._fireEvent("dialoghelp", this);
        event.stopPropagation();
        event.preventDefault();
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="prefpane">
    <resources>
      <stylesheet src="chrome://global/skin/preferences.css"/>
    </resources>
    <content>
      <xul:vbox class="content-box" xbl:inherits="flex">
        <children/>
      </xul:vbox>
    </content>
    <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 = Components.classes["@mozilla.org/preferences-service;1"]
                                 .getService(Components.interfaces.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.nodeType == Node.ELEMENT_NODE ? temp : aStartElement;
        ]]>
        </body>
      </method>

      <property name="DeferredTask" readonly="true">
        <getter><![CDATA[
          let module = {};
          Components.utils.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._content, "").height);
          targetHeight += parseInt(window.getComputedStyle(this._content, "").marginTop);
          targetHeight += parseInt(window.getComputedStyle(this._content, "").marginBottom);
          return targetHeight;
        </getter>
      </property>
      <field name="_content">
        document.getAnonymousElementByAttribute(this, "class", "content-box");
      </field>
    </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="xul:listitem"
           extends="chrome://global/content/bindings/radio.xml#radio">
    <resources>
      <stylesheet src="chrome://global/skin/preferences.css"/>
    </resources>
    <content>
      <xul:image class="paneButtonIcon" xbl:inherits="src"/>
      <xul:label class="paneButtonLabel" xbl:inherits="value=label"/>
    </content>
  </binding>

</bindings>


