<?xml version="1.0"?>
<!DOCTYPE bindings [
  <!ENTITY % verbosio.dtd SYSTEM "chrome://verbosio/content/verbosio.dtd">
  %verbosio.dtd;
  <!ENTITY % textbox.dtd  SYSTEM "chrome://xulwidgets/content/textbox.dtd">
  %textbox.dtd;
]>
<bindings id="bindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="precondition">
    <content>
      <xul:vbox>
        <children/>
        <xul:spacer flex="1"/>
        <xul:hbox>
          <xul:spacer flex="1"/>
          <xul:button anonid="backButton"
                      label="Back"
                      disabled="true"
                     />
          <xul:button anonid="nextButton"
                      label="Next"
                     />
          <xul:spacer/>
          <xul:button anonid="acceptButton" label="Ok"/>
          <xul:button anonid="cancelButton" label="Cancel"/>
          <xul:button anonid="helpButton"   label="Help"/>
        </xul:hbox>
      </xul:vbox>
    </content>
    <implementation>
      <field name="backButton">
        document.getAnonymousElementByAttribute(this, "anonid", "backButton");
      </field>

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

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

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

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

      <property name="visible">
        <getter>return window.getComputedStyle(this, null).visibility;</getter>
        <setter>
          if (Boolean(val))
          {
            var showNavButtons = (this.firstChild.getAttribute("wizarddeck") != "true");
            this.backButton.hidden = showNavButtons;
            this.nextButton.hidden = showNavButtons;
            this.nextButton.nextSibling.hidden = showNavButtons;
          }

          this.style.visibility = (Boolean(val)) ? "visible" : "hidden";
        </setter>
      </property>

      <constructor>
        this.visible = false;
      </constructor>
    </implementation>
    <handlers>
      <handler event="command" phase="at-target">
        switch (event.originalTarget)
        {
          case this.nextButton:
            this.firstChild.nextPage();
            return;

          case this.backButton:
            this.firstChild.previousPage();
            return;

          case this.acceptButton:
            var txn = this.firstChild.getTransaction();
            txmgr.doTransaction(txn);

            // Fall through

          case this.cancelButton:
            this.visible = false;
            serializeDocument();
            break;

          case this.helpButton:
            throw new Error("NOT_IMPLEMENTED");
        }
      </handler>
    </handlers>
  </binding>

  <binding id="markup-children"
           xmlns="http://www.mozilla.org/xbl">
    <implementation implements="xeIMarkupChildren">
      <field name="originalNodeList">([])</field>

      <method name="getMatchCount">
        <parameter name="aCheckNode"/>
        <body><![CDATA[
/* The template has a wild card here.  But how many nodes do we match?  The
* full isBaseNode check is very expensive...  We look at the next node
* because the previous nodes have already been checked.
*/
          var nextNode = this.nextSibling;
          var count = 0;
          var iteratorNode = aCheckNode;
          if (!nextNode) {
            // Oh.  We'll accept anything.
            while (iteratorNode) {
              count++;
              iteratorNode = iteratorNode.nextSibling;
            }

            return count;
          }

          while (iteratorNode) {
            if (iteratorNode.nodeType != nextNode.nodeType) {
              count++;
              iteratorNode = iteratorNode.nextSibling;
              continue;
            }

            if (iteratorNode.localName != nextNode.localName) {
              count++;
              iteratorNode = iteratorNode.nextSibling;
              continue;
            }

            if (((iteratorNode.nodeType == nsIDOMNode.ELEMENT_NODE) ||
                 (iteratorNode.nodeType == nsIDOMNode.ATTRIBUTE_NODE)) &&
                (iteratorNode.namespaceURI != nextNode.namespaceURI)) {
              count++;
              iteratorNode = iteratorNode.nextSibling;
              continue;
            }

/* We found a match between the iteratorNode and the nextNode.  While this may
* not be a perfect match (we're not checking recursively here), for the most
* common cases this should be adequate.
*/
            break;
          }

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

      <method name="replaceWithChildren">
        <body><![CDATA[
          var marker = document.createComment("markup-marker");
          var lastNode = this.originalNodeList[this.originalNodeList.length - 1];
          lastNode.parentNode.insertBefore(marker, lastNode.nextSibling);
          for (var i = 0; i < this.originalNodeList.length; i++) {
            var node = this.originalNodeList[i];
            this.parentNode.insertBefore(node, this);
          }
          marker.parentNode.replaceChild(this, marker);
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="markup-repeat-binding">
    <content>
      <xul:grid>
        <xul:columns anonid="columns">
          <xul:column/>
        </xul:columns>
        <xul:rows anonid="rows">
          <xul:row anonid="lastRow"/>
        </xul:rows>
      </xul:grid>
      <xul:box hidden="true" anonid="hiddenContainer">
        <children/>
      </xul:box>
    </content>

    <implementation implements="xeIMarkupRepeat">
      <property name="localid" readonly="true">
        <getter>return this.getAttribute("localid");</getter>
      </property>

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

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

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

      <field name="shortcutBase">
        document.getAnonymousElementByAttribute(this, "rowcommand", "shortcut");
      </field>

      <field name="minCount">
        this.hasAttribute("mincount") ?
          Number(this.getAttribute("mincount")) :
          0;
      </field>

      <field name="maxCount">
        this.hasAttribute("maxcount") ?
          Number(this.getAttribute("maxcount")) :
          Infinity;
      </field>

      <field name="linksArray">
        this.hasAttribute("links") ?
          this.getAttribute("links").split(",") :
          ([]);
      </field>

      <method name="getFragment">
        <parameter name="aStartNode"/>
        <body><![CDATA[
          var fragment = document.createDocumentFragment();
          var startNode = aStartNode || this.firstChild;
          for (var i = 0; i < this.childNodes.length; i++)
          {
            fragment.appendChild(startNode.cloneNode(true));
            startNode = startNode.nextSibling;
          }
          return fragment;
        ]]></body>
      </method>

      <field name="rowCount">
        (0)
      </field>

      <method name="getRow">
        <parameter name="aFragment"/>
        <body><![CDATA[
          const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          const XLINK_NS = "http://www.w3.org/1999/xlink";
          var row = document.createElementNS(XUL_NS, "xul:row");

          for (var i = 0; i < this.linksArray.length; i++)
          {
            var shortcut = this.shortcutBase.cloneNode(true);
            shortcut.setAttribute("value", "(" + this.linksArray[i] + " " + this.rowCount + ")");
            shortcut.setAttributeNS(XLINK_NS, "xlink:href", "#");
            row.appendChild(shortcut);
          }
          var hbox = document.createElementNS(XUL_NS, "xul:hbox");
          hbox.appendChild(aFragment);

          row.appendChild(hbox);
          return row;
        ]]></body>
      </method>

      <!-- xeIMarkupRepeat -->
      <method name="addRow">
        <parameter name="startNode"/>
        <body><![CDATA[
          dump("Adding row to repeat node with ");
          dump(startNode ? startNode.nodeName : startNode);
          dump("\n");
          var fragment = this.getFragment(startNode);
          var baseRow = this.getRow(fragment);

          var row = baseRow.cloneNode(true);
          row.parentIndex = this.rowCount;
          this.rowCount++;

          this.lastRow.parentNode.insertBefore(row, this.lastRow);
        ]]></body>
      </method>

      <!-- xeIMarkupRepeat -->
      <method name="clearRows">
        <body><![CDATA[
          while (this.rows.firstChild != this.lastRow)
          {
            this.rows.removeChild(this.rows.firstChild);
          }
          this.rowCount = 0;
        ]]></body>
      </method>

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

      <method name="initializeBase">
        <body><![CDATA[
          const MARKUP_NS = "http://verbosio.mozdev.org/namespaces/markup/";
          for (var i = 0; i < this.childNodes.length; i++) {
            var checkNode = this.childNodes[i];
            if ((checkNode instanceof Components.interfaces.nsIDOMElement) &&
                (checkNode.namespaceURI == MARKUP_NS) &&
                (checkNode.localName == "children")) {
              throw new Error("Assertion failure:  markup:repeat > markup:children is not allowed!");
            }
          }

          const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var i;

          for (i = 0; i < this.linksArray.length; i++)
          {
            this.columns.appendChild(document.createElementNS(XUL_NS, "xul:column"));
          }

          var startCount = this.getAttribute("startcount");
          if (isNaN(startCount))
          {
            startCount = 0;
          }

          startCount = Number(startCount);
          if ((startCount != Math.floor(startCount)) || (startCount < 0))
          {
            startCount = 0;
          }

          if (startCount == 0) {
            return;
          }

          var fragment = this.getFragment(null);
          var baseRow = this.getRow(fragment);

          for (i = 0; i < startCount; i++)
          {
            var row = baseRow.cloneNode(true);
            row.parentIndex = this.rowCount;
            this.rowCount++;
            this.lastRow.parentNode.insertBefore(row, this.lastRow);
          }
        ]]></body>
      </method>

      <!--*
        * Export the post-processing nodes of this repeat node.
        *
        * @param parallelRepeatElement The twin of this node.
        * @param attachOriginals True when including nodes outside template.
       -->
      <method name="exportEditedNode">
        <parameter name="parallelRepeatElement"/>
        <parameter name="attachOriginals"/>
        <body><![CDATA[
          var targetParent = parallelRepeatElement.parentNode;
          const MARKUP_NS = "http://verbosio.mozdev.org/namespaces/markup/";
          var iteratorNode = this.rows.firstChild;
          var i;
          while (iteratorNode != this.lastRow) {
            var hbox = iteratorNode.firstChild;

            var responseNode = hbox.cloneNode(true);

            if (attachOriginals) {
              var sourceChildren = hbox.getElementsByTagNameNS(MARKUP_NS, "children");
              var targetChildren = response.getElementsByTagNameNS(MARKUP_NS, "children");

              for (i = 0; i < sourceChildren.length; i++) {
                targetChildren[i].originalNodeList = sourceChildren[i].originalNodeList;
              }
            }

            var sourceRepeats = hbox.getElementsByTagNameNS(MARKUP_NS, "repeat");
            var targetRepeats = responseNode.getElementsByTagNameNS(MARKUP_NS, "repeat");

            for (i = 0; i < sourceRepeats.length; i++) {
              sourceRepeats[i].exportEditedNode(targetRepeats[i], attachOriginals);
            }

            while (responseNode.firstChild) {
              targetParent.insertBefore(responseNode.firstChild, parallelRepeatElement);
            }

            iteratorNode = iteratorNode.nextSibling;
          }

          targetParent.removeChild(parallelRepeatElement);
        ]]></body>
      </method>

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

    <handlers>
      <handler event="command" phase="at-target"><![CDATA[
        var targetNode = event.originalTarget;
        var rowCommand = targetNode.getAttribute("rowcommand");
        if (rowCommand == "shortcut")
        {
          alert("Hello!");
        }
      ]]></handler>
    </handlers>
  </binding>


  <binding id="markup-repeat-primary"
           extends="chrome://verbosio/content/template.xml#markup-repeat-binding">
    <content>
      <xul:grid>
        <xul:columns anonid="columns">
          <xul:column/>
          <xul:column/>
        </xul:columns>
        <xul:rows anonid="rows">
          <xul:row anonid="lastRow">
            <xul:box/>
            <xul:button rowcommand="addrow"
                        label="Add"/>
          </xul:row>
        </xul:rows>
      </xul:grid>
      <xul:box hidden="true" anonid="hiddenContainer">
        <children/>
        <xul:button rowcommand="deleterow"
                    anonid="deleteBaseButton"
                    label="Delete"/>
      </xul:box>
    </content>

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

      <property name="groupedSet" readonly="true">
        <getter><![CDATA[
          const MARKUP_NS = "http://verbosio.mozdev.org/namespaces/markup/";
          var node = this.parentNode;
          while (node) {
            if (!(node instanceof Components.interfaces.nsIDOMElement)) {
               node = null;
               break;
             }

            if ((node.namespaceURI != MARKUP_NS) || (node.localName != "template")) {
               node = node.parentNode;
               continue;
            }

            break;
          }

          if (!node) {
            throw new Error("Where's the markup template?");
          }

          return node.getElementsByAttribute("localid", this.localid);
        ]]></getter>
      </property>

      <method name="getRow">
        <parameter name="aFragment"/>
        <body><![CDATA[
          const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          const XLINK_NS = "http://www.w3.org/1999/xlink";
          var row = document.createElementNS(XUL_NS, "xul:row");

          for (var i = 0; i < this.linksArray.length; i++)
          {
            var shortcut = this.shortcutBase.cloneNode(true);
            shortcut.setAttribute("value", "(" + this.linksArray[i] + " " + this.rowCount + ")");
            shortcut.setAttributeNS(XLINK_NS, "xlink:href", "#");
            row.appendChild(shortcut);
          }
          var hbox = document.createElementNS(XUL_NS, "xul:hbox");
          hbox.appendChild(aFragment);

          row.appendChild(hbox);
          row.appendChild(this.deleteBaseButton.cloneNode(true));
          return row;
        ]]></body>
      </method>

      <constructor><![CDATA[
        this.deleteBaseButton = document.getAnonymousElementByAttribute(this, "anonid", "deleteBaseButton");
        this.deleteBaseButton.removeAttribute("anonid");
      ]]></constructor>
    </implementation>

    <handlers>
      <handler event="command"><![CDATA[
        var targetNode = event.originalTarget;
        var rowCommand = targetNode.getAttribute("rowcommand");
        if (rowCommand == "shortcut")
        {
          alert("Hello!");
        }

        if (rowCommand == "deleterow")
        {
          var set = this.groupedSet;
          var index = 0;

          var indexNode = targetNode.parentNode; // <xul:row/>
          while (indexNode.previousSibling) {
            indexNode = indexNode.previousSibling;
            index++;
          }

          /* Sometimes a user may have a <markup:children/> element as part
           * of the template, and that element refers to currently active
           * markup in the document.  So, in case the user forgot this,
           * we want to double-check.
           */
          var i;
          var ok = true;
          for (i = 0; i < set.length; i++) {
            ok = set[i].verifyRemoveRow(index);
            if (!ok) {
              break;
            }
          }

          if (!ok) {
            ok = this.userConfirmRemoveRow();
          }

          if (!ok) {
            return;
          }

          // User said, "Yeah, I don't care, kill it."  We warned him...
          for (i = 0; i < set.length; i++) {
            var deadRow = set[i].rows.childNodes[index];
            set[i].rows.removeChild(deadRow);
          }
        }

        if (rowCommand == "addrow")
        {
          var set = this.groupedSet;
          for (var i = 0; i < set.length; i++) {
            set[i].addRow(null);
          }
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="markup-entitybox">
    <resources>
      <stylesheet src="chrome://xulwidgets/skin/xulwidgets.css"/>
      <stylesheet src="chrome://verbosio/content/template.css"/>
    </resources>

    <content>
      <xul:vbox anonid="entityBox">
        <xul:hbox>
          <xul:textbox xulwidgets="true"
                       anonid="master-textbox"
                       xbl:inherits="&commonInherits;,value,type,maxlength,size,accesskey"
                      />
        </xul:hbox>
        <xul:vbox anonid="goalposts">
          <svg xmlns="http://www.w3.org/2000/svg"
               width="200px"
               height="20px"
              >
            <line x1="5"
                  y1="0"
                  x2="5"
                  y2="10"
                  fill="none"
                  stroke="red"
                  anonid="left-goal-post"
                 />
            <line x1="100"
                  y1="0"
                  x2="100"
                  y2="10"
                  fill="none"
                  stroke="red"
                  anonid="right-goal-post"
                 />
            <line x1="5"
                  y1="10"
                  x2="100"
                  y2="10"
                  fill="none"
                  stroke="red"
                  anonid="goal-post-bottom"
                 />
            <line x1="0"
                  y1="10"
                  x2="0"
                  y2="20"
                  fill="none"
                  stroke="red"
                  anonid="goal-pointer"
                 />
          </svg>
          <xul:description anonid="entityName">&amp;foo;</xul:description>
        </xul:vbox>
      </xul:vbox>
    </content>

    <implementation>
      <field name="entitySet">([])</field>
      <field name="selectedEntity">null</field>

      <field name="masterTextBox" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "master-textbox");
      </field>

      <property name="value">
        <getter>return this.masterTextBox.value;</getter>
        <setter>this.masterTextBox.value = val; return val;</setter>
      </property>

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

      <field name="leftGoalPost">
        document.getAnonymousElementByAttribute(this, "anonid", "left-goal-post");
      </field>
      <field name="rightGoalPost">
        document.getAnonymousElementByAttribute(this, "anonid", "right-goal-post");
      </field>
      <field name="goalPostBottom">
        document.getAnonymousElementByAttribute(this, "anonid", "goal-post-bottom");
      </field>
      <field name="goalPointer">
        document.getAnonymousElementByAttribute(this, "anonid", "goal-pointer");
      </field>

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

      <method name="placeGoalPosts">
        <parameter name="evt"/>
        <body><![CDATA[
          if (!this.entityRange) {
            this.rootElement = this.masterTextBox.editor.rootElement;
            this.entityRange = this.rootElement.ownerDocument.createRange();
          }

          var startIndex = evt.currentTarget.selectionStart;
          var endIndex   = evt.currentTarget.selectionEnd;
          var value = this.value;

          for (var i = 0; i < this.entitySet.length; i++) {
            var entityStart = value.indexOf(this.entitySet[i].source);
            if (entityStart == -1) {
              throw new Error("Entity not found!");
              continue;
            }
            var entityEnd   = entityStart + this.entitySet[i].source.length;
            // Any overlap at all, and we want this taken care of.
            if ((startIndex <= entityEnd) &&
                (endIndex >= entityStart)) {
              this.selectedEntity = this.entitySet[i];
              break;
            }
          }

          if (!this.selectedEntity) {
            this.goalposts.style.visibility = "hidden";
            return;
          }
          this.goalposts.style.visibility = "visible";

          // XXX ajvincent I hate magic numbers.  We need something better.
          const WIDTH_MULT = 6;
          var inputBoxObject = this.masterTextBox.inputField.parentNode.boxObject;
          var left = inputBoxObject.screenX - this.masterTextBox.boxObject.screenX + WIDTH_MULT;
          left += this.masterTextBox.value.indexOf(this.selectedEntity.source) * WIDTH_MULT - 2;
          var bottom = inputBoxObject.screenY + inputBoxObject.height + 10;
          var index = this.masterTextBox.value.indexOf(this.selectedEntity.source);
          var width = this.selectedEntity.source.length * WIDTH_MULT;

          this.leftGoalPost.setAttribute("x1", left);
          this.leftGoalPost.setAttribute("x2", left);

          this.rightGoalPost.setAttribute("x1", left + width);
          this.rightGoalPost.setAttribute("x2", left + width);

          this.goalPostBottom.setAttribute("x1", left);
          this.goalPostBottom.setAttribute("x2", left + width);

          this.masterTextBox.storedValue = this.masterTextBox.value;

          this.goalPointer.setAttribute("x1", left + width / 2);
          this.goalPointer.setAttribute("x2", left + width / 2);

          this.entityRange.setStart(this.rootElement.firstChild, index);
          this.entityRange.setEnd(
            this.rootElement.firstChild,
            index + this.selectedEntity.source.length
          );

          this.entityName.value = "&" + this.selectedEntity.name + ";";
        ]]></body>
      </method>

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

          if (this.masterTextBox.selectionStart == this.entityRange.endOffset + 1) {
            this.entityRange.setEnd(
              this.rootElement.firstChild,
              this.masterTextBox.selectionEnd
            );
          }

          this.selectedEntity.acceptNewValue(this.entityRange.toString());

          this.placeGoalPosts(evt);
        ]]></body>
      </method>

      <method name="addEntityInputs">
        <body>
          const XUL_NS = "&XUL_NS;";
        <![CDATA[
          const nsIArray  = Components.interfaces.nsIArray;
          const xeIEntity = Components.interfaces.xeIEntity;
          var attrValue = this.getUserData("value");
          this.setAttribute("value", attrValue);

          var userData = this.getUserData("entityMatches");
          // Yes, we want to throw a NO_INTERFACE if it's not a nsIArray.
          if (!userData || !(userData.QueryInterface(nsIArray))) {
            return;
          }

          var len = userData.length;
          for (var i = 0; i < len; i++) {
            // This will throw NO_INTERFACE if we don't have a xeIEntity object.
            this.entitySet[i] = userData.queryElementAt(i, xeIEntity);
          }

          this.entitySet.sort(function sortEntitySet(a, b) {
            var compA = attrValue.indexOf(a.source);
            var compB = attrValue.indexOf(b.source);

            if (compA < compB) {
              return +1;
            }
            if (compA > compB) {
              return -1;
            }

            compA = a.source.length;
            compB = b.source.length;

            if (compA < compB) {
              return +1;
            }
            if (compA > compB) {
              return -1;
            }

            return 0;
          });
        ]]></body>
      </method>

      <method name="hideGoalPosts">
        <parameter name="evt"/>
        <body><![CDATA[
          this.goalposts.style.visibility = "hidden";
        ]]></body>
      </method>

      <method name="_handleEvent">
        <parameter name="evt"/>
        <body><![CDATA[
          var owner = document.getBindingParent(evt.currentTarget);
          switch (evt.type) {
            case "input":
              owner.editGoalPosts(evt);
              break;

            case "select":
            case "focus":
              owner.placeGoalPosts(evt);
              break;

            case "blur":
              owner.hideGoalPosts(evt);
              break;
          }
        ]]></body>
      </method>
      <constructor><![CDATA[
        this.masterTextBox.addEventListener("input",  this._handleEvent, true);
        this.masterTextBox.addEventListener("select", this._handleEvent, true);
        this.masterTextBox.addEventListener("focus",  this._handleEvent, true);
        this.masterTextBox.addEventListener("blur",   this._handleEvent, true);
        this.hideGoalPosts(null);
      ]]></constructor>
    </implementation>
  </binding>

  <binding id="radiogroup"
           extends="chrome://global/content/bindings/radio.xml#radiogroup"
          >
    <implementation>
      <method name="_getRadioChildren">
        <body><![CDATA[
          if (this._radioChildren)
            return this._radioChildren;

          var radioChildren = [];
          var doc = this.ownerDocument;

          if (this.hasChildNodes()) {
            // Don't store the collected child nodes immediately,
            // collecting the child nodes could trigger constructors
            // which would blow away our list.

            const nsIDOMNodeFilter = Components.interfaces.nsIDOMNodeFilter;
            var iterator = doc.createTreeWalker(this,
                                                nsIDOMNodeFilter.SHOW_ELEMENT,
                                                this._filterRadioGroup,
                                                true);
            while (iterator.nextNode())
              radioChildren.push(iterator.currentNode);
            return this._radioChildren = radioChildren;
          }

          // We don't have child nodes.
          radioChildren = [];
          var elems = doc.getElementsByAttribute("group", this.id);
          for (var i = 0; i < elems.length; i++) {
            if ((elems[i].namespaceURI == XUL_NS) &&
                (elems[i].localName == "radio")) {
              radioChildren.push(elems[i]);
            }
          }
          this._radioChildren = radioChildren;
          return radioChildren;
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="radio"
           extends="chrome://global/content/bindings/radio.xml#radio"
          >
    <implementation>
      <property name="control" readonly="true">
        <getter><![CDATA[
          const XUL_NS = "http://www.mozilla.org/keymaster/"
                       + "gatekeeper/there.is.only.xul";
          var parent = this.parentNode;
          while (parent) {
            if ((parent.namespaceURI == XUL_NS) &&
                (parent.localName == "radiogroup")) {
              return parent;
            }
            parent = parent.parentNode;
          }

          var group = this.getAttribute("group");
          parent = this.ownerDocument.getElementById(group);
          if (!parent ||
              (parent.namespaceURI != XUL_NS) ||
              (parent.localName != "radiogroup")) {
            parent = null;
          }
          return parent;
        ]]></getter>
      </property>
    </implementation>
  </binding>

</bindings>
