summaryrefslogtreecommitdiffstats
path: root/dom/xul/templates/nsXULContentBuilder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xul/templates/nsXULContentBuilder.cpp')
-rw-r--r--dom/xul/templates/nsXULContentBuilder.cpp1976
1 files changed, 1976 insertions, 0 deletions
diff --git a/dom/xul/templates/nsXULContentBuilder.cpp b/dom/xul/templates/nsXULContentBuilder.cpp
new file mode 100644
index 000000000..71c285cc4
--- /dev/null
+++ b/dom/xul/templates/nsXULContentBuilder.cpp
@@ -0,0 +1,1976 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsContentCID.h"
+#include "nsIDocument.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMXULDocument.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsIServiceManager.h"
+#include "nsIXULDocument.h"
+
+#include "nsContentSupportMap.h"
+#include "nsRDFConMemberTestNode.h"
+#include "nsRDFPropertyTestNode.h"
+#include "nsXULSortService.h"
+#include "nsTemplateRule.h"
+#include "nsTemplateMap.h"
+#include "nsTArray.h"
+#include "nsXPIDLString.h"
+#include "nsGkAtoms.h"
+#include "nsXULContentUtils.h"
+#include "nsXULElement.h"
+#include "nsXULTemplateBuilder.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsAttrName.h"
+#include "nsNodeUtils.h"
+#include "mozAutoDocUpdate.h"
+#include "nsTextNode.h"
+#include "mozilla/dom/Element.h"
+
+#include "PLDHashTable.h"
+#include "rdf.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+//
+// Return values for EnsureElementHasGenericChild()
+//
+#define NS_ELEMENT_GOT_CREATED NS_RDF_NO_VALUE
+#define NS_ELEMENT_WAS_THERE NS_OK
+
+//----------------------------------------------------------------------
+//
+// nsXULContentBuilder
+//
+
+/**
+ * The content builder generates DOM nodes from a template. The actual content
+ * generation is done entirely inside BuildContentFromTemplate.
+ *
+ * Content generation is centered around the generation node (the node with
+ * uri="?member" on it). Nodes above the generation node are unique and
+ * generated only once. BuildContentFromTemplate will be passed the unique
+ * flag as an argument for content at this point and will recurse until it
+ * finds the generation node.
+ *
+ * Once the generation node has been found, the results for that content node
+ * are added to the content map, stored in mContentSupportMap.
+ *
+ * If recursion is allowed, generation continues, where the generation node
+ * becomes the container to insert into.
+ */
+class nsXULContentBuilder : public nsXULTemplateBuilder
+{
+public:
+ // nsIXULTemplateBuilder interface
+ NS_IMETHOD CreateContents(nsIContent* aElement, bool aForceCreation) override;
+
+ NS_IMETHOD HasGeneratedContent(nsIRDFResource* aResource,
+ nsIAtom* aTag,
+ bool* aGenerated) override;
+
+ NS_IMETHOD GetResultForContent(nsIDOMElement* aContent,
+ nsIXULTemplateResult** aResult) override;
+
+ // nsIMutationObserver interface
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+protected:
+ friend nsresult
+ NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ nsXULContentBuilder();
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCb) const override
+ {
+ mSortState.Traverse(aCb);
+ }
+
+ virtual void Uninit(bool aIsFinal) override;
+
+ // Implementation methods
+ nsresult
+ OpenContainer(nsIContent* aElement);
+
+ nsresult
+ CloseContainer(nsIContent* aElement);
+
+ /**
+ * Build content from a template for a given result. This will be called
+ * recursively or on demand and will be called for every node in the
+ * generated content tree.
+ */
+ nsresult
+ BuildContentFromTemplate(nsIContent *aTemplateNode,
+ nsIContent *aResourceNode,
+ nsIContent *aRealNode,
+ bool aIsUnique,
+ bool aIsSelfReference,
+ nsIXULTemplateResult* aChild,
+ bool aNotify,
+ nsTemplateMatch* aMatch,
+ nsIContent** aContainer,
+ int32_t* aNewIndexInContainer);
+
+ /**
+ * Copy the attributes from the template node to the node generated
+ * from it, performing any substitutions.
+ *
+ * @param aTemplateNode node within template
+ * @param aRealNode generated node to set attibutes upon
+ * @param aResult result to look up variable->value bindings in
+ * @param aNotify true to notify of DOM changes
+ */
+ nsresult
+ CopyAttributesToElement(nsIContent* aTemplateNode,
+ nsIContent* aRealNode,
+ nsIXULTemplateResult* aResult,
+ bool aNotify);
+
+ /**
+ * Add any necessary persistent attributes (persist="...") from the
+ * local store to a generated node.
+ *
+ * @param aTemplateNode node within template
+ * @param aRealNode generated node to set persisted attibutes upon
+ * @param aResult result to look up variable->value bindings in
+ */
+ nsresult
+ AddPersistentAttributes(Element* aTemplateNode,
+ nsIXULTemplateResult* aResult,
+ nsIContent* aRealNode);
+
+ /**
+ * Recalculate any attributes that have variable references. This will
+ * be called when a binding has been changed to update the attributes.
+ * The attributes are copied from the node aTemplateNode in the template
+ * to the generated node aRealNode, using the values from the result
+ * aResult. This method will operate recursively.
+ *
+ * @param aTemplateNode node within template
+ * @param aRealNode generated node to set attibutes upon
+ * @param aResult result to look up variable->value bindings in
+ */
+ nsresult
+ SynchronizeUsingTemplate(nsIContent *aTemplateNode,
+ nsIContent* aRealNode,
+ nsIXULTemplateResult* aResult);
+
+ /**
+ * Remove the generated node aContent from the DOM and the hashtables
+ * used by the content builder.
+ */
+ nsresult
+ RemoveMember(nsIContent* aContent);
+
+ /**
+ * Create the appropriate generated content for aElement, by calling
+ * CreateContainerContents.
+ *
+ * @param aElement element to generate content inside
+ * @param aForceCreation true to force creation for closed items such as menus
+ */
+ nsresult
+ CreateTemplateAndContainerContents(nsIContent* aElement,
+ bool aForceCreation);
+
+ /**
+ * Generate the results for a template, by calling
+ * CreateContainerContentsForQuerySet for each queryset.
+ *
+ * @param aElement element to generate content inside
+ * @param aResult reference point for query
+ * @param aForceCreation true to force creation for closed items such as menus
+ * @param aNotify true to notify of DOM changes as each element is inserted
+ * @param aNotifyAtEnd notify at the end of all DOM changes
+ */
+ nsresult
+ CreateContainerContents(nsIContent* aElement,
+ nsIXULTemplateResult* aResult,
+ bool aForceCreation,
+ bool aNotify,
+ bool aNotifyAtEnd);
+
+ /**
+ * Generate the results for a query.
+ *
+ * @param aElement element to generate content inside
+ * @param aResult reference point for query
+ * @param aNotify true to notify of DOM changes
+ * @param aContainer container content was added inside
+ * @param aNewIndexInContainer index with container in which content was added
+ */
+ nsresult
+ CreateContainerContentsForQuerySet(nsIContent* aElement,
+ nsIXULTemplateResult* aResult,
+ bool aNotify,
+ nsTemplateQuerySet* aQuerySet,
+ nsIContent** aContainer,
+ int32_t* aNewIndexInContainer);
+
+ /**
+ * Check if an element with a particular tag exists with a container.
+ * If it is not present, append a new element with that tag into the
+ * container.
+ *
+ * @param aParent parent container
+ * @param aNameSpaceID namespace of tag to locate or create
+ * @param aTag tag to locate or create
+ * @param aNotify true to notify of DOM changes
+ * @param aResult set to the found or created node.
+ */
+ nsresult
+ EnsureElementHasGenericChild(nsIContent* aParent,
+ int32_t aNameSpaceID,
+ nsIAtom* aTag,
+ bool aNotify,
+ nsIContent** aResult);
+
+ bool
+ IsOpen(nsIContent* aElement);
+
+ nsresult
+ RemoveGeneratedContent(nsIContent* aElement);
+
+ nsresult
+ GetElementsForResult(nsIXULTemplateResult* aResult,
+ nsCOMArray<nsIContent>& aElements);
+
+ nsresult
+ CreateElement(int32_t aNameSpaceID,
+ nsIAtom* aTag,
+ Element** aResult);
+
+ /**
+ * Set the container and empty attributes on a node. If
+ * aIgnoreNonContainers is true, then the element is not changed
+ * for non-containers. Otherwise, the container attribute will be set to
+ * false.
+ *
+ * @param aElement element to set attributes on
+ * @param aResult result to use to determine state of attributes
+ * @param aIgnoreNonContainers true to not change for non-containers
+ * @param aNotify true to notify of DOM changes
+ */
+ nsresult
+ SetContainerAttrs(nsIContent *aElement,
+ nsIXULTemplateResult* aResult,
+ bool aIgnoreNonContainers,
+ bool aNotify);
+
+ virtual nsresult
+ RebuildAll() override;
+
+ // GetInsertionLocations, ReplaceMatch and SynchronizeResult are inherited
+ // from nsXULTemplateBuilder
+
+ /**
+ * Return true if the result can be inserted into the template as
+ * generated content. For the content builder, aLocations will be set
+ * to the list of containers where the content should be inserted.
+ */
+ virtual bool
+ GetInsertionLocations(nsIXULTemplateResult* aOldResult,
+ nsCOMArray<nsIContent>** aLocations) override;
+
+ /**
+ * Remove the content associated with aOldResult which no longer matches,
+ * and/or generate content for a new match.
+ */
+ virtual nsresult
+ ReplaceMatch(nsIXULTemplateResult* aOldResult,
+ nsTemplateMatch* aNewMatch,
+ nsTemplateRule* aNewMatchRule,
+ void *aContext) override;
+
+ /**
+ * Synchronize a result bindings with the generated content for that
+ * result. This will be called as a result of the template builder's
+ * ResultBindingChanged method.
+ */
+ virtual nsresult
+ SynchronizeResult(nsIXULTemplateResult* aResult) override;
+
+ /**
+ * Compare a result to a content node. If the generated content for the
+ * result should come before aContent, set aSortOrder to -1. If it should
+ * come after, set sortOrder to 1. If both are equal, set to 0.
+ */
+ nsresult
+ CompareResultToNode(nsIXULTemplateResult* aResult, nsIContent* aContent,
+ int32_t* aSortOrder);
+
+ /**
+ * Insert a generated node into the container where it should go according
+ * to the current sort. aNode is the generated content node and aResult is
+ * the result for the generated node.
+ */
+ nsresult
+ InsertSortedNode(nsIContent* aContainer,
+ nsIContent* aNode,
+ nsIXULTemplateResult* aResult,
+ bool aNotify);
+
+ /**
+ * Maintains a mapping between elements in the DOM and the matches
+ * that they support.
+ */
+ nsContentSupportMap mContentSupportMap;
+
+ /**
+ * Maintains a mapping from an element in the DOM to the template
+ * element that it was created from.
+ */
+ nsTemplateMap mTemplateMap;
+
+ /**
+ * Information about the currently active sort
+ */
+ nsSortState mSortState;
+};
+
+nsresult
+NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+ NS_PRECONDITION(aOuter == nullptr, "no aggregation");
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsresult rv;
+ nsXULContentBuilder* result = new nsXULContentBuilder();
+ NS_ADDREF(result); // stabilize
+
+ rv = result->InitGlobals();
+
+ if (NS_SUCCEEDED(rv))
+ rv = result->QueryInterface(aIID, aResult);
+
+ NS_RELEASE(result);
+ return rv;
+}
+
+nsXULContentBuilder::nsXULContentBuilder()
+{
+ mSortState.initialized = false;
+}
+
+void
+nsXULContentBuilder::Uninit(bool aIsFinal)
+{
+ if (! aIsFinal && mRoot) {
+ nsresult rv = RemoveGeneratedContent(mRoot);
+ if (NS_FAILED(rv))
+ return;
+ }
+
+ // Nuke the content support map completely.
+ mContentSupportMap.Clear();
+ mTemplateMap.Clear();
+
+ mSortState.initialized = false;
+
+ nsXULTemplateBuilder::Uninit(aIsFinal);
+}
+
+nsresult
+nsXULContentBuilder::BuildContentFromTemplate(nsIContent *aTemplateNode,
+ nsIContent *aResourceNode,
+ nsIContent *aRealNode,
+ bool aIsUnique,
+ bool aIsSelfReference,
+ nsIXULTemplateResult* aChild,
+ bool aNotify,
+ nsTemplateMatch* aMatch,
+ nsIContent** aContainer,
+ int32_t* aNewIndexInContainer)
+{
+ // This is the mother lode. Here is where we grovel through an
+ // element in the template, copying children from the template
+ // into the "real" content tree, performing substitution as we go
+ // by looking stuff up using the results.
+ //
+ // |aTemplateNode| is the element in the "template tree", whose
+ // children we will duplicate and move into the "real" content
+ // tree.
+ //
+ // |aResourceNode| is the element in the "real" content tree that
+ // has the "id" attribute set to an result's id. This is
+ // not directly used here, but rather passed down to the XUL
+ // sort service to perform container-level sort.
+ //
+ // |aRealNode| is the element in the "real" content tree to which
+ // the new elements will be copied.
+ //
+ // |aIsUnique| is set to "true" so long as content has been
+ // "unique" (or "above" the resource element) so far in the
+ // template.
+ //
+ // |aIsSelfReference| should be set to "true" for cases where
+ // the reference and member variables are the same, indicating
+ // that the generated node is the same as the reference point,
+ // so generation should not recurse, or else an infinite loop
+ // would occur.
+ //
+ // |aChild| is the result for which we are building content.
+ //
+ // |aNotify| is set to "true" if content should be constructed
+ // "noisily"; that is, whether the document observers should be
+ // notified when new content is added to the content model.
+ //
+ // |aContainer| is an out parameter that will be set to the first
+ // container element in the "real" content tree to which content
+ // was appended.
+ //
+ // |aNewIndexInContainer| is an out parameter that will be set to
+ // the index in aContainer at which new content is first
+ // constructed.
+ //
+ // If |aNotify| is "false", then |aContainer| and
+ // |aNewIndexInContainer| are used to determine where in the
+ // content model new content is constructed. This allows a single
+ // notification to be propagated to document observers.
+ //
+
+ nsresult rv;
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsXULContentBuilder::BuildContentFromTemplate (is unique: %d)",
+ aIsUnique));
+
+ nsAutoString id;
+ aChild->GetId(id);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("Tags: [Template: %s Resource: %s Real: %s] for id %s",
+ nsAtomCString(aTemplateNode->NodeInfo()->NameAtom()).get(),
+ nsAtomCString(aResourceNode->NodeInfo()->NameAtom()).get(),
+ nsAtomCString(aRealNode->NodeInfo()->NameAtom()).get(), NS_ConvertUTF16toUTF8(id).get()));
+ }
+
+ // Iterate through all of the template children, constructing
+ // "real" content model nodes for each "template" child.
+ for (nsIContent* tmplKid = aTemplateNode->GetFirstChild();
+ tmplKid;
+ tmplKid = tmplKid->GetNextSibling()) {
+
+ int32_t nameSpaceID = tmplKid->GetNameSpaceID();
+
+ // Check whether this element is the generation element. The generation
+ // element is the element that is cookie-cutter copied once for each
+ // different result specified by |aChild|.
+ //
+ // Nodes that appear -above- the generation element
+ // (that is, are ancestors of the generation element in the
+ // content model) are unique across all values of |aChild|,
+ // and are created only once.
+ //
+ // Nodes that appear -below- the generation element (that is,
+ // are descendants of the generation element in the content
+ // model), are cookie-cutter copied for each distinct value of
+ // |aChild|.
+ //
+ // For example, in a <tree> template:
+ //
+ // <tree>
+ // <template>
+ // <treechildren> [1]
+ // <treeitem uri="rdf:*"> [2]
+ // <treerow> [3]
+ // <treecell value="rdf:urn:foo" /> [4]
+ // <treecell value="rdf:urn:bar" /> [5]
+ // </treerow>
+ // </treeitem>
+ // </treechildren>
+ // </template>
+ // </tree>
+ //
+ // The <treeitem> element [2] is the generation element. This
+ // element, and all of its descendants ([3], [4], and [5])
+ // will be duplicated for each different |aChild|.
+ // It's ancestor <treechildren> [1] is unique, and
+ // will only be created -once-, no matter how many <treeitem>s
+ // are created below it.
+ //
+ // isUnique will be true for nodes above the generation element,
+ // isGenerationElement will be true for the generation element,
+ // and both will be false for descendants
+ bool isGenerationElement = false;
+ bool isUnique = aIsUnique;
+
+ // We identify the resource element by presence of a
+ // "uri='rdf:*'" attribute. (We also support the older
+ // "uri='...'" syntax.)
+ if (tmplKid->HasAttr(kNameSpaceID_None, nsGkAtoms::uri) && aMatch->IsActive()) {
+ isGenerationElement = true;
+ isUnique = false;
+ }
+
+ MOZ_ASSERT_IF(isGenerationElement, tmplKid->IsElement());
+
+ nsIAtom *tag = tmplKid->NodeInfo()->NameAtom();
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("xultemplate[%p] building %s %s %s",
+ this, nsAtomCString(tag).get(),
+ (isGenerationElement ? "[resource]" : ""),
+ (isUnique ? "[unique]" : "")));
+ }
+
+ // Set to true if the child we're trying to create now
+ // already existed in the content model.
+ bool realKidAlreadyExisted = false;
+
+ nsCOMPtr<nsIContent> realKid;
+ if (isUnique) {
+ // The content is "unique"; that is, we haven't descended
+ // far enough into the template to hit the generation
+ // element yet. |EnsureElementHasGenericChild()| will
+ // conditionally create the element iff it isn't there
+ // already.
+ rv = EnsureElementHasGenericChild(aRealNode, nameSpaceID, tag, aNotify, getter_AddRefs(realKid));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (rv == NS_ELEMENT_WAS_THERE) {
+ realKidAlreadyExisted = true;
+ }
+ else {
+ // Potentially remember the index of this element as the first
+ // element that we've generated. Note that we remember
+ // this -before- we recurse!
+ if (aContainer && !*aContainer) {
+ *aContainer = aRealNode;
+ NS_ADDREF(*aContainer);
+
+ uint32_t indx = aRealNode->GetChildCount();
+
+ // Since EnsureElementHasGenericChild() added us, make
+ // sure to subtract one for our real index.
+ *aNewIndexInContainer = indx - 1;
+ }
+ }
+
+ // Recurse until we get to the resource element. Since
+ // -we're- unique, assume that our child will be
+ // unique. The check for the "resource" element at the top
+ // of the function will trip this to |false| as soon as we
+ // encounter it.
+ rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, true,
+ aIsSelfReference, aChild, aNotify, aMatch,
+ aContainer, aNewIndexInContainer);
+
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else if (isGenerationElement) {
+ // It's the "resource" element. Create a new element using
+ // the namespace ID and tag from the template element.
+ nsCOMPtr<Element> element;
+ rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
+ if (NS_FAILED(rv))
+ return rv;
+ realKid = element.forget();
+
+ // Add the resource element to the content support map so
+ // we can remove the match based on the content node later.
+ mContentSupportMap.Put(realKid, aMatch);
+
+ // Assign the element an 'id' attribute using result's id
+ nsAutoString id;
+ rv = aChild->GetId(id);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = realKid->SetAttr(kNameSpaceID_None, nsGkAtoms::id, id, false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Set up the element's 'container' and 'empty' attributes.
+ SetContainerAttrs(realKid, aChild, true, false);
+ }
+ else if (tag == nsGkAtoms::textnode &&
+ nameSpaceID == kNameSpaceID_XUL) {
+ // <xul:text value="..."> is replaced by text of the
+ // actual value of the 'rdf:resource' attribute for the
+ // given node.
+ // SynchronizeUsingTemplate contains code used to update textnodes,
+ // so make sure to modify both when changing this
+ char16_t attrbuf[128];
+ nsFixedString attrValue(attrbuf, ArrayLength(attrbuf), 0);
+ tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
+ if (!attrValue.IsEmpty()) {
+ nsAutoString value;
+ rv = SubstituteText(aChild, attrValue, value);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<nsTextNode> content =
+ new nsTextNode(mRoot->NodeInfo()->NodeInfoManager());
+
+ content->SetText(value, false);
+
+ rv = aRealNode->AppendChildTo(content, aNotify);
+ if (NS_FAILED(rv)) return rv;
+
+ // XXX Don't bother remembering text nodes as the
+ // first element we've generated?
+ }
+ }
+ else if (tmplKid->IsNodeOfType(nsINode::eTEXT)) {
+ nsCOMPtr<nsIDOMNode> tmplTextNode = do_QueryInterface(tmplKid);
+ if (!tmplTextNode) {
+ NS_ERROR("textnode not implementing nsIDOMNode??");
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIDOMNode> clonedNode;
+ tmplTextNode->CloneNode(false, 1, getter_AddRefs(clonedNode));
+ nsCOMPtr<nsIContent> clonedContent = do_QueryInterface(clonedNode);
+ if (!clonedContent) {
+ NS_ERROR("failed to clone textnode");
+ return NS_ERROR_FAILURE;
+ }
+ rv = aRealNode->AppendChildTo(clonedContent, aNotify);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ // It's just a generic element. Create it!
+ nsCOMPtr<Element> element;
+ rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
+ if (NS_FAILED(rv)) return rv;
+ realKid = element.forget();
+ }
+
+ if (realKid && !realKidAlreadyExisted) {
+ // Potentially remember the index of this element as the
+ // first element that we've generated.
+ if (aContainer && !*aContainer) {
+ *aContainer = aRealNode;
+ NS_ADDREF(*aContainer);
+
+ uint32_t indx = aRealNode->GetChildCount();
+
+ // Since we haven't inserted any content yet, our new
+ // index in the container will be the current count of
+ // elements in the container.
+ *aNewIndexInContainer = indx;
+ }
+
+ // Remember the template kid from which we created the
+ // real kid. This allows us to sync back up with the
+ // template to incrementally build content.
+ mTemplateMap.Put(realKid, tmplKid);
+
+ rv = CopyAttributesToElement(tmplKid, realKid, aChild, false);
+ if (NS_FAILED(rv)) return rv;
+
+ // Add any persistent attributes
+ if (isGenerationElement) {
+ rv = AddPersistentAttributes(tmplKid->AsElement(), aChild,
+ realKid);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // the unique content recurses up above. Also, don't recurse if
+ // this is a self reference (a reference to the same resource)
+ // or we'll end up regenerating the same content.
+ if (!aIsSelfReference && !isUnique) {
+ // this call creates the content inside the generation node,
+ // for example the label below:
+ // <vbox uri="?">
+ // <label value="?title"/>
+ // </vbox>
+ rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, false,
+ false, aChild, false, aMatch,
+ nullptr /* don't care */,
+ nullptr /* don't care */);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isGenerationElement) {
+ // build the next level of children
+ rv = CreateContainerContents(realKid, aChild, false,
+ false, false);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ // We'll _already_ have added the unique elements; but if
+ // it's -not- unique, then use the XUL sort service now to
+ // append the element to the content model.
+ if (! isUnique) {
+ rv = NS_ERROR_UNEXPECTED;
+
+ if (isGenerationElement)
+ rv = InsertSortedNode(aRealNode, realKid, aChild, aNotify);
+
+ if (NS_FAILED(rv)) {
+ rv = aRealNode->AppendChildTo(realKid, aNotify);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to insert element");
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CopyAttributesToElement(nsIContent* aTemplateNode,
+ nsIContent* aRealNode,
+ nsIXULTemplateResult* aResult,
+ bool aNotify)
+{
+ nsresult rv;
+
+ // Copy all attributes from the template to the new element
+ uint32_t numAttribs = aTemplateNode->GetAttrCount();
+
+ for (uint32_t attr = 0; attr < numAttribs; attr++) {
+ const nsAttrName* name = aTemplateNode->GetAttrNameAt(attr);
+ int32_t attribNameSpaceID = name->NamespaceID();
+ // Hold a strong reference here so that the atom doesn't go away
+ // during UnsetAttr.
+ nsCOMPtr<nsIAtom> attribName = name->LocalName();
+
+ // XXXndeakin ignore namespaces until bug 321182 is fixed
+ if (attribName != nsGkAtoms::id && attribName != nsGkAtoms::uri) {
+ // Create a buffer here, because there's a chance that an
+ // attribute in the template is going to be an RDF URI, which is
+ // usually longish.
+ char16_t attrbuf[128];
+ nsFixedString attribValue(attrbuf, ArrayLength(attrbuf), 0);
+ aTemplateNode->GetAttr(attribNameSpaceID, attribName, attribValue);
+ if (!attribValue.IsEmpty()) {
+ nsAutoString value;
+ rv = SubstituteText(aResult, attribValue, value);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // if the string is empty after substitutions, remove the
+ // attribute
+ if (!value.IsEmpty()) {
+ rv = aRealNode->SetAttr(attribNameSpaceID,
+ attribName,
+ name->GetPrefix(),
+ value,
+ aNotify);
+ }
+ else {
+ rv = aRealNode->UnsetAttr(attribNameSpaceID,
+ attribName,
+ aNotify);
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::AddPersistentAttributes(Element* aTemplateNode,
+ nsIXULTemplateResult* aResult,
+ nsIContent* aRealNode)
+{
+ if (!mRoot)
+ return NS_OK;
+
+ nsCOMPtr<nsIRDFResource> resource;
+ nsresult rv = GetResultResource(aResult, getter_AddRefs(resource));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString attribute, persist;
+ aTemplateNode->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
+
+ while (!persist.IsEmpty()) {
+ attribute.Truncate();
+
+ int32_t offset = persist.FindCharInSet(" ,");
+ if (offset > 0) {
+ persist.Left(attribute, offset);
+ persist.Cut(0, offset + 1);
+ }
+ else {
+ attribute = persist;
+ persist.Truncate();
+ }
+
+ attribute.Trim(" ");
+
+ if (attribute.IsEmpty())
+ break;
+
+ nsCOMPtr<nsIAtom> tag;
+ int32_t nameSpaceID;
+
+ RefPtr<mozilla::dom::NodeInfo> ni =
+ aTemplateNode->GetExistingAttrNameFromQName(attribute);
+ if (ni) {
+ tag = ni->NameAtom();
+ nameSpaceID = ni->NamespaceID();
+ }
+ else {
+ tag = NS_Atomize(attribute);
+ NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
+
+ nameSpaceID = kNameSpaceID_None;
+ }
+
+ nsCOMPtr<nsIRDFResource> property;
+ rv = nsXULContentUtils::GetResource(nameSpaceID, tag, getter_AddRefs(property));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRDFNode> target;
+ rv = mDB->GetTarget(resource, property, true, getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (! target)
+ continue;
+
+ nsCOMPtr<nsIRDFLiteral> value = do_QueryInterface(target);
+ NS_ASSERTION(value != nullptr, "unable to stomach that sort of node");
+ if (! value)
+ continue;
+
+ const char16_t* valueStr;
+ rv = value->GetValueConst(&valueStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aRealNode->SetAttr(nameSpaceID, tag, nsDependentString(valueStr),
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::SynchronizeUsingTemplate(nsIContent* aTemplateNode,
+ nsIContent* aRealElement,
+ nsIXULTemplateResult* aResult)
+{
+ // check all attributes on the template node; if they reference a resource,
+ // update the equivalent attribute on the content node
+ nsresult rv;
+ rv = CopyAttributesToElement(aTemplateNode, aRealElement, aResult, true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t count = aTemplateNode->GetChildCount();
+
+ for (uint32_t loop = 0; loop < count; ++loop) {
+ nsIContent *tmplKid = aTemplateNode->GetChildAt(loop);
+
+ if (! tmplKid)
+ break;
+
+ nsIContent *realKid = aRealElement->GetChildAt(loop);
+ if (! realKid)
+ break;
+
+ // check for text nodes and update them accordingly.
+ // This code is similar to that in BuildContentFromTemplate
+ if (tmplKid->NodeInfo()->Equals(nsGkAtoms::textnode,
+ kNameSpaceID_XUL)) {
+ char16_t attrbuf[128];
+ nsFixedString attrValue(attrbuf, ArrayLength(attrbuf), 0);
+ tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
+ if (!attrValue.IsEmpty()) {
+ nsAutoString value;
+ rv = SubstituteText(aResult, attrValue, value);
+ if (NS_FAILED(rv)) return rv;
+ realKid->SetText(value, true);
+ }
+ }
+
+ rv = SynchronizeUsingTemplate(tmplKid, realKid, aResult);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::RemoveMember(nsIContent* aContent)
+{
+ nsCOMPtr<nsIContent> parent = aContent->GetParent();
+ if (parent) {
+ int32_t pos = parent->IndexOf(aContent);
+
+ NS_ASSERTION(pos >= 0, "parent doesn't think this child has an index");
+ if (pos < 0) return NS_OK;
+
+ // Note: RemoveChildAt sets |child|'s document to null so that
+ // it'll get knocked out of the XUL doc's resource-to-element
+ // map.
+ parent->RemoveChildAt(pos, true);
+ }
+
+ // Remove from the content support map.
+ mContentSupportMap.Remove(aContent);
+
+ // Remove from the template map
+ mTemplateMap.Remove(aContent);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CreateTemplateAndContainerContents(nsIContent* aElement,
+ bool aForceCreation)
+{
+ // Generate both 1) the template content for the current element,
+ // and 2) recursive subcontent (if the current element refers to a
+ // container result).
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Info,
+ ("nsXULContentBuilder::CreateTemplateAndContainerContents start - flags: %d",
+ mFlags));
+
+ if (! mQueryProcessor)
+ return NS_OK;
+
+ // for the root element, get the ref attribute and generate content
+ if (aElement == mRoot) {
+ if (! mRootResult) {
+ nsAutoString ref;
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, ref);
+
+ if (! ref.IsEmpty()) {
+ nsresult rv = mQueryProcessor->TranslateRef(mDataSource, ref,
+ getter_AddRefs(mRootResult));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ if (mRootResult) {
+ CreateContainerContents(aElement, mRootResult, aForceCreation,
+ false, true);
+ }
+ }
+ else if (!(mFlags & eDontRecurse)) {
+ // The content map will contain the generation elements (the ones that
+ // are given ids) and only those elements, so get the reference point
+ // from the corresponding match.
+ nsTemplateMatch *match = nullptr;
+ if (mContentSupportMap.Get(aElement, &match))
+ CreateContainerContents(aElement, match->mResult, aForceCreation,
+ false, true);
+ }
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Info,
+ ("nsXULContentBuilder::CreateTemplateAndContainerContents end"));
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CreateContainerContents(nsIContent* aElement,
+ nsIXULTemplateResult* aResult,
+ bool aForceCreation,
+ bool aNotify,
+ bool aNotifyAtEnd)
+{
+ if (!aForceCreation && !IsOpen(aElement))
+ return NS_OK;
+
+ // don't generate children if recursion or child processing isn't allowed
+ if (aResult != mRootResult) {
+ if (mFlags & eDontRecurse)
+ return NS_OK;
+
+ bool mayProcessChildren;
+ nsresult rv = aResult->GetMayProcessChildren(&mayProcessChildren);
+ if (NS_FAILED(rv) || !mayProcessChildren)
+ return rv;
+ }
+
+ nsCOMPtr<nsIRDFResource> refResource;
+ GetResultResource(aResult, getter_AddRefs(refResource));
+ if (! refResource)
+ return NS_ERROR_FAILURE;
+
+ // Avoid re-entrant builds for the same resource.
+ if (IsActivated(refResource))
+ return NS_OK;
+
+ ActivationEntry entry(refResource, &mTop);
+
+ // Compile the rules now, if they haven't been already.
+ if (! mQueriesCompiled) {
+ nsresult rv = CompileQueries();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ if (mQuerySets.Length() == 0)
+ return NS_OK;
+
+ // See if the element's templates contents have been generated:
+ // this prevents a re-entrant call from triggering another
+ // generation.
+ nsXULElement *xulcontent = nsXULElement::FromContent(aElement);
+ if (xulcontent) {
+ if (xulcontent->GetTemplateGenerated())
+ return NS_OK;
+
+ // Now mark the element's contents as being generated so that
+ // any re-entrant calls don't trigger an infinite recursion.
+ xulcontent->SetTemplateGenerated();
+ }
+
+ int32_t newIndexInContainer = -1;
+ nsIContent* container = nullptr;
+
+ int32_t querySetCount = mQuerySets.Length();
+
+ for (int32_t r = 0; r < querySetCount; r++) {
+ nsTemplateQuerySet* queryset = mQuerySets[r];
+
+ nsIAtom* tag = queryset->GetTag();
+ if (tag && tag != aElement->NodeInfo()->NameAtom())
+ continue;
+
+ CreateContainerContentsForQuerySet(aElement, aResult, aNotify, queryset,
+ &container, &newIndexInContainer);
+ }
+
+ if (aNotifyAtEnd && container) {
+ MOZ_AUTO_DOC_UPDATE(container->GetUncomposedDoc(), UPDATE_CONTENT_MODEL,
+ true);
+ nsNodeUtils::ContentAppended(container,
+ container->GetChildAt(newIndexInContainer),
+ newIndexInContainer);
+ }
+
+ NS_IF_RELEASE(container);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CreateContainerContentsForQuerySet(nsIContent* aElement,
+ nsIXULTemplateResult* aResult,
+ bool aNotify,
+ nsTemplateQuerySet* aQuerySet,
+ nsIContent** aContainer,
+ int32_t* aNewIndexInContainer)
+{
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsAutoString id;
+ aResult->GetId(id);
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsXULContentBuilder::CreateContainerContentsForQuerySet start for ref %s\n",
+ NS_ConvertUTF16toUTF8(id).get()));
+ }
+
+ if (! mQueryProcessor)
+ return NS_OK;
+
+ nsCOMPtr<nsISimpleEnumerator> results;
+ nsresult rv = mQueryProcessor->GenerateResults(mDataSource, aResult,
+ aQuerySet->mCompiledQuery,
+ getter_AddRefs(results));
+ if (NS_FAILED(rv) || !results)
+ return rv;
+
+ bool hasMoreResults;
+ rv = results->HasMoreElements(&hasMoreResults);
+
+ for (; NS_SUCCEEDED(rv) && hasMoreResults;
+ rv = results->HasMoreElements(&hasMoreResults)) {
+ nsCOMPtr<nsISupports> nr;
+ rv = results->GetNext(getter_AddRefs(nr));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIXULTemplateResult> nextresult = do_QueryInterface(nr);
+ if (!nextresult)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIRDFResource> resultid;
+ rv = GetResultResource(nextresult, getter_AddRefs(resultid));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!resultid)
+ continue;
+
+ nsTemplateMatch *newmatch =
+ nsTemplateMatch::Create(aQuerySet->Priority(),
+ nextresult, aElement);
+ if (!newmatch)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // check if there is already an existing match. If so, a previous
+ // query already generated content so the match is just added to the
+ // end of the set of matches.
+
+ bool generateContent = true;
+
+ nsTemplateMatch* prevmatch = nullptr;
+ nsTemplateMatch* existingmatch = nullptr;
+ nsTemplateMatch* removematch = nullptr;
+ if (mMatchMap.Get(resultid, &existingmatch)){
+ // check if there is an existing match that matched a rule
+ while (existingmatch) {
+ // break out once we've reached a query in the list with a
+ // higher priority, as the new match list is sorted by
+ // priority, and the new match should be inserted here
+ int32_t priority = existingmatch->QuerySetPriority();
+ if (priority > aQuerySet->Priority())
+ break;
+
+ // skip over non-matching containers
+ if (existingmatch->GetContainer() == aElement) {
+ // if the same priority is already found, replace it. This can happen
+ // when a container is removed and readded
+ if (priority == aQuerySet->Priority()) {
+ removematch = existingmatch;
+ break;
+ }
+
+ if (existingmatch->IsActive())
+ generateContent = false;
+ }
+
+ prevmatch = existingmatch;
+ existingmatch = existingmatch->mNext;
+ }
+ }
+
+ if (removematch) {
+ // remove the generated content for the existing match
+ rv = ReplaceMatch(removematch->mResult, nullptr, nullptr, aElement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mFlags & eLoggingEnabled)
+ OutputMatchToLog(resultid, removematch, false);
+ }
+
+ if (generateContent) {
+ // find the rule that matches. If none match, the content does not
+ // need to be generated
+
+ int16_t ruleindex;
+ nsTemplateRule* matchedrule = nullptr;
+ rv = DetermineMatchedRule(aElement, nextresult, aQuerySet,
+ &matchedrule, &ruleindex);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ if (matchedrule) {
+ rv = newmatch->RuleMatched(aQuerySet, matchedrule,
+ ruleindex, nextresult);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ // Grab the template node
+ nsCOMPtr<nsIContent> action = matchedrule->GetAction();
+ BuildContentFromTemplate(action, aElement, aElement, true,
+ mRefVariable == matchedrule->GetMemberVariable(),
+ nextresult, aNotify, newmatch,
+ aContainer, aNewIndexInContainer);
+ }
+ }
+
+ if (mFlags & eLoggingEnabled)
+ OutputMatchToLog(resultid, newmatch, true);
+
+ if (prevmatch) {
+ prevmatch->mNext = newmatch;
+ }
+ else {
+ mMatchMap.Put(resultid, newmatch);
+ }
+
+ if (removematch) {
+ newmatch->mNext = removematch->mNext;
+ nsTemplateMatch::Destroy(removematch, true);
+ }
+ else {
+ newmatch->mNext = existingmatch;
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsXULContentBuilder::EnsureElementHasGenericChild(nsIContent* parent,
+ int32_t nameSpaceID,
+ nsIAtom* tag,
+ bool aNotify,
+ nsIContent** result)
+{
+ nsresult rv;
+
+ rv = nsXULContentUtils::FindChildByTag(parent, nameSpaceID, tag, result);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (rv == NS_RDF_NO_VALUE) {
+ // we need to construct a new child element.
+ nsCOMPtr<Element> element;
+
+ rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // XXX Note that the notification ensures we won't batch insertions! This could be bad! - Dave
+ rv = parent->AppendChildTo(element, aNotify);
+ if (NS_FAILED(rv))
+ return rv;
+
+ element.forget(result);
+ return NS_ELEMENT_GOT_CREATED;
+ }
+ else {
+ return NS_ELEMENT_WAS_THERE;
+ }
+}
+
+bool
+nsXULContentBuilder::IsOpen(nsIContent* aElement)
+{
+ // Determine if this is a <treeitem> or <menu> element
+
+ // XXXhyatt Use the XBL service to obtain a base tag.
+ if (aElement->IsAnyOfXULElements(nsGkAtoms::menu,
+ nsGkAtoms::menubutton,
+ nsGkAtoms::toolbarbutton,
+ nsGkAtoms::button,
+ nsGkAtoms::treeitem))
+ return aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
+ nsGkAtoms::_true, eCaseMatters);
+ return true;
+}
+
+nsresult
+nsXULContentBuilder::RemoveGeneratedContent(nsIContent* aElement)
+{
+ // Keep a queue of "ungenerated" elements that we have to probe
+ // for generated content.
+ AutoTArray<nsIContent*, 8> ungenerated;
+ if (ungenerated.AppendElement(aElement) == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ uint32_t count;
+ while (0 != (count = ungenerated.Length())) {
+ // Pull the next "ungenerated" element off the queue.
+ uint32_t last = count - 1;
+ nsCOMPtr<nsIContent> element = ungenerated[last];
+ ungenerated.RemoveElementAt(last);
+
+ uint32_t i = element->GetChildCount();
+
+ while (i-- > 0) {
+ nsCOMPtr<nsIContent> child = element->GetChildAt(i);
+
+ // Optimize for the <template> element, because we *know*
+ // it won't have any generated content: there's no reason
+ // to even check this subtree.
+ // XXX should this check |child| rather than |element|? Otherwise
+ // it should be moved outside the inner loop. Bug 297290.
+ if (element->NodeInfo()->Equals(nsGkAtoms::_template,
+ kNameSpaceID_XUL) ||
+ !element->IsElement())
+ continue;
+
+ // If the element is in the template map, then we
+ // assume it's been generated and nuke it.
+ nsCOMPtr<nsIContent> tmpl;
+ mTemplateMap.GetTemplateFor(child, getter_AddRefs(tmpl));
+
+ if (! tmpl) {
+ // No 'template' attribute, so this must not have been
+ // generated. We'll need to examine its kids.
+ if (ungenerated.AppendElement(child) == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ continue;
+ }
+
+ // If we get here, it's "generated". Bye bye!
+ element->RemoveChildAt(i, true);
+
+ // Remove this and any children from the content support map.
+ mContentSupportMap.Remove(child);
+
+ // Remove from the template map
+ mTemplateMap.Remove(child);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::GetElementsForResult(nsIXULTemplateResult* aResult,
+ nsCOMArray<nsIContent>& aElements)
+{
+ // if the root has been removed from the document, just return
+ // since there won't be any generated content any more
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetComposedDoc());
+ if (! xuldoc)
+ return NS_OK;
+
+ nsAutoString id;
+ aResult->GetId(id);
+
+ xuldoc->GetElementsForID(id, aElements);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CreateElement(int32_t aNameSpaceID,
+ nsIAtom* aTag,
+ Element** aResult)
+{
+ nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc();
+ NS_ASSERTION(doc != nullptr, "not initialized");
+ if (! doc)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo =
+ doc->NodeInfoManager()->GetNodeInfo(aTag, nullptr, aNameSpaceID,
+ nsIDOMNode::ELEMENT_NODE);
+
+ return NS_NewElement(aResult, nodeInfo.forget(), NOT_FROM_PARSER);
+}
+
+nsresult
+nsXULContentBuilder::SetContainerAttrs(nsIContent *aElement,
+ nsIXULTemplateResult* aResult,
+ bool aIgnoreNonContainers,
+ bool aNotify)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ bool iscontainer;
+ aResult->GetIsContainer(&iscontainer);
+
+ if (aIgnoreNonContainers && !iscontainer)
+ return NS_OK;
+
+ NS_NAMED_LITERAL_STRING(true_, "true");
+ NS_NAMED_LITERAL_STRING(false_, "false");
+
+ const nsAString& newcontainer =
+ iscontainer ? true_ : false_;
+
+ aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::container,
+ newcontainer, aNotify);
+
+ if (iscontainer && !(mFlags & eDontTestEmpty)) {
+ bool isempty;
+ aResult->GetIsEmpty(&isempty);
+
+ const nsAString& newempty =
+ (iscontainer && isempty) ? true_ : false_;
+
+ aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::empty,
+ newempty, aNotify);
+ }
+
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsIXULTemplateBuilder methods
+//
+
+NS_IMETHODIMP
+nsXULContentBuilder::CreateContents(nsIContent* aElement, bool aForceCreation)
+{
+ NS_PRECONDITION(aElement != nullptr, "null ptr");
+ if (! aElement)
+ return NS_ERROR_NULL_POINTER;
+
+ // don't build contents for closed elements. aForceCreation will be true
+ // when a menu is about to be opened, so the content should be built anyway.
+ if (!aForceCreation && !IsOpen(aElement))
+ return NS_OK;
+
+ return CreateTemplateAndContainerContents(aElement, aForceCreation);
+}
+
+NS_IMETHODIMP
+nsXULContentBuilder::HasGeneratedContent(nsIRDFResource* aResource,
+ nsIAtom* aTag,
+ bool* aGenerated)
+{
+ *aGenerated = false;
+ NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_STATE(mRootResult);
+
+ nsCOMPtr<nsIRDFResource> rootresource;
+ nsresult rv = mRootResult->GetResource(getter_AddRefs(rootresource));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // the root resource is always acceptable
+ if (aResource == rootresource) {
+ if (!aTag || mRoot->NodeInfo()->NameAtom() == aTag)
+ *aGenerated = true;
+ }
+ else {
+ const char* uri;
+ aResource->GetValueConst(&uri);
+
+ NS_ConvertUTF8toUTF16 refID(uri);
+
+ // just return if the node is no longer in a document
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetComposedDoc());
+ if (! xuldoc)
+ return NS_OK;
+
+ nsCOMArray<nsIContent> elements;
+ xuldoc->GetElementsForID(refID, elements);
+
+ uint32_t cnt = elements.Count();
+
+ for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
+ nsCOMPtr<nsIContent> content = elements.SafeObjectAt(i);
+
+ do {
+ nsTemplateMatch* match;
+ if (content == mRoot || mContentSupportMap.Get(content, &match)) {
+ // If we've got a tag, check it to ensure we're consistent.
+ if (!aTag || content->NodeInfo()->NameAtom() == aTag) {
+ *aGenerated = true;
+ return NS_OK;
+ }
+ }
+
+ content = content->GetParent();
+ } while (content);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULContentBuilder::GetResultForContent(nsIDOMElement* aElement,
+ nsIXULTemplateResult** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aElement);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+ if (content == mRoot) {
+ *aResult = mRootResult;
+ }
+ else {
+ nsTemplateMatch *match = nullptr;
+ if (mContentSupportMap.Get(content, &match))
+ *aResult = match->mResult;
+ else
+ *aResult = nullptr;
+ }
+
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// nsIDocumentObserver methods
+//
+
+void
+nsXULContentBuilder::AttributeChanged(nsIDocument* aDocument,
+ Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+
+ // Handle "open" and "close" cases. We do this handling before
+ // we've notified the observer, so that content is already created
+ // for the frame system to walk.
+ if (aElement->GetNameSpaceID() == kNameSpaceID_XUL &&
+ aAttribute == nsGkAtoms::open) {
+ // We're on a XUL tag, and an ``open'' attribute changed.
+ if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
+ nsGkAtoms::_true, eCaseMatters))
+ OpenContainer(aElement);
+ else
+ CloseContainer(aElement);
+ }
+
+ if ((aNameSpaceID == kNameSpaceID_XUL) &&
+ ((aAttribute == nsGkAtoms::sort) ||
+ (aAttribute == nsGkAtoms::sortDirection) ||
+ (aAttribute == nsGkAtoms::sortResource) ||
+ (aAttribute == nsGkAtoms::sortResource2)))
+ mSortState.initialized = false;
+
+ // Pass along to the generic template builder.
+ nsXULTemplateBuilder::AttributeChanged(aDocument, aElement, aNameSpaceID,
+ aAttribute, aModType, aOldValue);
+}
+
+void
+nsXULContentBuilder::NodeWillBeDestroyed(const nsINode* aNode)
+{
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ // Break circular references
+ mContentSupportMap.Clear();
+
+ nsXULTemplateBuilder::NodeWillBeDestroyed(aNode);
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsXULTemplateBuilder methods
+//
+
+bool
+nsXULContentBuilder::GetInsertionLocations(nsIXULTemplateResult* aResult,
+ nsCOMArray<nsIContent>** aLocations)
+{
+ *aLocations = nullptr;
+
+ nsAutoString ref;
+ nsresult rv = aResult->GetBindingFor(mRefVariable, ref);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetComposedDoc());
+ if (! xuldoc)
+ return false;
+
+ *aLocations = new nsCOMArray<nsIContent>;
+ NS_ENSURE_TRUE(*aLocations, false);
+
+ xuldoc->GetElementsForID(ref, **aLocations);
+ uint32_t count = (*aLocations)->Count();
+
+ bool found = false;
+
+ for (uint32_t t = 0; t < count; t++) {
+ nsCOMPtr<nsIContent> content = (*aLocations)->SafeObjectAt(t);
+
+ nsTemplateMatch* refmatch;
+ if (content == mRoot || mContentSupportMap.Get(content, &refmatch)) {
+ // See if we've built the container contents for "content"
+ // yet. If not, we don't need to build any content. This
+ // happens, for example, if we receive an assertion on a
+ // closed folder in a tree widget or on a menu that hasn't
+ // yet been opened.
+ nsXULElement *xulcontent = nsXULElement::FromContent(content);
+ if (!xulcontent || xulcontent->GetTemplateGenerated()) {
+ found = true;
+ continue;
+ }
+ }
+
+ // clear the item in the list since we don't want to insert there
+ (*aLocations)->ReplaceObjectAt(nullptr, t);
+ }
+
+ return found;
+}
+
+nsresult
+nsXULContentBuilder::ReplaceMatch(nsIXULTemplateResult* aOldResult,
+ nsTemplateMatch* aNewMatch,
+ nsTemplateRule* aNewMatchRule,
+ void *aContext)
+
+{
+ nsresult rv;
+ nsIContent* content = static_cast<nsIContent*>(aContext);
+
+ // update the container attributes for the match
+ if (content) {
+ nsAutoString ref;
+ if (aNewMatch)
+ rv = aNewMatch->mResult->GetBindingFor(mRefVariable, ref);
+ else
+ rv = aOldResult->GetBindingFor(mRefVariable, ref);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!ref.IsEmpty()) {
+ nsCOMPtr<nsIXULTemplateResult> refResult;
+ rv = GetResultForId(ref, getter_AddRefs(refResult));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (refResult)
+ SetContainerAttrs(content, refResult, false, true);
+ }
+ }
+
+ if (aOldResult) {
+ nsCOMArray<nsIContent> elements;
+ rv = GetElementsForResult(aOldResult, elements);
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t count = elements.Count();
+
+ for (int32_t e = int32_t(count) - 1; e >= 0; --e) {
+ nsCOMPtr<nsIContent> child = elements.SafeObjectAt(e);
+
+ nsTemplateMatch* match;
+ if (mContentSupportMap.Get(child, &match)) {
+ if (content == match->GetContainer())
+ RemoveMember(child);
+ }
+ }
+ }
+
+ if (aNewMatch) {
+ nsCOMPtr<nsIContent> action = aNewMatchRule->GetAction();
+ return BuildContentFromTemplate(action, content, content, true,
+ mRefVariable == aNewMatchRule->GetMemberVariable(),
+ aNewMatch->mResult, true, aNewMatch,
+ nullptr, nullptr);
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsXULContentBuilder::SynchronizeResult(nsIXULTemplateResult* aResult)
+{
+ nsCOMArray<nsIContent> elements;
+ GetElementsForResult(aResult, elements);
+
+ uint32_t cnt = elements.Count();
+
+ for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
+ nsCOMPtr<nsIContent> element = elements.SafeObjectAt(i);
+
+ nsTemplateMatch* match;
+ if (! mContentSupportMap.Get(element, &match))
+ continue;
+
+ nsCOMPtr<nsIContent> templateNode;
+ mTemplateMap.GetTemplateFor(element, getter_AddRefs(templateNode));
+
+ NS_ASSERTION(templateNode, "couldn't find template node for element");
+ if (! templateNode)
+ continue;
+
+ // this node was created by a XUL template, so update it accordingly
+ SynchronizeUsingTemplate(templateNode, element, aResult);
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// Implementation methods
+//
+
+nsresult
+nsXULContentBuilder::OpenContainer(nsIContent* aElement)
+{
+ if (aElement != mRoot) {
+ if (mFlags & eDontRecurse)
+ return NS_OK;
+
+ bool rightBuilder = false;
+
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aElement->GetComposedDoc());
+ if (! xuldoc)
+ return NS_OK;
+
+ // See if we're responsible for this element
+ nsIContent* content = aElement;
+ do {
+ nsCOMPtr<nsIXULTemplateBuilder> builder;
+ xuldoc->GetTemplateBuilderFor(content, getter_AddRefs(builder));
+ if (builder) {
+ if (builder == this)
+ rightBuilder = true;
+ break;
+ }
+
+ content = content->GetParent();
+ } while (content);
+
+ if (! rightBuilder)
+ return NS_OK;
+ }
+
+ CreateTemplateAndContainerContents(aElement, false);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CloseContainer(nsIContent* aElement)
+{
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::RebuildAll()
+{
+ NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
+
+ // Bail out early if we are being torn down.
+ nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc();
+ if (!doc)
+ return NS_OK;
+
+ if (mQueriesCompiled)
+ Uninit(false);
+
+ nsresult rv = CompileQueries();
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mQuerySets.Length() == 0)
+ return NS_OK;
+
+ nsXULElement *xulcontent = nsXULElement::FromContent(mRoot);
+ if (xulcontent)
+ xulcontent->ClearTemplateGenerated();
+
+ // Now, regenerate both the template- and container-generated
+ // contents for the current element...
+ CreateTemplateAndContainerContents(mRoot, false);
+
+ return NS_OK;
+}
+
+/**** Sorting Methods ****/
+
+nsresult
+nsXULContentBuilder::CompareResultToNode(nsIXULTemplateResult* aResult,
+ nsIContent* aContent,
+ int32_t* aSortOrder)
+{
+ NS_ASSERTION(aSortOrder, "CompareResultToNode: null out param aSortOrder");
+
+ *aSortOrder = 0;
+
+ nsTemplateMatch *match = nullptr;
+ if (!mContentSupportMap.Get(aContent, &match)) {
+ *aSortOrder = mSortState.sortStaticsLast ? -1 : 1;
+ return NS_OK;
+ }
+
+ if (!mQueryProcessor)
+ return NS_OK;
+
+ if (mSortState.direction == nsSortState_natural) {
+ // sort in natural order
+ nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
+ nullptr, mSortState.sortHints,
+ aSortOrder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // iterate over each sort key and compare. If the nodes are equal,
+ // continue to compare using the next sort key. If not equal, stop.
+ int32_t length = mSortState.sortKeys.Count();
+ for (int32_t t = 0; t < length; t++) {
+ nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
+ mSortState.sortKeys[t],
+ mSortState.sortHints, aSortOrder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*aSortOrder)
+ break;
+ }
+ }
+
+ // flip the sort order if performing a descending sorting
+ if (mSortState.direction == nsSortState_descending)
+ *aSortOrder = -*aSortOrder;
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::InsertSortedNode(nsIContent* aContainer,
+ nsIContent* aNode,
+ nsIXULTemplateResult* aResult,
+ bool aNotify)
+{
+ nsresult rv;
+
+ if (!mSortState.initialized) {
+ nsAutoString sort, sortDirection, sortHints;
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, sortDirection);
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, sortHints);
+ sortDirection += ' ';
+ sortDirection += sortHints;
+ rv = XULSortServiceImpl::InitializeSortState(mRoot, aContainer,
+ sort, sortDirection, &mSortState);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // when doing a natural sort, items will typically be sorted according to
+ // the order they appear in the datasource. For RDF, cache whether the
+ // reference parent is an RDF Seq. That way, the items can be sorted in the
+ // order they are in the Seq.
+ mSortState.isContainerRDFSeq = false;
+ if (mSortState.direction == nsSortState_natural) {
+ nsCOMPtr<nsISupports> ref;
+ nsresult rv = aResult->GetBindingObjectFor(mRefVariable, getter_AddRefs(ref));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRDFResource> container = do_QueryInterface(ref);
+
+ if (container) {
+ rv = gRDFContainerUtils->IsSeq(mDB, container, &mSortState.isContainerRDFSeq);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ bool childAdded = false;
+ uint32_t numChildren = aContainer->GetChildCount();
+
+ if (mSortState.direction != nsSortState_natural ||
+ (mSortState.direction == nsSortState_natural && mSortState.isContainerRDFSeq))
+ {
+ // because numChildren gets modified
+ int32_t realNumChildren = numChildren;
+ nsIContent *child = nullptr;
+
+ // rjc says: determine where static XUL ends and generated XUL/RDF begins
+ int32_t staticCount = 0;
+
+ nsAutoString staticValue;
+ aContainer->GetAttr(kNameSpaceID_None, nsGkAtoms::staticHint, staticValue);
+ if (!staticValue.IsEmpty())
+ {
+ // found "static" XUL element count hint
+ nsresult strErr = NS_OK;
+ staticCount = staticValue.ToInteger(&strErr);
+ if (NS_FAILED(strErr))
+ staticCount = 0;
+ } else {
+ // compute the "static" XUL element count
+ for (nsIContent* child = aContainer->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ if (nsContentUtils::HasNonEmptyAttr(child, kNameSpaceID_None,
+ nsGkAtoms::_template))
+ break;
+ else
+ ++staticCount;
+ }
+
+ if (mSortState.sortStaticsLast) {
+ // indicate that static XUL comes after RDF-generated content by
+ // making negative
+ staticCount = -staticCount;
+ }
+
+ // save the "static" XUL element count hint
+ nsAutoString valueStr;
+ valueStr.AppendInt(staticCount);
+ aContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::staticHint, valueStr, false);
+ }
+
+ if (staticCount <= 0) {
+ numChildren += staticCount;
+ staticCount = 0;
+ } else if (staticCount > (int32_t)numChildren) {
+ staticCount = numChildren;
+ numChildren -= staticCount;
+ }
+
+ // figure out where to insert the node when a sort order is being imposed
+ if (numChildren > 0) {
+ nsIContent *temp;
+ int32_t direction;
+
+ // rjc says: The following is an implementation of a fairly optimal
+ // binary search insertion sort... with interpolation at either end-point.
+
+ if (mSortState.lastWasFirst) {
+ child = aContainer->GetChildAt(staticCount);
+ temp = child;
+ rv = CompareResultToNode(aResult, temp, &direction);
+ if (direction < 0) {
+ aContainer->InsertChildAt(aNode, staticCount, aNotify);
+ childAdded = true;
+ } else
+ mSortState.lastWasFirst = false;
+ } else if (mSortState.lastWasLast) {
+ child = aContainer->GetChildAt(realNumChildren - 1);
+ temp = child;
+ rv = CompareResultToNode(aResult, temp, &direction);
+ if (direction > 0) {
+ aContainer->InsertChildAt(aNode, realNumChildren, aNotify);
+ childAdded = true;
+ } else
+ mSortState.lastWasLast = false;
+ }
+
+ int32_t left = staticCount + 1, right = realNumChildren, x;
+ while (!childAdded && right >= left) {
+ x = (left + right) / 2;
+ child = aContainer->GetChildAt(x - 1);
+ temp = child;
+
+ rv = CompareResultToNode(aResult, temp, &direction);
+ if ((x == left && direction < 0) ||
+ (x == right && direction >= 0) ||
+ left == right)
+ {
+ int32_t thePos = (direction > 0 ? x : x - 1);
+ aContainer->InsertChildAt(aNode, thePos, aNotify);
+ childAdded = true;
+
+ mSortState.lastWasFirst = (thePos == staticCount);
+ mSortState.lastWasLast = (thePos >= realNumChildren);
+
+ break;
+ }
+ if (direction < 0)
+ right = x - 1;
+ else
+ left = x + 1;
+ }
+ }
+ }
+
+ // if the child hasn't been inserted yet, just add it at the end. Note
+ // that an append isn't done as there may be static content afterwards.
+ if (!childAdded)
+ aContainer->InsertChildAt(aNode, numChildren, aNotify);
+
+ return NS_OK;
+}