<?xml version="1.0"?>
<!-- ***** BEGIN LICENSE BLOCK *****
  - Version: MPL 1.1/GPL 2.0/LGPL 2.1
  -
  - The contents of this file are subject to the Mozilla Public License Version
  - 1.1 (the "License"); you may not use this file except in compliance with
  - the License. You may obtain a copy of the License at
  - http://www.mozilla.org/MPL/
  -
  - Software distributed under the License is distributed on an "AS IS" basis,
  - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  - for the specific language governing rights and limitations under the
  - License.
  -
  - The Original Code is Verbosio XML editor.
  -
  - The Initial Developer of the Original Code is
  - Alexander J. Vincent <ajvincent@gmail.com>.
  - Portions created by the Initial Developer are Copyright (C) 2007
  - the Initial Developer. All Rights Reserved.
  -
  - Contributor(s):
  -
  - Alternatively, the contents of this file may be used under the terms of
  - either the GNU General Public License Version 2 or later (the "GPL"), or
  - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  - in which case the provisions of the GPL or the LGPL are applicable instead
  - of those above. If you wish to allow use of your version of this file only
  - under the terms of either the GPL or the LGPL, and not to allow others to
  - use your version of this file under the terms of the MPL, indicate your
  - decision by deleting the provisions above and replace them with the notice
  - and other provisions required by the LGPL or the GPL. If you do not delete
  - the provisions above, a recipient may use your version of this file under
  - the terms of any one of the MPL, the GPL or the LGPL.
  -
  - ***** END LICENSE BLOCK ***** -->
