You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flex.apache.org by jm...@apache.org on 2013/10/08 16:03:21 UTC

[06/62] [abbrv] [partial] Merged Apache Flex 4.9.0 release branch

http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/f690ea2f/modules/thirdparty/batik/sources/org/apache/flex/forks/batik/bridge/svg12/DefaultXBLManager.java
----------------------------------------------------------------------
diff --git a/modules/thirdparty/batik/sources/org/apache/flex/forks/batik/bridge/svg12/DefaultXBLManager.java b/modules/thirdparty/batik/sources/org/apache/flex/forks/batik/bridge/svg12/DefaultXBLManager.java
new file mode 100644
index 0000000..2b39fa1
--- /dev/null
+++ b/modules/thirdparty/batik/sources/org/apache/flex/forks/batik/bridge/svg12/DefaultXBLManager.java
@@ -0,0 +1,2089 @@
+/*
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (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.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+package org.apache.flex.forks.batik.bridge.svg12;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.event.EventListenerList;
+
+import org.apache.flex.forks.batik.bridge.BridgeContext;
+import org.apache.flex.forks.batik.bridge.BridgeException;
+import org.apache.flex.forks.batik.bridge.ErrorConstants;
+import org.apache.flex.forks.batik.dom.AbstractAttrNS;
+import org.apache.flex.forks.batik.dom.AbstractDocument;
+import org.apache.flex.forks.batik.dom.AbstractNode;
+import org.apache.flex.forks.batik.dom.events.NodeEventTarget;
+import org.apache.flex.forks.batik.dom.svg12.BindableElement;
+import org.apache.flex.forks.batik.dom.svg12.XBLEventSupport;
+import org.apache.flex.forks.batik.dom.svg12.XBLOMContentElement;
+import org.apache.flex.forks.batik.dom.svg12.XBLOMDefinitionElement;
+import org.apache.flex.forks.batik.dom.svg12.XBLOMImportElement;
+import org.apache.flex.forks.batik.dom.svg12.XBLOMShadowTreeElement;
+import org.apache.flex.forks.batik.dom.svg12.XBLOMTemplateElement;
+import org.apache.flex.forks.batik.dom.xbl.NodeXBL;
+import org.apache.flex.forks.batik.dom.xbl.ShadowTreeEvent;
+import org.apache.flex.forks.batik.dom.xbl.XBLManager;
+import org.apache.flex.forks.batik.dom.xbl.XBLManagerData;
+import org.apache.flex.forks.batik.dom.xbl.XBLShadowTreeElement;
+import org.apache.flex.forks.batik.util.DoublyIndexedTable;
+import org.apache.flex.forks.batik.util.XBLConstants;
+import org.apache.flex.forks.batik.util.XMLConstants;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.events.DocumentEvent;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.EventTarget;
+import org.w3c.dom.events.MutationEvent;
+
+/**
+ * A full featured sXBL manager.
+ *
+ * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a>
+ * @version $Id: DefaultXBLManager.java 592621 2007-11-07 05:58:12Z cam $
+ */
+public class DefaultXBLManager implements XBLManager, XBLConstants {
+
+    /**
+     * Whether XBL processing is currently taking place.
+     */
+    protected boolean isProcessing;
+
+    /**
+     * The document.
+     */
+    protected Document document;
+
+    /**
+     * The BridgeContext.
+     */
+    protected BridgeContext ctx;
+
+    /**
+     * Map of namespace URI/local name pairs to ordered sets of
+     * definition records.
+     */
+    protected DoublyIndexedTable definitionLists = new DoublyIndexedTable();
+
+    /**
+     * Map of definition element/import element pairs to definition records.
+     */
+    protected DoublyIndexedTable definitions = new DoublyIndexedTable();
+
+    /**
+     * Map of shadow trees to content managers.
+     */
+    protected Map contentManagers = new HashMap();
+
+    /**
+     * Map of import elements to import records.
+     */
+    protected Map imports = new HashMap();
+
+    /**
+     * DOM node inserted listener for the document.
+     */
+    protected DocInsertedListener docInsertedListener
+        = new DocInsertedListener();
+
+    /**
+     * DOM node removed listener for the document.
+     */
+    protected DocRemovedListener docRemovedListener
+        = new DocRemovedListener();
+
+    /**
+     * DOM subtree mutation listener for the document.
+     */
+    protected DocSubtreeListener docSubtreeListener
+        = new DocSubtreeListener();
+
+    /**
+     * DOM attribute listener for import elements.
+     */
+    protected ImportAttrListener importAttrListener = new ImportAttrListener();
+
+    /**
+     * DOM attribute listener for referencing definition elements.
+     */
+    protected RefAttrListener refAttrListener = new RefAttrListener();
+
+    /**
+     * Global event listener list for XBL binding related events.
+     */
+    protected EventListenerList bindingListenerList = new EventListenerList();
+
+    /**
+     * Global event listener list for ContentSelectionChanged events.
+     */
+    protected EventListenerList contentSelectionChangedListenerList
+        = new EventListenerList();
+
+    /**
+     * Creates a new DefaultXBLManager for the given document.
+     */
+    public DefaultXBLManager(Document doc, BridgeContext ctx) {
+        document = doc;
+        this.ctx = ctx;
+        ImportRecord ir = new ImportRecord(null, null);
+        imports.put(null, ir);
+    }
+
+    /**
+     * Starts XBL processing on the document.
+     */
+    public void startProcessing() {
+        if (isProcessing) {
+            return;
+        }
+
+        // Get list of all current definitions in the document.
+        NodeList nl = document.getElementsByTagNameNS(XBL_NAMESPACE_URI,
+                                                      XBL_DEFINITION_TAG);
+        XBLOMDefinitionElement[] defs
+            = new XBLOMDefinitionElement[nl.getLength()];
+        for (int i = 0; i < defs.length; i++) {
+            defs[i] = (XBLOMDefinitionElement) nl.item(i);
+        }
+
+        // Get list of all imports in the document.
+        nl = document.getElementsByTagNameNS(XBL_NAMESPACE_URI,
+                                             XBL_IMPORT_TAG);
+        Element[] imports
+            = new Element[nl.getLength()];
+        for (int i = 0; i < imports.length; i++) {
+            imports[i] = (Element) nl.item(i);
+        }
+
+        // Add document listeners.
+        AbstractDocument doc = (AbstractDocument) document;
+        XBLEventSupport es = (XBLEventSupport) doc.initializeEventSupport();
+        es.addImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeRemoved",
+             docRemovedListener, true);
+        es.addImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeInserted",
+             docInsertedListener, true);
+        es.addImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMSubtreeModified",
+             docSubtreeListener, true);
+
+        // Add definitions.
+        for (int i = 0; i < defs.length; i++) {
+            if (defs[i].getAttributeNS(null, XBL_REF_ATTRIBUTE).length() != 0) {
+                addDefinitionRef(defs[i]);
+            } else {
+                String ns = defs[i].getElementNamespaceURI();
+                String ln = defs[i].getElementLocalName();
+                addDefinition(ns, ln, defs[i], null);
+            }
+        }
+
+        // Add imports.
+        for (int i = 0; i < imports.length; i++) {
+            addImport(imports[i]);
+        }
+
+        // Bind all of the bindable elements in the document that have a
+        // matching definition.
+        isProcessing = true;
+        bind(document.getDocumentElement());
+    }
+
+    /**
+     * Stops XBL processing on the document.
+     */
+    public void stopProcessing() {
+        if (!isProcessing) {
+            return;
+        }
+        isProcessing = false;
+
+        // Remove document listeners.
+        AbstractDocument doc = (AbstractDocument) document;
+        XBLEventSupport es = (XBLEventSupport) doc.initializeEventSupport();
+        es.removeImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeRemoved",
+             docRemovedListener, true);
+        es.removeImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeInserted",
+             docInsertedListener, true);
+        es.removeImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMSubtreeModified",
+             docSubtreeListener, true);
+
+        // Remove all imports.
+        int nSlots = imports.values().size();
+        ImportRecord[] irs = new ImportRecord[ nSlots ];
+        imports.values().toArray( irs );
+        for (int i = 0; i < irs.length; i++) {
+            ImportRecord ir = irs[i];
+            if (ir.importElement.getLocalName().equals(XBL_DEFINITION_TAG)) {
+                removeDefinitionRef(ir.importElement);
+            } else {
+                removeImport(ir.importElement);
+            }
+        }
+
+        // Remove all bindings.
+        Object[] defRecs = definitions.getValuesArray();
+        definitions.clear();
+        for (int i = 0; i < defRecs.length; i++) {
+            DefinitionRecord defRec = (DefinitionRecord) defRecs[i];
+            TreeSet defs = (TreeSet) definitionLists.get(defRec.namespaceURI,
+                                                         defRec.localName);
+            if (defs != null) {
+                while (!defs.isEmpty()) {
+                    defRec = (DefinitionRecord) defs.first();
+                    defs.remove(defRec);
+                    removeDefinition(defRec);
+                }
+                definitionLists.put(defRec.namespaceURI, defRec.localName, null);
+            }
+        }
+        definitionLists = new DoublyIndexedTable();
+        contentManagers.clear();
+    }
+
+    /**
+     * Returns whether XBL processing is currently enabled.
+     */
+    public boolean isProcessing() {
+        return isProcessing;
+    }
+
+    /**
+     * Adds a definition through its referring definition element (one
+     * with a 'ref' attribute).
+     */
+    protected void addDefinitionRef(Element defRef) {
+        String ref = defRef.getAttributeNS(null, XBL_REF_ATTRIBUTE);
+        Element e = ctx.getReferencedElement(defRef, ref);
+        if (!XBL_NAMESPACE_URI.equals(e.getNamespaceURI())
+                || !XBL_DEFINITION_TAG.equals(e.getLocalName())) {
+            throw new BridgeException
+                (ctx, defRef, ErrorConstants.ERR_URI_BAD_TARGET,
+                 new Object[] { ref });
+        }
+        ImportRecord ir = new ImportRecord(defRef, e);
+        imports.put(defRef, ir);
+
+        NodeEventTarget et = (NodeEventTarget) defRef;
+        et.addEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMAttrModified",
+             refAttrListener, false, null);
+
+        XBLOMDefinitionElement d = (XBLOMDefinitionElement) defRef;
+        String ns = d.getElementNamespaceURI();
+        String ln = d.getElementLocalName();
+        addDefinition(ns, ln, (XBLOMDefinitionElement) e, defRef);
+    }
+
+    /**
+     * Removes a definition through its referring definition element (one
+     * with a 'ref' attribute).
+     */
+    protected void removeDefinitionRef(Element defRef) {
+        ImportRecord ir = (ImportRecord) imports.get(defRef);
+        NodeEventTarget et = (NodeEventTarget) defRef;
+        et.removeEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMAttrModified",
+             refAttrListener, false);
+        DefinitionRecord defRec
+            = (DefinitionRecord) definitions.get(ir.node, defRef);
+        removeDefinition(defRec);
+        imports.remove(defRef);
+    }
+
+    /**
+     * Imports bindings from another document.
+     */
+    protected void addImport(Element imp) {
+        String bindings = imp.getAttributeNS(null, XBL_BINDINGS_ATTRIBUTE);
+        Node n = ctx.getReferencedNode(imp, bindings);
+        if (n.getNodeType() == Node.ELEMENT_NODE
+                && !(XBL_NAMESPACE_URI.equals(n.getNamespaceURI())
+                        && XBL_XBL_TAG.equals(n.getLocalName()))) {
+            throw new BridgeException
+                (ctx, imp, ErrorConstants.ERR_URI_BAD_TARGET,
+                 new Object[] { n });
+        }
+        ImportRecord ir = new ImportRecord(imp, n);
+        imports.put(imp, ir);
+
+        NodeEventTarget et = (NodeEventTarget) imp;
+        et.addEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMAttrModified",
+             importAttrListener, false, null);
+
+        et = (NodeEventTarget) n;
+        et.addEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMNodeInserted",
+             ir.importInsertedListener, false, null);
+        et.addEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMNodeRemoved",
+             ir.importRemovedListener, false, null);
+        et.addEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMSubtreeModified",
+             ir.importSubtreeListener, false, null);
+        addImportedDefinitions(imp, n);
+    }
+
+    /**
+     * Adds the definitions in the given imported subtree.
+     */
+    protected void addImportedDefinitions(Element imp, Node n) {
+        if (n instanceof XBLOMDefinitionElement) {
+            XBLOMDefinitionElement def = (XBLOMDefinitionElement) n;
+            String ns = def.getElementNamespaceURI();
+            String ln = def.getElementLocalName();
+            addDefinition(ns, ln, def, imp);
+        } else {
+            n = n.getFirstChild();
+            while (n != null) {
+                addImportedDefinitions(imp, n);
+                n = n.getNextSibling();
+            }
+        }
+    }
+
+    /**
+     * Removes an import.
+     */
+    protected void removeImport(Element imp) {
+        ImportRecord ir = (ImportRecord) imports.get(imp);
+        NodeEventTarget et = (NodeEventTarget) ir.node;
+        et.removeEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMNodeInserted",
+             ir.importInsertedListener, false);
+        et.removeEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMNodeRemoved",
+             ir.importRemovedListener, false);
+        et.removeEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMSubtreeModified",
+             ir.importSubtreeListener, false);
+
+        et = (NodeEventTarget) imp;
+        et.removeEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMAttrModified",
+             importAttrListener, false);
+
+        Object[] defRecs = definitions.getValuesArray();
+        for (int i = 0; i < defRecs.length; i++) {
+            DefinitionRecord defRec = (DefinitionRecord) defRecs[i];
+            if (defRec.importElement == imp) {
+                removeDefinition(defRec);
+            }
+        }
+        imports.remove(imp);
+    }
+
+    /**
+     * Adds an xbl:definition element to the list of definitions that
+     * could possibly affect elements with the specified QName.  This
+     * may or may not actually cause a new binding to come in to effect,
+     * as this new definition element may be added earlier in the
+     * document than another already in effect.
+     *
+     * @param namespaceURI the namespace URI of the bound elements
+     * @param localName the local name of the bound elements
+     * @param def the xbl:definition element
+     * @param imp the xbl:import or xbl;definition element through which
+     *            this definition is being added, or null if the binding
+     *            is in the original document
+     */
+    protected void addDefinition(String namespaceURI,
+                                 String localName,
+                                 XBLOMDefinitionElement def,
+                                 Element imp) {
+        ImportRecord ir = (ImportRecord) imports.get(imp);
+        DefinitionRecord oldDefRec = null;
+        DefinitionRecord defRec;
+        TreeSet defs = (TreeSet) definitionLists.get(namespaceURI, localName);
+        if (defs == null) {
+            defs = new TreeSet();
+            definitionLists.put(namespaceURI, localName, defs);
+        } else if (defs.size() > 0) {
+            oldDefRec = (DefinitionRecord) defs.first();
+        }
+        XBLOMTemplateElement template = null;
+        for (Node n = def.getFirstChild(); n != null; n = n.getNextSibling()) {
+            if (n instanceof XBLOMTemplateElement) {
+                template = (XBLOMTemplateElement) n;
+                break;
+            }
+        }
+        defRec = new DefinitionRecord(namespaceURI, localName, def,
+                                      template, imp);
+        defs.add(defRec);
+        definitions.put(def, imp, defRec);
+        addDefinitionElementListeners(def, ir);
+        if (defs.first() != defRec) {
+            return;
+        }
+        if (oldDefRec != null) {
+            XBLOMDefinitionElement oldDef = oldDefRec.definition;
+            XBLOMTemplateElement oldTemplate = oldDefRec.template;
+            if (oldTemplate != null) {
+                removeTemplateElementListeners(oldTemplate, ir);
+            }
+            removeDefinitionElementListeners(oldDef, ir);
+        }
+        if (template != null) {
+            addTemplateElementListeners(template, ir);
+        }
+        if (isProcessing) {
+            rebind(namespaceURI, localName, document.getDocumentElement());
+        }
+    }
+
+    /**
+     * Adds DOM mutation listeners to the given definition element.
+     */
+    protected void addDefinitionElementListeners(XBLOMDefinitionElement def,
+                                                 ImportRecord ir) {
+        XBLEventSupport es = (XBLEventSupport) def.initializeEventSupport();
+        es.addImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMAttrModified",
+             ir.defAttrListener, false);
+        es.addImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeInserted",
+             ir.defNodeInsertedListener, false);
+        es.addImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeRemoved",
+             ir.defNodeRemovedListener, false);
+    }
+
+    /**
+     * Adds DOM mutation listeners to the given template element.
+     */
+    protected void addTemplateElementListeners(XBLOMTemplateElement template,
+                                               ImportRecord ir) {
+        XBLEventSupport es
+            = (XBLEventSupport) template.initializeEventSupport();
+        es.addImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMAttrModified",
+             ir.templateMutationListener, false);
+        es.addImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeInserted",
+             ir.templateMutationListener, false);
+        es.addImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeRemoved",
+             ir.templateMutationListener, false);
+        es.addImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMCharacterDataModified",
+             ir.templateMutationListener, false);
+    }
+
+    /**
+     * Removes an xbl:definition element from the list of definitions that
+     * could possibly affect elements with the specified QName.  This
+     * will only cause a new binding to come in to effect if it is currently
+     * active.
+     */
+    protected void removeDefinition(DefinitionRecord defRec) {
+        TreeSet defs = (TreeSet) definitionLists.get(defRec.namespaceURI,
+                                                     defRec.localName);
+        if (defs == null) {
+            return;
+        }
+        Element imp = defRec.importElement;
+        ImportRecord ir = (ImportRecord) imports.get(imp);
+        DefinitionRecord activeDefRec = (DefinitionRecord) defs.first();
+        defs.remove(defRec);
+        definitions.remove(defRec.definition, imp);
+        removeDefinitionElementListeners(defRec.definition, ir);
+        if (defRec != activeDefRec) {
+            return;
+        }
+        if (defRec.template != null) {
+            removeTemplateElementListeners(defRec.template, ir);
+        }
+        rebind(defRec.namespaceURI, defRec.localName,
+               document.getDocumentElement());
+    }
+
+    /**
+     * Removes DOM mutation listeners from the given definition element.
+     */
+    protected void removeDefinitionElementListeners
+            (XBLOMDefinitionElement def,
+             ImportRecord ir) {
+        XBLEventSupport es = (XBLEventSupport) def.initializeEventSupport();
+        es.removeImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMAttrModified",
+             ir.defAttrListener, false);
+        es.removeImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeInserted",
+             ir.defNodeInsertedListener, false);
+        es.removeImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeRemoved",
+             ir.defNodeRemovedListener, false);
+    }
+
+    /**
+     * Removes DOM mutation listeners from the given template element.
+     */
+    protected void removeTemplateElementListeners
+            (XBLOMTemplateElement template,
+             ImportRecord ir) {
+        XBLEventSupport es
+            = (XBLEventSupport) template.initializeEventSupport();
+        es.removeImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMAttrModified",
+             ir.templateMutationListener, false);
+        es.removeImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeInserted",
+             ir.templateMutationListener, false);
+        es.removeImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMNodeRemoved",
+             ir.templateMutationListener, false);
+        es.removeImplementationEventListenerNS
+            (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+             "DOMCharacterDataModified",
+             ir.templateMutationListener, false);
+    }
+
+    /**
+     * Returns the definition record of the active definition for namespace
+     * URI/local name pair.
+     */
+    protected DefinitionRecord getActiveDefinition(String namespaceURI,
+                                                   String localName) {
+        TreeSet defs = (TreeSet) definitionLists.get(namespaceURI, localName);
+        if (defs == null || defs.size() == 0) {
+            return null;
+        }
+        return (DefinitionRecord) defs.first();
+    }
+
+    /**
+     * Unbinds each bindable element in the given element's subtree.
+     */
+    protected void unbind(Element e) {
+        if (e instanceof BindableElement) {
+            setActiveDefinition((BindableElement) e, null);
+        } else {
+            NodeList nl = getXblScopedChildNodes(e);
+            for (int i = 0; i < nl.getLength(); i++) {
+                Node n = nl.item(i);
+                if (n.getNodeType() == Node.ELEMENT_NODE) {
+                    unbind((Element) n);
+                }
+            }
+        }
+    }
+
+    /**
+     * Binds each bindable element in the given element's subtree.
+     */
+    protected void bind(Element e) {
+        AbstractDocument doc = (AbstractDocument) e.getOwnerDocument();
+        if (doc != document) {
+            XBLManager xm = doc.getXBLManager();
+            if (xm instanceof DefaultXBLManager) {
+                ((DefaultXBLManager) xm).bind(e);
+                return;
+            }
+        }
+
+        if (e instanceof BindableElement) {
+            DefinitionRecord defRec
+                = getActiveDefinition(e.getNamespaceURI(),
+                                      e.getLocalName());
+            setActiveDefinition((BindableElement) e, defRec);
+        } else {
+            NodeList nl = getXblScopedChildNodes(e);
+            for (int i = 0; i < nl.getLength(); i++) {
+                Node n = nl.item(i);
+                if (n.getNodeType() == Node.ELEMENT_NODE) {
+                    bind((Element) n);
+                }
+            }
+        }
+    }
+
+    /**
+     * Rebinds each bindable element of the given name in the given element's
+     * subtree.
+     */
+    protected void rebind(String namespaceURI, String localName, Element e) {
+        AbstractDocument doc = (AbstractDocument) e.getOwnerDocument();
+        if (doc != document) {
+            XBLManager xm = doc.getXBLManager();
+            if (xm instanceof DefaultXBLManager) {
+                ((DefaultXBLManager) xm).rebind(namespaceURI, localName, e);
+                return;
+            }
+        }
+
+        if (e instanceof BindableElement
+                && namespaceURI.equals(e.getNamespaceURI())
+                && localName.equals(e.getLocalName())) {
+            DefinitionRecord defRec
+                = getActiveDefinition(e.getNamespaceURI(),
+                                      e.getLocalName());
+            setActiveDefinition((BindableElement) e, defRec);
+        } else {
+            NodeList nl = getXblScopedChildNodes(e);
+            for (int i = 0; i < nl.getLength(); i++) {
+                Node n = nl.item(i);
+                if (n.getNodeType() == Node.ELEMENT_NODE) {
+                    rebind(namespaceURI, localName, (Element) n);
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets the given definition as the active one for a particular
+     * bindable element.
+     */
+    protected void setActiveDefinition(BindableElement elt,
+                                       DefinitionRecord defRec) {
+        XBLRecord rec = getRecord(elt);
+        rec.definitionElement = defRec == null ? null : defRec.definition;
+        if (defRec != null
+                && defRec.definition != null
+                && defRec.template != null) {
+            setXblShadowTree(elt, cloneTemplate(defRec.template));
+        } else {
+            setXblShadowTree(elt, null);
+        }
+    }
+
+    /**
+     * Sets the shadow tree for the given bindable element.
+     */
+    protected void setXblShadowTree(BindableElement elt,
+                                    XBLOMShadowTreeElement newShadow) {
+        XBLOMShadowTreeElement oldShadow
+            = (XBLOMShadowTreeElement) getXblShadowTree(elt);
+        if (oldShadow != null) {
+            fireShadowTreeEvent(elt, XBL_UNBINDING_EVENT_TYPE, oldShadow);
+            ContentManager cm = getContentManager(oldShadow);
+            if (cm != null) {
+                cm.dispose();
+            }
+            elt.setShadowTree(null);
+            XBLRecord rec = getRecord(oldShadow);
+            rec.boundElement = null;
+            oldShadow.removeEventListenerNS
+                (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+                 "DOMSubtreeModified",
+                 docSubtreeListener, false);
+        }
+        if (newShadow != null) {
+            newShadow.addEventListenerNS
+                (XMLConstants.XML_EVENTS_NAMESPACE_URI,
+                 "DOMSubtreeModified",
+                 docSubtreeListener, false, null);
+            fireShadowTreeEvent(elt, XBL_PREBIND_EVENT_TYPE, newShadow);
+            elt.setShadowTree(newShadow);
+            XBLRecord rec = getRecord(newShadow);
+            rec.boundElement = elt;
+            AbstractDocument doc
+                = (AbstractDocument) elt.getOwnerDocument();
+            XBLManager xm = doc.getXBLManager();
+            ContentManager cm = new ContentManager(newShadow, xm);
+            setContentManager(newShadow, cm);
+        }
+        invalidateChildNodes(elt);
+        if (newShadow != null) {
+            NodeList nl = getXblScopedChildNodes(elt);
+            for (int i = 0; i < nl.getLength(); i++) {
+                Node n = nl.item(i);
+                if (n.getNodeType() == Node.ELEMENT_NODE) {
+                    bind((Element) n);
+                }
+            }
+            dispatchBindingChangedEvent(elt, newShadow);
+            fireShadowTreeEvent(elt, XBL_BOUND_EVENT_TYPE, newShadow);
+        } else {
+            dispatchBindingChangedEvent(elt, newShadow);
+        }
+    }
+
+    /**
+     * Fires a ShadowTreeEvent of the given type on this element.
+     */
+    protected void fireShadowTreeEvent(BindableElement elt,
+                                       String type,
+                                       XBLShadowTreeElement e) {
+        DocumentEvent de = (DocumentEvent) elt.getOwnerDocument();
+        ShadowTreeEvent evt
+            = (ShadowTreeEvent) de.createEvent("ShadowTreeEvent");
+        evt.initShadowTreeEventNS(XBL_NAMESPACE_URI, type, true, false, e);
+        elt.dispatchEvent(evt);
+    }
+
+    /**
+     * Clones a template element for use as a shadow tree.
+     */
+    protected XBLOMShadowTreeElement cloneTemplate
+            (XBLOMTemplateElement template) {
+        XBLOMShadowTreeElement clone =
+            (XBLOMShadowTreeElement)
+            template.getOwnerDocument().createElementNS(XBL_NAMESPACE_URI,
+                                                        XBL_SHADOW_TREE_TAG);
+        NamedNodeMap attrs = template.getAttributes();
+        for (int i = 0; i < attrs.getLength(); i++) {
+            Attr attr = (Attr) attrs.item(i);
+            if (attr instanceof AbstractAttrNS) {
+                clone.setAttributeNodeNS(attr);
+            } else {
+                clone.setAttributeNode(attr);
+            }
+        }
+        for (Node n = template.getFirstChild();
+                n != null;
+                n = n.getNextSibling()) {
+            clone.appendChild(n.cloneNode(true));
+        }
+        return clone;
+    }
+
+    /**
+     * Get the parent of a node in the fully flattened tree.
+     */
+    public Node getXblParentNode(Node n) {
+        Node contentElement = getXblContentElement(n);
+        Node parent = contentElement == null
+                        ? n.getParentNode()
+                        : contentElement.getParentNode();
+        if (parent instanceof XBLOMContentElement) {
+            parent = parent.getParentNode();
+        }
+        if (parent instanceof XBLOMShadowTreeElement) {
+            parent = getXblBoundElement(parent);
+        }
+        return parent;
+    }
+
+    /**
+     * Get the list of child nodes of a node in the fully flattened tree.
+     */
+    public NodeList getXblChildNodes(Node n) {
+        XBLRecord rec = getRecord(n);
+        if (rec.childNodes == null) {
+            rec.childNodes = new XblChildNodes(rec);
+        }
+        return rec.childNodes;
+    }
+
+    /**
+     * Get the list of child nodes of a node in the fully flattened tree
+     * that are within the same shadow scope.
+     */
+    public NodeList getXblScopedChildNodes(Node n) {
+        XBLRecord rec = getRecord(n);
+        if (rec.scopedChildNodes == null) {
+            rec.scopedChildNodes = new XblScopedChildNodes(rec);
+        }
+        return rec.scopedChildNodes;
+    }
+
+    /**
+     * Get the first child node of a node in the fully flattened tree.
+     */
+    public Node getXblFirstChild(Node n) {
+        NodeList nl = getXblChildNodes(n);
+        return nl.item(0);
+    }
+
+    /**
+     * Get the last child node of a node in the fully flattened tree.
+     */
+    public Node getXblLastChild(Node n) {
+        NodeList nl = getXblChildNodes(n);
+        return nl.item(nl.getLength() - 1);
+    }
+
+    /**
+     * Get the node which directly precedes a node in the xblParentNode's
+     * xblChildNodes list.
+     */
+    public Node getXblPreviousSibling(Node n) {
+        Node p = getXblParentNode(n);
+        if (p == null || getRecord(p).childNodes == null) {
+            return n.getPreviousSibling();
+        }
+        XBLRecord rec = getRecord(n);
+        if (!rec.linksValid) {
+            updateLinks(n);
+        }
+        return rec.previousSibling;
+    }
+
+    /**
+     * Get the node which directly follows a node in the xblParentNode's
+     * xblChildNodes list.
+     */
+    public Node getXblNextSibling(Node n) {
+        Node p = getXblParentNode(n);
+        if (p == null || getRecord(p).childNodes == null) {
+            return n.getNextSibling();
+        }
+        XBLRecord rec = getRecord(n);
+        if (!rec.linksValid) {
+            updateLinks(n);
+        }
+        return rec.nextSibling;
+    }
+
+    /**
+     * Get the first element child of a node in the fully flattened tree.
+     */
+    public Element getXblFirstElementChild(Node n) {
+        n = getXblFirstChild(n);
+        while (n != null && n.getNodeType() != Node.ELEMENT_NODE) {
+            n = getXblNextSibling(n);
+        }
+        return (Element) n;
+    }
+
+    /**
+     * Get the last element child of a node in the fully flattened tree.
+     */
+    public Element getXblLastElementChild(Node n) {
+        n = getXblLastChild(n);
+        while (n != null && n.getNodeType() != Node.ELEMENT_NODE) {
+            n = getXblPreviousSibling(n);
+        }
+        return (Element) n;
+    }
+
+    /**
+     * Get the first element that precedes the a node in the
+     * xblParentNode's xblChildNodes list.
+     */
+    public Element getXblPreviousElementSibling(Node n) {
+        do {
+            n = getXblPreviousSibling(n);
+        } while (n != null && n.getNodeType() != Node.ELEMENT_NODE);
+        return (Element) n;
+    }
+
+    /**
+     * Get the first element that follows a node in the
+     * xblParentNode's xblChildNodes list.
+     */
+    public Element getXblNextElementSibling(Node n) {
+        do {
+            n = getXblNextSibling(n);
+        } while (n != null && n.getNodeType() != Node.ELEMENT_NODE);
+        return (Element) n;
+    }
+
+    /**
+     * Get the bound element whose shadow tree a node resides in.
+     */
+    public Element getXblBoundElement(Node n) {
+        while (n != null && !(n instanceof XBLShadowTreeElement)) {
+            XBLOMContentElement content = getXblContentElement(n);
+            if (content != null) {
+                n = content;
+            }
+            n = n.getParentNode();
+        }
+        if (n == null) {
+            return null;
+        }
+        return getRecord(n).boundElement;
+    }
+
+    /**
+     * Get the shadow tree of a node.
+     */
+    public Element getXblShadowTree(Node n) {
+        if (n instanceof BindableElement) {
+            BindableElement elt = (BindableElement) n;
+            return elt.getShadowTree();
+        }
+        return null;
+    }
+
+    /**
+     * Get the xbl:definition elements currently binding an element.
+     */
+    public NodeList getXblDefinitions(Node n) {
+        final String namespaceURI = n.getNamespaceURI();
+        final String localName = n.getLocalName();
+        return new NodeList() {
+            public Node item(int i) {
+                TreeSet defs = (TreeSet) definitionLists.get(namespaceURI, localName);
+                if (defs != null && defs.size() != 0 && i == 0) {
+                    DefinitionRecord defRec = (DefinitionRecord) defs.first();
+                    return defRec.definition;
+                }
+                return null;
+            }
+            public int getLength() {
+                Set defs = (TreeSet) definitionLists.get(namespaceURI, localName);
+                return defs != null && defs.size() != 0 ? 1 : 0;
+            }
+        };
+    }
+
+    /**
+     * Returns the XBL record for the given node.
+     */
+    protected XBLRecord getRecord(Node n) {
+        XBLManagerData xmd = (XBLManagerData) n;
+        XBLRecord rec = (XBLRecord) xmd.getManagerData();
+        if (rec == null) {
+            rec = new XBLRecord();
+            rec.node = n;
+            xmd.setManagerData(rec);
+        }
+        return rec;
+    }
+
+    /**
+     * Updates the xblPreviousSibling and xblNextSibling properties of the
+     * given XBL node.
+     */
+    protected void updateLinks(Node n) {
+        XBLRecord rec = getRecord(n);
+        rec.previousSibling = null;
+        rec.nextSibling = null;
+        rec.linksValid = true;
+        Node p = getXblParentNode(n);
+        if (p != null) {
+            NodeList xcn = getXblChildNodes(p);
+            if (xcn instanceof XblChildNodes) {
+                ((XblChildNodes) xcn).update();
+            }
+        }
+    }
+
+    /**
+     * Returns the content element that caused the given node to be
+     * present in the flattened tree.
+     */
+    public XBLOMContentElement getXblContentElement(Node n) {
+        return getRecord(n).contentElement;
+    }
+
+    /**
+     * Determines the number of nodes events should bubble if the
+     * mouse pointer has moved from one element to another.
+     * @param from the element from which the mouse pointer moved
+     * @param to   the element to which the mouse pointer moved
+     */
+    public static int computeBubbleLimit(Node from, Node to) {
+        ArrayList fromList = new ArrayList(10);
+        ArrayList toList = new ArrayList(10);
+        while (from != null) {
+            fromList.add(from);
+            from = ((NodeXBL) from).getXblParentNode();
+        }
+        while (to != null) {
+            toList.add(to);
+            to = ((NodeXBL) to).getXblParentNode();
+        }
+        int fromSize = fromList.size();
+        int toSize = toList.size();
+        for (int i = 0; i < fromSize && i < toSize; i++) {
+            Node n1 = (Node) fromList.get(fromSize - i - 1);
+            Node n2 = (Node) toList.get(toSize - i - 1);
+            if (n1 != n2) {
+                Node prevBoundElement = ((NodeXBL) n1).getXblBoundElement();
+                while (i > 0 && prevBoundElement != fromList.get(fromSize - i - 1)) {
+                    i--;
+                }
+                return fromSize - i - 1;
+            }
+        }
+        return 1;
+    }
+
+    /**
+     * Returns the ContentManager that handles the shadow tree the given
+     * node resides in.
+     */
+    public ContentManager getContentManager(Node n) {
+        Node b = getXblBoundElement(n);
+        if (b != null) {
+            Element s = getXblShadowTree(b);
+            if (s != null) {
+                ContentManager cm;
+                Document doc = b.getOwnerDocument();
+                if (doc != document) {
+                    DefaultXBLManager xm = (DefaultXBLManager)
+                        ((AbstractDocument) doc).getXBLManager();
+                    cm = (ContentManager) xm.contentManagers.get(s);
+                } else {
+                    cm = (ContentManager) contentManagers.get(s);
+                }
+                return cm;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Records the ContentManager that handles the given shadow tree.
+     */
+    void setContentManager(Element shadow, ContentManager cm) {
+        if (cm == null) {
+            contentManagers.remove(shadow);
+        } else {
+            contentManagers.put(shadow, cm);
+        }
+    }
+
+    /**
+     * Mark the xblChildNodes and xblScopedChildNodes variables
+     * as invalid.
+     */
+    public void invalidateChildNodes(Node n) {
+        XBLRecord rec = getRecord(n);
+        if (rec.childNodes != null) {
+            rec.childNodes.invalidate();
+        }
+        if (rec.scopedChildNodes != null) {
+            rec.scopedChildNodes.invalidate();
+        }
+    }
+
+    /**
+     * Adds the specified ContentSelectionChangedListener to the
+     * global listener list.
+     */
+    public void addContentSelectionChangedListener
+            (ContentSelectionChangedListener l) {
+        contentSelectionChangedListenerList.add
+            (ContentSelectionChangedListener.class, l);
+    }
+
+    /**
+     * Removes the specified ContentSelectionChangedListener from the
+     * global listener list.
+     */
+    public void removeContentSelectionChangedListener
+            (ContentSelectionChangedListener l) {
+        contentSelectionChangedListenerList.remove
+            (ContentSelectionChangedListener.class, l);
+    }
+
+    /**
+     * Returns an array of the gloabl ContentSelectionChangedListeners.
+     */
+    protected Object[] getContentSelectionChangedListeners() {
+        return contentSelectionChangedListenerList.getListenerList();
+    }
+
+    /**
+     * Called by the ContentManager of a shadow tree to indicate some
+     * selected nodes have changed.
+     */
+    void shadowTreeSelectedContentChanged(Set deselected, Set selected) {
+        Iterator i = deselected.iterator();
+        while (i.hasNext()) {
+            Node n = (Node) i.next();
+            if (n.getNodeType() == Node.ELEMENT_NODE) {
+                unbind((Element) n);
+            }
+        }
+        i = selected.iterator();
+        while (i.hasNext()) {
+            Node n = (Node) i.next();
+            if (n.getNodeType() == Node.ELEMENT_NODE) {
+                bind((Element) n);
+            }
+        }
+    }
+
+    /**
+     * Adds the specified BindingListener to the global listener list.
+     */
+    public void addBindingListener(BindingListener l) {
+        bindingListenerList.add(BindingListener.class, l);
+    }
+
+    /**
+     * Removes the specified BindingListener from the global listener list.
+     */
+    public void removeBindingListener(BindingListener l) {
+        bindingListenerList.remove(BindingListener.class, l);
+    }
+
+    /**
+     * Dispatches a BindingEvent the registered listeners.
+     * @param bindableElement the bindable element whose binding has changed
+     * @param shadowTree the new shadow tree of the bindable element
+     */
+    protected void dispatchBindingChangedEvent(Element bindableElement,
+                                               Element shadowTree) {
+        Object[] ls = bindingListenerList.getListenerList();
+        for (int i = ls.length - 2; i >= 0; i -= 2) {
+            BindingListener l = (BindingListener) ls[i + 1];
+            l.bindingChanged(bindableElement, shadowTree);
+        }
+    }
+
+    /**
+     * Returns whether the given definition element is the active one
+     * for its element name.
+     */
+    protected boolean isActiveDefinition(XBLOMDefinitionElement def,
+                                         Element imp) {
+        DefinitionRecord defRec = (DefinitionRecord) definitions.get(def, imp);
+        if (defRec == null) {
+            return false;
+        }
+        return defRec == getActiveDefinition(defRec.namespaceURI,
+                                             defRec.localName);
+    }
+
+    /**
+     * Record class for storing information about an XBL definition.
+     */
+    protected class DefinitionRecord implements Comparable {
+
+        /**
+         * The namespace URI.
+         */
+        public String namespaceURI;
+
+        /**
+         * The local name.
+         */
+        public String localName;
+
+        /**
+         * The definition element.
+         */
+        public XBLOMDefinitionElement definition;
+
+        /**
+         * The template element for this definition.
+         */
+        public XBLOMTemplateElement template;
+
+        /**
+         * The import element that imported this definition.
+         */
+        public Element importElement;
+
+        /**
+         * Creates a new DefinitionRecord.
+         */
+        public DefinitionRecord(String ns,
+                                String ln,
+                                XBLOMDefinitionElement def,
+                                XBLOMTemplateElement t,
+                                Element imp) {
+            namespaceURI = ns;
+            localName = ln;
+            definition = def;
+            template = t;
+            importElement = imp;
+        }
+
+        /**
+         * Returns whether two definition records are the same.
+         */
+        public boolean equals(Object other) {
+            return compareTo(other) == 0;
+        }
+
+        /**
+         * Compares two definition records.
+         */
+        public int compareTo(Object other) {
+            DefinitionRecord rec = (DefinitionRecord) other;
+            AbstractNode n1, n2;
+            if (importElement == null) {
+                n1 = definition;
+                if (rec.importElement == null) {
+                    n2 = rec.definition;
+                } else {
+                    n2 = (AbstractNode) rec.importElement;
+                }
+            } else if (rec.importElement == null) {
+                n1 = (AbstractNode) importElement;
+                n2 = rec.definition;
+            } else if (definition.getOwnerDocument()
+                        == rec.definition.getOwnerDocument()) {
+                n1 = definition;
+                n2 = rec.definition;
+            } else {
+                n1 = (AbstractNode) importElement;
+                n2 = (AbstractNode) rec.importElement;
+            }
+            short comp = n1.compareDocumentPosition(n2);
+            if ((comp & AbstractNode.DOCUMENT_POSITION_PRECEDING) != 0) {
+                return -1;
+            }
+            if ((comp & AbstractNode.DOCUMENT_POSITION_FOLLOWING) != 0) {
+                return 1;
+            }
+            return 0;
+        }
+    }
+
+    /**
+     * Record class for storing information about an XBL import.
+     */
+    protected class ImportRecord {
+
+        /**
+         * The import element.
+         */
+        public Element importElement;
+
+        /**
+         * The imported tree.
+         */
+        public Node node;
+
+        /**
+         * The DOM node inserted listener for definitions accessed through
+         * this import.
+         */
+        public DefNodeInsertedListener defNodeInsertedListener;
+
+        /**
+         * The DOM node removed listener for definitions accessed through
+         * this import.
+         */
+        public DefNodeRemovedListener defNodeRemovedListener;
+
+        /**
+         * The DOM attribute mutation listener for definitions accessed through
+         * this import.
+         */
+        public DefAttrListener defAttrListener;
+
+        /**
+         * The DOM node inserted listener for the imported tree.
+         */
+        public ImportInsertedListener importInsertedListener;
+
+        /**
+         * The DOM node removed listener for the imported tree.
+         */
+        public ImportRemovedListener importRemovedListener;
+
+        /**
+         * The DOM subtree modified listener for the imported tree.
+         */
+        public ImportSubtreeListener importSubtreeListener;
+
+        /**
+         * The DOM subtree modified listener for templates of definitions
+         * accessed through this import.
+         */
+        public TemplateMutationListener templateMutationListener;
+
+        /**
+         * Creates a new ImportRecord.
+         */
+        public ImportRecord(Element imp, Node n) {
+            importElement = imp;
+            node = n;
+            defNodeInsertedListener = new DefNodeInsertedListener(imp);
+            defNodeRemovedListener = new DefNodeRemovedListener(imp);
+            defAttrListener = new DefAttrListener(imp);
+            importInsertedListener = new ImportInsertedListener(imp);
+            importRemovedListener = new ImportRemovedListener();
+            importSubtreeListener
+                = new ImportSubtreeListener(imp, importRemovedListener);
+            templateMutationListener = new TemplateMutationListener(imp);
+        }
+    }
+
+    /**
+     * DOM node inserted listener for imported XBL trees.
+     */
+    protected class ImportInsertedListener implements EventListener {
+
+        /**
+         * The import element.
+         */
+        protected Element importElement;
+
+        /**
+         * Creates a new ImportInsertedListener.
+         */
+        public ImportInsertedListener(Element importElement) {
+            this.importElement = importElement;
+        }
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            EventTarget target = evt.getTarget();
+            if (target instanceof XBLOMDefinitionElement) {
+                XBLOMDefinitionElement def = (XBLOMDefinitionElement) target;
+                addDefinition(def.getElementNamespaceURI(),
+                              def.getElementLocalName(),
+                              def,
+                              importElement);
+            }
+        }
+    }
+
+    /**
+     * DOM node removed listener for imported XBL trees.
+     */
+    protected class ImportRemovedListener implements EventListener {
+
+        /**
+         * List of definition elements to be removed from the document.
+         */
+        protected LinkedList toBeRemoved = new LinkedList();
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            toBeRemoved.add(evt.getTarget());
+        }
+    }
+
+    /**
+     * DOM subtree listener for imported XBL trees.
+     */
+    protected class ImportSubtreeListener implements EventListener {
+
+        /**
+         * The import element.
+         */
+        protected Element importElement;
+
+        /**
+         * The ImportedRemovedListener to check for to-be-removed definitions.
+         */
+        protected ImportRemovedListener importRemovedListener;
+
+        /**
+         * Creates a new ImportSubtreeListener.
+         */
+        public ImportSubtreeListener(Element imp,
+                                     ImportRemovedListener irl) {
+            importElement = imp;
+            importRemovedListener = irl;
+        }
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            Object[] defs = importRemovedListener.toBeRemoved.toArray();
+            importRemovedListener.toBeRemoved.clear();
+            for (int i = 0; i < defs.length; i++) {
+                XBLOMDefinitionElement def = (XBLOMDefinitionElement) defs[i];
+                DefinitionRecord defRec
+                    = (DefinitionRecord) definitions.get(def, importElement);
+                removeDefinition(defRec);
+            }
+        }
+    }
+
+    /**
+     * DOM node inserted listener for the document.
+     */
+    protected class DocInsertedListener implements EventListener {
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            EventTarget target = evt.getTarget();
+            if (target instanceof XBLOMDefinitionElement) {
+                // only handle definition elements in document-level scope
+                if (getXblBoundElement((Node) target) == null) {       // ??? suspect cast ???
+                    XBLOMDefinitionElement def
+                        = (XBLOMDefinitionElement) target;
+                    if (def.getAttributeNS(null, XBL_REF_ATTRIBUTE).length()
+                            == 0) {
+                        addDefinition(def.getElementNamespaceURI(),
+                                      def.getElementLocalName(),
+                                      def,
+                                      null);
+                    } else {
+                        addDefinitionRef(def);
+                    }
+                }
+            } else if (target instanceof XBLOMImportElement) {
+                // only handle import elements in document-level scope
+                if (getXblBoundElement((Node) target) == null) {      // ??? suspect cast ???
+                    addImport((Element) target);
+                }
+            } else {
+                evt = XBLEventSupport.getUltimateOriginalEvent(evt);
+                target = evt.getTarget();
+                Node parent = getXblParentNode((Node) target);
+                if (parent != null) {
+                    invalidateChildNodes(parent);
+                }
+                if (target instanceof BindableElement) {
+                    // Only bind it if it's not the descendent of a bound
+                    // element.  If it is, and this new element will be
+                    // selected by an xbl:content element in the shadow tree,
+                    // the ContentManager will bind it.
+                    for (Node n = ((Node) target).getParentNode();
+                            n != null;
+                            n = n.getParentNode()) {
+                        if (n instanceof BindableElement
+                                && getRecord(n).definitionElement != null) {
+                            return;
+                        }
+                    }
+                    bind((Element) target);
+                }
+            }
+        }
+    }
+
+    /**
+     * DOM node removed listener for the document.
+     */
+    protected class DocRemovedListener implements EventListener {
+
+        /**
+         * List of definition elements to be removed from the document.
+         */
+        protected LinkedList defsToBeRemoved = new LinkedList();
+
+        /**
+         * List of import elements to be removed from the document.
+         */
+        protected LinkedList importsToBeRemoved = new LinkedList();
+
+        /**
+         * List of nodes to have their XBL child lists invalidated.
+         */
+        protected LinkedList nodesToBeInvalidated = new LinkedList();
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            EventTarget target = evt.getTarget();
+            if (target instanceof XBLOMDefinitionElement) {
+                // only handle definition elements in document-level scope
+                if (getXblBoundElement((Node) target) == null) {
+                    defsToBeRemoved.add(target);
+                }
+            } else if (target instanceof XBLOMImportElement) {
+                // only handle import elements in document-level scope
+                if (getXblBoundElement((Node) target) == null) {
+                    importsToBeRemoved.add(target);
+                }
+            }
+
+            Node parent = getXblParentNode((Node) target);
+            if (parent != null) {
+                nodesToBeInvalidated.add(parent);
+            }
+        }
+    }
+
+    /**
+     * DOM subtree mutation listener for the document.
+     */
+    protected class DocSubtreeListener implements EventListener {
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            Object[] defs = docRemovedListener.defsToBeRemoved.toArray();
+            docRemovedListener.defsToBeRemoved.clear();
+            for (int i = 0; i < defs.length; i++) {
+                XBLOMDefinitionElement def = (XBLOMDefinitionElement) defs[i];
+                if (def.getAttributeNS(null, XBL_REF_ATTRIBUTE).length() == 0) {
+                    DefinitionRecord defRec
+                        = (DefinitionRecord) definitions.get(def, null);
+                    removeDefinition(defRec);
+                } else {
+                    removeDefinitionRef(def);
+                }
+            }
+
+            Object[] imps = docRemovedListener.importsToBeRemoved.toArray();
+            docRemovedListener.importsToBeRemoved.clear();
+            for (int i = 0; i < imps.length; i++) {
+                removeImport((Element) imps[i]);
+            }
+
+            Object[] nodes = docRemovedListener.nodesToBeInvalidated.toArray();
+            docRemovedListener.nodesToBeInvalidated.clear();
+            for (int i = 0; i < nodes.length; i++) {
+                invalidateChildNodes((Node) nodes[i]);
+            }
+        }
+    }
+
+    /**
+     * DOM mutation listener for template elements.
+     */
+    protected class TemplateMutationListener implements EventListener {
+
+        /**
+         * The import element.
+         */
+        protected Element importElement;
+
+        /**
+         * Creates a new TemplateMutationListener.
+         */
+        public TemplateMutationListener(Element imp) {
+            importElement = imp;
+        }
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            Node n = (Node) evt.getTarget();
+            while (n != null && !(n instanceof XBLOMDefinitionElement)) {
+                n = n.getParentNode();
+            }
+
+            DefinitionRecord defRec
+                = (DefinitionRecord) definitions.get(n, importElement);
+            if (defRec == null) {
+                return;
+            }
+
+            rebind(defRec.namespaceURI, defRec.localName,
+                   document.getDocumentElement());
+        }
+    }
+
+    /**
+     * DOM attribute mutation listener for definition elements.
+     */
+    protected class DefAttrListener implements EventListener {
+
+        /**
+         * The import element.
+         */
+        protected Element importElement;
+
+        /**
+         * Creates a new DefAttrListener.
+         */
+        public DefAttrListener(Element imp) {
+            importElement = imp;
+        }
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            EventTarget target = evt.getTarget();
+            if (!(target instanceof XBLOMDefinitionElement)) {
+                return;
+            }
+
+            XBLOMDefinitionElement def = (XBLOMDefinitionElement) target;
+            if (!isActiveDefinition(def, importElement)) {
+                return;
+            }
+
+            MutationEvent mevt = (MutationEvent) evt;
+            String attrName = mevt.getAttrName();
+            if (attrName.equals(XBL_ELEMENT_ATTRIBUTE)) {
+                DefinitionRecord defRec =
+                    (DefinitionRecord) definitions.get(def, importElement);
+                removeDefinition(defRec);
+
+                addDefinition(def.getElementNamespaceURI(),
+                              def.getElementLocalName(),
+                              def,
+                              importElement);
+            } else if (attrName.equals(XBL_REF_ATTRIBUTE)) {
+                if (mevt.getNewValue().length() != 0) {
+                    DefinitionRecord defRec =
+                        (DefinitionRecord) definitions.get(def, importElement);
+                    removeDefinition(defRec);
+                    addDefinitionRef(def);
+                }
+            }
+        }
+    }
+
+    /**
+     * DOM node inserted listener for definition elements.
+     */
+    protected class DefNodeInsertedListener implements EventListener {
+
+        /**
+         * The import element.
+         */
+        protected Element importElement;
+
+        /**
+         * Creates a new DefNodeInsertedListener.
+         */
+        public DefNodeInsertedListener(Element imp) {
+            importElement = imp;
+        }
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            MutationEvent mevt = (MutationEvent) evt;
+            Node parent = mevt.getRelatedNode();
+            if (!(parent instanceof XBLOMDefinitionElement)) {
+                return;
+            }
+
+            EventTarget target = evt.getTarget();
+            if (!(target instanceof XBLOMTemplateElement)) {
+                return;
+            }
+            XBLOMTemplateElement template = (XBLOMTemplateElement) target;
+
+            DefinitionRecord defRec
+                = (DefinitionRecord) definitions.get(parent, importElement);
+            if (defRec == null) {
+                return;
+            }
+
+            ImportRecord ir = (ImportRecord) imports.get(importElement);
+
+            if (defRec.template != null) {
+                for (Node n = parent.getFirstChild();
+                        n != null;
+                        n = n.getNextSibling()) {
+                    if (n == template) {
+                        removeTemplateElementListeners(defRec.template, ir);
+                        defRec.template = template;
+                        break;
+                    } else if (n == defRec.template) {
+                        return;
+                    }
+                }
+            } else {
+                defRec.template = template;
+            }
+            addTemplateElementListeners(template, ir);
+            rebind(defRec.namespaceURI, defRec.localName,
+                   document.getDocumentElement());
+        }
+    }
+
+    /**
+     * DOM node removed listener for definition elements.
+     */
+    protected class DefNodeRemovedListener implements EventListener {
+
+        /**
+         * The import element.
+         */
+        protected Element importElement;
+
+        /**
+         * Creates a new DefNodeRemovedListener.
+         */
+        public DefNodeRemovedListener(Element imp) {
+            importElement = imp;
+        }
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            MutationEvent mevt = (MutationEvent) evt;
+            Node parent = mevt.getRelatedNode();
+            if (!(parent instanceof XBLOMDefinitionElement)) {
+                return;
+            }
+
+            EventTarget target = evt.getTarget();
+            if (!(target instanceof XBLOMTemplateElement)) {
+                return;
+            }
+            XBLOMTemplateElement template = (XBLOMTemplateElement) target;
+
+            DefinitionRecord defRec
+                = (DefinitionRecord) definitions.get(parent, importElement);
+            if (defRec == null || defRec.template != template) {
+                return;
+            }
+
+            ImportRecord ir = (ImportRecord) imports.get(importElement);
+
+            removeTemplateElementListeners(template, ir);
+            defRec.template = null;
+
+            for (Node n = template.getNextSibling();
+                    n != null;
+                    n = n.getNextSibling()) {
+                if (n instanceof XBLOMTemplateElement) {
+                    defRec.template = (XBLOMTemplateElement) n;
+                    break;
+                }
+            }
+
+            addTemplateElementListeners(defRec.template, ir);
+            rebind(defRec.namespaceURI, defRec.localName,
+                   document.getDocumentElement());
+        }
+    }
+
+    /**
+     * DOM attribute mutation listener for import elements.
+     */
+    protected class ImportAttrListener implements EventListener {
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            EventTarget target = evt.getTarget();
+            if (target != evt.getCurrentTarget()) {
+                return;
+            }
+
+            MutationEvent mevt = (MutationEvent) evt;
+            if (mevt.getAttrName().equals(XBL_BINDINGS_ATTRIBUTE)) {
+                Element imp = (Element) target;
+                removeImport(imp);
+                addImport(imp);
+            }
+        }
+    }
+
+    /**
+     * DOM attribute mutation listener for referencing definition elements.
+     */
+    protected class RefAttrListener implements EventListener {
+
+        /**
+         * Handles the event.
+         */
+        public void handleEvent(Event evt) {
+            EventTarget target = evt.getTarget();
+            if (target != evt.getCurrentTarget()) {
+                return;
+            }
+
+            MutationEvent mevt = (MutationEvent) evt;
+            if (mevt.getAttrName().equals(XBL_REF_ATTRIBUTE)) {
+                Element defRef = (Element) target;
+                removeDefinitionRef(defRef);
+                if (mevt.getNewValue().length() == 0) {
+                    XBLOMDefinitionElement def
+                        = (XBLOMDefinitionElement) defRef;
+                    String ns = def.getElementNamespaceURI();
+                    String ln = def.getElementLocalName();
+                    addDefinition(ns, ln,
+                                  (XBLOMDefinitionElement) defRef, null);
+                } else {
+                    addDefinitionRef(defRef);
+                }
+            }
+        }
+    }
+
+    /**
+     * XBL record.
+     */
+    protected class XBLRecord {
+
+        /**
+         * The node.
+         */
+        public Node node;
+
+        /**
+         * The xblChildNodes NodeList for this node.
+         */
+        public XblChildNodes childNodes;
+
+        /**
+         * The xblScopedChildNodes NodeList for this node.
+         */
+        public XblScopedChildNodes scopedChildNodes;
+
+        /**
+         * The content element which caused this node to appear in the
+         * flattened tree.
+         */
+        public XBLOMContentElement contentElement;
+
+        /**
+         * The definition element that applies to this element.
+         */
+        public XBLOMDefinitionElement definitionElement;
+
+        /**
+         * The bound element that owns this shadow tree, if this node
+         * is an XBLOMShadowTreeElement.
+         */
+        public BindableElement boundElement;
+
+        /**
+         * Whether the next/previous links are valid.
+         */
+        public boolean linksValid;
+
+        /**
+         * The following sibling in the flattened tree.
+         */
+        public Node nextSibling;
+
+        /**
+         * The previous sibling in the flattened tree.
+         */
+        public Node previousSibling;
+    }
+
+    /**
+     * To iterate over the XBL child nodes.
+     */
+    protected class XblChildNodes implements NodeList {
+
+        /**
+         * The XBLRecord.
+         */
+        protected XBLRecord record;
+
+        /**
+         * The nodes.
+         */
+        protected List nodes;
+
+        /**
+         * The number of nodes.
+         */
+        protected int size;
+
+        /**
+         * Creates a new XblChildNodes.
+         */
+        public XblChildNodes(XBLRecord rec) {
+            record = rec;
+            nodes = new ArrayList();
+            size = -1;
+        }
+
+        /**
+         * Update the NodeList.
+         */
+        protected void update() {
+            size = 0;
+            Node shadowTree = getXblShadowTree(record.node);
+            Node last = null;
+            Node m = shadowTree == null ? record.node.getFirstChild()
+                                        : shadowTree.getFirstChild();
+            while (m != null) {
+                last = collectXblChildNodes(m, last);
+                m = m.getNextSibling();
+            }
+            if (last != null) {
+                XBLRecord rec = getRecord(last);
+                rec.nextSibling = null;
+                rec.linksValid = true;
+            }
+        }
+
+        /**
+         * Find the XBL child nodes of this element.
+         */
+        protected Node collectXblChildNodes(Node n, Node prev) {
+            boolean isChild = false;
+            if (n.getNodeType() == Node.ELEMENT_NODE) {
+                if (!XBL_NAMESPACE_URI.equals(n.getNamespaceURI())) {
+                    isChild = true;
+                } else if (n instanceof XBLOMContentElement) {
+                    ContentManager cm = getContentManager(n);
+                    if (cm != null) {
+                        NodeList selected =
+                            cm.getSelectedContent((XBLOMContentElement) n);
+                        for (int i = 0; i < selected.getLength(); i++) {
+                            prev = collectXblChildNodes(selected.item(i),
+                                                        prev);
+                        }
+                    }
+                }
+            } else {
+                isChild = true;
+            }
+            if (isChild) {
+                nodes.add(n);
+                size++;
+                if (prev != null) {
+                    XBLRecord rec = getRecord(prev);
+                    rec.nextSibling = n;
+                    rec.linksValid = true;
+                }
+                XBLRecord rec = getRecord(n);
+                rec.previousSibling = prev;
+                rec.linksValid = true;
+                prev = n;
+            }
+            return prev;
+        }
+
+        /**
+         * Mark the xblNextSibling and xblPreviousSibling variables
+         * on each node in the list as invalid, then invalidate the
+         * NodeList.
+         */
+        public void invalidate() {
+            for (int i = 0; i < size; i++) {
+                XBLRecord rec = getRecord((Node) nodes.get(i));
+                rec.previousSibling = null;
+                rec.nextSibling = null;
+                rec.linksValid = false;
+            }
+            nodes.clear();
+            size = -1;
+        }
+
+        /**
+         * Returns the first node in the list.
+         */
+        public Node getFirstNode() {
+            if (size == -1) {
+                update();
+            }
+            return size == 0 ? null : (Node) nodes.get(0);
+        }
+
+        /**
+         * Returns the last node in the list.
+         */
+        public Node getLastNode() {
+            if (size == -1) {
+                update();
+            }
+            return size == 0 ? null : (Node) nodes.get( nodes.size() -1 );
+        }
+
+        /**
+         * <b>DOM</b>: Implements {@link org.w3c.dom.NodeList#item(int)}.
+         */
+        public Node item(int index) {
+            if (size == -1) {
+                update();
+            }
+            if (index < 0 || index >= size) {
+                return null;
+            }
+            return (Node) nodes.get(index);
+        }
+
+        /**
+         * <b>DOM</b>: Implements {@link org.w3c.dom.NodeList#getLength()}.
+         */
+        public int getLength() {
+            if (size == -1) {
+                update();
+            }
+            return size;
+        }
+    }
+
+    /**
+     * To iterate over the scoped XBL child nodes.
+     */
+    protected class XblScopedChildNodes extends XblChildNodes {
+
+        /**
+         * Creates a new XblScopedChildNodes object.
+         */
+        public XblScopedChildNodes(XBLRecord rec) {
+            super(rec);
+        }
+
+        /**
+         * Update the NodeList.
+         */
+        protected void update() {
+            size = 0;
+            Node shadowTree = getXblShadowTree(record.node);
+            Node n = shadowTree == null ? record.node.getFirstChild()
+                                        : shadowTree.getFirstChild();
+            while (n != null) {
+                collectXblScopedChildNodes(n);
+                n = n.getNextSibling();
+            }
+        }
+
+        /**
+         * Find the XBL child nodes of this element.
+         */
+        protected void collectXblScopedChildNodes(Node n) {
+            boolean isChild = false;
+            if (n.getNodeType() == Node.ELEMENT_NODE) {
+                if (!n.getNamespaceURI().equals(XBL_NAMESPACE_URI)) {
+                    isChild = true;
+                } else if (n instanceof XBLOMContentElement) {
+                    ContentManager cm = getContentManager(n);
+                    if (cm != null) {
+                        NodeList selected =
+                            cm.getSelectedContent((XBLOMContentElement) n);
+                        for (int i = 0; i < selected.getLength(); i++) {
+                            collectXblScopedChildNodes(selected.item(i));
+                        }
+                    }
+                }
+            } else {
+                isChild = true;
+            }
+            if (isChild) {
+                nodes.add(n);
+                size++;
+            }
+        }
+    }
+}