/* -*- 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& 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** 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 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->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 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; RefPtr 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; RefPtr 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 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). 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 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 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; element.forget(result); return NS_ELEMENT_GOT_CREATED; } else { return NS_ELEMENT_WAS_THERE; } } bool nsXULContentBuilder::IsOpen(nsIContent* aElement) { // Determine if this is a or 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 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