diff -r 000000000000 -r 6474c204b198 content/xul/templates/src/nsXULContentBuilder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/xul/templates/src/nsXULContentBuilder.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1988 @@ +/* -*- 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 "nsINodeInfo.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 "pldhash.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); + + NS_IMETHOD HasGeneratedContent(nsIRDFResource* aResource, + nsIAtom* aTag, + bool* aGenerated); + + NS_IMETHOD GetResultForContent(nsIDOMElement* aContent, + nsIXULTemplateResult** aResult); + + // 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 &cb) const + { + mSortState.Traverse(cb); + } + + virtual void Uninit(bool aIsFinal); + + // 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& 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(); + + // 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** aLocations); + + /** + * 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); + + /** + * 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); + + /** + * 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(); + if (!result) + return NS_ERROR_OUT_OF_MEMORY; + + 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; + +#ifdef PR_LOGGING + if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { + PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, + ("nsXULContentBuilder::BuildContentFromTemplate (is unique: %d)", + aIsUnique)); + + nsAutoString id; + aChild->GetId(id); + + PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, + ("Tags: [Template: %s Resource: %s Real: %s] for id %s", + nsAtomCString(aTemplateNode->Tag()).get(), + nsAtomCString(aResourceNode->Tag()).get(), + nsAtomCString(aRealNode->Tag()).get(), NS_ConvertUTF16toUTF8(id).get())); + } +#endif + + // 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 template: + // + // + // + // + // + // The 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 [1] is unique, and + // will only be created -once-, no matter how many 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->Tag(); + +#ifdef PR_LOGGING + if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { + PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, + ("xultemplate[%p] building %s %s %s", + this, nsAtomCString(tag).get(), + (isGenerationElement ? "[resource]" : ""), + (isUnique ? "[unique]" : ""))); + } +#endif + + // Set to true if the child we're trying to create now + // already existed in the content model. + bool realKidAlreadyExisted = false; + + nsCOMPtr 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; + 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) { + // 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; + + nsRefPtr 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 tmplTextNode = do_QueryInterface(tmplKid); + if (!tmplTextNode) { + NS_ERROR("textnode not implementing nsIDOMNode??"); + return NS_ERROR_FAILURE; + } + nsCOMPtr clonedNode; + tmplTextNode->CloneNode(false, 1, getter_AddRefs(clonedNode)); + nsCOMPtr 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; + 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: + // + // + 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 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 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 tag; + int32_t nameSpaceID; + + nsCOMPtr ni = + aTemplateNode->GetExistingAttrNameFromQName(attribute); + if (ni) { + tag = ni->NameAtom(); + nameSpaceID = ni->NamespaceID(); + } + else { + tag = do_GetAtom(attribute); + NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY); + + nameSpaceID = kNameSpaceID_None; + } + + nsCOMPtr property; + rv = nsXULContentUtils::GetResource(nameSpaceID, tag, getter_AddRefs(property)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr target; + rv = mDB->GetTarget(resource, property, true, getter_AddRefs(target)); + NS_ENSURE_SUCCESS(rv, rv); + + if (! target) + continue; + + nsCOMPtr 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 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). + + PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, + ("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); + } + + PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, + ("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 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->Tag()) + continue; + + CreateContainerContentsForQuerySet(aElement, aResult, aNotify, queryset, + &container, &newIndexInContainer); + } + + if (aNotifyAtEnd && container) { + MOZ_AUTO_DOC_UPDATE(container->GetCurrentDoc(), 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) +{ +#ifdef PR_LOGGING + if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { + nsAutoString id; + aResult->GetId(id); + PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, + ("nsXULContentBuilder::CreateContainerContentsForQuerySet start for ref %s\n", + NS_ConvertUTF16toUTF8(id).get())); + } +#endif + + if (! mQueryProcessor) + return NS_OK; + + nsCOMPtr 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 nr; + rv = results->GetNext(getter_AddRefs(nr)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr nextresult = do_QueryInterface(nr); + if (!nextresult) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr 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 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; + + 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; + + *result = element; + NS_ADDREF(*result); + return NS_ELEMENT_GOT_CREATED; + } + else { + return NS_ELEMENT_WAS_THERE; + } +} + +bool +nsXULContentBuilder::IsOpen(nsIContent* aElement) +{ + // Determine if this is a or element + if (!aElement->IsXUL()) + return true; + + // XXXhyatt Use the XBL service to obtain a base tag. + nsIAtom *tag = aElement->Tag(); + if (tag == nsGkAtoms::menu || + tag == nsGkAtoms::menubutton || + tag == nsGkAtoms::toolbarbutton || + tag == nsGkAtoms::button || + tag == 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. + nsAutoTArray 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 element = ungenerated[last]; + ungenerated.RemoveElementAt(last); + + uint32_t i = element->GetChildCount(); + + while (i-- > 0) { + nsCOMPtr child = element->GetChildAt(i); + + // Optimize for the