/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Robert Churchill * David Hyatt * Chris Waterson * Pierre Phaneuf * Neil Deakin * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #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 "jsapi.h" #include "pldhash.h" #include "rdf.h" //---------------------------------------------------------------------- // // 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, PRBool aForceCreation); NS_IMETHOD HasGeneratedContent(nsIRDFResource* aResource, nsIAtom* aTag, PRBool* aGenerated); NS_IMETHOD GetResultForContent(nsIDOMElement* aContent, nsIXULTemplateResult** aResult); // nsIMutationObserver interface NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED protected: friend NS_IMETHODIMP NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult); nsXULContentBuilder(); void Traverse(nsCycleCollectionTraversalCallback &cb) const { mSortState.Traverse(cb); } virtual void Uninit(PRBool 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, PRBool aIsUnique, PRBool aIsSelfReference, nsIXULTemplateResult* aChild, PRBool aNotify, nsTemplateMatch* aMatch, nsIContent** aContainer, PRInt32* 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, PRBool 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(nsIContent* 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, PRBool 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, PRBool aForceCreation, PRBool aNotify, PRBool 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, PRBool aNotify, nsTemplateQuerySet* aQuerySet, nsIContent** aContainer, PRInt32* 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, PRInt32 aNameSpaceID, nsIAtom* aTag, PRBool aNotify, nsIContent** aResult); PRBool IsOpen(nsIContent* aElement); nsresult RemoveGeneratedContent(nsIContent* aElement); nsresult GetElementsForResult(nsIXULTemplateResult* aResult, nsCOMArray& aElements); nsresult CreateElement(PRInt32 aNameSpaceID, nsIAtom* aTag, nsIContent** 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, PRBool aIgnoreNonContainers, PRBool 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 PRBool 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, PRInt32* 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, PRBool 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; }; NS_IMETHODIMP NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult) { NS_PRECONDITION(aOuter == nsnull, "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 = PR_FALSE; } void nsXULContentBuilder::Uninit(PRBool 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 = PR_FALSE; nsXULTemplateBuilder::Uninit(aIsFinal); } nsresult nsXULContentBuilder::BuildContentFromTemplate(nsIContent *aTemplateNode, nsIContent *aResourceNode, nsIContent *aRealNode, PRBool aIsUnique, PRBool aIsSelfReference, nsIXULTemplateResult* aChild, PRBool aNotify, nsTemplateMatch* aMatch, nsIContent** aContainer, PRInt32* 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)); const char *tmpln, *resn, *realn; aTemplateNode->Tag()->GetUTF8String(&tmpln); aResourceNode->Tag()->GetUTF8String(&resn); aRealNode->Tag()->GetUTF8String(&realn); nsAutoString id; aChild->GetId(id); PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, ("Tags: [Template: %s Resource: %s Real: %s] for id %s", tmpln, resn, realn, NS_ConvertUTF16toUTF8(id).get())); } #endif // Iterate through all of the template children, constructing // "real" content model nodes for each "template" child. PRUint32 count = aTemplateNode->GetChildCount(); for (PRUint32 kid = 0; kid < count; kid++) { nsIContent *tmplKid = aTemplateNode->GetChildAt(kid); PRInt32 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 PRBool isGenerationElement = PR_FALSE; PRBool 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 = PR_TRUE; isUnique = PR_FALSE; } } nsIAtom *tag = tmplKid->Tag(); #ifdef PR_LOGGING if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { const char *tagname; tag->GetUTF8String(&tagname); PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, ("xultemplate[%p] building %s %s %s", this, tagname, (isGenerationElement ? "[resource]" : ""), (isUnique ? "[unique]" : ""))); } #endif // Set to PR_TRUE if the child we're trying to create now // already existed in the content model. PRBool realKidAlreadyExisted = PR_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 = PR_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); PRUint32 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, PR_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. rv = CreateElement(nameSpaceID, tag, getter_AddRefs(realKid)); if (NS_FAILED(rv)) return rv; // 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, PR_FALSE); if (NS_FAILED(rv)) return rv; if (! aNotify) { // XUL document will watch us, and take care of making // sure that we get added to or removed from the // element map if aNotify is true. If not, we gotta do // it ourselves. Yay. nsCOMPtr xuldoc = do_QueryInterface(mRoot->GetDocument()); if (xuldoc) xuldoc->AddElementForID(realKid); } // Set up the element's 'container' and 'empty' attributes. SetContainerAttrs(realKid, aChild, PR_TRUE, PR_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 PRUnichar attrbuf[128]; nsFixedString attrValue(attrbuf, NS_ARRAY_LENGTH(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; nsCOMPtr content; rv = NS_NewTextNode(getter_AddRefs(content), mRoot->NodeInfo()->NodeInfoManager()); if (NS_FAILED(rv)) return rv; content->SetText(value, PR_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(PR_FALSE, 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! rv = CreateElement(nameSpaceID, tag, getter_AddRefs(realKid)); if (NS_FAILED(rv)) return rv; } 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); PRUint32 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, PR_FALSE); if (NS_FAILED(rv)) return rv; // Add any persistent attributes if (isGenerationElement) { rv = AddPersistentAttributes(tmplKid, 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, PR_FALSE, PR_FALSE, aChild, PR_FALSE, aMatch, nsnull /* don't care */, nsnull /* don't care */); if (NS_FAILED(rv)) return rv; if (isGenerationElement) { // build the next level of children rv = CreateContainerContents(realKid, aChild, PR_FALSE, PR_FALSE, PR_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, PRBool aNotify) { nsresult rv; // Copy all attributes from the template to the new element PRUint32 numAttribs = aTemplateNode->GetAttrCount(); for (PRUint32 attr = 0; attr < numAttribs; attr++) { const nsAttrName* name = aTemplateNode->GetAttrNameAt(attr); PRInt32 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. PRUnichar attrbuf[128]; nsFixedString attribValue(attrbuf, NS_ARRAY_LENGTH(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(nsIContent* 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(); PRInt32 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; PRInt32 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, PR_TRUE, getter_AddRefs(target)); NS_ENSURE_SUCCESS(rv, rv); if (! target) continue; nsCOMPtr value = do_QueryInterface(target); NS_ASSERTION(value != nsnull, "unable to stomach that sort of node"); if (! value) continue; const PRUnichar* valueStr; rv = value->GetValueConst(&valueStr); NS_ENSURE_SUCCESS(rv, rv); rv = aRealNode->SetAttr(nameSpaceID, tag, nsDependentString(valueStr), PR_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, PR_TRUE); if (NS_FAILED(rv)) return rv; PRUint32 count = aTemplateNode->GetChildCount(); for (PRUint32 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)) { PRUnichar attrbuf[128]; nsFixedString attrValue(attrbuf, NS_ARRAY_LENGTH(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, PR_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) { PRInt32 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. nsresult rv = parent->RemoveChildAt(pos, PR_TRUE); if (NS_FAILED(rv)) return rv; } // 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, PRBool 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, PR_FALSE, PR_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 = nsnull; if (mContentSupportMap.Get(aElement, &match)) CreateContainerContents(aElement, match->mResult, aForceCreation, PR_FALSE, PR_TRUE); } PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, ("nsXULContentBuilder::CreateTemplateAndContainerContents end")); return NS_OK; } nsresult nsXULContentBuilder::CreateContainerContents(nsIContent* aElement, nsIXULTemplateResult* aResult, PRBool aForceCreation, PRBool aNotify, PRBool 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; PRBool 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(); } PRInt32 newIndexInContainer = -1; nsIContent* container = nsnull; PRInt32 querySetCount = mQuerySets.Length(); for (PRInt32 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, PR_TRUE); nsNodeUtils::ContentAppended(container, newIndexInContainer); } NS_IF_RELEASE(container); return NS_OK; } nsresult nsXULContentBuilder::CreateContainerContentsForQuerySet(nsIContent* aElement, nsIXULTemplateResult* aResult, PRBool aNotify, nsTemplateQuerySet* aQuerySet, nsIContent** aContainer, PRInt32* 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; PRBool 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(mPool, 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. PRBool generateContent = PR_TRUE; nsTemplateMatch* prevmatch = nsnull; nsTemplateMatch* existingmatch = nsnull; nsTemplateMatch* removematch = nsnull; 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 PRInt32 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 = PR_FALSE; } prevmatch = existingmatch; existingmatch = existingmatch->mNext; } } if (removematch) { // remove the generated content for the existing match rv = ReplaceMatch(removematch->mResult, nsnull, nsnull, aElement); if (NS_FAILED(rv)) return rv; } if (generateContent) { // find the rule that matches. If none match, the content does not // need to be generated PRInt16 ruleindex; nsTemplateRule* matchedrule = nsnull; rv = DetermineMatchedRule(aElement, nextresult, aQuerySet, &matchedrule, &ruleindex); if (NS_FAILED(rv)) { nsTemplateMatch::Destroy(mPool, newmatch, PR_FALSE); return rv; } if (matchedrule) { rv = newmatch->RuleMatched(aQuerySet, matchedrule, ruleindex, nextresult); if (NS_FAILED(rv)) { nsTemplateMatch::Destroy(mPool, newmatch, PR_FALSE); return rv; } // Grab the template node nsCOMPtr action = matchedrule->GetAction(); BuildContentFromTemplate(action, aElement, aElement, PR_TRUE, mRefVariable == matchedrule->GetMemberVariable(), nextresult, aNotify, newmatch, aContainer, aNewIndexInContainer); } } if (prevmatch) { prevmatch->mNext = newmatch; } else if (!mMatchMap.Put(resultid, newmatch)) { nsTemplateMatch::Destroy(mPool, newmatch, PR_TRUE); return NS_ERROR_OUT_OF_MEMORY; } if (removematch) { newmatch->mNext = removematch->mNext; nsTemplateMatch::Destroy(mPool, removematch, PR_TRUE); } else { newmatch->mNext = existingmatch; } } return rv; } nsresult nsXULContentBuilder::EnsureElementHasGenericChild(nsIContent* parent, PRInt32 nameSpaceID, nsIAtom* tag, PRBool 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; } } PRBool nsXULContentBuilder::IsOpen(nsIContent* aElement) { // Determine if this is a or element if (!aElement->IsXUL()) return PR_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 PR_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) == nsnull) return NS_ERROR_OUT_OF_MEMORY; PRUint32 count; while (0 != (count = ungenerated.Length())) { // Pull the next "ungenerated" element off the queue. PRUint32 last = count - 1; nsIContent* element = ungenerated[last]; ungenerated.RemoveElementAt(last); PRUint32 i = element->GetChildCount(); while (i-- > 0) { nsCOMPtr child = element->GetChildAt(i); // Optimize for the