<!DOCTYPE bindings [
  <!ENTITY % verbosioLocale SYSTEM "chrome://verbosio/locale/verbosio.dtd">
  %verbosioLocale;
  <!ENTITY % commonDTD SYSTEM "chrome://verbosio/content/verbosio.dtd">
  %commonDTD;
]>
<bindings
   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">
  <!-- Borrowed from XUL Widgets's menulistset binding.  When we're further
       along, merge this code back in.
    -->
  <binding id="namespace-menulistset"
          >
    <content fields="namespaceURI,prefix">
      <children/>
      <xul:box xbl:inherits="orient" anonid="_contentBox"/>
    </content>

    <implementation>
      <field name="fields">([])</field>

      <field name="isSelectionChanging">false</field>

      <field name="_contentBox">
        document.getAnonymousElementByAttribute(this, "anonid", "_contentBox");
      </field>

      <method name="addMenuList">
        <parameter name="fieldName"/>
        <body>
          const XUL_NS    = "&XUL_NS;";
        <![CDATA[
          var menulist = document.createElementNS(XUL_NS, "xul:menulist");
          menulist.setAttribute("anonid", fieldName);
          menulist.appendChild(document.createElementNS(XUL_NS, "xul:menupopup"));
          this._contentBox.appendChild(menulist);
          for (var i = 0; i < this.childNodes.length; i++)
          {
            var itemValue = this.childNodes[i].getAttribute(fieldName);
            menulist.appendItem(itemValue, itemValue, null);
          }

          if (this.getAttribute("editable") == "true")
          {
            menulist.setAttribute("editable", "true");
          }

          menulist.selectedIndex = this.selectedIndex;
        ]]></body>
      </method>

      <method name="updateSelection">
        <parameter name="evt"/>
        <body><![CDATA[
          if (this.isSelectionChanging)
          {
            return;
          }

          this.isSelectionChanging = true;
          var controlList = evt.originalTarget.parentNode.parentNode;
          var newSelectedIndex = controlList.selectedIndex;
          for (var i = 0; i < this._contentBox.childNodes.length; i++)
          {
            var menulist = this._contentBox.childNodes[i];
            if (menulist == controlList)
            {
              continue;
            }
            if (menulist.selectedIndex != newSelectedIndex)
            {
              menulist.selectedIndex = newSelectedIndex;
            }
          }
          this.isSelectionChanging = false;
        ]]></body>
      </method>

      <field name="initialized">false</field>

      <method name="init">
        <body><![CDATA[
          if (this.initialized) {
            return;
          }
          var fields = this.getAttribute("fields").split(",");
          for (var i = 0; i < fields.length; i++)
          {
            this.addMenuList(fields[i]);
          }
          this.initialized = true;
        ]]></body>
      </method>

      <constructor><![CDATA[
        this.init();
      ]]></constructor>

      <method name="reset">
        <body><![CDATA[
          var menulist = this._contentBox.firstChild;
          while (menulist) {
            var popup = menulist.firstChild;
            menulist.replaceChild(popup.cloneNode(false), popup);
            menulist = menulist.nextSibling;
          }
        ]]></body>
      </method>

      <method name="updateNamespaceMap">
        <parameter name="aNode"/>
        <body><![CDATA[
          this.init();
          var map = verbosio.getNamespacesMap(aNode);
          this.reset();

          this._contentBox.firstChild.appendItem("", "", null);
          this._contentBox.lastChild.appendItem("(null)", "", null);

          for (var prop in map) {
            this._contentBox.firstChild.appendItem(map[prop], map[prop], null);
            this._contentBox.lastChild.appendItem(prop, prop, null);
          }

          this._contentBox.firstChild.appendItem(XMLNS_NS, XMLNS_NS, null);
          this._contentBox.lastChild.appendItem("xmlns", "xmlns", null);

          this._contentBox.firstChild.appendItem(XML_NS, XML_NS, null);
          this._contentBox.lastChild.appendItem("xml", "xml", null);

          var menulist;
          if (this.hasAttribute("disabled")) {
            menulist = this._contentBox.firstChild;
            while (menulist) {
              menulist.setAttribute("disabled", "true");
              menulist = menulist.nextSibling;
            }
          } else {
            menulist = this._contentBox.firstChild;
            while (menulist) {
              menulist.removeAttribute("disabled");
              menulist = menulist.nextSibling;
            }
          }
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="change"><![CDATA[
        this.updateSelection(event);
      ]]></handler>
      <handler event="command"><![CDATA[
        this.updateSelection(event);
      ]]></handler>

      <handler event="DOMAttrModified" phase="target">
        if ((event.relatedNode.namespaceURI != null) ||
            (event.relatedNode.localName != "disabled")) {
          return;
        }

        const DOMMutationEvent = Components.interfaces.nsIDOMMutationEvent;
        var menulist = this._contentBox.firstChild;
        if (event.attrChange == DOMMutationEvent.REMOVAL) {
          while (menulist) {
            menulist.removeAttribute("disabled");
            menulist = menulist.nextSibling;
          }
        } else {
          while (menulist) {
            menulist.setAttribute("disabled", event.newValue);
            menulist = menulist.nextSibling;
          }
        }
      </handler>
    </handlers>
  </binding>

  <binding id="documentViewer">
    <content>
      <xul:vbox>
        <xul:tabs anonid="publicURITabs">
          <xul:tab label="New Document" anonid="defaultURITab"/>
        </xul:tabs>

        <xul:deck anonid="documentViewDeck">
          <xul:box>
            <children/>
          </xul:box>
        </xul:deck>
        <xul:tabs class="tabs-bottom" anonid="viewTabs"/>
      </xul:vbox>
    </content>

    <implementation>
      <field name="publicURITabs" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "publicURITabs");
      </field>

      <field name="defaultURITab" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "defaultURITab");
      </field>

      <field name="deck" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "documentViewDeck");
      </field>

      <property name="selectedPanel">
        <getter>return this.deck.selectedPanel</getter>
        <setter>this.deck.selectedPanel = val;</setter>
      </property>

      <field name="viewTabs" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "viewTabs");
      </field>

      <field name="currentWrapper">
        null
      </field>

      <method name="appendDocTab">
        <parameter name="wrapper"/>
        <body><![CDATA[
          var uri = wrapper.publicURI;
          var slash = (uri.indexOf("/") == -1) ? "\\" : "/";
          var fileName = uri.substr(uri.lastIndexOf(slash) + 1);
          var tab = this.publicURITabs.appendItem(fileName, null);
          if (this.publicURITabs.firstChild == this.defaultURITab)
            this.publicURITabs.removeChild(this.defaultURITab);
          tab.documentWrapper = wrapper;
          tab.setUserData("documentWrapper", wrapper, null);

          var item = document.createElementNS(XUL_NS, "xul:menuitem");
          item.setUserData("docTab", tab, null);
          item.setAttribute("label", fileName);
          documentsMenuPopup.appendChild(item);

          return tab;
        ]]></body>
      </method>

      <method name="appendViewerTab">
        <parameter name="label"/>
        <parameter name="scopeid"/>
        <parameter name="accesskey"/>
        <body><![CDATA[
          var tab = document.createElementNS(XUL_NS, "xul:tab");
          tab.setAttribute("class", "tab-bottom");
          tab.setAttribute("label", label);
          assert(scopeid, "No scope ID?");
          tab.setAttribute("scopeid", scopeid);
          tab.setAttribute("disabled", "true");
          this.viewTabs.appendChild(tab);

          var item = document.createElementNS(XUL_NS, "xul:menuitem");
          item.setUserData("viewerTab", tab, null);
          item.setAttribute("label", label);
          if (accesskey) {
            item.setAttribute("accesskey", accesskey);
          }
          viewersMenuPopup.appendChild(item);
        ]]></body>
      </method>

      <method name="insertDocPackTab">
        <parameter name="panel"/>
        <body><![CDATA[
          var label = panel.getAttribute("tablabel");
          assert(label, "No label from panel?");

          // Document pack tabs come before document wrapper tabs.
          for (var i = 0; i < this.publicURITabs.childNodes.length; i++) {
            var tab = this.publicURITabs.childNodes[i];
            if (typeof tab.linkedPanel == "undefined") {
              break;
            }
          }

          if (this.publicURITabs.firstChild == this.defaultURITab)
            this.publicURITabs.removeChild(this.defaultURITab);

          var retval = this.publicURITabs.insertItemAt(i, label, null);
          panel.setAttribute("anonid", "verbosio-doc-pack-panel:" + label);
          this.deck.appendChild(panel);
          retval.linkedPanel = "verbosio-doc-pack-panel:" + label;
          return retval;
        ]]></body>
      </method>

      <!--
         this.viewers.docTabs[wrapper.publicURI].viewTabs[viewTab.scopeId] = {
           disabled: !rootNode,
           index: rootNode ? this.deck.childNodes.length : -1,
           node: rootNode
         }
        -->
      <field name="viewers">({
        docTabs: {}
      })</field>

      <!--*
        - Get a viewer object (usually a node).
        -
        - @param wrapper xeIDocumentWrapper object.
        - @param viewTab XUL tab element in the viewTabs section.
        - @param addViewer Boolean; if true, add the viewer to our registry.
        -
        - @return Viewer object for the wrapper and view tab.
       -->
      <method name="getViewer">
        <parameter name="wrapper"/>
        <parameter name="viewTab"/>
        <parameter name="addViewer"/>
        <body><![CDATA[
          var publicURI = wrapper.publicURI;
          var scopeId   = viewTab.getAttribute("scopeid");
          try {
            assert(scopeId, "No scope ID found!");
          }
          catch (e) {
            dump(e.stack + "\n");
            throw e;
          }
          var scope     = scopeRegistry.getScope(scopeId);

          if (addViewer) {
            var rootNode = scope.getViewerNode(wrapper);
          }

          if (!(publicURI in this.viewers.docTabs) ||
              !(this.viewers.docTabs[publicURI])) {
            this.viewers.docTabs[publicURI] = {
              viewTabs: {}
            };
          }

          var docViewers = this.viewers.docTabs[publicURI];
          assert(docViewers, "No document viewers?");
          if (addViewer && (!(scopeId in docViewers.viewTabs))) {
            dump("Setting docViewers.viewTabs['" + scopeId + "'] to " + rootNode + "\n");
            docViewers.viewTabs[scopeId] = rootNode;
          }

          if (addViewer && rootNode) {
            this.deck.appendChild(rootNode);
          }

          return docViewers.viewTabs[scopeId];
        ]]></body>
      </method>

      <method name="selectDocTab">
        <parameter name="tabIndex"/>
        <body><![CDATA[
          dump("Selecting document tab " + tabIndex + "...\n");
          this.publicURITabs.selectedIndex = tabIndex;
          var tab = this.publicURITabs.selectedItem;
          assert(tab, "No tab?");
          var i;
          if (tab == this.defaultURITab) {
            dump("default URI tab\n");
            for (i = 0; i < this.viewTabs.childNodes.length; i++) {
              tab = this.viewTabs.childNodes[i];
              tab.setAttribute("disabled", true);
            }
            this.currentWrapper = null;

            return;
          }

          if (tab.getAttribute("disabled") == "true") {
            dump("Tab disabled\n");
            return;
          }

          var linkedPanelId = typeof tab.linkedPanel == "string" ?
                              tab.linkedPanel :
                              null;
          var newPanel = null;
          if (linkedPanelId) {
            dump(linkedPanelId + "\n");
            for (var i = 0; i < this.deck.childNodes.length; i++) {
              var subPanel = this.deck.childNodes[i];
              dump("subPanel.anonid: " + subPanel.getAttribute("anonid") + "\n");
            }
            newPanel = document.getAnonymousElementByAttribute(this,
              "anonid",
              linkedPanelId);
            assert(newPanel, "No new panel?");
          }

          var wrapper = null;
          if (newPanel) {
            /* Tabs shouldn't have panels, unless the tab has no viewer sets.
               Example:  a document pack's metadata panels.
             */
            var viewTab = this.viewTabs.firstChild;
            // XXX ajvincent a11y need alt+pgup, alt+pgdn disabled!
            this.viewTabs.setAttribute("disabled", "true");
            while (viewTab) {
              viewTab.setAttribute("disabled", "true");
              viewTab = viewTab.nextSibling;
            }
          } else {
            this.viewTabs.removeAttribute("disabled");

            wrapper = tab.documentWrapper;
            var enabledTabs = [];
            try {
              var currentViewTab = this.viewTabs.selectedItem;
            } catch(e if Components.results.NS_ERROR_FAILURE) {
              dump("<xul:tabs> didn't have a selected item!\n\n");
              this.viewTabs.selectedIndex = 0;
              var currentViewTab = this.viewTabs.selectedItem;
            }

            dump("Getting viewer...\n");
            newPanel = this.getViewer(wrapper, currentViewTab, false);

            // Determine which viewers are valid, and select the default viewer.
            for (i = 0; i < this.viewTabs.childNodes.length; i++) {
              tab = this.viewTabs.childNodes[i];
              var viewer = this.getViewer(wrapper, tab, false);
              if (viewer) {
                tab.removeAttribute("disabled");
                if (!newPanel) {
                  newPanel = viewer;
                }
              } else {
                tab.setAttribute("disabled", "true");
              }
            }
          } // end if (newPanel);

          assert(newPanel, "We should have a new panel by now!");

          dump("tab disabled attribute: " + tab.getAttribute("disabled") + "\n");
          dump("tab label: " + tab.getAttribute("label") + "\n");
          dump("Changing deck...\n\n");

          // Establish range container descendant indexes.
          var descIndexes1 = null;
          var descIndexes2 = null;
          if (wrapper &&
               (typeof wrapper.selectionRange == "object") &&
               isInstance(wrapper.selectionRange, "nsIDOMRange") == 2) {
            var range = wrapper.selectionRange;
            descIndexes1 = getDescendantIndexes(range.startContainer);
            descIndexes2 = getDescendantIndexes(range.endContainer);
          }

          var oldPanel = this.deck.selectedPanel;
          if (oldPanel && typeof oldPanel.notifyEvent == "function") {
            oldPanel.notifyEvent("blur", null, descIndexes1, descIndexes2);
          }
          this.deck.selectedPanel = newPanel;
          this.currentWrapper = wrapper;
          newPanel.notifyEvent("focus", null, descIndexes1, descIndexes2);
        ]]></body>
      </method>

      <method name="removeDocTab">
        <parameter name="tabIndex"/>
        <body><![CDATA[
          var tab = this.publicURITabs.childNodes[tabIndex];
          var wrapper = tab.getUserData("documentWrapper");
          if (wrapper) {
            // Clear all the viewers.
            var panels = this.viewers.docTabs[wrapper.publicURI].viewTabs;
            assert(panels, "No panels???");
            for (var scopeid in panels) {
              // Remember, text wrappers don't have viewers for everything.
              if (panels[scopeid] != null) {
                this.deck.removeChild(panels[scopeid]);
              }
            }
            this.viewers.docTabs[wrapper.publicURI] = null;
          }

          if ((tabIndex == 0) && (this.publicURITabs.childNodes.length == 1)) {
            this.publicURITabs.replaceChild(this.defaultURITab, tab);
            this.selectDocTab(0);
            return;
          }

          this.publicURITabs.removeChild(tab);
          this.selectDocTab(0);
        ]]></body>
      </method>

      <method name="selectViewerTab">
        <parameter name="tab"/>
        <body><![CDATA[
          if (tab.getAttribute("disabled") == "true") {
            dump("Tab disabled\n");
            return;
          }

          var newPanel = this.getViewer(this.currentWrapper, tab, false);
          if (!newPanel) {
            dump("No tab found, bailing out of command event handler.\n");
            return;
          }

          dump("tab disabled attribute: " + tab.getAttribute("disabled") + "\n");
          dump("tab label: " + tab.getAttribute("label") + "\n");
          dump("Changing deck...\n\n");

          // Establish range container descendant indexes.
          var descIndexes1 = null;
          var descIndexes2 = null;

          var wrapper = this.publicURITabs.selectedItem.documentWrapper;
          if ((typeof wrapper.selectionRange == "object") &&
               isInstance(wrapper.selectionRange, "nsIDOMRange") == 2) {
            var range = wrapper.selectionRange;
            descIndexes1 = getDescendantIndexes(range.startContainer);
            descIndexes2 = getDescendantIndexes(range.endContainer);
          }

          var oldPanel = this.deck.selectedPanel;
          if (oldPanel && typeof oldPanel.notifyEvent == "function") {
            oldPanel.notifyEvent("blur", null, descIndexes1, descIndexes2);
          }

          newPanel.notifyEvent("focus", null, descIndexes1, descIndexes2);
          this.deck.selectedPanel = newPanel;
        ]]></body>
      </method>

      <field name="_keyEventHandler" readonly="true"><![CDATA[({
        owner: this,
        accel: null,
        handleEvent: function handleEvent(aEvent) {
          if (!this.accel) {
            const httpClass = "@mozilla.org/network/protocol;1?name=http";
            const nsIHPH = Components.interfaces.nsIHttpProtocolHandler;
            var protocol = Components.classes[httpClass].getService(nsIHPH);
            this.accel = (protocol.platform == "Macintosh") ?
                         "metaKey" :
                         "ctrlKey";
          }

          if (!aEvent.isTrusted) {
            // Don't let untrusted events mess with tabs.
            return;
          }

          var tabs = null;
          if (this.accel in aEvent && aEvent[this.accel]) {
            tabs = this.owner.publicURITabs;
          } else if ("altKey" in aEvent && aEvent.altKey) {
            tabs = this.owner.viewTabs;
          } else {
            return;
          }

          if (tabs.hasAttribute("disabled")) {
            return;
          }

          const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent;
          switch (aEvent.keyCode) {
            case nsIDOMKeyEvent.DOM_VK_PAGE_UP:
              tabs.advanceSelectedTab(-1, true);
              break;
            case nsIDOMKeyEvent.DOM_VK_PAGE_DOWN:
              tabs.advanceSelectedTab(+1, true);
              break;
            default:
              return;
          }

          if (tabs == this.owner.viewTabs) {
            assert(tabs.selectedItem, "No selected tab??");
            this.owner.selectViewerTab(tabs.selectedItem);
          } else {
            this.owner.selectDocTab(tabs.selectedIndex);
          }
          event.stopPropagation();
          event.preventDefault();
        }
      })]]></field>

      <constructor>
        document.addEventListener("keypress", this._keyEventHandler, false);
        this.selectDocTab(0);
      </constructor>

      <destructor>
        document.removeEventListener("keypress", this._keyEventHandler, false);
      </destructor>
    </implementation>

    <handlers>
      <handler event="command" phase="bubbling"><![CDATA[
        const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
        if (event.target.namespaceURI != XUL_NS) {
          dump("namespaceURI: " + event.target.namespaceURI + "\n");
          return;
        }

        if (event.originalTarget.localName != "tab") {
          dump("originalTarget: " + event.originalTarget.nodeName + "\n");
          return;
        }

        var tab = event.originalTarget;

        if (tab.parentNode == this.publicURITabs) {
          this.selectDocTab(this.publicURITabs.selectedIndex);
          return;
        }

        if (tab.parentNode != this.viewTabs) {
          dump("originalTarget.parentNode: " + tab.parentNode.nodeName + "\n");
          return;
        }

        if (event.getPreventDefault()) {
          dump("getPreventDefault\n");
          return;
        }

        this.selectViewerTab(tab);
      ]]></handler>
    </handlers>
  </binding>

  <binding id="xul:precondition-ui">
    <content>
      <xul:vbox class="scrollable">
        <children/>
      </xul:vbox>
      <xul:hbox>
        <xul:description anonid="invalidMessage"/>
        <xul:spacer flex="1"/>
        <xul:menulist anonid="insertWhereMenu"
                      label="&insertReplace.label;"
                      xbl:inherits="disabled=insertmenudisabled"
                      value="REPLACE_SELECTION">
          <xul:menupopup>
            <xul:menuitem label="&insertReplace.label;"
                          value="REPLACE_SELECTION"/>
            <xul:menuitem label="&insertBeforeSelection.label;"
                          value="BEFORE_SELECTION"/>
            <xul:menuitem label="&insertAfterSelection.label;"
                          value="AFTER_SELECTION"/>
            <xul:menuitem label="&insertFirstChild.label;"
                          value="FIRST_CHILD_OF_SELECTION"/>
            <xul:menuitem label="&insertLastChild.label;"
                          value="LAST_CHILD_OF_SELECTION"/>
            <xul:menuitem label="&insertAsParent.label;"
                          value="AS_PARENT_OF_SELECTION"/>
          </xul:menupopup>
        </xul:menulist>

        <xul:button anonid="backButton"
                    hidden="true"
                    disabled="true"
                    label="&backButton.label;"
                    oncommand="preconditionUIBox.rewind();"
                   />
        <xul:button anonid="acceptButton"
                    disabled="true"
                    label="&acceptButton.label;"
                    oncommand="preconditionUIBox.advance();"
                   />
        <xul:button anonid="cancelButton"
                    label="&cancelButton.label;"
                    oncommand="this.precondition = null;"
                   />
        <xul:button anonid="helpButton"
                    label="&helpButton.label;"
                    oncommand="window.open(this.precondition.helpURI);"
                   />
      </xul:hbox>
    </content>

    <implementation>
      <!--
        - Container for messages taking about invalid precondition content.
        -->
      <field name="invalidMessage" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "invalidMessage");
      </field>

      <!--
        - Menu designating where a particular transaction's generated content goes.
        -->
      <field name="insertWhereMenu" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "insertWhereMenu");
      </field>

      <!-- @note The following menu items may be enabled, disabled, or selected
                 by the precondition at any time. -->

      <!--
        - Flag to indicate replacing the selection with generated content.
        -->
      <field name="replaceSelectionItem" readonly="true">
        document.getAnonymousElementByAttribute(this, "value", "REPLACE_SELECTION");
      </field>

      <!--
        - Flag to indicate inserting generated content before the selection.
        -->
      <field name="beforeSelectionItem" readonly="true">
        document.getAnonymousElementByAttribute(this, "value", "BEFORE_SELECTION");
      </field>

      <!--
        - Flag to indicate inserting generated content before the selection.
        -->
      <field name="afterSelectionItem" readonly="true">
        document.getAnonymousElementByAttribute(this, "value", "AFTER_SELECTION");
      </field>

      <!--
        - Flag to indicate inserting generated content as the first child node of the selection.
        -->
      <field name="firstChildSelectionItem" readonly="true">
        document.getAnonymousElementByAttribute(this, "value", "FIRST_CHILD_OF_SELECTION");
      </field>

      <!--
        - Flag to indicate inserting generated content as the last child node of the selection.
        -->
      <field name="lastChildSelectionItem" readonly="true">
        document.getAnonymousElementByAttribute(this, "value", "LAST_CHILD_OF_SELECTION");
      </field>

      <!--
        - Flag to indicate wrapping generated content as the parent node of the selection.
        -->
      <field name="asParentOfSelectionItem" readonly="true">
        document.getAnonymousElementByAttribute(this, "value", "AS_PARENT_OF_SELECTION");
      </field>

      <!--
        - Button to rewind the precondition (if it is a wizard) back one page.
        -->
      <field name="backButton" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "backButton");
      </field>

      <!--
        - Button to trigger accepting the pending action for the document.
        -->
      <field name="acceptButton" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "acceptButton");
      </field>

      <!--
        - Button to trigger canceling the pending action for the document.
        -->
      <field name="cancelButton" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "cancelButton");
      </field>

      <!--
        - Button to trigger a help dialog for the pending action.
        -->
      <field name="helpButton" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "helpButton");
      </field>

      <method name="updateAcceptDisabled">
        <body><![CDATA[
          var disabled = this.precondition.invalidMessage;
          this.acceptButton.disabled = Boolean(disabled);
          var textNode = document.createTextNode(disabled);
          if (this.invalidMessage.firstChild) {
            this.invalidMessage.replaceChild(textNode, this.invalidMessage.firstChild);
          } else {
            this.invalidMessage.appendChild(textNode);
          }

          if (this.isWizard) {
            this.backButton.disabled = (this.precondition._pageStack.length <= 1);

            var label = verbosioLocaleStrings.getString(
                         (this.precondition.onLastPage) ?
                         "acceptButton.label" :
                         "nextButton.label");
            this.acceptButton.setAttribute("label", label);
          }
        ]]></body>
      </method>

      <!-- Wizard precondition handling methods. -->
      <property name="isWizard" readonly="true">
        <getter>return this.precondition.hasAttribute("wizard")</getter>
      </property>

      <method name="rewind">
        <body><![CDATA[
          if (this.isWizard) {
            this.precondition.rewind(null);
            return;
          }

          throw new Error("Called rewind for a non-wizard precondition?");
        ]]></body>
      </method>

      <method name="advance">
        <body><![CDATA[
          if (this.isWizard) {
            this.precondition.advance(null);
            return;
          }

          this.accept();
        ]]></body>
      </method>

      <property name="precondition">
        <getter>return this.firstChild;</getter>
        <setter>
          while (this.firstChild) {
            this.removeChild(this.firstChild);
          }
          if (!val)
            return;

          this.insertWhereMenu.removeAttribute("disabled");
          this.appendChild(val);
          this.updateAcceptDisabled();
          var item = this.insertWhereMenu.firstChild.firstChild;

          while (item.disabled) {
            item = item.nextSibling;
          }
          this.insertWhereMenu.selectedItem = item;
          this.helpButton.disabled = Boolean(val.helpURI);

          const xeIPrecondition = Components.interfaces.xeIPrecondition;
          assert(val instanceof xeIPrecondition, "Check argument!");

          var label;

          if (val.hasAttribute("wizard")) {
            assert(val.childNodes.length > 1, "Why use a wizard?");
            val.selectedIndex = 0;
            this.insertWhereMenu.hidden = true;
            this.backButton.hidden = false;
            this.backButton.disabled = true;
            label = verbosioLocaleStrings.getString("nextButton.label");
          } else {
            this.insertWhereMenu.hidden = false;
            this.backButton.hidden = true;
            label = verbosioLocaleStrings.getString("acceptButton.label");
          }

          this.acceptButton.setAttribute("label", label);
        </setter>
      </property>

      <method name="accept">
        <body><![CDATA[
          var pre = this.precondition;
          verbosio.advanceTxn(pre.transaction,
                              pre.wrapper);
          verbosio.advanceTxn(null, pre.wrapper);
          this.precondition = null;
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="DOMAttrModified"><![CDATA[
        if (event.attrName == "invalid")
        {
          this.updateAcceptDisabled();
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="precondition-wizard"
           extends="chrome://global/content/bindings/general.xml#deck">
    <implementation implements="xeIPrecondition">
      <field name="transaction">null</field>
      <field name="wrapper">null</field>

      <property name="invalidMessage" readonly="true">
        <getter><![CDATA[
          var panel = this.selectedPanel;
          var invalid = panel.getElementsByAttribute("invalid", "true").item(0);
          if (!invalid) {
            return null;
          }
          return (typeof invalid.invalidMessage == "string") ?
                 invalid.invalidMessage :
                 "Invalid field";
        ]]></getter>
      </property>

      <field name="helpURI">null</field>

<!-- Much of this binding comes from mozilla.org's toolkit wizard.xml binding. -->

      <method name="rewind">
        <body><![CDATA[
          assert(this._pageStack.length > 1, "Can't rewind?");

          if (this.selectedPanel && !this._fireEvent(this.selectedPanel, "pagehide"))
            return;

          if (this.selectedPanel && !this._fireEvent(this.selectedPanel, "pagerewound"))
            return;

          if (!this._fireEvent(this, "wizardback"))
            return;

          this._pageStack.pop();
          this.selectedPanel = this._pageStack[this._pageStack.length - 1];
          this.setAttribute("pagestep", this._pageStack.length);

          this.parentNode.updateAcceptDisabled();
        ]]></body>
      </method>

      <method name="advance">
        <parameter name="aPageId"/>
        <body><![CDATA[
          assert(!this.invalidMessage, "Trying to advance while invalid?");

          if (this.selectedPanel &&
              !this._fireEvent(this.selectedPanel, "pagehide"))
            return;

          if (this.selectedPanel &&
              !this._fireEvent(this.selectedPanel, "pageadvanced"))
            return;

          if (this.onLastPage) {
            if (this._fireEvent(this, "wizardfinish")) {
              this.parentNode.accept();
            }
          } else {
            if (!this._fireEvent(this, "wizardnext"))
              return;

            var page;
            if (aPageId)
              page = this.getPageById(aPageId);
            else {
              if (this.selectedPanel) {
                if (this._accessMethod == "random")
                  page = this.getPageById(this.selectedPanel.next);
                else
                  page = this.selectedPanel.nextSibling;
              } else
                page = this.firstChild;
            }

            if (page) {
              this._pageStack.push(page);
              this.setAttribute("pagestep", this._pageStack.length);

              this.selectedPanel = page;

              this.parentNode.updateAcceptDisabled();
            }
          }
        ]]></body>
      </method>

      <method name="_fireEvent">
        <parameter name="aTarget"/>
        <parameter name="aType"/>
        <body>
        <![CDATA[
          var event = document.createEvent("Events");
          event.initEvent(aType, true, true);

          // handle dom event handlers
          var noCancel = aTarget.dispatchEvent(event);
          if (!noCancel) {
            return false;
          }

          // handle any xml attribute event handlers
          var handler = aTarget.getAttribute("on"+aType);
          if (handler != "") {
            // Treat an error as a cancellation.
            noCancel = false;
            try {
              var fn = new Function("event", handler);
              noCancel = fn.apply(aTarget, [event]);
            }
            catch (e) {
              // do nothing;
            }
          }

          return noCancel;
        ]]>
        </body>
      </method>

      <property name="onLastPage">
        <getter><![CDATA[
          var cp = this.selectedPanel;
          return cp && ((this._accessMethod == "sequential" && cp == this.lastChild) ||
                       (this._accessMethod == "random" && cp.next == ""));
         ]]></getter>
      </property>

      <method name="getPageById">
        <parameter name="aPageId"/>
        <body><![CDATA[
          var els = this.getElementsByAttribute("pageid", aPageId);
          return els.item(0);
        ]]></body>
      </method>

      <method name="_initPages">
        <body><![CDATA[
          var meth = "sequential";
          var pages = this.childNodes;
          for (var i = 0; i < pages.length; ++i) {
            var page = pages[i];
            page.pageIndex = i;
            var next = page.getAttribute("next");
            if (next != "") {
              meth = "random";
            }
          }
          this._accessMethod = meth;
        ]]></body>
      </method>

      <constructor>
        this._pageStack = [this.firstChild];
        this._initPages();
      </constructor>
    </implementation>
  </binding>

  <binding id="xul:value">
    <implementation>
      <property readonly="true" onget="return this.firstChild.value;"/>
    </implementation>
  </binding>
</bindings>