<!DOCTYPE bindings [
  <!ENTITY % verbosioLocale SYSTEM "chrome://verbosio/locale/verbosio.dtd">
  %verbosioLocale;
  <!ENTITY % commonDTD SYSTEM "chrome://verbosio/content/verbosio.dtd">
  %commonDTD;
  <!ENTITY documentPath
    "chrome://verbosio/content/bindings/valuepanel.xml"
  >
]>
<bindings
  xmlns="http://www.mozilla.org/xbl"
  xmlns:xbl="http://www.mozilla.org/xbl"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <binding id="xul:valuepanel-basic">
    <implementation implements="xeIValuePanel">
      <method name="getPriorityForNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          // We don't support anything.
          return 0;
        ]]></body>
      </method>

      <method name="showNode">
        <parameter name="aNode"/>
        <body><![CDATA[
           // Do nothing
           return;
        ]]></body>
      </method>

      <method name="selectText">
        <parameter name="startIndex"/>
        <parameter name="endIndex"/>
        <body><![CDATA[
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="xul:valuepanel-element"
           extends="&documentPath;#xul:valuepanel-basic">
    <!-- XXX ajvincent Localize! -->
    <content>
      <xul:hbox>
        <xul:vbox style="overflow: -moz-scrollbars-vertical; height: 200px;">
          <xul:tree style="width: 300px; height: 100px;"
                    seltype="single"
                    context="_child"
                   >
            <xul:menupopup>
              <xul:menuitem label="Insert..."
                            anoncommand="cmd_insert_attr"
                           />
              <xul:menuitem label="Delete"
                            anoncommand="cmd_delete_attr"
                           />
            </xul:menupopup>
            <xul:treecols anonid="attr-cols">
              <xul:treecol label="Namespace URI"
                           flex="2"
                           propertyname="namespaceURI"
                          />
              <xul:splitter class="tree-splitter"/>
              <xul:treecol label="Prefix"
                           flex="1"
                           propertyname="prefix"
                          />
              <xul:splitter class="tree-splitter"/>
              <xul:treecol label="Local Name"
                           flex="1"
                           propertyname="localName"
                          />
              <xul:splitter class="tree-splitter"/>
              <xul:treecol label="Value"
                           flex="4"
                           propertyname="nodeValue"
                          />
            </xul:treecols>
            <xul:treechildren anonid="attr-kids"/>
          </xul:tree>

          <xul:splitter/>

          <xul:tree style="width: 300px; height: 100px;"
                    seltype="single"
                    context="_child"
                   >
            <xul:menupopup>
              <xul:menuitem label="Insert..."/>
              <xul:menuitem label="Delete"
                           />
            </xul:menupopup>
            <xul:treecols anonid="ns-cols">
              <xul:treecol flex="1"
                           label="Namespace Prefix"
                          />
              <xul:splitter class="tree-splitter"/>
              <xul:treecol flex="4"
                           label="Namespace URI"
                          />
              <xul:splitter class="tree-splitter"/>
              <xul:treecol flex="1"
                           label="On this element"
                          />
            </xul:treecols>
            <xul:treechildren anonid="ns-kids"/>
          </xul:tree>
        </xul:vbox>

        <xul:vbox>
          <xul:menulistset type="xml-namespaces"
                           editable="true"
                           disabled="true"
                           anonid="namespace-map-list"
                          />
          <xul:valuepanel type="cdata"
                          anonid="attrEditBox"
                         />
        </xul:vbox>
      </xul:hbox>

      <xul:box hidden="true">
        <xul:treeitem anonid="attr-item">
          <xul:treerow>
            <xul:treecell/>
            <xul:treecell/>
            <xul:treecell/>
            <xul:treecell/>
          </xul:treerow>
        </xul:treeitem>

        <xul:treeitem anonid="ns-item">
          <xul:treerow>
            <xul:treecell/>
            <xul:treecell/>
            <xul:treecell/>
          </xul:treerow>
        </xul:treeitem>
      </xul:box>
    </content>

    <implementation implements="xeIValuePanel">
      <field name="attrTreeKids" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "attr-kids");
      </field>

      <field name="namespaceTreeKids" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "ns-kids");
      </field>

      <field name="attrCols" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "attr-cols");
      </field>

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

      <field name="baseAttrItem" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "attr-item")
                .cloneNode(true);
      </field>

      <field name="baseNamespaceItem" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "ns-item")
                .cloneNode(true);
      </field>

      <field name="namespaceMapList" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "namespace-map-list")
      </field>

      <field name="nullMgr" readonly="true">
        Components.classes["@mozilla.org/transactionmanager;1"]
                  .createInstance(Components.interfaces.nsITransactionManager);
      </field>

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

      <method name="reset">
        <body><![CDATA[
          while (this.attrTreeKids.firstChild) {
            this.attrTreeKids.removeChild(this.attrTreeKids.firstChild);
          }

          while (this.namespaceTreeKids.firstChild) {
            this.namespaceTreeKids.removeChild(this.namespaceTreeKids.firstChild);
          }

          this.attrEditBox.disabled = true;

          if (this.currentElementNode) {
            var node = this.currentElementNode;
            node.removeEventListener("DOMAttrModified",
                                     this.updateAttrItemEvent,
                                     true);
          }
        ]]></body>
      </method>

      <method name="addAttributeItem">
        <parameter name="aAttrNode"/>
        <body><![CDATA[
          var attrItem = this.baseAttrItem.cloneNode(true);
          var col = this.attrCols.firstChild;
          var cell = attrItem.firstChild.firstChild;
          while (col) {
            var propertyName = col.getAttribute("propertyname");
            cell.setAttribute("label", aAttrNode[propertyName]);
            col = col.nextSibling;
            if (col) {
              // skip over splitters
              col = col.nextSibling;
            }
            cell = cell.nextSibling;
          }
          assert(!cell, "Tree columns and tree cells didn't line up?");

          attrItem.setUserData("originalNode", aAttrNode, null);
          dump("addAttributeItem value: " + aAttrNode.nodeValue + "\n");
          var data = attrItem.getUserData("originalNode");
          dump("data value: " + data.nodeValue + "\n");
          this.attrTreeKids.appendChild(attrItem);
        ]]></body>
      </method>

      <method name="addNamespaceItem">
        <parameter name="aPrefix"/>
        <parameter name="aURI"/>
        <parameter name="aOnElement"/>
        <body><![CDATA[
          var attrItem = this.baseNamespaceItem.cloneNode(true);
          var attrRow = attrItem.firstChild;
          attrRow.childNodes[0].setAttribute("label", aPrefix);
          attrRow.childNodes[1].setAttribute("label", aURI);
          attrRow.childNodes[2].setAttribute("label", aOnElement);
          this.namespaceTreeKids.appendChild(attrItem);

          if (aOnElement) {
            var attrs = this.currentElementNode.attributes;
            var found = false;
            for (var i = 0; i < attrs.length; i++) {
              if ((attrs[i].namespaceURI == XMLNS_NS) &&
                  (attrs[i].localName == aPrefix)) {
                assert(attrs[i].nodeValue == aURI, "Check URI!");
                found = true;
                attrItem.setUserData("originalNode", attrs[i], null);
                break;
              }
            }

            assert(found, "aOnElement is true, but attribute node wasn't on element!");
          }
        ]]></body>
      </method>

      <method name="getPriorityForNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          const nsIDOMNode = Components.interfaces.nsIDOMNode;
          if (aNode.nodeType == nsIDOMNode.ELEMENT_NODE) {
            return 1;
          }

          // We don't support other nodes.
          return 0;
        ]]></body>
      </method>

      <method name="showNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          this.reset();

          this.currentElementNode = aNode;

          var attr = null;
          var attributesSet = aNode.attributes;
          for (var i = 0; i < attributesSet.length; i++) {
            attr = attributesSet[i];
            if (attr.namespaceURI != XMLNS_NS) {
              this.addAttributeItem(attr);
            }
          }

          var namespaceMap = verbosio.getNamespacesMap(aNode);
          for (var prefix in namespaceMap) {
            var uri = namespaceMap[prefix];
            this.addNamespaceItem(prefix, uri,
                                  aNode.hasAttributeNS(XMLNS_NS, prefix));
          }

          this.namespaceMapList.setAttribute("disabled", "true");

          aNode.addEventListener("DOMAttrModified",
                                 this.updateAttrItemEvent,
                                 true);
        ]]></body>
      </method>

      <field name="updateAttrItemEvent" readonly="true">({
        attrTreeKids: this.attrTreeKids,
        namespaceTreeKids: this.namespaceTreeKids,
        valuePanel: this,
        handleEvent: function handleEvent(aEvent) {
          var nsIDOMMutationEvent = Components.interfaces.nsIDOMMutationEvent;
          var attrNode = aEvent.relatedNode;
          switch (aEvent.attrChange) {
            case nsIDOMMutationEvent.MODIFICATION:
              var item = this.attrTreeKids.firstChild;
              while (item) {
                if (item.getUserData("originalNode") == attrNode) {
                  var cell = item.firstChild.lastChild;
                  cell.setAttribute("label", aEvent.newValue);
                  return;
                }
                item = item.nextSibling;
              }

              // Okay, that didn't work.  Is this a namespace attr?
              assert(attrNode.namespaceURI == XMLNS_NS, "Which attribute is this?");

              item = this.namespaceTreeKids.firstChild;
              while (item) {
                var cell = item.firstChild.firstChild;
                if (cell.getAttribute("label") == attrNode.localName) {
                  cell.nextSibling.setAttribute("label", aEvent.newValue);
                  return;
                }
                item = item.nextSibling;
              }

              assert(false, "Didn't find namespace attribute!");
              break;

            case nsIDOMMutationEvent.ADDITION:
              if (attrNode.namespaceURI == XMLNS_NS) {
                // Check to see if the localName is in the namespace map.
                item = this.namespaceTreeKids.firstChild;
                while (item) {
                  var cell = item.firstChild.firstChild;
                  if (cell.getAttribute("label") == attrNode.localName) {
                    cell.nextSibling.setAttribute("label", aEvent.newValue);
                    item.firstChild.lastChild.setAttribute("label", "true");
                    return;
                  }
                  item = item.nextSibling;
                }

                // It's not in the namespace map.  Add it.
                this.valuePanel.addNamespaceItem(attrNode.localName, attrNode.nodeValue, "true");
                return;
              }

              // It's an ordinary attribute.
              this.valuePanel.addAttributeItem(attrNode);
              break;

            case nsIDOMMutationEvent.REMOVAL:
              if (attrNode.namespaceURI == XMLNS_NS) {
                // Check to see if the localName is in the namespace map.
                item = this.namespaceTreeKids.firstChild;
                while (item) {
                  var cell = item.firstChild.firstChild;
                  if (cell.getAttribute("label") == attrNode.localName) {
                    this.namespaceTreeKids.removeChild(item);
                    return;
                  }
                  item = item.nextSibling;
                }

                assert(false, "Didn't find XMLNS attribute to remove?");
                return;
              }

              var item = this.attrTreeKids.firstChild;
              dump("attrNode: " + attrNode + " (" + attrNode.nodeName + "='" + attrNode.nodeValue + "')\n");
              while (item) {
                var data = item.getUserData("originalNode");
/* XXX ajvincent For some reason, checking attrNode against data directly is
 * not working.  But we get the same properties, the same parent node (null)...
 * the two nodes are equal.  So we remove the node that equals the data node.
 */
                dump("data: " + data + " (" + data.nodeName + "='" + data.nodeValue + "')\n");
                if (data.isSameNode(attrNode)) {
                  this.attrTreeKids.removeChild(item);
                  return;
                }
                item = item.nextSibling;
              }

              assert(false, "Didn't find attribute to remove?");
              break;

            default:
              assert(false, "Invalid attrChange property on event!");
          } // end switch (aEvent.attrChange)
        } // end handleEvent(aEvent)
      })</field>

      <!-- Note this function will be called by an event handler. -->
      <method name="handleAttrSwitchEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (this.valuePanel.handlingCount > 0) {
            return;
          }

          this.valuePanel.handlingCount++;

          const nsITransactionManager = Components.interfaces.nsITransactionManager;
          this.namespaceSelection.clearSelection();

          var index = aEvent.target.currentIndex;
          var item = (index >= 0) ?
                     aEvent.currentTarget.view.getItemAtIndex(index) :
                     null;
          if (!item) {
            this.valuePanel.handlingCount--;
            return;
          }

          var originalNode = item.getUserData("originalNode");
          assert((originalNode instanceof Components.interfaces.nsIDOMAttr),
                 "Failed to initialize with an attribute node?");

          this.attrEditBox.showNode(originalNode);
          var map = this.valuePanel.namespaceMapList;
          map._contentBox.firstChild.value = originalNode.namespaceURI;

          this.valuePanel.handlingCount--;
        ]]></body>
      </method>

      <!-- Note this function will be called by an event handler. -->
      <method name="handleNamespaceSwitchEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (this.valuePanel.handlingCount > 0) {
            return;
          }

          this.valuePanel.handlingCount++;

          const nsITransactionManager = Components.interfaces.nsITransactionManager;
          this.attrSelection.clearSelection();

          var index = aEvent.target.currentIndex;
          var item = (index >= 0) ?
                     aEvent.currentTarget.view.getItemAtIndex(index) :
                     null;
          if (!item) {
            this.valuePanel.handlingCount--;
            return;
          }

          var onElement = item.firstChild.lastChild.getAttribute("label") == "true";

          var originalNode = item.getUserData("originalNode");
          assert(Boolean(originalNode) == onElement, "onElement but no original node?");
          if (originalNode) {
            assert(originalNode instanceof Components.interfaces.nsIDOMAttr,
                   "Failed to initialize with an attribute node?");
          }

          if (originalNode) {
            this.attrEditBox.showNode(originalNode);
            var map = this.valuePanel.namespaceMapList;
            map._contentBox.firstChild.value = originalNode.namespaceURI;
          } else {
            this.attrEditBox.setAttribute("disabled", "true");
          }

          this.valuePanel.handlingCount--;
        ]]></body>
      </method>

      <field name="handlingCount">0</field>

      <field name="commandController"
             readonly="true"
            ><![CDATA[
        ({
          valuePanel: this,

          supportsCommand: function supportsCommand(aCommandName) {
            if ((aCommandName == "cmd_delete_attr") ||
                (aCommandName == "cmd_insert_attr")) {
              return true;
            }
            return false;
          },

          isCommandEnabled: function isCommandEnabled(aCommandName) {
            if (aCommandName == "cmd_delete_attr") {
              var item = this.valuePanel.attrTreeKids.parentNode.selectedItem;
              if (!item) {
                item = this.valuePanel.namespaceTreeKids.parentNode.selectedItem;
              }
              if (!item) {
                return false;
              }

              var originalNode = item.getUserData("originalNode");
              if (!originalNode) {
                return false;
              }

              return true;
            }

            if (aCommandName == "cmd_insert_attr") {
              return true;
            }

            return false;
          },

          doCommand: function doCommand(aCommandName) {
            var index;
            var item;
            var tree;
            switch (aCommandName) {
              case "cmd_delete_attr":
                var tree = this.valuePanel.attrTreeKids.parentNode;
                index = tree.currentIndex;
                item = (index > -1) ?
                        tree.view.getItemAtIndex(index) :
                        null;
                if (!item) {
                  tree = this.valuePanel.namespaceTreeKids.parentNode;
                  index = tree.currentIndex;
                  item = (index > -1) ?
                          tree.view.getItemAtIndex(index) :
                          null;
                }
                if (!item) {
                  throw Components.results.NS_ERROR_FAILURE;
                }

                var originalNode = item.getUserData("originalNode");
                if (!originalNode) {
                  throw Components.results.NS_ERROR_FAILURE;
                }

                var txn = new DeleteAttrTxn(originalNode);
                verbosio.advanceTxn(txn, documentViewer.currentWrapper);
                verbosio.advanceTxn(null, documentViewer.currentWrapper);

                return;

              case "cmd_insert_attr":
                var pre = document.createElementNS(XUL_NS, "precondition");
                pre.setAttribute("type", "add-attr");
                preconditionUIBox.precondition = pre;
                pre.initialize(this.valuePanel.currentElementNode,
                               documentViewer.currentWrapper);
                return;

              default:
                dump("'" + aCommandName + "'\n");
            }
            throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
          },

          onEvent: function onEvent(aEvent) {
            throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
          },

          updateCommands: function updateCommands() {
            var commandList = ["cmd_delete_attr",
                               "cmd_insert_attr"];
            for (var i = 0; i < commandList.length; i++) {
              var commandName = commandList[i];
              var isEnabled = this.isCommandEnabled(commandName);
              var command = document.getElementById(commandName);
              if (isEnabled) {
                command.removeAttribute("disabled");
              } else {
                command.setAttribute("disabled", true);
              }
            }
          }
        })
      ]]></field>

      <constructor>
        this.baseAttrItem.removeAttribute("anonid");
        this.baseNamespaceItem.removeAttribute("anonid");

        const nsITransactionManager = Components.interfaces.nsITransactionManager;

        var attrItemHandler = {
          namespaceSelection: this.namespaceTreeKids.parentNode.view.selection,
          currentEditNode: null,
          currentEditListener: null,
          attrEditBox: this.attrEditBox,
          valuePanel: this,
          get internalTxMgr() {
            return this.textEditBox.editor.transactionManager;
          },
          set internalTxMgr(val) {
            assert(val instanceof nsITransactionManager, "no txmgr?");
            this.textEditBox.editor.transactionManager = val;
          },
          nullMgr: Components.classes["@mozilla.org/transactionmanager;1"]
                             .createInstance(nsITransactionManager),

          handleEvent: this.handleAttrSwitchEvent
        } // end attrItemHandler
        this.attrTreeKids.parentNode.addEventListener("select", attrItemHandler, true);

        var nsItemHandler = {
          attrSelection: this.attrTreeKids.parentNode.view.selection,
          currentEditNode: null,
          currentEditListener: null,
          attrEditBox: this.attrEditBox,
          valuePanel: this,
          get internalTxMgr() {
            return this.textEditBox.editor.transactionManager;
          },
          set internalTxMgr(val) {
            assert(val instanceof nsITransactionManager, "no txmgr?");
            this.textEditBox.editor.transactionManager = val;
          },
          nullMgr: Components.classes["@mozilla.org/transactionmanager;1"]
                             .createInstance(nsITransactionManager),

          handleEvent: this.handleNamespaceSwitchEvent
        } // end attrItemHandler
        this.namespaceTreeKids.parentNode.addEventListener("select", nsItemHandler, true);

        this.controllers.appendController(this.commandController);
      </constructor>
    </implementation>

    <handlers>
      <handler event="command" phase="capturing">
        var target = event.originalTarget.parentNode.parentNode;
        var commandName = event.originalTarget.getAttribute("anoncommand");
        if (target == this.attrTreeKids.parentNode) {
          this.commandController.doCommand(commandName);
        }
      </handler>
      <handler event="keypress" phase="capturing" keycode="VK_DELETE">
      </handler>
    </handlers>
  </binding>

  <binding id="xul:valuepanel-cdata"
           extends="&documentPath;#xul:valuepanel-basic">
    <content>
      <xul:hbox>
        <xul:textbox multiline="true"
                     xulwidgets="true"
                     rows="10"
                     cols="60"
                     contents="code"
                     anonid="textEditBox"
                    />
      </xul:hbox>
    </content>

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

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

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

      <property name="internalTxMgr">
        <getter>return this.textEditBox.editor.transactionManager;</getter>
        <setter>
          if (val instanceof Components.interfaces.nsITransactionManager) {
            this.textEditBox.editor.transactionManager = val;
            return val;
          }

          return null;
        </setter>
      </property>

      <method name="getPriorityForNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          const nsIDOMNode = Components.interfaces.nsIDOMNode;
          if ((aNode.nodeType == nsIDOMNode.CDATA_SECTION_NODE) ||
              (aNode.nodeType == nsIDOMNode.TEXT_NODE)) {
            // We provide basic support for text & character data nodes.
            return 1;
          }

          // We don't support other nodes.
          return 0;
        ]]></body>
      </method>

      <field name="nullMgr">
        Components.classes["@mozilla.org/transactionmanager;1"]
                  .createInstance(Components.interfaces.nsITransactionManager);
      </field>

      <method name="showNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          if (this.currentEditNode == aNode) {
            this.textEditBox.removeAttribute("disabled");
            return; // Why do extra work?
          }

          if (this.currentEditListener) {
            this.internalTxMgr.RemoveListener(this.currentEditListener);
            this.currentEditNode = null;
            this.currentEditListener = null;
          }

          this.textEditBox.setAttribute("disabled", "true");

          var txn = documentViewer.currentWrapper.pendingAction;
          if (txn && typeof txn.wrappedJSObject == "object") {
            txn = txn.wrappedJSObject;
          }
          if ((txn instanceof UpdateTextNodeTxn) &&
              (txn.node == aNode)) {
            this.internalTxMgr = this.nullMgr;
            this.textEditBox.value = txn.newValue;
            this.internalTxMgr = txn.textTxMgr;
            if (txn.textbox) {
              // Avoid a conflict.
              txn.textbox.setAttribute("disabled", true);
              txn.textbox.editor.transactionManager =
                Components.classes["@mozilla.org/transactionmanager;1"]
                          .createInstance(Components.interfaces.nsITransactionManager);
                txn.textbox = this.textEditBox;
            }
            this.nullMgr.clear();
          } else {
            this.internalTxMgr.clear();
            this.textEditBox.value = aNode.nodeValue;
            this.internalTxMgr.clear();
          }
          var listener = new TextboxTxnListener(this.textEditBox,
                                                aNode,
                                                documentViewer.currentWrapper);
          this.currentEditListener = listener;
          this.currentEditNode = aNode;
          this.internalTxMgr.AddListener(listener);

          this.textEditBox.removeAttribute("disabled");
        ]]></body>
      </method>

      <method name="selectText">
        <parameter name="startIndex"/>
        <parameter name="endIndex"/>
        <body><![CDATA[
          this.textEditBox.setSelectionRange(startIndex, endIndex);
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="xul:valuepanel-pi"
           extends="&documentPath;#xul:valuepanel-basic">
    <content>
      <xul:vbox>
        <xul:hbox>
          <xul:description>&lt;?</xul:description>
          <xul:description anonid="target"/>
        </xul:hbox>
        <xul:valuepanel type="cdata"
                        anonid="dataEditBox"
                       />
        <xul:hbox>
          <xul:description>?&gt;</xul:description>
        </xul:hbox>
      </xul:vbox>
    </content>

    <implementation implements="xeIValuePanel">
      <field name="targetField">
        document.getAnonymousElementByAttribute(this, "anonid", "target");
      </field>

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

      <method name="getPriorityForNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          const nsIDOMNode = Components.interfaces.nsIDOMNode;
          if (aNode.nodeType == nsIDOMNode.PROCESSING_INSTRUCTION_NODE) {
            // We provide basic support for processing instruction nodes.
            return 1;
          }

          // We don't support other nodes.
          return 0;
        ]]></body>
      </method>

      <method name="showNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          this.targetField.value = aNode.target;
          this.dataField.showNode(aNode);
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="xul:valuepanel-comment"
           extends="&documentPath;#xul:valuepanel-basic">
    <content>
      <xul:vbox>
        <xul:description>&lt;!--</xul:description>
        <xul:valuepanel type="cdata"
                        anonid="commentEditBox"
                       />
        <xul:description>--&gt;</xul:description>
      </xul:vbox>
    </content>

    <implementation implements="xeIValuePanel">
      <field name="commentField">
        document.getAnonymousElementByAttribute(this, "anonid", "commentEditBox");
      </field>

      <method name="getPriorityForNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          const nsIDOMNode = Components.interfaces.nsIDOMNode;
          if (aNode.nodeType == nsIDOMNode.COMMENT_NODE) {
            // We provide basic support for comment nodes.
            return 1;
          }

          // We don't support other nodes.
          return 0;
        ]]></body>
      </method>

      <method name="showNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          this.commentField.showNode(aNode);
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="xul:valuepanel-document"
           extends="&documentPath;#xul:valuepanel-basic">
    <content>
      (Document panel.)
    </content>

    <implementation implements="xeIValuePanel">
      <method name="getPriorityForNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          const nsIDOMNode = Components.interfaces.nsIDOMNode;
          if (aNode.nodeType == nsIDOMNode.DOCUMENT_NODE) {
            // We provide basic support for comment nodes.
            return 1;
          }

          // We don't support other nodes.
          return 0;
        ]]></body>
      </method>

      <method name="showNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          // Do nothing.
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="xul:valuepanel-doctype"
           extends="&documentPath;#xul:valuepanel-basic">
    <content>
      <xul:vbox>
        <xul:hbox>
          <xul:description>DTD location:</xul:description>
          <xul:textbox anonid="systemId"
                       readonly="true"
                       size="20"
                       contents="code"
                      />
        </xul:hbox>
        <xul:hbox>
          <xul:description>DTD summary:</xul:description>
          <xul:textbox anonid="publicId"
                       readonly="true"
                       size="20"
                       contents="code"
                      />
        </xul:hbox>
        <xul:description>Internal subset:</xul:description>
        <xul:textbox anonid="internalSubset"
                     readonly="true"
                     multiline="true"
                     rows="10"
                     cols="30"
                     contents="code"
                    />
      </xul:vbox>
    </content>

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

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

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

      <method name="getPriorityForNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          const nsIDOMNode = Components.interfaces.nsIDOMNode;
          if (aNode.nodeType == nsIDOMNode.DOCUMENT_TYPE_NODE) {
            // We provide basic support for comment nodes.
            return 1;
          }

          // We don't support other nodes.
          return 0;
        ]]></body>
      </method>

      <method name="showNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          this.publicId.value = aNode.publicId || "";
          this.systemId.value = aNode.systemId || "";
          this.internalSubset.value = aNode.internalSubset || "";
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="xul:valuepanel-source">
    <!-- XXX ajvincent Localize! -->
    <content>
      <xul:grid>
        <xul:rows>
          <xul:row/>
          <xul:row/>
        </xul:rows>

        <xul:columns>
          <xul:column>
            <xul:description>Type in your XML markup here.</xul:description>
            <xul:hbox>
              <xul:textbox anonid="sourceMarkup"
                           contents="code"
                           xulwidgets="true"
                           multiline="true"
                           rows="10"
                           cols="80"
                           invalid="true"
                          />
            </xul:hbox>
          </xul:column>

          <xul:column>
            <xul:description>Available namespaces:</xul:description>
            <xul:hbox>
              <xul:tree style="width: 300px; height: 100px;"
                        seltype="single"
                       >
                <xul:treecols anonid="ns-cols">
                  <xul:treecol flex="1"
                               label="Namespace Prefix"
                              />
                  <xul:splitter class="tree-splitter"/>
                  <xul:treecol flex="4"
                               label="Namespace URI"
                              />
                  <xul:splitter class="tree-splitter"/>
                  <xul:treecol flex="1"
                               label="On this element"
                              />
                </xul:treecols>
                <xul:treechildren anonid="ns-kids"/>
              </xul:tree>
            </xul:hbox>
          </xul:column>
        </xul:columns>
      </xul:grid>

      <xul:box hidden="true">
        <xul:treeitem anonid="ns-item">
          <xul:treerow>
            <xul:treecell/>
            <xul:treecell/>
            <xul:treecell/>
          </xul:treerow>
        </xul:treeitem>
      </xul:box>
    </content>

    <implementation implements="xeIValuePanel">
      <method name="getPriorityForNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          // We support everything, but not strongly.
          return 1;
        ]]></body>
      </method>

      <method name="showNode">
        <parameter name="aNode"/>
        <body><![CDATA[
          // Do nothing
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
          return;
        ]]></body>
      </method>

      <method name="selectText">
        <parameter name="startIndex"/>
        <parameter name="endIndex"/>
        <body><![CDATA[
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        ]]></body>
      </method>

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

      <field name="namespaceTreeKids" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "ns-kids");
      </field>

      <field name="baseNamespaceItem" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "ns-item")
                .cloneNode(true);
      </field>

      <field name="saxContentHandler" readonly="true"><![CDATA[
        ({
           elementCount: 0,
           elementFound: false,
           nonWhitespaceFound: false,

           startDocument: function startDocument() {
             this.elementCount = 0;
             this.elementFound = false;
             this.nonWhitespaceFound = false;
           },
           endDocument: function endDocument() {},

           startElement:
           function startElement(aURI, aLocalName, aQName, aAttrs) {
             if (this.elementCount > 0) {
               this.elementFound = true;
             }
             this.elementCount++;
           },
           endElement: function endElement(aURI, aLocalName, aQName) {},

           characters: function characters(aValue) {
             if (/\S/.test(aValue)) {
               this.nonWhitespaceFound = true;
             }
           },

           processingInstruction: function pi(aTarget, aData) {},
           ignorableWhitespace: function white(aData) {},
           startPrefixMapping: function map(aPrefix, aURI) {},
           endPrefixMapping: function endMap(aPrefix) {}
         })
      ]]></field>

      <field name="saxErrorHandler" readonly="true"><![CDATA[
        ({
           locator: null,
           error: null,

           reset: function reset() {
             this.locator = null;
             this.error   = null;
           },

           fatalError: function fatalError(locator, error)
           {
             this.locator = locator;
             this.error   = error;
           },

           error: function error(locator, error)
           {
             // do nothing
           },

           ignorableWarning: function ignorableWarning(locator, error)
           {
             // do nothing
           }
         })
      ]]></field>

      <field name="saxReader" readonly="true"><![CDATA[
        Components.classes["@mozilla.org/saxparser/xmlreader;1"]
                  .createInstance(Components.interfaces.nsISAXXMLReader);
      ]]></field>

      <field name="elemOpenTag">""</field>

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

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

      <method name="addNamespaceItem">
        <parameter name="aPrefix"/>
        <parameter name="aURI"/>
        <parameter name="aOnElement"/>
        <body><![CDATA[
          var attrItem = this.baseNamespaceItem.cloneNode(true);
          var attrRow = attrItem.firstChild;
          attrRow.childNodes[0].setAttribute("label", aPrefix);
          attrRow.childNodes[1].setAttribute("label", aURI);
          attrRow.childNodes[2].setAttribute("label", aOnElement);
          this.namespaceTreeKids.appendChild(attrItem);

          if (aOnElement) {
            var attrs = this.currentNode.attributes;
            var found = false;
            for (var i = 0; i < attrs.length; i++) {
              if ((attrs[i].namespaceURI == XMLNS_NS) &&
                  (attrs[i].localName == aPrefix)) {
                assert(attrs[i].nodeValue == aURI, "Check URI!");
                found = true;
                break;
              }
            }

            assert(found, "aOnElement is true, but attribute node wasn't on element!");
          }
        ]]></body>
      </method>

      <property name="sourceFragment" readonly="true">
        <getter><![CDATA[
          return this.elemOpenTag + this.sourceMarkup.value + "</foo>";
        ]]></getter>
      </property>

      <method name="initializeNamespaceMap">
        <parameter name="aNode"/>
        <body><![CDATA[
          assert(!this.mInitialized, "Already initialized?");

          var namespaceMap = verbosio.getNamespacesMap(aNode);
          this.currentNode = aNode;
          for (var prefix in namespaceMap) {
            var uri = namespaceMap[prefix];
            this.addNamespaceItem(prefix, uri,
                                  aNode.hasAttributeNS(XMLNS_NS, prefix));
          }

          this.elemOpenTag = "<foo\n";
          for (var prop in namespaceMap) {
            if (prop != "xmlns") {
              this.elemOpenTag += "xmlns:" + prop + "='" + namespaceMap[prop] + "'\n";
            }
          }
          this.elemOpenTag += ">";

          const nsIDOMDocument = Components.interfaces.nsIDOMDocument;
          if (aNode.parentNode instanceof nsIDOMDocument) {
            // Watch out for elements, non-whitespace characters!
            this.saxReader.contentHandler = this.saxContentHandler;
          }
          this.saxReader.errorHandler = this.saxErrorHandler;

          this.sourceMarkup.validateNow = function no_op() {};
          this.mInitialized = true;
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="input" phase="capturing"><![CDATA[
        assert(this.mInitialized, "Not initialized?");

        // Parse the XML!
        this.saxErrorHandler.reset();
        this.saxReader.parseFromString(this.sourceFragment, "application/xml");
        dump("Error found: " + this.saxErrorHandler.error + "\n");
        dump("Element found: " + this.saxContentHandler.elementFound + "\n");
        dump("Text found: " + this.saxContentHandler.nonWhitespaceFound + "\n");
        this.sourceMarkup.invalid = (this.saxErrorHandler.error ||
                                     this.saxContentHandler.elementFound ||
                                     this.saxContentHandler.nonWhitespaceFound);
      ]]></handler>
    </handlers>
  </binding>

  <binding id="add-attribute">
    <!-- XXX ajvincent Localize! -->
    <content>
      <vbox anonid="anonContentBox"
            xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
        <hbox>
          <description>Namespace:</description>
          <menulistset type="xml-namespaces"
                       anonid="new-attr-namespace-set"
                       editable="true"
                      />
          <description>Local name:</description>
          <textbox anonid="new-attr-name"
                   size="20"
                   contents="code"
                   invalid="true"
                   xulwidgets="true"
                  />
        </hbox>
        <hbox>
          <textbox anonid="new-attr-value"
                   multiline="true"
                   rows="5"
                   cols="80"
                   xulwidgets="true"
                   contents="code"
                  />
        </hbox>

        <description anonid="statusBox">Ok</description>
      </vbox>
    </content>

    <implementation implements="xeIPrecondition">
      <field name="_transaction">null</field>
      <property name="transaction" readonly="true">
        <getter>return this._transaction;</getter>
      </property>

      <field name="_wrapper">null</field>
      <property name="wrapper" readonly="true">
        <getter>return this._wrapper;</getter>
      </property>

      <field name="_invalidMessage">null</field>
      <property name="invalidMessage" readonly="true">
        <getter>return this._invalidMessage;</getter>
      </property>

      <field name="_helpURI">null</field>
      <property name="helpURI" readonly="true">
        <getter>return this._helpURI;</getter>
      </property>

      <field name="menulistset" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "new-attr-namespace-set");
      </field>

      <field name="attrNameBox" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "new-attr-name");
      </field>

      <field name="attrValueBox" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "new-attr-value");
      </field>

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

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

      <field name="saxErrorHandler" readonly="true"><![CDATA[
        ({
           locator: null,
           error: null,

           reset: function reset() {
             this.locator = null;
             this.error   = null;
           },

           fatalError: function fatalError(locator, error)
           {
             this.locator = locator;
             this.error   = error;
           },

           error: function error(locator, error)
           {
             // do nothing
           },

           ignorableWarning: function ignorableWarning(locator, error)
           {
             // do nothing
           }
         })
      ]]></field>

      <field name="saxReader"><![CDATA[
        Components.classes["@mozilla.org/saxparser/xmlreader;1"]
                  .createInstance(Components.interfaces.nsISAXXMLReader);
      ]]></field>

      <field name="elemOpenTag">""</field>

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

      <property name="invalid" readonly="true">
        <getter><![CDATA[
          var invalidNodes = this.anonContentBox.getElementsByAttribute("invalid", "*");
          return Boolean(invalidNodes.length);
        ]]></getter>
      </property>

      <method name="initialize">
        <parameter name="aElement"/>
        <parameter name="aWrapper"/>
        <body><![CDATA[
          assert(!this.mInitialized, "Already initialized?");
          assert(aElement instanceof Components.interfaces.nsIDOMElement,
                 "Not an element?");
          dumpJSStack();
          this.currentElementNode = aElement;
          this._wrapper = aWrapper;

          var map = verbosio.getNamespacesMap(aElement);
          this.menulistset.updateNamespaceMap(aElement);
          this.elemOpenTag = "<foo\n";
          for (var prop in map) {
            if (prop != "xmlns") {
              this.elemOpenTag += "xmlns:" + prop + "='" + map[prop] + "'\n";
            }
          }
          this.elemOpenTag += ">";

          var aPre = this;

          this._transaction = {
           // xeITransaction
           preconditionLocalId: null,

           // xeITransaction
           description: "New attribute on element",

           // xeITransaction
           ownerWrapper: aWrapper,

           // nsITransaction
           doTransaction: function doTransaction() {
             var nsURI = aPre.menulistset._contentBox.firstChild.value;
             var prefix = aPre.menulistset._contentBox.lastChild.value;
             prefix = prefix ? prefix + ':' : '';
             var localName = aPre.attrNameBox.value;
             var doc = aWrapper.wrappedDocument;
             this.generatedAttrNode = doc.createAttributeNS(nsURI, prefix + localName);
             this.generatedAttrNode.nodeValue = aPre.attrValueBox.value;

             aPre.currentElementNode.setAttributeNode(this.generatedAttrNode);
           },

           // nsITransaction
           undoTransaction: function undoTransaction() {
             aPre.currentElementNode.removeAttributeNode(this.generatedAttrNode);
           },

           // nsITransaction
           redoTransaction: function redoTransaction() {
             aPre.currentElementNode.setAttributeNode(this.generatedAttrNode);
           },

           // nsITransaction
           isTransient: false,

           // nsITransaction
           merge: function merge(aTransaction) {
             return false;
           },

           // nsISupports
           QueryInterface: function QueryInterface(aIID) {
             if (aIID.equals(Components.interfaces.xeITransaction) ||
                 aIID.equals(Components.interfaces.nsITransaction) ||
                 aIID.equals(Components.interfaces.nsISupports)) {
               return this;
             }

             throw Components.results.NS_ERROR_NO_INTERFACE;
             return null;
           }
         } // end this._transaction

         this.mInitialized = true;
        ]]></body>
      </method>

      <constructor><![CDATA[
        this.parentNode.insertWhereMenu.setAttribute("disabled", "true");
        this.saxReader.errorHandler = this.saxErrorHandler;
      ]]></constructor>
    </implementation>

    <handlers>
      <handler event="input" phase="capturing"><![CDATA[
        assert(this.mInitialized, "Not initialized?");

        var nsURI = this.menulistset._contentBox.firstChild.value;
        var prefix = this.menulistset._contentBox.lastChild.value;
        prefix = prefix ? prefix + ':' : '';
        var localName = this.attrNameBox.value;
        var subElement = "<sub " + (prefix + localName) + "='";
        subElement += encodeURIComponent(this.attrValueBox.value) + "'/>";

        // Parse the XML!
        var xml = this.elemOpenTag + subElement + "</foo>";
        this.saxErrorHandler.reset();
        this.saxReader.parseFromString(xml, "application/xml");
        // XXX ajvincent Localize!
        if (!this.saxErrorHandler.error) {
          this._invalidMessage = null;
        } else {
          this._invalidMessage = "Invalid markup";
        }

        this.parentNode.updateAcceptDisabled();
      ]]></handler>
    </handlers>
  </binding>

  <binding id="whole-content">
    <content>
      <xul:valuepanel type="source" anonid="valuepanel"/>
    </content>

    <implementation implements="xeIPrecondition">
      <field name="_transaction">null</field>
      <property name="transaction" readonly="true">
        <getter>return this._transaction;</getter>
      </property>

      <field name="_wrapper">null</field>
      <property name="wrapper" readonly="true">
        <getter>return this._wrapper;</getter>
      </property>

      <field name="_invalidMessage">null</field>
      <property name="invalidMessage" readonly="true">
        <getter>return this._invalidMessage;</getter>
      </property>

      <field name="_helpURI">null</field>
      <property name="helpURI" readonly="true">
        <getter>return this._helpURI;</getter>
      </property>

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

      <field name="docContainer">false</field>
    </implementation>

    <handlers>
      <handler event="input"><![CDATA[
        if (this.valuepanel.mInitialized) {
          /* XXX ajvincent Localize! */
          this._invalidMessage = this.valuepanel.sourceMarkup.invalid ?
                                 "Not well-formed." :
                                 null;
          this.parentNode.updateAcceptDisabled();
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="insert-content"
           extends="&documentPath;#whole-content">
    <implementation>
      <method name="initialize">
        <parameter name="aWrapper"/>
        <body><![CDATA[
          const nsIDOMElement = Components.interfaces.nsIDOMElement;
          const nsIDOMDocument = Components.interfaces.nsIDOMDocument;
          const nsIDOMText = Components.interfaces.nsIDOMText;

          var range = aWrapper.selectionRange;

          // New elements, non-whitespace text can't be at the doc level.
          if ((range.startContainer == doc) || (range.endContainer == doc)) {
            this.docContainer = true;
          }

          var startNode = range.startContainer.childNodes[range.startOffset];
          var isSingleNode = ((range.startContainer == range.endContainer) &&
                              !(range.startContainer instanceof nsIDOMText) &&
                              (range.startOffset + 1 == range.endOffset));
          var iterator = this.parentNode.insertWhereMenu.firstChild.firstChild;

          while (iterator) {
            var disabled = false;
            switch (iterator.getAttribute("value")) {
              case "REPLACE_SELECTION":
              case "AS_PARENT_OF_SELECTION":
                disabled = !(xeDeleteTransaction.prototype.isCommandEnabled(aWrapper));
                break;

              case "FIRST_CHILD_OF_SELECTION":
              case "LAST_CHILD_OF_SELECTION":
                if (!isSingleNode || !(startNode instanceof nsIDOMElement)) {
                  disabled = true;
                }
                break;
            }
            iterator.disabled = disabled;
            iterator = iterator.nextSibling;
          }

          var nsNode = isSingleNode ?
                       startNode :
                       range.commonAncestorContainer;
          if (nsNode instanceof nsIDOMDocument) {
            nsNode = nsNode.documentElement;
          }
          if (!(nsNode instanceof nsIDOMElement)) {
            nsNode = nsNode.parentNode;
          }
          this.valuepanel.initializeNamespaceMap(nsNode);

          var pre = this;

          this._wrapper = aWrapper;
          this._transaction = new xeRecordedTransaction();
          this._transaction.addProperties({
           // xeITransaction
           preconditionLocalId: null,

           // xeITransaction
           description: "New content",

           // xeITransaction
           ownerWrapper: aWrapper,

           insertPosition: "",

           checkSingleNode: function checkSingleNode() {
             var msg = "Did the selection change?";
             assert(isSingleNode, msg);
             var isNowSingleNode = ((range.startContainer == range.endContainer) &&
                                    !(range.startContainer instanceof nsIDOMText) &&
                                    (range.startOffset + 1 == range.endOffset));
             assert(isNowSingleNode, msg);
             var currentSingleNode = range.startContainer.childNodes[range.startOffset];
             assert(currentSingleNode == startNode, msg);
           },

           // xeIRecordedTransaction
           doRecordedTransaction: function doTransaction() {
             var source = pre.valuepanel.sourceFragment;
             var subdoc = parser.parseFromString(source, "application/xml");
             var subroot = subdoc.documentElement;
             var targetDoc = pre.wrapper.wrappedDocument;
             var fragment = targetDoc.createDocumentFragment();
             while (subroot.firstChild) {
               fragment.appendChild(targetDoc.adoptNode(subroot.firstChild));
             }

             this.insertPosition = pre.parentNode.insertWhereMenu.value;

             switch (this.insertPosition) {
               case "REPLACE_SELECTION":
                 range.deleteContents();
                 range.insertNode(fragment);
                 break;

               case "BEFORE_SELECTION":
                 range.insertNode(fragment);
                 break;

               case "AFTER_SELECTION":
                 var endContainer = range.endContainer;
                 if (endContainer instanceof nsIDOMText) {
                   endContainer.splitText(range.endOffset);
                   range.setEndAfter(endContainer);
                   endContainer = range.endContainer;
                   assert(!(endContainer instanceof nsIDOMText), "Whaaa?!?");
                 }
                 var refNode = endContainer.childNodes[range.endOffset];

                 endContainer.insertBefore(fragment, refNode);
                 break;

               case "FIRST_CHILD_OF_SELECTION":
                 this.checkSingleNode();
                 startNode.insertBefore(fragment, startNode.firstChild);
                 break;

               case "LAST_CHILD_OF_SELECTION":
                 this.checkSingleNode();
                 startNode.appendChild(fragment);
                 break;

               case "AS_PARENT_OF_SELECTION":
                 assert(fragment.childNodes.length == 1, "No clear parent node?");
                 assert(fragment.firstChild instanceof nsIDOMElement, "Not an element parent?");
                 range.surroundContents(fragment.firstChild);
                 break;

               default:
                 throw new Error("Not implemented!");
             }

             range.collapse(true);
           },

           // nsITransaction
           merge: function merge(aTransaction) {
             return false;
           }
         }); // end this._transaction
        ]]></body>
      </method>
    </implementation>
  </binding>
</bindings>
