michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "nsContentCID.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsIDOMXULDocument.h" michael@0: #include "nsINodeInfo.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIXULDocument.h" michael@0: michael@0: #include "nsContentSupportMap.h" michael@0: #include "nsRDFConMemberTestNode.h" michael@0: #include "nsRDFPropertyTestNode.h" michael@0: #include "nsXULSortService.h" michael@0: #include "nsTemplateRule.h" michael@0: #include "nsTemplateMap.h" michael@0: #include "nsTArray.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsXULContentUtils.h" michael@0: #include "nsXULElement.h" michael@0: #include "nsXULTemplateBuilder.h" michael@0: #include "nsNodeInfoManager.h" michael@0: #include "nsContentCreatorFunctions.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsAttrName.h" michael@0: #include "nsNodeUtils.h" michael@0: #include "mozAutoDocUpdate.h" michael@0: #include "nsTextNode.h" michael@0: #include "mozilla/dom/Element.h" michael@0: michael@0: #include "pldhash.h" michael@0: #include "rdf.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // michael@0: // Return values for EnsureElementHasGenericChild() michael@0: // michael@0: #define NS_ELEMENT_GOT_CREATED NS_RDF_NO_VALUE michael@0: #define NS_ELEMENT_WAS_THERE NS_OK michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // michael@0: // nsXULContentBuilder michael@0: // michael@0: michael@0: /** michael@0: * The content builder generates DOM nodes from a template. The actual content michael@0: * generation is done entirely inside BuildContentFromTemplate. michael@0: * michael@0: * Content generation is centered around the generation node (the node with michael@0: * uri="?member" on it). Nodes above the generation node are unique and michael@0: * generated only once. BuildContentFromTemplate will be passed the unique michael@0: * flag as an argument for content at this point and will recurse until it michael@0: * finds the generation node. michael@0: * michael@0: * Once the generation node has been found, the results for that content node michael@0: * are added to the content map, stored in mContentSupportMap. michael@0: * michael@0: * If recursion is allowed, generation continues, where the generation node michael@0: * becomes the container to insert into. michael@0: */ michael@0: class nsXULContentBuilder : public nsXULTemplateBuilder michael@0: { michael@0: public: michael@0: // nsIXULTemplateBuilder interface michael@0: NS_IMETHOD CreateContents(nsIContent* aElement, bool aForceCreation); michael@0: michael@0: NS_IMETHOD HasGeneratedContent(nsIRDFResource* aResource, michael@0: nsIAtom* aTag, michael@0: bool* aGenerated); michael@0: michael@0: NS_IMETHOD GetResultForContent(nsIDOMElement* aContent, michael@0: nsIXULTemplateResult** aResult); michael@0: michael@0: // nsIMutationObserver interface michael@0: NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED michael@0: NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED michael@0: michael@0: protected: michael@0: friend nsresult michael@0: NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult); michael@0: michael@0: nsXULContentBuilder(); michael@0: michael@0: void Traverse(nsCycleCollectionTraversalCallback &cb) const michael@0: { michael@0: mSortState.Traverse(cb); michael@0: } michael@0: michael@0: virtual void Uninit(bool aIsFinal); michael@0: michael@0: // Implementation methods michael@0: nsresult michael@0: OpenContainer(nsIContent* aElement); michael@0: michael@0: nsresult michael@0: CloseContainer(nsIContent* aElement); michael@0: michael@0: /** michael@0: * Build content from a template for a given result. This will be called michael@0: * recursively or on demand and will be called for every node in the michael@0: * generated content tree. michael@0: */ michael@0: nsresult michael@0: BuildContentFromTemplate(nsIContent *aTemplateNode, michael@0: nsIContent *aResourceNode, michael@0: nsIContent *aRealNode, michael@0: bool aIsUnique, michael@0: bool aIsSelfReference, michael@0: nsIXULTemplateResult* aChild, michael@0: bool aNotify, michael@0: nsTemplateMatch* aMatch, michael@0: nsIContent** aContainer, michael@0: int32_t* aNewIndexInContainer); michael@0: michael@0: /** michael@0: * Copy the attributes from the template node to the node generated michael@0: * from it, performing any substitutions. michael@0: * michael@0: * @param aTemplateNode node within template michael@0: * @param aRealNode generated node to set attibutes upon michael@0: * @param aResult result to look up variable->value bindings in michael@0: * @param aNotify true to notify of DOM changes michael@0: */ michael@0: nsresult michael@0: CopyAttributesToElement(nsIContent* aTemplateNode, michael@0: nsIContent* aRealNode, michael@0: nsIXULTemplateResult* aResult, michael@0: bool aNotify); michael@0: michael@0: /** michael@0: * Add any necessary persistent attributes (persist="...") from the michael@0: * local store to a generated node. michael@0: * michael@0: * @param aTemplateNode node within template michael@0: * @param aRealNode generated node to set persisted attibutes upon michael@0: * @param aResult result to look up variable->value bindings in michael@0: */ michael@0: nsresult michael@0: AddPersistentAttributes(Element* aTemplateNode, michael@0: nsIXULTemplateResult* aResult, michael@0: nsIContent* aRealNode); michael@0: michael@0: /** michael@0: * Recalculate any attributes that have variable references. This will michael@0: * be called when a binding has been changed to update the attributes. michael@0: * The attributes are copied from the node aTemplateNode in the template michael@0: * to the generated node aRealNode, using the values from the result michael@0: * aResult. This method will operate recursively. michael@0: * michael@0: * @param aTemplateNode node within template michael@0: * @param aRealNode generated node to set attibutes upon michael@0: * @param aResult result to look up variable->value bindings in michael@0: */ michael@0: nsresult michael@0: SynchronizeUsingTemplate(nsIContent *aTemplateNode, michael@0: nsIContent* aRealNode, michael@0: nsIXULTemplateResult* aResult); michael@0: michael@0: /** michael@0: * Remove the generated node aContent from the DOM and the hashtables michael@0: * used by the content builder. michael@0: */ michael@0: nsresult michael@0: RemoveMember(nsIContent* aContent); michael@0: michael@0: /** michael@0: * Create the appropriate generated content for aElement, by calling michael@0: * CreateContainerContents. michael@0: * michael@0: * @param aElement element to generate content inside michael@0: * @param aForceCreation true to force creation for closed items such as menus michael@0: */ michael@0: nsresult michael@0: CreateTemplateAndContainerContents(nsIContent* aElement, michael@0: bool aForceCreation); michael@0: michael@0: /** michael@0: * Generate the results for a template, by calling michael@0: * CreateContainerContentsForQuerySet for each queryset. michael@0: * michael@0: * @param aElement element to generate content inside michael@0: * @param aResult reference point for query michael@0: * @param aForceCreation true to force creation for closed items such as menus michael@0: * @param aNotify true to notify of DOM changes as each element is inserted michael@0: * @param aNotifyAtEnd notify at the end of all DOM changes michael@0: */ michael@0: nsresult michael@0: CreateContainerContents(nsIContent* aElement, michael@0: nsIXULTemplateResult* aResult, michael@0: bool aForceCreation, michael@0: bool aNotify, michael@0: bool aNotifyAtEnd); michael@0: michael@0: /** michael@0: * Generate the results for a query. michael@0: * michael@0: * @param aElement element to generate content inside michael@0: * @param aResult reference point for query michael@0: * @param aNotify true to notify of DOM changes michael@0: * @param aContainer container content was added inside michael@0: * @param aNewIndexInContainer index with container in which content was added michael@0: */ michael@0: nsresult michael@0: CreateContainerContentsForQuerySet(nsIContent* aElement, michael@0: nsIXULTemplateResult* aResult, michael@0: bool aNotify, michael@0: nsTemplateQuerySet* aQuerySet, michael@0: nsIContent** aContainer, michael@0: int32_t* aNewIndexInContainer); michael@0: michael@0: /** michael@0: * Check if an element with a particular tag exists with a container. michael@0: * If it is not present, append a new element with that tag into the michael@0: * container. michael@0: * michael@0: * @param aParent parent container michael@0: * @param aNameSpaceID namespace of tag to locate or create michael@0: * @param aTag tag to locate or create michael@0: * @param aNotify true to notify of DOM changes michael@0: * @param aResult set to the found or created node. michael@0: */ michael@0: nsresult michael@0: EnsureElementHasGenericChild(nsIContent* aParent, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aTag, michael@0: bool aNotify, michael@0: nsIContent** aResult); michael@0: michael@0: bool michael@0: IsOpen(nsIContent* aElement); michael@0: michael@0: nsresult michael@0: RemoveGeneratedContent(nsIContent* aElement); michael@0: michael@0: nsresult michael@0: GetElementsForResult(nsIXULTemplateResult* aResult, michael@0: nsCOMArray& aElements); michael@0: michael@0: nsresult michael@0: CreateElement(int32_t aNameSpaceID, michael@0: nsIAtom* aTag, michael@0: Element** aResult); michael@0: michael@0: /** michael@0: * Set the container and empty attributes on a node. If michael@0: * aIgnoreNonContainers is true, then the element is not changed michael@0: * for non-containers. Otherwise, the container attribute will be set to michael@0: * false. michael@0: * michael@0: * @param aElement element to set attributes on michael@0: * @param aResult result to use to determine state of attributes michael@0: * @param aIgnoreNonContainers true to not change for non-containers michael@0: * @param aNotify true to notify of DOM changes michael@0: */ michael@0: nsresult michael@0: SetContainerAttrs(nsIContent *aElement, michael@0: nsIXULTemplateResult* aResult, michael@0: bool aIgnoreNonContainers, michael@0: bool aNotify); michael@0: michael@0: virtual nsresult michael@0: RebuildAll(); michael@0: michael@0: // GetInsertionLocations, ReplaceMatch and SynchronizeResult are inherited michael@0: // from nsXULTemplateBuilder michael@0: michael@0: /** michael@0: * Return true if the result can be inserted into the template as michael@0: * generated content. For the content builder, aLocations will be set michael@0: * to the list of containers where the content should be inserted. michael@0: */ michael@0: virtual bool michael@0: GetInsertionLocations(nsIXULTemplateResult* aOldResult, michael@0: nsCOMArray** aLocations); michael@0: michael@0: /** michael@0: * Remove the content associated with aOldResult which no longer matches, michael@0: * and/or generate content for a new match. michael@0: */ michael@0: virtual nsresult michael@0: ReplaceMatch(nsIXULTemplateResult* aOldResult, michael@0: nsTemplateMatch* aNewMatch, michael@0: nsTemplateRule* aNewMatchRule, michael@0: void *aContext); michael@0: michael@0: /** michael@0: * Synchronize a result bindings with the generated content for that michael@0: * result. This will be called as a result of the template builder's michael@0: * ResultBindingChanged method. michael@0: */ michael@0: virtual nsresult michael@0: SynchronizeResult(nsIXULTemplateResult* aResult); michael@0: michael@0: /** michael@0: * Compare a result to a content node. If the generated content for the michael@0: * result should come before aContent, set aSortOrder to -1. If it should michael@0: * come after, set sortOrder to 1. If both are equal, set to 0. michael@0: */ michael@0: nsresult michael@0: CompareResultToNode(nsIXULTemplateResult* aResult, nsIContent* aContent, michael@0: int32_t* aSortOrder); michael@0: michael@0: /** michael@0: * Insert a generated node into the container where it should go according michael@0: * to the current sort. aNode is the generated content node and aResult is michael@0: * the result for the generated node. michael@0: */ michael@0: nsresult michael@0: InsertSortedNode(nsIContent* aContainer, michael@0: nsIContent* aNode, michael@0: nsIXULTemplateResult* aResult, michael@0: bool aNotify); michael@0: michael@0: /** michael@0: * Maintains a mapping between elements in the DOM and the matches michael@0: * that they support. michael@0: */ michael@0: nsContentSupportMap mContentSupportMap; michael@0: michael@0: /** michael@0: * Maintains a mapping from an element in the DOM to the template michael@0: * element that it was created from. michael@0: */ michael@0: nsTemplateMap mTemplateMap; michael@0: michael@0: /** michael@0: * Information about the currently active sort michael@0: */ michael@0: nsSortState mSortState; michael@0: }; michael@0: michael@0: nsresult michael@0: NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult) michael@0: { michael@0: NS_PRECONDITION(aOuter == nullptr, "no aggregation"); michael@0: if (aOuter) michael@0: return NS_ERROR_NO_AGGREGATION; michael@0: michael@0: nsresult rv; michael@0: nsXULContentBuilder* result = new nsXULContentBuilder(); michael@0: if (!result) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(result); // stabilize michael@0: michael@0: rv = result->InitGlobals(); michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = result->QueryInterface(aIID, aResult); michael@0: michael@0: NS_RELEASE(result); michael@0: return rv; michael@0: } michael@0: michael@0: nsXULContentBuilder::nsXULContentBuilder() michael@0: { michael@0: mSortState.initialized = false; michael@0: } michael@0: michael@0: void michael@0: nsXULContentBuilder::Uninit(bool aIsFinal) michael@0: { michael@0: if (! aIsFinal && mRoot) { michael@0: nsresult rv = RemoveGeneratedContent(mRoot); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: } michael@0: michael@0: // Nuke the content support map completely. michael@0: mContentSupportMap.Clear(); michael@0: mTemplateMap.Clear(); michael@0: michael@0: mSortState.initialized = false; michael@0: michael@0: nsXULTemplateBuilder::Uninit(aIsFinal); michael@0: } michael@0: michael@0: nsresult michael@0: nsXULContentBuilder::BuildContentFromTemplate(nsIContent *aTemplateNode, michael@0: nsIContent *aResourceNode, michael@0: nsIContent *aRealNode, michael@0: bool aIsUnique, michael@0: bool aIsSelfReference, michael@0: nsIXULTemplateResult* aChild, michael@0: bool aNotify, michael@0: nsTemplateMatch* aMatch, michael@0: nsIContent** aContainer, michael@0: int32_t* aNewIndexInContainer) michael@0: { michael@0: // This is the mother lode. Here is where we grovel through an michael@0: // element in the template, copying children from the template michael@0: // into the "real" content tree, performing substitution as we go michael@0: // by looking stuff up using the results. michael@0: // michael@0: // |aTemplateNode| is the element in the "template tree", whose michael@0: // children we will duplicate and move into the "real" content michael@0: // tree. michael@0: // michael@0: // |aResourceNode| is the element in the "real" content tree that michael@0: // has the "id" attribute set to an result's id. This is michael@0: // not directly used here, but rather passed down to the XUL michael@0: // sort service to perform container-level sort. michael@0: // michael@0: // |aRealNode| is the element in the "real" content tree to which michael@0: // the new elements will be copied. michael@0: // michael@0: // |aIsUnique| is set to "true" so long as content has been michael@0: // "unique" (or "above" the resource element) so far in the michael@0: // template. michael@0: // michael@0: // |aIsSelfReference| should be set to "true" for cases where michael@0: // the reference and member variables are the same, indicating michael@0: // that the generated node is the same as the reference point, michael@0: // so generation should not recurse, or else an infinite loop michael@0: // would occur. michael@0: // michael@0: // |aChild| is the result for which we are building content. michael@0: // michael@0: // |aNotify| is set to "true" if content should be constructed michael@0: // "noisily"; that is, whether the document observers should be michael@0: // notified when new content is added to the content model. michael@0: // michael@0: // |aContainer| is an out parameter that will be set to the first michael@0: // container element in the "real" content tree to which content michael@0: // was appended. michael@0: // michael@0: // |aNewIndexInContainer| is an out parameter that will be set to michael@0: // the index in aContainer at which new content is first michael@0: // constructed. michael@0: // michael@0: // If |aNotify| is "false", then |aContainer| and michael@0: // |aNewIndexInContainer| are used to determine where in the michael@0: // content model new content is constructed. This allows a single michael@0: // notification to be propagated to document observers. michael@0: // michael@0: michael@0: nsresult rv; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { michael@0: PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, michael@0: ("nsXULContentBuilder::BuildContentFromTemplate (is unique: %d)", michael@0: aIsUnique)); michael@0: michael@0: nsAutoString id; michael@0: aChild->GetId(id); michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, michael@0: ("Tags: [Template: %s Resource: %s Real: %s] for id %s", michael@0: nsAtomCString(aTemplateNode->Tag()).get(), michael@0: nsAtomCString(aResourceNode->Tag()).get(), michael@0: nsAtomCString(aRealNode->Tag()).get(), NS_ConvertUTF16toUTF8(id).get())); michael@0: } michael@0: #endif michael@0: michael@0: // Iterate through all of the template children, constructing michael@0: // "real" content model nodes for each "template" child. michael@0: for (nsIContent* tmplKid = aTemplateNode->GetFirstChild(); michael@0: tmplKid; michael@0: tmplKid = tmplKid->GetNextSibling()) { michael@0: michael@0: int32_t nameSpaceID = tmplKid->GetNameSpaceID(); michael@0: michael@0: // Check whether this element is the generation element. The generation michael@0: // element is the element that is cookie-cutter copied once for each michael@0: // different result specified by |aChild|. michael@0: // michael@0: // Nodes that appear -above- the generation element michael@0: // (that is, are ancestors of the generation element in the michael@0: // content model) are unique across all values of |aChild|, michael@0: // and are created only once. michael@0: // michael@0: // Nodes that appear -below- the generation element (that is, michael@0: // are descendants of the generation element in the content michael@0: // model), are cookie-cutter copied for each distinct value of michael@0: // |aChild|. michael@0: // michael@0: // For example, in a template: michael@0: // michael@0: // michael@0: // michael@0: // michael@0: // michael@0: // The element [2] is the generation element. This michael@0: // element, and all of its descendants ([3], [4], and [5]) michael@0: // will be duplicated for each different |aChild|. michael@0: // It's ancestor [1] is unique, and michael@0: // will only be created -once-, no matter how many s michael@0: // are created below it. michael@0: // michael@0: // isUnique will be true for nodes above the generation element, michael@0: // isGenerationElement will be true for the generation element, michael@0: // and both will be false for descendants michael@0: bool isGenerationElement = false; michael@0: bool isUnique = aIsUnique; michael@0: michael@0: // We identify the resource element by presence of a michael@0: // "uri='rdf:*'" attribute. (We also support the older michael@0: // "uri='...'" syntax.) michael@0: if (tmplKid->HasAttr(kNameSpaceID_None, nsGkAtoms::uri) && aMatch->IsActive()) { michael@0: isGenerationElement = true; michael@0: isUnique = false; michael@0: } michael@0: michael@0: MOZ_ASSERT_IF(isGenerationElement, tmplKid->IsElement()); michael@0: michael@0: nsIAtom *tag = tmplKid->Tag(); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { michael@0: PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, michael@0: ("xultemplate[%p] building %s %s %s", michael@0: this, nsAtomCString(tag).get(), michael@0: (isGenerationElement ? "[resource]" : ""), michael@0: (isUnique ? "[unique]" : ""))); michael@0: } michael@0: #endif michael@0: michael@0: // Set to true if the child we're trying to create now michael@0: // already existed in the content model. michael@0: bool realKidAlreadyExisted = false; michael@0: michael@0: nsCOMPtr realKid; michael@0: if (isUnique) { michael@0: // The content is "unique"; that is, we haven't descended michael@0: // far enough into the template to hit the generation michael@0: // element yet. |EnsureElementHasGenericChild()| will michael@0: // conditionally create the element iff it isn't there michael@0: // already. michael@0: rv = EnsureElementHasGenericChild(aRealNode, nameSpaceID, tag, aNotify, getter_AddRefs(realKid)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (rv == NS_ELEMENT_WAS_THERE) { michael@0: realKidAlreadyExisted = true; michael@0: } michael@0: else { michael@0: // Potentially remember the index of this element as the first michael@0: // element that we've generated. Note that we remember michael@0: // this -before- we recurse! michael@0: if (aContainer && !*aContainer) { michael@0: *aContainer = aRealNode; michael@0: NS_ADDREF(*aContainer); michael@0: michael@0: uint32_t indx = aRealNode->GetChildCount(); michael@0: michael@0: // Since EnsureElementHasGenericChild() added us, make michael@0: // sure to subtract one for our real index. michael@0: *aNewIndexInContainer = indx - 1; michael@0: } michael@0: } michael@0: michael@0: // Recurse until we get to the resource element. Since michael@0: // -we're- unique, assume that our child will be michael@0: // unique. The check for the "resource" element at the top michael@0: // of the function will trip this to |false| as soon as we michael@0: // encounter it. michael@0: rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, true, michael@0: aIsSelfReference, aChild, aNotify, aMatch, michael@0: aContainer, aNewIndexInContainer); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: else if (isGenerationElement) { michael@0: // It's the "resource" element. Create a new element using michael@0: // the namespace ID and tag from the template element. michael@0: nsCOMPtr element; michael@0: rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: realKid = element.forget(); michael@0: michael@0: // Add the resource element to the content support map so michael@0: // we can remove the match based on the content node later. michael@0: mContentSupportMap.Put(realKid, aMatch); michael@0: michael@0: // Assign the element an 'id' attribute using result's id michael@0: nsAutoString id; michael@0: rv = aChild->GetId(id); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = realKid->SetAttr(kNameSpaceID_None, nsGkAtoms::id, id, false); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Set up the element's 'container' and 'empty' attributes. michael@0: SetContainerAttrs(realKid, aChild, true, false); michael@0: } michael@0: else if (tag == nsGkAtoms::textnode && michael@0: nameSpaceID == kNameSpaceID_XUL) { michael@0: // is replaced by text of the michael@0: // actual value of the 'rdf:resource' attribute for the michael@0: // given node. michael@0: // SynchronizeUsingTemplate contains code used to update textnodes, michael@0: // so make sure to modify both when changing this michael@0: char16_t attrbuf[128]; michael@0: nsFixedString attrValue(attrbuf, ArrayLength(attrbuf), 0); michael@0: tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue); michael@0: if (!attrValue.IsEmpty()) { michael@0: nsAutoString value; michael@0: rv = SubstituteText(aChild, attrValue, value); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsRefPtr content = michael@0: new nsTextNode(mRoot->NodeInfo()->NodeInfoManager()); michael@0: michael@0: content->SetText(value, false); michael@0: michael@0: rv = aRealNode->AppendChildTo(content, aNotify); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // XXX Don't bother remembering text nodes as the michael@0: // first element we've generated? michael@0: } michael@0: } michael@0: else if (tmplKid->IsNodeOfType(nsINode::eTEXT)) { michael@0: nsCOMPtr tmplTextNode = do_QueryInterface(tmplKid); michael@0: if (!tmplTextNode) { michael@0: NS_ERROR("textnode not implementing nsIDOMNode??"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: nsCOMPtr clonedNode; michael@0: tmplTextNode->CloneNode(false, 1, getter_AddRefs(clonedNode)); michael@0: nsCOMPtr clonedContent = do_QueryInterface(clonedNode); michael@0: if (!clonedContent) { michael@0: NS_ERROR("failed to clone textnode"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: rv = aRealNode->AppendChildTo(clonedContent, aNotify); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: else { michael@0: // It's just a generic element. Create it! michael@0: nsCOMPtr element; michael@0: rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: realKid = element.forget(); michael@0: } michael@0: michael@0: if (realKid && !realKidAlreadyExisted) { michael@0: // Potentially remember the index of this element as the michael@0: // first element that we've generated. michael@0: if (aContainer && !*aContainer) { michael@0: *aContainer = aRealNode; michael@0: NS_ADDREF(*aContainer); michael@0: michael@0: uint32_t indx = aRealNode->GetChildCount(); michael@0: michael@0: // Since we haven't inserted any content yet, our new michael@0: // index in the container will be the current count of michael@0: // elements in the container. michael@0: *aNewIndexInContainer = indx; michael@0: } michael@0: michael@0: // Remember the template kid from which we created the michael@0: // real kid. This allows us to sync back up with the michael@0: // template to incrementally build content. michael@0: mTemplateMap.Put(realKid, tmplKid); michael@0: michael@0: rv = CopyAttributesToElement(tmplKid, realKid, aChild, false); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Add any persistent attributes michael@0: if (isGenerationElement) { michael@0: rv = AddPersistentAttributes(tmplKid->AsElement(), aChild, michael@0: realKid); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: // the unique content recurses up above. Also, don't recurse if michael@0: // this is a self reference (a reference to the same resource) michael@0: // or we'll end up regenerating the same content. michael@0: if (!aIsSelfReference && !isUnique) { michael@0: // this call creates the content inside the generation node, michael@0: // for example the label below: michael@0: // michael@0: // michael@0: rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, false, michael@0: false, aChild, false, aMatch, michael@0: nullptr /* don't care */, michael@0: nullptr /* don't care */); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (isGenerationElement) { michael@0: // build the next level of children michael@0: rv = CreateContainerContents(realKid, aChild, false, michael@0: false, false); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: } michael@0: michael@0: // We'll _already_ have added the unique elements; but if michael@0: // it's -not- unique, then use the XUL sort service now to michael@0: // append the element to the content model. michael@0: if (! isUnique) { michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: michael@0: if (isGenerationElement) michael@0: rv = InsertSortedNode(aRealNode, realKid, aChild, aNotify); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: rv = aRealNode->AppendChildTo(realKid, aNotify); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "unable to insert element"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULContentBuilder::CopyAttributesToElement(nsIContent* aTemplateNode, michael@0: nsIContent* aRealNode, michael@0: nsIXULTemplateResult* aResult, michael@0: bool aNotify) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Copy all attributes from the template to the new element michael@0: uint32_t numAttribs = aTemplateNode->GetAttrCount(); michael@0: michael@0: for (uint32_t attr = 0; attr < numAttribs; attr++) { michael@0: const nsAttrName* name = aTemplateNode->GetAttrNameAt(attr); michael@0: int32_t attribNameSpaceID = name->NamespaceID(); michael@0: // Hold a strong reference here so that the atom doesn't go away michael@0: // during UnsetAttr. michael@0: nsCOMPtr attribName = name->LocalName(); michael@0: michael@0: // XXXndeakin ignore namespaces until bug 321182 is fixed michael@0: if (attribName != nsGkAtoms::id && attribName != nsGkAtoms::uri) { michael@0: // Create a buffer here, because there's a chance that an michael@0: // attribute in the template is going to be an RDF URI, which is michael@0: // usually longish. michael@0: char16_t attrbuf[128]; michael@0: nsFixedString attribValue(attrbuf, ArrayLength(attrbuf), 0); michael@0: aTemplateNode->GetAttr(attribNameSpaceID, attribName, attribValue); michael@0: if (!attribValue.IsEmpty()) { michael@0: nsAutoString value; michael@0: rv = SubstituteText(aResult, attribValue, value); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // if the string is empty after substitutions, remove the michael@0: // attribute michael@0: if (!value.IsEmpty()) { michael@0: rv = aRealNode->SetAttr(attribNameSpaceID, michael@0: attribName, michael@0: name->GetPrefix(), michael@0: value, michael@0: aNotify); michael@0: } michael@0: else { michael@0: rv = aRealNode->UnsetAttr(attribNameSpaceID, michael@0: attribName, michael@0: aNotify); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULContentBuilder::AddPersistentAttributes(Element* aTemplateNode, michael@0: nsIXULTemplateResult* aResult, michael@0: nsIContent* aRealNode) michael@0: { michael@0: if (!mRoot) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr resource; michael@0: nsresult rv = GetResultResource(aResult, getter_AddRefs(resource)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString attribute, persist; michael@0: aTemplateNode->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist); michael@0: michael@0: while (!persist.IsEmpty()) { michael@0: attribute.Truncate(); michael@0: michael@0: int32_t offset = persist.FindCharInSet(" ,"); michael@0: if (offset > 0) { michael@0: persist.Left(attribute, offset); michael@0: persist.Cut(0, offset + 1); michael@0: } michael@0: else { michael@0: attribute = persist; michael@0: persist.Truncate(); michael@0: } michael@0: michael@0: attribute.Trim(" "); michael@0: michael@0: if (attribute.IsEmpty()) michael@0: break; michael@0: michael@0: nsCOMPtr tag; michael@0: int32_t nameSpaceID; michael@0: michael@0: nsCOMPtr ni = michael@0: aTemplateNode->GetExistingAttrNameFromQName(attribute); michael@0: if (ni) { michael@0: tag = ni->NameAtom(); michael@0: nameSpaceID = ni->NamespaceID(); michael@0: } michael@0: else { michael@0: tag = do_GetAtom(attribute); michael@0: NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nameSpaceID = kNameSpaceID_None; michael@0: } michael@0: michael@0: nsCOMPtr property; michael@0: rv = nsXULContentUtils::GetResource(nameSpaceID, tag, getter_AddRefs(property)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr target; michael@0: rv = mDB->GetTarget(resource, property, true, getter_AddRefs(target)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (! target) michael@0: continue; michael@0: michael@0: nsCOMPtr value = do_QueryInterface(target); michael@0: NS_ASSERTION(value != nullptr, "unable to stomach that sort of node"); michael@0: if (! value) michael@0: continue; michael@0: michael@0: const char16_t* valueStr; michael@0: rv = value->GetValueConst(&valueStr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aRealNode->SetAttr(nameSpaceID, tag, nsDependentString(valueStr), michael@0: false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULContentBuilder::SynchronizeUsingTemplate(nsIContent* aTemplateNode, michael@0: nsIContent* aRealElement, michael@0: nsIXULTemplateResult* aResult) michael@0: { michael@0: // check all attributes on the template node; if they reference a resource, michael@0: // update the equivalent attribute on the content node michael@0: nsresult rv; michael@0: rv = CopyAttributesToElement(aTemplateNode, aRealElement, aResult, true); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: uint32_t count = aTemplateNode->GetChildCount(); michael@0: michael@0: for (uint32_t loop = 0; loop < count; ++loop) { michael@0: nsIContent *tmplKid = aTemplateNode->GetChildAt(loop); michael@0: michael@0: if (! tmplKid) michael@0: break; michael@0: michael@0: nsIContent *realKid = aRealElement->GetChildAt(loop); michael@0: if (! realKid) michael@0: break; michael@0: michael@0: // check for text nodes and update them accordingly. michael@0: // This code is similar to that in BuildContentFromTemplate michael@0: if (tmplKid->NodeInfo()->Equals(nsGkAtoms::textnode, michael@0: kNameSpaceID_XUL)) { michael@0: char16_t attrbuf[128]; michael@0: nsFixedString attrValue(attrbuf, ArrayLength(attrbuf), 0); michael@0: tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue); michael@0: if (!attrValue.IsEmpty()) { michael@0: nsAutoString value; michael@0: rv = SubstituteText(aResult, attrValue, value); michael@0: if (NS_FAILED(rv)) return rv; michael@0: realKid->SetText(value, true); michael@0: } michael@0: } michael@0: michael@0: rv = SynchronizeUsingTemplate(tmplKid, realKid, aResult); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULContentBuilder::RemoveMember(nsIContent* aContent) michael@0: { michael@0: nsCOMPtr parent = aContent->GetParent(); michael@0: if (parent) { michael@0: int32_t pos = parent->IndexOf(aContent); michael@0: michael@0: NS_ASSERTION(pos >= 0, "parent doesn't think this child has an index"); michael@0: if (pos < 0) return NS_OK; michael@0: michael@0: // Note: RemoveChildAt sets |child|'s document to null so that michael@0: // it'll get knocked out of the XUL doc's resource-to-element michael@0: // map. michael@0: parent->RemoveChildAt(pos, true); michael@0: } michael@0: michael@0: // Remove from the content support map. michael@0: mContentSupportMap.Remove(aContent); michael@0: michael@0: // Remove from the template map michael@0: mTemplateMap.Remove(aContent); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULContentBuilder::CreateTemplateAndContainerContents(nsIContent* aElement, michael@0: bool aForceCreation) michael@0: { michael@0: // Generate both 1) the template content for the current element, michael@0: // and 2) recursive subcontent (if the current element refers to a michael@0: // container result). michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, michael@0: ("nsXULContentBuilder::CreateTemplateAndContainerContents start - flags: %d", michael@0: mFlags)); michael@0: michael@0: if (! mQueryProcessor) michael@0: return NS_OK; michael@0: michael@0: // for the root element, get the ref attribute and generate content michael@0: if (aElement == mRoot) { michael@0: if (! mRootResult) { michael@0: nsAutoString ref; michael@0: mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, ref); michael@0: michael@0: if (! ref.IsEmpty()) { michael@0: nsresult rv = mQueryProcessor->TranslateRef(mDataSource, ref, michael@0: getter_AddRefs(mRootResult)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: if (mRootResult) { michael@0: CreateContainerContents(aElement, mRootResult, aForceCreation, michael@0: false, true); michael@0: } michael@0: } michael@0: else if (!(mFlags & eDontRecurse)) { michael@0: // The content map will contain the generation elements (the ones that michael@0: // are given ids) and only those elements, so get the reference point michael@0: // from the corresponding match. michael@0: nsTemplateMatch *match = nullptr; michael@0: if (mContentSupportMap.Get(aElement, &match)) michael@0: CreateContainerContents(aElement, match->mResult, aForceCreation, michael@0: false, true); michael@0: } michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, michael@0: ("nsXULContentBuilder::CreateTemplateAndContainerContents end")); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULContentBuilder::CreateContainerContents(nsIContent* aElement, michael@0: nsIXULTemplateResult* aResult, michael@0: bool aForceCreation, michael@0: bool aNotify, michael@0: bool aNotifyAtEnd) michael@0: { michael@0: if (!aForceCreation && !IsOpen(aElement)) michael@0: return NS_OK; michael@0: michael@0: // don't generate children if recursion or child processing isn't allowed michael@0: if (aResult != mRootResult) { michael@0: if (mFlags & eDontRecurse) michael@0: return NS_OK; michael@0: michael@0: bool mayProcessChildren; michael@0: nsresult rv = aResult->GetMayProcessChildren(&mayProcessChildren); michael@0: if (NS_FAILED(rv) || !mayProcessChildren) michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr refResource; michael@0: GetResultResource(aResult, getter_AddRefs(refResource)); michael@0: if (! refResource) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Avoid re-entrant builds for the same resource. michael@0: if (IsActivated(refResource)) michael@0: return NS_OK; michael@0: michael@0: ActivationEntry entry(refResource, &mTop); michael@0: michael@0: // Compile the rules now, if they haven't been already. michael@0: if (! mQueriesCompiled) { michael@0: nsresult rv = CompileQueries(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: if (mQuerySets.Length() == 0) michael@0: return NS_OK; michael@0: michael@0: // See if the element's templates contents have been generated: michael@0: // this prevents a re-entrant call from triggering another michael@0: // generation. michael@0: nsXULElement *xulcontent = nsXULElement::FromContent(aElement); michael@0: if (xulcontent) { michael@0: if (xulcontent->GetTemplateGenerated()) michael@0: return NS_OK; michael@0: michael@0: // Now mark the element's contents as being generated so that michael@0: // any re-entrant calls don't trigger an infinite recursion. michael@0: xulcontent->SetTemplateGenerated(); michael@0: } michael@0: michael@0: int32_t newIndexInContainer = -1; michael@0: nsIContent* container = nullptr; michael@0: michael@0: int32_t querySetCount = mQuerySets.Length(); michael@0: michael@0: for (int32_t r = 0; r < querySetCount; r++) { michael@0: nsTemplateQuerySet* queryset = mQuerySets[r]; michael@0: michael@0: nsIAtom* tag = queryset->GetTag(); michael@0: if (tag && tag != aElement->Tag()) michael@0: continue; michael@0: michael@0: CreateContainerContentsForQuerySet(aElement, aResult, aNotify, queryset, michael@0: &container, &newIndexInContainer); michael@0: } michael@0: michael@0: if (aNotifyAtEnd && container) { michael@0: MOZ_AUTO_DOC_UPDATE(container->GetCurrentDoc(), UPDATE_CONTENT_MODEL, michael@0: true); michael@0: nsNodeUtils::ContentAppended(container, michael@0: container->GetChildAt(newIndexInContainer), michael@0: newIndexInContainer); michael@0: } michael@0: michael@0: NS_IF_RELEASE(container); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULContentBuilder::CreateContainerContentsForQuerySet(nsIContent* aElement, michael@0: nsIXULTemplateResult* aResult, michael@0: bool aNotify, michael@0: nsTemplateQuerySet* aQuerySet, michael@0: nsIContent** aContainer, michael@0: int32_t* aNewIndexInContainer) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { michael@0: nsAutoString id; michael@0: aResult->GetId(id); michael@0: PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, michael@0: ("nsXULContentBuilder::CreateContainerContentsForQuerySet start for ref %s\n", michael@0: NS_ConvertUTF16toUTF8(id).get())); michael@0: } michael@0: #endif michael@0: michael@0: if (! mQueryProcessor) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr results; michael@0: nsresult rv = mQueryProcessor->GenerateResults(mDataSource, aResult, michael@0: aQuerySet->mCompiledQuery, michael@0: getter_AddRefs(results)); michael@0: if (NS_FAILED(rv) || !results) michael@0: return rv; michael@0: michael@0: bool hasMoreResults; michael@0: rv = results->HasMoreElements(&hasMoreResults); michael@0: michael@0: for (; NS_SUCCEEDED(rv) && hasMoreResults; michael@0: rv = results->HasMoreElements(&hasMoreResults)) { michael@0: nsCOMPtr nr; michael@0: rv = results->GetNext(getter_AddRefs(nr)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr nextresult = do_QueryInterface(nr); michael@0: if (!nextresult) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: nsCOMPtr resultid; michael@0: rv = GetResultResource(nextresult, getter_AddRefs(resultid)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!resultid) michael@0: continue; michael@0: michael@0: nsTemplateMatch *newmatch = michael@0: nsTemplateMatch::Create(aQuerySet->Priority(), michael@0: nextresult, aElement); michael@0: if (!newmatch) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // check if there is already an existing match. If so, a previous michael@0: // query already generated content so the match is just added to the michael@0: // end of the set of matches. michael@0: michael@0: bool generateContent = true; michael@0: michael@0: nsTemplateMatch* prevmatch = nullptr; michael@0: nsTemplateMatch* existingmatch = nullptr; michael@0: nsTemplateMatch* removematch = nullptr; michael@0: if (mMatchMap.Get(resultid, &existingmatch)){ michael@0: // check if there is an existing match that matched a rule michael@0: while (existingmatch) { michael@0: // break out once we've reached a query in the list with a michael@0: // higher priority, as the new match list is sorted by michael@0: // priority, and the new match should be inserted here michael@0: int32_t priority = existingmatch->QuerySetPriority(); michael@0: if (priority > aQuerySet->Priority()) michael@0: break; michael@0: michael@0: // skip over non-matching containers michael@0: if (existingmatch->GetContainer() == aElement) { michael@0: // if the same priority is already found, replace it. This can happen michael@0: // when a container is removed and readded michael@0: if (priority == aQuerySet->Priority()) { michael@0: removematch = existingmatch; michael@0: break; michael@0: } michael@0: michael@0: if (existingmatch->IsActive()) michael@0: generateContent = false; michael@0: } michael@0: michael@0: prevmatch = existingmatch; michael@0: existingmatch = existingmatch->mNext; michael@0: } michael@0: } michael@0: michael@0: if (removematch) { michael@0: // remove the generated content for the existing match michael@0: rv = ReplaceMatch(removematch->mResult, nullptr, nullptr, aElement); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (mFlags & eLoggingEnabled) michael@0: OutputMatchToLog(resultid, removematch, false); michael@0: } michael@0: michael@0: if (generateContent) { michael@0: // find the rule that matches. If none match, the content does not michael@0: // need to be generated michael@0: michael@0: int16_t ruleindex; michael@0: nsTemplateRule* matchedrule = nullptr; michael@0: rv = DetermineMatchedRule(aElement, nextresult, aQuerySet, michael@0: &matchedrule, &ruleindex); michael@0: if (NS_FAILED(rv)) { michael@0: nsTemplateMatch::Destroy(newmatch, false); michael@0: return rv; michael@0: } michael@0: michael@0: if (matchedrule) { michael@0: rv = newmatch->RuleMatched(aQuerySet, matchedrule, michael@0: ruleindex, nextresult); michael@0: if (NS_FAILED(rv)) { michael@0: nsTemplateMatch::Destroy(newmatch, false); michael@0: return rv; michael@0: } michael@0: michael@0: // Grab the template node michael@0: nsCOMPtr action = matchedrule->GetAction(); michael@0: BuildContentFromTemplate(action, aElement, aElement, true, michael@0: mRefVariable == matchedrule->GetMemberVariable(), michael@0: nextresult, aNotify, newmatch, michael@0: aContainer, aNewIndexInContainer); michael@0: } michael@0: } michael@0: michael@0: if (mFlags & eLoggingEnabled) michael@0: OutputMatchToLog(resultid, newmatch, true); michael@0: michael@0: if (prevmatch) { michael@0: prevmatch->mNext = newmatch; michael@0: } michael@0: else { michael@0: mMatchMap.Put(resultid, newmatch); michael@0: } michael@0: michael@0: if (removematch) { michael@0: newmatch->mNext = removematch->mNext; michael@0: nsTemplateMatch::Destroy(removematch, true); michael@0: } michael@0: else { michael@0: newmatch->mNext = existingmatch; michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULContentBuilder::EnsureElementHasGenericChild(nsIContent* parent, michael@0: int32_t nameSpaceID, michael@0: nsIAtom* tag, michael@0: bool aNotify, michael@0: nsIContent** result) michael@0: { michael@0: nsresult rv; michael@0: michael@0: rv = nsXULContentUtils::FindChildByTag(parent, nameSpaceID, tag, result); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (rv == NS_RDF_NO_VALUE) { michael@0: // we need to construct a new child element. michael@0: nsCOMPtr element; michael@0: michael@0: rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // XXX Note that the notification ensures we won't batch insertions! This could be bad! - Dave michael@0: rv = parent->AppendChildTo(element, aNotify); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: *result = element; michael@0: NS_ADDREF(*result); michael@0: return NS_ELEMENT_GOT_CREATED; michael@0: } michael@0: else { michael@0: return NS_ELEMENT_WAS_THERE; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsXULContentBuilder::IsOpen(nsIContent* aElement) michael@0: { michael@0: // Determine if this is a or element michael@0: if (!aElement->IsXUL()) michael@0: return true; michael@0: michael@0: // XXXhyatt Use the XBL service to obtain a base tag. michael@0: nsIAtom *tag = aElement->Tag(); michael@0: if (tag == nsGkAtoms::menu || michael@0: tag == nsGkAtoms::menubutton || michael@0: tag == nsGkAtoms::toolbarbutton || michael@0: tag == nsGkAtoms::button || michael@0: tag == nsGkAtoms::treeitem) michael@0: return aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULContentBuilder::RemoveGeneratedContent(nsIContent* aElement) michael@0: { michael@0: // Keep a queue of "ungenerated" elements that we have to probe michael@0: // for generated content. michael@0: nsAutoTArray ungenerated; michael@0: if (ungenerated.AppendElement(aElement) == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: uint32_t count; michael@0: while (0 != (count = ungenerated.Length())) { michael@0: // Pull the next "ungenerated" element off the queue. michael@0: uint32_t last = count - 1; michael@0: nsCOMPtr element = ungenerated[last]; michael@0: ungenerated.RemoveElementAt(last); michael@0: michael@0: uint32_t i = element->GetChildCount(); michael@0: michael@0: while (i-- > 0) { michael@0: nsCOMPtr child = element->GetChildAt(i); michael@0: michael@0: // Optimize for the