Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / content / xul / templates / src / nsXULContentBuilder.cpp
blobdc7b5479368bc9465b6525f3470a8f21cef649ae
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Robert Churchill <rjc@netscape.com>
24 * David Hyatt <hyatt@netscape.com>
25 * Chris Waterson <waterson@netscape.com>
26 * Pierre Phaneuf <pp@ludusdesign.com>
27 * Neil Deakin <enndeakin@sympatico.ca>
29 * Alternatively, the contents of this file may be used under the terms of
30 * either of the GNU General Public License Version 2 or later (the "GPL"),
31 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 * in which case the provisions of the GPL or the LGPL are applicable instead
33 * of those above. If you wish to allow use of your version of this file only
34 * under the terms of either the GPL or the LGPL, and not to allow others to
35 * use your version of this file under the terms of the MPL, indicate your
36 * decision by deleting the provisions above and replace them with the notice
37 * and other provisions required by the GPL or the LGPL. If you do not delete
38 * the provisions above, a recipient may use your version of this file under
39 * the terms of any one of the MPL, the GPL or the LGPL.
41 * ***** END LICENSE BLOCK ***** */
43 #include "nsContentCID.h"
44 #include "nsIDocument.h"
45 #include "nsIDOMNodeList.h"
46 #include "nsIDOMXULDocument.h"
47 #include "nsINodeInfo.h"
48 #include "nsIServiceManager.h"
49 #include "nsIXULDocument.h"
51 #include "nsContentSupportMap.h"
52 #include "nsRDFConMemberTestNode.h"
53 #include "nsRDFPropertyTestNode.h"
54 #include "nsXULSortService.h"
55 #include "nsTemplateRule.h"
56 #include "nsTemplateMap.h"
57 #include "nsVoidArray.h"
58 #include "nsXPIDLString.h"
59 #include "nsGkAtoms.h"
60 #include "nsXULContentUtils.h"
61 #include "nsXULElement.h"
62 #include "nsXULTemplateBuilder.h"
63 #include "nsSupportsArray.h"
64 #include "nsNodeInfoManager.h"
65 #include "nsContentCreatorFunctions.h"
66 #include "nsContentUtils.h"
67 #include "nsAttrName.h"
68 #include "nsNodeUtils.h"
69 #include "mozAutoDocUpdate.h"
71 #include "jsapi.h"
72 #include "pldhash.h"
73 #include "rdf.h"
75 //----------------------------------------------------------------------
77 // Return values for EnsureElementHasGenericChild()
79 #define NS_ELEMENT_GOT_CREATED NS_RDF_NO_VALUE
80 #define NS_ELEMENT_WAS_THERE NS_OK
82 //----------------------------------------------------------------------
84 // nsXULContentBuilder
87 /**
88 * The content builder generates DOM nodes from a template. The actual content
89 * generation is done entirely inside BuildContentFromTemplate.
91 * Content generation is centered around the generation node (the node with
92 * uri="?member" on it). Nodes above the generation node are unique and
93 * generated only once. BuildContentFromTemplate will be passed the unique
94 * flag as an argument for content at this point and will recurse until it
95 * finds the generation node.
97 * Once the generation node has been found, the results for that content node
98 * are added to the content map, stored in mContentSupportMap.
100 * If recursion is allowed, generation continues, where the generation node
101 * becomes the container to insert into.
103 class nsXULContentBuilder : public nsXULTemplateBuilder
105 public:
106 // nsIXULTemplateBuilder interface
107 NS_IMETHOD CreateContents(nsIContent* aElement, PRBool aForceCreation);
109 NS_IMETHOD HasGeneratedContent(nsIRDFResource* aResource,
110 nsIAtom* aTag,
111 PRBool* aGenerated);
113 NS_IMETHOD GetResultForContent(nsIDOMElement* aContent,
114 nsIXULTemplateResult** aResult);
116 // nsIMutationObserver interface
117 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
118 NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
120 protected:
121 friend NS_IMETHODIMP
122 NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult);
124 nsXULContentBuilder();
126 void Traverse(nsCycleCollectionTraversalCallback &cb) const
128 mSortState.Traverse(cb);
131 virtual void Uninit(PRBool aIsFinal);
133 // Implementation methods
134 nsresult
135 OpenContainer(nsIContent* aElement);
137 nsresult
138 CloseContainer(nsIContent* aElement);
141 * Build content from a template for a given result. This will be called
142 * recursively or on demand and will be called for every node in the
143 * generated content tree.
145 nsresult
146 BuildContentFromTemplate(nsIContent *aTemplateNode,
147 nsIContent *aResourceNode,
148 nsIContent *aRealNode,
149 PRBool aIsUnique,
150 PRBool aIsSelfReference,
151 nsIXULTemplateResult* aChild,
152 PRBool aNotify,
153 nsTemplateMatch* aMatch,
154 nsIContent** aContainer,
155 PRInt32* aNewIndexInContainer);
158 * Copy the attributes from the template node to the node generated
159 * from it, performing any substitutions.
161 * @param aTemplateNode node within template
162 * @param aRealNode generated node to set attibutes upon
163 * @param aResult result to look up variable->value bindings in
164 * @param aNotify true to notify of DOM changes
166 nsresult
167 CopyAttributesToElement(nsIContent* aTemplateNode,
168 nsIContent* aRealNode,
169 nsIXULTemplateResult* aResult,
170 PRBool aNotify);
173 * Add any necessary persistent attributes (persist="...") from the
174 * local store to a generated node.
176 * @param aTemplateNode node within template
177 * @param aRealNode generated node to set persisted attibutes upon
178 * @param aResult result to look up variable->value bindings in
180 nsresult
181 AddPersistentAttributes(nsIContent* aTemplateNode,
182 nsIXULTemplateResult* aResult,
183 nsIContent* aRealNode);
186 * Recalculate any attributes that have variable references. This will
187 * be called when a binding has been changed to update the attributes.
188 * The attributes are copied from the node aTemplateNode in the template
189 * to the generated node aRealNode, using the values from the result
190 * aResult. This method will operate recursively.
192 * @param aTemplateNode node within template
193 * @param aRealNode generated node to set attibutes upon
194 * @param aResult result to look up variable->value bindings in
196 nsresult
197 SynchronizeUsingTemplate(nsIContent *aTemplateNode,
198 nsIContent* aRealNode,
199 nsIXULTemplateResult* aResult);
202 * Remove the generated node aContent from the DOM and the hashtables
203 * used by the content builder.
205 nsresult
206 RemoveMember(nsIContent* aContent);
209 * Create the appropriate generated content for aElement, by calling
210 * CreateContainerContents.
212 * @param aElement element to generate content inside
213 * @param aForceCreation true to force creation for closed items such as menus
215 nsresult
216 CreateTemplateAndContainerContents(nsIContent* aElement,
217 PRBool aForceCreation);
220 * Generate the results for a template, by calling
221 * CreateContainerContentsForQuerySet for each queryset.
223 * @param aElement element to generate content inside
224 * @param aResult reference point for query
225 * @param aForceCreation true to force creation for closed items such as menus
226 * @param aNotify true to notify of DOM changes as each element is inserted
227 * @param aNotifyAtEnd notify at the end of all DOM changes
229 nsresult
230 CreateContainerContents(nsIContent* aElement,
231 nsIXULTemplateResult* aResult,
232 PRBool aForceCreation,
233 PRBool aNotify,
234 PRBool aNotifyAtEnd);
237 * Generate the results for a query.
239 * @param aElement element to generate content inside
240 * @param aResult reference point for query
241 * @param aNotify true to notify of DOM changes
242 * @param aContainer container content was added inside
243 * @param aNewIndexInContainer index with container in which content was added
245 nsresult
246 CreateContainerContentsForQuerySet(nsIContent* aElement,
247 nsIXULTemplateResult* aResult,
248 PRBool aNotify,
249 nsTemplateQuerySet* aQuerySet,
250 nsIContent** aContainer,
251 PRInt32* aNewIndexInContainer);
254 * Check if an element with a particular tag exists with a container.
255 * If it is not present, append a new element with that tag into the
256 * container.
258 * @param aParent parent container
259 * @param aNameSpaceID namespace of tag to locate or create
260 * @param aTag tag to locate or create
261 * @param aNotify true to notify of DOM changes
262 * @param aResult set to the found or created node.
264 nsresult
265 EnsureElementHasGenericChild(nsIContent* aParent,
266 PRInt32 aNameSpaceID,
267 nsIAtom* aTag,
268 PRBool aNotify,
269 nsIContent** aResult);
271 PRBool
272 IsOpen(nsIContent* aElement);
274 nsresult
275 RemoveGeneratedContent(nsIContent* aElement);
277 nsresult
278 GetElementsForResult(nsIXULTemplateResult* aResult,
279 nsCOMArray<nsIContent>& aElements);
281 nsresult
282 CreateElement(PRInt32 aNameSpaceID,
283 nsIAtom* aTag,
284 nsIContent** aResult);
287 * Set the container and empty attributes on a node. If
288 * aIgnoreNonContainers is true, then the element is not changed
289 * for non-containers. Otherwise, the container attribute will be set to
290 * false.
292 * @param aElement element to set attributes on
293 * @param aResult result to use to determine state of attributes
294 * @param aIgnoreNonContainers true to not change for non-containers
295 * @param aNotify true to notify of DOM changes
297 nsresult
298 SetContainerAttrs(nsIContent *aElement,
299 nsIXULTemplateResult* aResult,
300 PRBool aIgnoreNonContainers,
301 PRBool aNotify);
303 virtual nsresult
304 RebuildAll();
306 // GetInsertionLocations, ReplaceMatch and SynchronizeResult are inherited
307 // from nsXULTemplateBuilder
310 * Return true if the result can be inserted into the template as
311 * generated content. For the content builder, aLocations will be set
312 * to the list of containers where the content should be inserted.
314 virtual PRBool
315 GetInsertionLocations(nsIXULTemplateResult* aOldResult,
316 nsCOMArray<nsIContent>** aLocations);
319 * Remove the content associated with aOldResult which no longer matches,
320 * and/or generate content for a new match.
322 virtual nsresult
323 ReplaceMatch(nsIXULTemplateResult* aOldResult,
324 nsTemplateMatch* aNewMatch,
325 nsTemplateRule* aNewMatchRule,
326 void *aContext);
329 * Synchronize a result bindings with the generated content for that
330 * result. This will be called as a result of the template builder's
331 * ResultBindingChanged method.
333 virtual nsresult
334 SynchronizeResult(nsIXULTemplateResult* aResult);
337 * Compare a result to a content node. If the generated content for the
338 * result should come before aContent, set aSortOrder to -1. If it should
339 * come after, set sortOrder to 1. If both are equal, set to 0.
341 nsresult
342 CompareResultToNode(nsIXULTemplateResult* aResult, nsIContent* aContent,
343 PRInt32* aSortOrder);
346 * Insert a generated node into the container where it should go according
347 * to the current sort. aNode is the generated content node and aResult is
348 * the result for the generated node.
350 nsresult
351 InsertSortedNode(nsIContent* aContainer,
352 nsIContent* aNode,
353 nsIXULTemplateResult* aResult,
354 PRBool aNotify);
357 * Maintains a mapping between elements in the DOM and the matches
358 * that they support.
360 nsContentSupportMap mContentSupportMap;
363 * Maintains a mapping from an element in the DOM to the template
364 * element that it was created from.
366 nsTemplateMap mTemplateMap;
369 * Information about the currently active sort
371 nsSortState mSortState;
374 NS_IMETHODIMP
375 NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult)
377 NS_PRECONDITION(aOuter == nsnull, "no aggregation");
378 if (aOuter)
379 return NS_ERROR_NO_AGGREGATION;
381 nsresult rv;
382 nsXULContentBuilder* result = new nsXULContentBuilder();
383 if (!result)
384 return NS_ERROR_OUT_OF_MEMORY;
386 NS_ADDREF(result); // stabilize
388 rv = result->InitGlobals();
390 if (NS_SUCCEEDED(rv))
391 rv = result->QueryInterface(aIID, aResult);
393 NS_RELEASE(result);
394 return rv;
397 nsXULContentBuilder::nsXULContentBuilder()
399 mSortState.initialized = PR_FALSE;
402 void
403 nsXULContentBuilder::Uninit(PRBool aIsFinal)
405 if (! aIsFinal && mRoot) {
406 nsresult rv = RemoveGeneratedContent(mRoot);
407 if (NS_FAILED(rv))
408 return;
411 // Nuke the content support map completely.
412 mContentSupportMap.Clear();
413 mTemplateMap.Clear();
415 mSortState.initialized = PR_FALSE;
417 nsXULTemplateBuilder::Uninit(aIsFinal);
420 nsresult
421 nsXULContentBuilder::BuildContentFromTemplate(nsIContent *aTemplateNode,
422 nsIContent *aResourceNode,
423 nsIContent *aRealNode,
424 PRBool aIsUnique,
425 PRBool aIsSelfReference,
426 nsIXULTemplateResult* aChild,
427 PRBool aNotify,
428 nsTemplateMatch* aMatch,
429 nsIContent** aContainer,
430 PRInt32* aNewIndexInContainer)
432 // This is the mother lode. Here is where we grovel through an
433 // element in the template, copying children from the template
434 // into the "real" content tree, performing substitution as we go
435 // by looking stuff up using the results.
437 // |aTemplateNode| is the element in the "template tree", whose
438 // children we will duplicate and move into the "real" content
439 // tree.
441 // |aResourceNode| is the element in the "real" content tree that
442 // has the "id" attribute set to an result's id. This is
443 // not directly used here, but rather passed down to the XUL
444 // sort service to perform container-level sort.
446 // |aRealNode| is the element in the "real" content tree to which
447 // the new elements will be copied.
449 // |aIsUnique| is set to "true" so long as content has been
450 // "unique" (or "above" the resource element) so far in the
451 // template.
453 // |aIsSelfReference| should be set to "true" for cases where
454 // the reference and member variables are the same, indicating
455 // that the generated node is the same as the reference point,
456 // so generation should not recurse, or else an infinite loop
457 // would occur.
459 // |aChild| is the result for which we are building content.
461 // |aNotify| is set to "true" if content should be constructed
462 // "noisily"; that is, whether the document observers should be
463 // notified when new content is added to the content model.
465 // |aContainer| is an out parameter that will be set to the first
466 // container element in the "real" content tree to which content
467 // was appended.
469 // |aNewIndexInContainer| is an out parameter that will be set to
470 // the index in aContainer at which new content is first
471 // constructed.
473 // If |aNotify| is "false", then |aContainer| and
474 // |aNewIndexInContainer| are used to determine where in the
475 // content model new content is constructed. This allows a single
476 // notification to be propagated to document observers.
479 nsresult rv;
481 #ifdef PR_LOGGING
482 if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) {
483 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
484 ("nsXULContentBuilder::BuildContentFromTemplate (is unique: %d)",
485 aIsUnique));
487 const char *tmpln, *resn, *realn;
488 aTemplateNode->Tag()->GetUTF8String(&tmpln);
489 aResourceNode->Tag()->GetUTF8String(&resn);
490 aRealNode->Tag()->GetUTF8String(&realn);
492 nsAutoString id;
493 aChild->GetId(id);
495 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
496 ("Tags: [Template: %s Resource: %s Real: %s] for id %s",
497 tmpln, resn, realn, NS_ConvertUTF16toUTF8(id).get()));
499 #endif
501 // Iterate through all of the template children, constructing
502 // "real" content model nodes for each "template" child.
503 PRUint32 count = aTemplateNode->GetChildCount();
505 for (PRUint32 kid = 0; kid < count; kid++) {
506 nsIContent *tmplKid = aTemplateNode->GetChildAt(kid);
508 PRInt32 nameSpaceID = tmplKid->GetNameSpaceID();
510 // Check whether this element is the generation element. The generation
511 // element is the element that is cookie-cutter copied once for each
512 // different result specified by |aChild|.
514 // Nodes that appear -above- the generation element
515 // (that is, are ancestors of the generation element in the
516 // content model) are unique across all values of |aChild|,
517 // and are created only once.
519 // Nodes that appear -below- the generation element (that is,
520 // are descendants of the generation element in the content
521 // model), are cookie-cutter copied for each distinct value of
522 // |aChild|.
524 // For example, in a <tree> template:
526 // <tree>
527 // <template>
528 // <treechildren> [1]
529 // <treeitem uri="rdf:*"> [2]
530 // <treerow> [3]
531 // <treecell value="rdf:urn:foo" /> [4]
532 // <treecell value="rdf:urn:bar" /> [5]
533 // </treerow>
534 // </treeitem>
535 // </treechildren>
536 // </template>
537 // </tree>
539 // The <treeitem> element [2] is the generation element. This
540 // element, and all of its descendants ([3], [4], and [5])
541 // will be duplicated for each different |aChild|.
542 // It's ancestor <treechildren> [1] is unique, and
543 // will only be created -once-, no matter how many <treeitem>s
544 // are created below it.
546 // isUnique will be true for nodes above the generation element,
547 // isGenerationElement will be true for the generation element,
548 // and both will be false for descendants
549 PRBool isGenerationElement = PR_FALSE;
550 PRBool isUnique = aIsUnique;
553 // We identify the resource element by presence of a
554 // "uri='rdf:*'" attribute. (We also support the older
555 // "uri='...'" syntax.)
556 if (tmplKid->HasAttr(kNameSpaceID_None, nsGkAtoms::uri) && aMatch->IsActive()) {
557 isGenerationElement = PR_TRUE;
558 isUnique = PR_FALSE;
562 nsIAtom *tag = tmplKid->Tag();
564 #ifdef PR_LOGGING
565 if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) {
566 const char *tagname;
567 tag->GetUTF8String(&tagname);
568 PR_LOG(gXULTemplateLog, PR_LOG_DEBUG,
569 ("xultemplate[%p] building %s %s %s",
570 this, tagname,
571 (isGenerationElement ? "[resource]" : ""),
572 (isUnique ? "[unique]" : "")));
574 #endif
576 // Set to PR_TRUE if the child we're trying to create now
577 // already existed in the content model.
578 PRBool realKidAlreadyExisted = PR_FALSE;
580 nsCOMPtr<nsIContent> realKid;
581 if (isUnique) {
582 // The content is "unique"; that is, we haven't descended
583 // far enough into the template to hit the generation
584 // element yet. |EnsureElementHasGenericChild()| will
585 // conditionally create the element iff it isn't there
586 // already.
587 rv = EnsureElementHasGenericChild(aRealNode, nameSpaceID, tag, aNotify, getter_AddRefs(realKid));
588 if (NS_FAILED(rv))
589 return rv;
591 if (rv == NS_ELEMENT_WAS_THERE) {
592 realKidAlreadyExisted = PR_TRUE;
594 else {
595 // Potentially remember the index of this element as the first
596 // element that we've generated. Note that we remember
597 // this -before- we recurse!
598 if (aContainer && !*aContainer) {
599 *aContainer = aRealNode;
600 NS_ADDREF(*aContainer);
602 PRUint32 indx = aRealNode->GetChildCount();
604 // Since EnsureElementHasGenericChild() added us, make
605 // sure to subtract one for our real index.
606 *aNewIndexInContainer = indx - 1;
610 // Recurse until we get to the resource element. Since
611 // -we're- unique, assume that our child will be
612 // unique. The check for the "resource" element at the top
613 // of the function will trip this to |false| as soon as we
614 // encounter it.
615 rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, PR_TRUE,
616 aIsSelfReference, aChild, aNotify, aMatch,
617 aContainer, aNewIndexInContainer);
619 if (NS_FAILED(rv))
620 return rv;
622 else if (isGenerationElement) {
623 // It's the "resource" element. Create a new element using
624 // the namespace ID and tag from the template element.
625 rv = CreateElement(nameSpaceID, tag, getter_AddRefs(realKid));
626 if (NS_FAILED(rv))
627 return rv;
629 // Add the resource element to the content support map so
630 // we can remove the match based on the content node later.
631 mContentSupportMap.Put(realKid, aMatch);
633 // Assign the element an 'id' attribute using result's id
634 nsAutoString id;
635 rv = aChild->GetId(id);
636 if (NS_FAILED(rv))
637 return rv;
639 rv = realKid->SetAttr(kNameSpaceID_None, nsGkAtoms::id, id, PR_FALSE);
640 if (NS_FAILED(rv))
641 return rv;
643 if (! aNotify) {
644 // XUL document will watch us, and take care of making
645 // sure that we get added to or removed from the
646 // element map if aNotify is true. If not, we gotta do
647 // it ourselves. Yay.
648 nsCOMPtr<nsIXULDocument> xuldoc =
649 do_QueryInterface(mRoot->GetDocument());
650 if (xuldoc)
651 xuldoc->AddElementForID(realKid);
654 // Set up the element's 'container' and 'empty' attributes.
655 SetContainerAttrs(realKid, aChild, PR_TRUE, PR_FALSE);
657 else if (tag == nsGkAtoms::textnode &&
658 nameSpaceID == kNameSpaceID_XUL) {
659 // <xul:text value="..."> is replaced by text of the
660 // actual value of the 'rdf:resource' attribute for the
661 // given node.
662 // SynchronizeUsingTemplate contains code used to update textnodes,
663 // so make sure to modify both when changing this
664 PRUnichar attrbuf[128];
665 nsFixedString attrValue(attrbuf, NS_ARRAY_LENGTH(attrbuf), 0);
666 tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
667 if (!attrValue.IsEmpty()) {
668 nsAutoString value;
669 rv = SubstituteText(aChild, attrValue, value);
670 if (NS_FAILED(rv)) return rv;
672 nsCOMPtr<nsIContent> content;
673 rv = NS_NewTextNode(getter_AddRefs(content),
674 mRoot->NodeInfo()->NodeInfoManager());
675 if (NS_FAILED(rv)) return rv;
677 content->SetText(value, PR_FALSE);
679 rv = aRealNode->AppendChildTo(content, aNotify);
680 if (NS_FAILED(rv)) return rv;
682 // XXX Don't bother remembering text nodes as the
683 // first element we've generated?
686 else if (tmplKid->IsNodeOfType(nsINode::eTEXT)) {
687 nsCOMPtr<nsIDOMNode> tmplTextNode = do_QueryInterface(tmplKid);
688 if (!tmplTextNode) {
689 NS_ERROR("textnode not implementing nsIDOMNode??");
690 return NS_ERROR_FAILURE;
692 nsCOMPtr<nsIDOMNode> clonedNode;
693 tmplTextNode->CloneNode(PR_FALSE, getter_AddRefs(clonedNode));
694 nsCOMPtr<nsIContent> clonedContent = do_QueryInterface(clonedNode);
695 if (!clonedContent) {
696 NS_ERROR("failed to clone textnode");
697 return NS_ERROR_FAILURE;
699 rv = aRealNode->AppendChildTo(clonedContent, aNotify);
700 if (NS_FAILED(rv)) return rv;
702 else {
703 // It's just a generic element. Create it!
704 rv = CreateElement(nameSpaceID, tag, getter_AddRefs(realKid));
705 if (NS_FAILED(rv)) return rv;
708 if (realKid && !realKidAlreadyExisted) {
709 // Potentially remember the index of this element as the
710 // first element that we've generated.
711 if (aContainer && !*aContainer) {
712 *aContainer = aRealNode;
713 NS_ADDREF(*aContainer);
715 PRUint32 indx = aRealNode->GetChildCount();
717 // Since we haven't inserted any content yet, our new
718 // index in the container will be the current count of
719 // elements in the container.
720 *aNewIndexInContainer = indx;
723 // Remember the template kid from which we created the
724 // real kid. This allows us to sync back up with the
725 // template to incrementally build content.
726 mTemplateMap.Put(realKid, tmplKid);
728 rv = CopyAttributesToElement(tmplKid, realKid, aChild, PR_FALSE);
729 if (NS_FAILED(rv)) return rv;
731 // Add any persistent attributes
732 if (isGenerationElement) {
733 rv = AddPersistentAttributes(tmplKid, aChild, realKid);
734 if (NS_FAILED(rv)) return rv;
737 // the unique content recurses up above. Also, don't recurse if
738 // this is a self reference (a reference to the same resource)
739 // or we'll end up regenerating the same content.
740 if (!aIsSelfReference && !isUnique) {
741 // this call creates the content inside the generation node,
742 // for example the label below:
743 // <vbox uri="?">
744 // <label value="?title"/>
745 // </vbox>
746 rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, PR_FALSE,
747 PR_FALSE, aChild, PR_FALSE, aMatch,
748 nsnull /* don't care */,
749 nsnull /* don't care */);
750 if (NS_FAILED(rv)) return rv;
752 if (isGenerationElement && !(mFlags & eDontRecurse)) {
753 // if recursion is allowed, continue by building the next
754 // level of children
755 rv = CreateContainerContents(realKid, aChild, PR_FALSE,
756 PR_FALSE, PR_FALSE);
757 if (NS_FAILED(rv)) return rv;
761 // We'll _already_ have added the unique elements; but if
762 // it's -not- unique, then use the XUL sort service now to
763 // append the element to the content model.
764 if (! isUnique) {
765 rv = NS_ERROR_UNEXPECTED;
767 if (isGenerationElement)
768 rv = InsertSortedNode(aRealNode, realKid, aChild, aNotify);
770 if (NS_FAILED(rv)) {
771 rv = aRealNode->AppendChildTo(realKid, aNotify);
772 NS_ASSERTION(NS_SUCCEEDED(rv), "unable to insert element");
778 return NS_OK;
781 nsresult
782 nsXULContentBuilder::CopyAttributesToElement(nsIContent* aTemplateNode,
783 nsIContent* aRealNode,
784 nsIXULTemplateResult* aResult,
785 PRBool aNotify)
787 nsresult rv;
789 // Copy all attributes from the template to the new element
790 PRUint32 numAttribs = aTemplateNode->GetAttrCount();
792 for (PRUint32 attr = 0; attr < numAttribs; attr++) {
793 const nsAttrName* name = aTemplateNode->GetAttrNameAt(attr);
794 PRInt32 attribNameSpaceID = name->NamespaceID();
795 // Hold a strong reference here so that the atom doesn't go away
796 // during UnsetAttr.
797 nsCOMPtr<nsIAtom> attribName = name->LocalName();
799 // XXXndeakin ignore namespaces until bug 321182 is fixed
800 if (attribName != nsGkAtoms::id && attribName != nsGkAtoms::uri) {
801 // Create a buffer here, because there's a chance that an
802 // attribute in the template is going to be an RDF URI, which is
803 // usually longish.
804 PRUnichar attrbuf[128];
805 nsFixedString attribValue(attrbuf, NS_ARRAY_LENGTH(attrbuf), 0);
806 aTemplateNode->GetAttr(attribNameSpaceID, attribName, attribValue);
807 if (!attribValue.IsEmpty()) {
808 nsAutoString value;
809 rv = SubstituteText(aResult, attribValue, value);
810 if (NS_FAILED(rv))
811 return rv;
813 // if the string is empty after substitutions, remove the
814 // attribute
815 if (!value.IsEmpty()) {
816 rv = aRealNode->SetAttr(attribNameSpaceID,
817 attribName,
818 name->GetPrefix(),
819 value,
820 aNotify);
822 else {
823 rv = aRealNode->UnsetAttr(attribNameSpaceID,
824 attribName,
825 aNotify);
828 if (NS_FAILED(rv))
829 return rv;
834 return NS_OK;
837 nsresult
838 nsXULContentBuilder::AddPersistentAttributes(nsIContent* aTemplateNode,
839 nsIXULTemplateResult* aResult,
840 nsIContent* aRealNode)
842 if (!mRoot)
843 return NS_OK;
845 nsCOMPtr<nsIRDFResource> resource;
846 nsresult rv = GetResultResource(aResult, getter_AddRefs(resource));
847 NS_ENSURE_SUCCESS(rv, rv);
849 nsAutoString attribute, persist;
850 aTemplateNode->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
852 while (!persist.IsEmpty()) {
853 attribute.Truncate();
855 PRInt32 offset = persist.FindCharInSet(" ,");
856 if (offset > 0) {
857 persist.Left(attribute, offset);
858 persist.Cut(0, offset + 1);
860 else {
861 attribute = persist;
862 persist.Truncate();
865 attribute.Trim(" ");
867 if (attribute.IsEmpty())
868 break;
870 nsCOMPtr<nsIAtom> tag;
871 PRInt32 nameSpaceID;
873 nsCOMPtr<nsINodeInfo> ni =
874 aTemplateNode->GetExistingAttrNameFromQName(attribute);
875 if (ni) {
876 tag = ni->NameAtom();
877 nameSpaceID = ni->NamespaceID();
879 else {
880 tag = do_GetAtom(attribute);
881 NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
883 nameSpaceID = kNameSpaceID_None;
886 nsCOMPtr<nsIRDFResource> property;
887 rv = nsXULContentUtils::GetResource(nameSpaceID, tag, getter_AddRefs(property));
888 NS_ENSURE_SUCCESS(rv, rv);
890 nsCOMPtr<nsIRDFNode> target;
891 rv = mDB->GetTarget(resource, property, PR_TRUE, getter_AddRefs(target));
892 NS_ENSURE_SUCCESS(rv, rv);
894 if (! target)
895 continue;
897 nsCOMPtr<nsIRDFLiteral> value = do_QueryInterface(target);
898 NS_ASSERTION(value != nsnull, "unable to stomach that sort of node");
899 if (! value)
900 continue;
902 const PRUnichar* valueStr;
903 rv = value->GetValueConst(&valueStr);
904 NS_ENSURE_SUCCESS(rv, rv);
906 rv = aRealNode->SetAttr(nameSpaceID, tag, nsDependentString(valueStr),
907 PR_FALSE);
908 NS_ENSURE_SUCCESS(rv, rv);
911 return NS_OK;
914 nsresult
915 nsXULContentBuilder::SynchronizeUsingTemplate(nsIContent* aTemplateNode,
916 nsIContent* aRealElement,
917 nsIXULTemplateResult* aResult)
919 // check all attributes on the template node; if they reference a resource,
920 // update the equivalent attribute on the content node
921 nsresult rv;
922 rv = CopyAttributesToElement(aTemplateNode, aRealElement, aResult, PR_TRUE);
923 if (NS_FAILED(rv))
924 return rv;
926 PRUint32 count = aTemplateNode->GetChildCount();
928 for (PRUint32 loop = 0; loop < count; ++loop) {
929 nsIContent *tmplKid = aTemplateNode->GetChildAt(loop);
931 if (! tmplKid)
932 break;
934 nsIContent *realKid = aRealElement->GetChildAt(loop);
935 if (! realKid)
936 break;
938 // check for text nodes and update them accordingly.
939 // This code is similar to that in BuildContentFromTemplate
940 if (tmplKid->NodeInfo()->Equals(nsGkAtoms::textnode,
941 kNameSpaceID_XUL)) {
942 PRUnichar attrbuf[128];
943 nsFixedString attrValue(attrbuf, NS_ARRAY_LENGTH(attrbuf), 0);
944 tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
945 if (!attrValue.IsEmpty()) {
946 nsAutoString value;
947 rv = SubstituteText(aResult, attrValue, value);
948 if (NS_FAILED(rv)) return rv;
949 realKid->SetText(value, PR_TRUE);
953 rv = SynchronizeUsingTemplate(tmplKid, realKid, aResult);
954 if (NS_FAILED(rv)) return rv;
957 return NS_OK;
960 nsresult
961 nsXULContentBuilder::RemoveMember(nsIContent* aContent)
963 nsCOMPtr<nsIContent> parent = aContent->GetParent();
964 if (parent) {
965 PRInt32 pos = parent->IndexOf(aContent);
967 NS_ASSERTION(pos >= 0, "parent doesn't think this child has an index");
968 if (pos < 0) return NS_OK;
970 // Note: RemoveChildAt sets |child|'s document to null so that
971 // it'll get knocked out of the XUL doc's resource-to-element
972 // map.
973 nsresult rv = parent->RemoveChildAt(pos, PR_TRUE);
974 if (NS_FAILED(rv)) return rv;
977 // Remove from the content support map.
978 mContentSupportMap.Remove(aContent);
980 // Remove from the template map
981 mTemplateMap.Remove(aContent);
983 return NS_OK;
986 nsresult
987 nsXULContentBuilder::CreateTemplateAndContainerContents(nsIContent* aElement,
988 PRBool aForceCreation)
990 // Generate both 1) the template content for the current element,
991 // and 2) recursive subcontent (if the current element refers to a
992 // container result).
994 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
995 ("nsXULContentBuilder::CreateTemplateAndContainerContents start - flags: %d",
996 mFlags));
998 if (! mQueryProcessor)
999 return NS_OK;
1001 // for the root element, get the ref attribute and generate content
1002 if (aElement == mRoot) {
1003 if (! mRootResult) {
1004 nsAutoString ref;
1005 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, ref);
1007 if (! ref.IsEmpty()) {
1008 nsresult rv = mQueryProcessor->TranslateRef(mDataSource, ref,
1009 getter_AddRefs(mRootResult));
1010 if (NS_FAILED(rv))
1011 return rv;
1015 if (mRootResult) {
1016 CreateContainerContents(aElement, mRootResult, aForceCreation,
1017 PR_FALSE, PR_TRUE);
1020 else if (!(mFlags & eDontRecurse)) {
1021 // The content map will contain the generation elements (the ones that
1022 // are given ids) and only those elements, so get the reference point
1023 // from the corresponding match.
1024 nsTemplateMatch *match = nsnull;
1025 if (mContentSupportMap.Get(aElement, &match)) {
1026 // don't generate children if child processing isn't allowed
1027 PRBool mayProcessChildren;
1028 nsresult rv = match->mResult->GetMayProcessChildren(&mayProcessChildren);
1029 if (NS_FAILED(rv) || !mayProcessChildren)
1030 return rv;
1032 CreateContainerContents(aElement, match->mResult, aForceCreation,
1033 PR_FALSE, PR_TRUE);
1037 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
1038 ("nsXULContentBuilder::CreateTemplateAndContainerContents end"));
1040 return NS_OK;
1043 nsresult
1044 nsXULContentBuilder::CreateContainerContents(nsIContent* aElement,
1045 nsIXULTemplateResult* aResult,
1046 PRBool aForceCreation,
1047 PRBool aNotify,
1048 PRBool aNotifyAtEnd)
1050 if (!aForceCreation && !IsOpen(aElement))
1051 return NS_OK;
1053 nsCOMPtr<nsIRDFResource> refResource;
1054 GetResultResource(aResult, getter_AddRefs(refResource));
1055 if (! refResource)
1056 return NS_ERROR_FAILURE;
1058 // Avoid re-entrant builds for the same resource.
1059 if (IsActivated(refResource))
1060 return NS_OK;
1062 ActivationEntry entry(refResource, &mTop);
1064 // Compile the rules now, if they haven't been already.
1065 if (! mQueriesCompiled) {
1066 nsresult rv = CompileQueries();
1067 if (NS_FAILED(rv))
1068 return rv;
1071 if (mQuerySets.Length() == 0)
1072 return NS_OK;
1074 // See if the element's templates contents have been generated:
1075 // this prevents a re-entrant call from triggering another
1076 // generation.
1077 nsXULElement *xulcontent = nsXULElement::FromContent(aElement);
1078 if (xulcontent) {
1079 if (xulcontent->GetTemplateGenerated())
1080 return NS_OK;
1082 // Now mark the element's contents as being generated so that
1083 // any re-entrant calls don't trigger an infinite recursion.
1084 xulcontent->SetTemplateGenerated();
1087 PRInt32 newIndexInContainer = -1;
1088 nsIContent* container = nsnull;
1090 PRInt32 querySetCount = mQuerySets.Length();
1092 for (PRInt32 r = 0; r < querySetCount; r++) {
1093 nsTemplateQuerySet* queryset = mQuerySets[r];
1095 nsIAtom* tag = queryset->GetTag();
1096 if (tag && tag != aElement->Tag())
1097 continue;
1099 CreateContainerContentsForQuerySet(aElement, aResult, aNotify, queryset,
1100 &container, &newIndexInContainer);
1103 if (aNotifyAtEnd && container) {
1104 MOZ_AUTO_DOC_UPDATE(container->GetCurrentDoc(), UPDATE_CONTENT_MODEL,
1105 PR_TRUE);
1106 nsNodeUtils::ContentAppended(container, newIndexInContainer);
1109 NS_IF_RELEASE(container);
1111 return NS_OK;
1114 nsresult
1115 nsXULContentBuilder::CreateContainerContentsForQuerySet(nsIContent* aElement,
1116 nsIXULTemplateResult* aResult,
1117 PRBool aNotify,
1118 nsTemplateQuerySet* aQuerySet,
1119 nsIContent** aContainer,
1120 PRInt32* aNewIndexInContainer)
1122 #ifdef PR_LOGGING
1123 if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) {
1124 nsAutoString id;
1125 aResult->GetId(id);
1126 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
1127 ("nsXULContentBuilder::CreateContainerContentsForQuerySet start for ref %s\n",
1128 NS_ConvertUTF16toUTF8(id).get()));
1130 #endif
1132 if (! mQueryProcessor)
1133 return NS_OK;
1135 nsCOMPtr<nsISimpleEnumerator> results;
1136 nsresult rv = mQueryProcessor->GenerateResults(mDataSource, aResult,
1137 aQuerySet->mCompiledQuery,
1138 getter_AddRefs(results));
1139 if (NS_FAILED(rv) || !results)
1140 return rv;
1142 PRBool hasMoreResults;
1143 rv = results->HasMoreElements(&hasMoreResults);
1145 for (; NS_SUCCEEDED(rv) && hasMoreResults;
1146 rv = results->HasMoreElements(&hasMoreResults)) {
1147 nsCOMPtr<nsISupports> nr;
1148 rv = results->GetNext(getter_AddRefs(nr));
1149 if (NS_FAILED(rv))
1150 return rv;
1152 nsCOMPtr<nsIXULTemplateResult> nextresult = do_QueryInterface(nr);
1153 if (!nextresult)
1154 return NS_ERROR_UNEXPECTED;
1156 nsCOMPtr<nsIRDFResource> resultid;
1157 rv = GetResultResource(nextresult, getter_AddRefs(resultid));
1158 if (NS_FAILED(rv))
1159 return rv;
1161 if (!resultid)
1162 continue;
1164 nsTemplateMatch *newmatch =
1165 nsTemplateMatch::Create(mPool, aQuerySet->Priority(),
1166 nextresult, aElement);
1167 if (!newmatch)
1168 return NS_ERROR_OUT_OF_MEMORY;
1170 // check if there is already an existing match. If so, a previous
1171 // query already generated content so the match is just added to the
1172 // end of the set of matches.
1174 PRBool generateContent = PR_TRUE;
1176 nsTemplateMatch* prevmatch = nsnull;
1177 nsTemplateMatch* existingmatch = nsnull;
1178 nsTemplateMatch* removematch = nsnull;
1179 if (mMatchMap.Get(resultid, &existingmatch)){
1180 // check if there is an existing match that matched a rule
1181 while (existingmatch) {
1182 // break out once we've reached a query in the list with a
1183 // higher priority, as the new match list is sorted by
1184 // priority, and the new match should be inserted here
1185 PRInt32 priority = existingmatch->QuerySetPriority();
1186 if (priority > aQuerySet->Priority())
1187 break;
1189 // skip over non-matching containers
1190 if (existingmatch->GetContainer() == aElement) {
1191 // if the same priority is already found, replace it. This can happen
1192 // when a container is removed and readded
1193 if (priority == aQuerySet->Priority()) {
1194 removematch = existingmatch;
1195 break;
1198 if (existingmatch->IsActive())
1199 generateContent = PR_FALSE;
1202 prevmatch = existingmatch;
1203 existingmatch = existingmatch->mNext;
1207 if (removematch) {
1208 // remove the generated content for the existing match
1209 rv = ReplaceMatch(removematch->mResult, nsnull, nsnull, aElement);
1210 if (NS_FAILED(rv))
1211 return rv;
1214 if (generateContent) {
1215 // find the rule that matches. If none match, the content does not
1216 // need to be generated
1218 PRInt16 ruleindex;
1219 nsTemplateRule* matchedrule = nsnull;
1220 rv = DetermineMatchedRule(aElement, nextresult, aQuerySet,
1221 &matchedrule, &ruleindex);
1222 if (NS_FAILED(rv)) {
1223 nsTemplateMatch::Destroy(mPool, newmatch, PR_FALSE);
1224 return rv;
1227 if (matchedrule) {
1228 rv = newmatch->RuleMatched(aQuerySet, matchedrule,
1229 ruleindex, nextresult);
1230 if (NS_FAILED(rv)) {
1231 nsTemplateMatch::Destroy(mPool, newmatch, PR_FALSE);
1232 return rv;
1235 // Grab the template node
1236 nsCOMPtr<nsIContent> action = matchedrule->GetAction();
1237 BuildContentFromTemplate(action, aElement, aElement, PR_TRUE,
1238 mRefVariable == matchedrule->GetMemberVariable(),
1239 nextresult, aNotify, newmatch,
1240 aContainer, aNewIndexInContainer);
1244 if (prevmatch) {
1245 prevmatch->mNext = newmatch;
1247 else if (!mMatchMap.Put(resultid, newmatch)) {
1248 nsTemplateMatch::Destroy(mPool, newmatch, PR_TRUE);
1249 return NS_ERROR_OUT_OF_MEMORY;
1252 if (removematch) {
1253 newmatch->mNext = removematch->mNext;
1254 nsTemplateMatch::Destroy(mPool, removematch, PR_TRUE);
1256 else {
1257 newmatch->mNext = existingmatch;
1261 return rv;
1264 nsresult
1265 nsXULContentBuilder::EnsureElementHasGenericChild(nsIContent* parent,
1266 PRInt32 nameSpaceID,
1267 nsIAtom* tag,
1268 PRBool aNotify,
1269 nsIContent** result)
1271 nsresult rv;
1273 rv = nsXULContentUtils::FindChildByTag(parent, nameSpaceID, tag, result);
1274 if (NS_FAILED(rv))
1275 return rv;
1277 if (rv == NS_RDF_NO_VALUE) {
1278 // we need to construct a new child element.
1279 nsCOMPtr<nsIContent> element;
1281 rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
1282 if (NS_FAILED(rv))
1283 return rv;
1285 // XXX Note that the notification ensures we won't batch insertions! This could be bad! - Dave
1286 rv = parent->AppendChildTo(element, aNotify);
1287 if (NS_FAILED(rv))
1288 return rv;
1290 *result = element;
1291 NS_ADDREF(*result);
1292 return NS_ELEMENT_GOT_CREATED;
1294 else {
1295 return NS_ELEMENT_WAS_THERE;
1299 PRBool
1300 nsXULContentBuilder::IsOpen(nsIContent* aElement)
1302 // Determine if this is a <treeitem> or <menu> element
1303 if (!aElement->IsNodeOfType(nsINode::eXUL))
1304 return PR_TRUE;
1306 // XXXhyatt Use the XBL service to obtain a base tag.
1307 nsIAtom *tag = aElement->Tag();
1308 if (tag == nsGkAtoms::menu ||
1309 tag == nsGkAtoms::menubutton ||
1310 tag == nsGkAtoms::toolbarbutton ||
1311 tag == nsGkAtoms::button ||
1312 tag == nsGkAtoms::treeitem)
1313 return aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
1314 nsGkAtoms::_true, eCaseMatters);
1315 return PR_TRUE;
1318 nsresult
1319 nsXULContentBuilder::RemoveGeneratedContent(nsIContent* aElement)
1321 // Keep a queue of "ungenerated" elements that we have to probe
1322 // for generated content.
1323 nsAutoVoidArray ungenerated;
1324 if (!ungenerated.AppendElement(aElement))
1325 return NS_ERROR_OUT_OF_MEMORY;
1327 PRInt32 count;
1328 while (0 != (count = ungenerated.Count())) {
1329 // Pull the next "ungenerated" element off the queue.
1330 PRInt32 last = count - 1;
1331 nsIContent* element = static_cast<nsIContent*>(ungenerated[last]);
1332 ungenerated.RemoveElementAt(last);
1334 PRUint32 i = element->GetChildCount();
1336 while (i-- > 0) {
1337 nsCOMPtr<nsIContent> child = element->GetChildAt(i);
1339 // Optimize for the <template> element, because we *know*
1340 // it won't have any generated content: there's no reason
1341 // to even check this subtree.
1342 // XXX should this check |child| rather than |element|? Otherwise
1343 // it should be moved outside the inner loop. Bug 297290.
1344 if (element->NodeInfo()->Equals(nsGkAtoms::_template,
1345 kNameSpaceID_XUL) ||
1346 !element->IsNodeOfType(nsINode::eELEMENT))
1347 continue;
1349 // If the element is in the template map, then we
1350 // assume it's been generated and nuke it.
1351 nsCOMPtr<nsIContent> tmpl;
1352 mTemplateMap.GetTemplateFor(child, getter_AddRefs(tmpl));
1354 if (! tmpl) {
1355 // No 'template' attribute, so this must not have been
1356 // generated. We'll need to examine its kids.
1357 if (!ungenerated.AppendElement(child))
1358 return NS_ERROR_OUT_OF_MEMORY;
1359 continue;
1362 // If we get here, it's "generated". Bye bye!
1363 element->RemoveChildAt(i, PR_TRUE);
1365 // Remove this and any children from the content support map.
1366 mContentSupportMap.Remove(child);
1368 // Remove from the template map
1369 mTemplateMap.Remove(child);
1373 return NS_OK;
1376 nsresult
1377 nsXULContentBuilder::GetElementsForResult(nsIXULTemplateResult* aResult,
1378 nsCOMArray<nsIContent>& aElements)
1380 // if the root has been removed from the document, just return
1381 // since there won't be any generated content any more
1382 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetDocument());
1383 if (! xuldoc)
1384 return NS_OK;
1386 nsAutoString id;
1387 aResult->GetId(id);
1389 return xuldoc->GetElementsForID(id, aElements);
1392 nsresult
1393 nsXULContentBuilder::CreateElement(PRInt32 aNameSpaceID,
1394 nsIAtom* aTag,
1395 nsIContent** aResult)
1397 nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
1398 NS_ASSERTION(doc != nsnull, "not initialized");
1399 if (! doc)
1400 return NS_ERROR_NOT_INITIALIZED;
1402 nsresult rv;
1403 nsCOMPtr<nsIContent> result;
1405 nsCOMPtr<nsINodeInfo> nodeInfo;
1406 nodeInfo = doc->NodeInfoManager()->GetNodeInfo(aTag, nsnull, aNameSpaceID);
1408 rv = NS_NewElement(getter_AddRefs(result), aNameSpaceID, nodeInfo,
1409 PR_FALSE);
1410 if (NS_FAILED(rv))
1411 return rv;
1413 *aResult = result;
1414 NS_ADDREF(*aResult);
1415 return NS_OK;
1418 nsresult
1419 nsXULContentBuilder::SetContainerAttrs(nsIContent *aElement,
1420 nsIXULTemplateResult* aResult,
1421 PRBool aIgnoreNonContainers,
1422 PRBool aNotify)
1424 NS_PRECONDITION(aResult != nsnull, "null ptr");
1425 if (! aResult)
1426 return NS_ERROR_NULL_POINTER;
1428 PRBool iscontainer;
1429 aResult->GetIsContainer(&iscontainer);
1431 if (aIgnoreNonContainers && !iscontainer)
1432 return NS_OK;
1434 NS_NAMED_LITERAL_STRING(true_, "true");
1435 NS_NAMED_LITERAL_STRING(false_, "false");
1437 const nsAString& newcontainer =
1438 iscontainer ? true_ : false_;
1440 aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::container,
1441 newcontainer, aNotify);
1443 if (iscontainer && !(mFlags & eDontTestEmpty)) {
1444 PRBool isempty;
1445 aResult->GetIsEmpty(&isempty);
1447 const nsAString& newempty =
1448 (iscontainer && isempty) ? true_ : false_;
1450 aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::empty,
1451 newempty, aNotify);
1454 return NS_OK;
1458 //----------------------------------------------------------------------
1460 // nsIXULTemplateBuilder methods
1463 NS_IMETHODIMP
1464 nsXULContentBuilder::CreateContents(nsIContent* aElement, PRBool aForceCreation)
1466 NS_PRECONDITION(aElement != nsnull, "null ptr");
1467 if (! aElement)
1468 return NS_ERROR_NULL_POINTER;
1470 // don't build contents for closed elements. aForceCreation will be true
1471 // when a menu is about to be opened, so the content should be built anyway.
1472 if (!aForceCreation && !IsOpen(aElement))
1473 return NS_OK;
1475 return CreateTemplateAndContainerContents(aElement, aForceCreation);
1478 NS_IMETHODIMP
1479 nsXULContentBuilder::HasGeneratedContent(nsIRDFResource* aResource,
1480 nsIAtom* aTag,
1481 PRBool* aGenerated)
1483 *aGenerated = PR_FALSE;
1484 NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
1485 NS_ENSURE_STATE(mRootResult);
1487 nsCOMPtr<nsIRDFResource> rootresource;
1488 nsresult rv = mRootResult->GetResource(getter_AddRefs(rootresource));
1489 if (NS_FAILED(rv))
1490 return rv;
1492 // the root resource is always acceptable
1493 if (aResource == rootresource) {
1494 if (!aTag || mRoot->Tag() == aTag)
1495 *aGenerated = PR_TRUE;
1497 else {
1498 const char* uri;
1499 aResource->GetValueConst(&uri);
1501 NS_ConvertUTF8toUTF16 refID(uri);
1503 // just return if the node is no longer in a document
1504 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetDocument());
1505 if (! xuldoc)
1506 return NS_OK;
1508 nsCOMArray<nsIContent> elements;
1509 xuldoc->GetElementsForID(refID, elements);
1511 PRUint32 cnt = elements.Count();
1513 for (PRInt32 i = PRInt32(cnt) - 1; i >= 0; --i) {
1514 nsCOMPtr<nsIContent> content = elements.SafeObjectAt(i);
1516 do {
1517 nsTemplateMatch* match;
1518 if (content == mRoot || mContentSupportMap.Get(content, &match)) {
1519 // If we've got a tag, check it to ensure we're consistent.
1520 if (!aTag || content->Tag() == aTag) {
1521 *aGenerated = PR_TRUE;
1522 return NS_OK;
1526 content = content->GetParent();
1527 } while (content);
1531 return NS_OK;
1534 NS_IMETHODIMP
1535 nsXULContentBuilder::GetResultForContent(nsIDOMElement* aElement,
1536 nsIXULTemplateResult** aResult)
1538 NS_ENSURE_ARG_POINTER(aElement);
1539 NS_ENSURE_ARG_POINTER(aResult);
1541 nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
1542 if (content == mRoot) {
1543 *aResult = mRootResult;
1545 else {
1546 nsTemplateMatch *match = nsnull;
1547 if (mContentSupportMap.Get(content, &match))
1548 *aResult = match->mResult;
1549 else
1550 *aResult = nsnull;
1553 NS_IF_ADDREF(*aResult);
1554 return NS_OK;
1557 //----------------------------------------------------------------------
1559 // nsIDocumentObserver methods
1562 void
1563 nsXULContentBuilder::AttributeChanged(nsIDocument* aDocument,
1564 nsIContent* aContent,
1565 PRInt32 aNameSpaceID,
1566 nsIAtom* aAttribute,
1567 PRInt32 aModType,
1568 PRUint32 aStateMask)
1570 // Handle "open" and "close" cases. We do this handling before
1571 // we've notified the observer, so that content is already created
1572 // for the frame system to walk.
1573 if ((aContent->GetNameSpaceID() == kNameSpaceID_XUL) &&
1574 (aAttribute == nsGkAtoms::open)) {
1575 // We're on a XUL tag, and an ``open'' attribute changed.
1576 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
1577 nsGkAtoms::_true, eCaseMatters))
1578 OpenContainer(aContent);
1579 else
1580 CloseContainer(aContent);
1583 if ((aNameSpaceID == kNameSpaceID_XUL) &&
1584 ((aAttribute == nsGkAtoms::sort) ||
1585 (aAttribute == nsGkAtoms::sortDirection) ||
1586 (aAttribute == nsGkAtoms::sortResource) ||
1587 (aAttribute == nsGkAtoms::sortResource2)))
1588 mSortState.initialized = PR_FALSE;
1590 // Pass along to the generic template builder.
1591 nsXULTemplateBuilder::AttributeChanged(aDocument, aContent, aNameSpaceID,
1592 aAttribute, aModType, aStateMask);
1595 void
1596 nsXULContentBuilder::NodeWillBeDestroyed(const nsINode* aNode)
1598 // Break circular references
1599 mContentSupportMap.Clear();
1601 nsXULTemplateBuilder::NodeWillBeDestroyed(aNode);
1605 //----------------------------------------------------------------------
1607 // nsXULTemplateBuilder methods
1610 PRBool
1611 nsXULContentBuilder::GetInsertionLocations(nsIXULTemplateResult* aResult,
1612 nsCOMArray<nsIContent>** aLocations)
1614 *aLocations = nsnull;
1616 nsAutoString ref;
1617 nsresult rv = aResult->GetBindingFor(mRefVariable, ref);
1618 if (NS_FAILED(rv))
1619 return PR_FALSE;
1621 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetDocument());
1622 if (! xuldoc)
1623 return PR_FALSE;
1625 *aLocations = new nsCOMArray<nsIContent>;
1626 NS_ENSURE_TRUE(*aLocations, PR_FALSE);
1628 xuldoc->GetElementsForID(ref, **aLocations);
1629 PRUint32 count = (*aLocations)->Count();
1631 PRBool found = PR_FALSE;
1633 for (PRUint32 t = 0; t < count; t++) {
1634 nsCOMPtr<nsIContent> content = (*aLocations)->SafeObjectAt(t);
1636 nsTemplateMatch* refmatch;
1637 if (content == mRoot || mContentSupportMap.Get(content, &refmatch)) {
1638 // See if we've built the container contents for "content"
1639 // yet. If not, we don't need to build any content. This
1640 // happens, for example, if we receive an assertion on a
1641 // closed folder in a tree widget or on a menu that hasn't
1642 // yet been opened.
1643 nsXULElement *xulcontent = nsXULElement::FromContent(content);
1644 if (!xulcontent || xulcontent->GetTemplateGenerated()) {
1645 found = PR_TRUE;
1646 continue;
1650 // clear the item in the list since we don't want to insert there
1651 (*aLocations)->ReplaceObjectAt(nsnull, t);
1654 return found;
1657 nsresult
1658 nsXULContentBuilder::ReplaceMatch(nsIXULTemplateResult* aOldResult,
1659 nsTemplateMatch* aNewMatch,
1660 nsTemplateRule* aNewMatchRule,
1661 void *aContext)
1664 nsresult rv;
1665 nsIContent* content = static_cast<nsIContent*>(aContext);
1667 // update the container attributes for the match
1668 if (content) {
1669 nsAutoString ref;
1670 if (aNewMatch)
1671 rv = aNewMatch->mResult->GetBindingFor(mRefVariable, ref);
1672 else
1673 rv = aOldResult->GetBindingFor(mRefVariable, ref);
1674 if (NS_FAILED(rv))
1675 return rv;
1677 if (!ref.IsEmpty()) {
1678 nsCOMPtr<nsIXULTemplateResult> refResult;
1679 rv = GetResultForId(ref, getter_AddRefs(refResult));
1680 if (NS_FAILED(rv))
1681 return rv;
1683 if (refResult)
1684 SetContainerAttrs(content, refResult, PR_FALSE, PR_TRUE);
1688 if (aOldResult) {
1689 nsCOMArray<nsIContent> elements;
1690 rv = GetElementsForResult(aOldResult, elements);
1691 if (NS_FAILED(rv))
1692 return rv;
1694 PRUint32 count = elements.Count();
1696 for (PRInt32 e = PRInt32(count) - 1; e >= 0; --e) {
1697 nsCOMPtr<nsIContent> child = elements.SafeObjectAt(e);
1699 nsTemplateMatch* match;
1700 if (mContentSupportMap.Get(child, &match)) {
1701 if (content == match->GetContainer())
1702 RemoveMember(child);
1707 if (aNewMatch) {
1708 nsCOMPtr<nsIContent> action = aNewMatchRule->GetAction();
1709 return BuildContentFromTemplate(action, content, content, PR_TRUE,
1710 mRefVariable == aNewMatchRule->GetMemberVariable(),
1711 aNewMatch->mResult, PR_TRUE, aNewMatch,
1712 nsnull, nsnull);
1715 return NS_OK;
1719 nsresult
1720 nsXULContentBuilder::SynchronizeResult(nsIXULTemplateResult* aResult)
1722 nsCOMArray<nsIContent> elements;
1723 GetElementsForResult(aResult, elements);
1725 PRUint32 cnt = elements.Count();
1727 for (PRInt32 i = PRInt32(cnt) - 1; i >= 0; --i) {
1728 nsCOMPtr<nsIContent> element = elements.SafeObjectAt(i);
1730 nsTemplateMatch* match;
1731 if (! mContentSupportMap.Get(element, &match))
1732 continue;
1734 nsCOMPtr<nsIContent> templateNode;
1735 mTemplateMap.GetTemplateFor(element, getter_AddRefs(templateNode));
1737 NS_ASSERTION(templateNode, "couldn't find template node for element");
1738 if (! templateNode)
1739 continue;
1741 // this node was created by a XUL template, so update it accordingly
1742 SynchronizeUsingTemplate(templateNode, element, aResult);
1745 return NS_OK;
1748 //----------------------------------------------------------------------
1750 // Implementation methods
1753 nsresult
1754 nsXULContentBuilder::OpenContainer(nsIContent* aElement)
1756 if (aElement != mRoot) {
1757 if (mFlags & eDontRecurse)
1758 return NS_OK;
1760 PRBool rightBuilder = PR_FALSE;
1762 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aElement->GetDocument());
1763 if (! xuldoc)
1764 return NS_OK;
1766 // See if we're responsible for this element
1767 nsIContent* content = aElement;
1768 do {
1769 nsCOMPtr<nsIXULTemplateBuilder> builder;
1770 xuldoc->GetTemplateBuilderFor(content, getter_AddRefs(builder));
1771 if (builder) {
1772 if (builder == this)
1773 rightBuilder = PR_TRUE;
1774 break;
1777 content = content->GetParent();
1778 } while (content);
1780 if (! rightBuilder)
1781 return NS_OK;
1784 CreateTemplateAndContainerContents(aElement, PR_FALSE);
1786 return NS_OK;
1789 nsresult
1790 nsXULContentBuilder::CloseContainer(nsIContent* aElement)
1792 return NS_OK;
1795 nsresult
1796 nsXULContentBuilder::RebuildAll()
1798 NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
1800 // Bail out early if we are being torn down.
1801 nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
1802 if (!doc)
1803 return NS_OK;
1805 if (mQueriesCompiled)
1806 Uninit(PR_FALSE);
1808 nsresult rv = CompileQueries();
1809 if (NS_FAILED(rv))
1810 return rv;
1812 if (mQuerySets.Length() == 0)
1813 return NS_OK;
1815 nsXULElement *xulcontent = nsXULElement::FromContent(mRoot);
1816 if (xulcontent)
1817 xulcontent->ClearTemplateGenerated();
1819 // Now, regenerate both the template- and container-generated
1820 // contents for the current element...
1821 CreateTemplateAndContainerContents(mRoot, PR_FALSE);
1823 return NS_OK;
1826 /**** Sorting Methods ****/
1828 nsresult
1829 nsXULContentBuilder::CompareResultToNode(nsIXULTemplateResult* aResult,
1830 nsIContent* aContent,
1831 PRInt32* aSortOrder)
1833 NS_ASSERTION(aSortOrder, "CompareResultToNode: null out param aSortOrder");
1835 *aSortOrder = 0;
1837 nsTemplateMatch *match = nsnull;
1838 if (!mContentSupportMap.Get(aContent, &match)) {
1839 *aSortOrder = mSortState.sortStaticsLast ? -1 : 1;
1840 return NS_OK;
1843 if (!mQueryProcessor)
1844 return NS_OK;
1846 if (mSortState.direction == nsSortState_natural) {
1847 // sort in natural order
1848 nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
1849 nsnull, aSortOrder);
1850 NS_ENSURE_SUCCESS(rv, rv);
1852 else {
1853 // iterate over each sort key and compare. If the nodes are equal,
1854 // continue to compare using the next sort key. If not equal, stop.
1855 PRInt32 length = mSortState.sortKeys.Count();
1856 for (PRInt32 t = 0; t < length; t++) {
1857 nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
1858 mSortState.sortKeys[t], aSortOrder);
1859 NS_ENSURE_SUCCESS(rv, rv);
1861 if (*aSortOrder)
1862 break;
1866 // flip the sort order if performing a descending sorting
1867 if (mSortState.direction == nsSortState_descending)
1868 *aSortOrder = -*aSortOrder;
1870 return NS_OK;
1873 nsresult
1874 nsXULContentBuilder::InsertSortedNode(nsIContent* aContainer,
1875 nsIContent* aNode,
1876 nsIXULTemplateResult* aResult,
1877 PRBool aNotify)
1879 nsresult rv;
1881 if (!mSortState.initialized) {
1882 nsAutoString sort, sortDirection;
1883 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
1884 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, sortDirection);
1885 rv = XULSortServiceImpl::InitializeSortState(mRoot, aContainer,
1886 sort, sortDirection, &mSortState);
1887 NS_ENSURE_SUCCESS(rv, rv);
1890 // when doing a natural sort, items will typically be sorted according to
1891 // the order they appear in the datasource. For RDF, cache whether the
1892 // reference parent is an RDF Seq. That way, the items can be sorted in the
1893 // order they are in the Seq.
1894 mSortState.isContainerRDFSeq = PR_FALSE;
1895 if (mSortState.direction == nsSortState_natural) {
1896 nsCOMPtr<nsISupports> ref;
1897 nsresult rv = aResult->GetBindingObjectFor(mRefVariable, getter_AddRefs(ref));
1898 NS_ENSURE_SUCCESS(rv, rv);
1900 nsCOMPtr<nsIRDFResource> container = do_QueryInterface(ref);
1902 if (container) {
1903 rv = gRDFContainerUtils->IsSeq(mDB, container, &mSortState.isContainerRDFSeq);
1904 NS_ENSURE_SUCCESS(rv, rv);
1908 PRBool childAdded = PR_FALSE;
1909 PRUint32 numChildren = aContainer->GetChildCount();
1911 if (mSortState.direction != nsSortState_natural ||
1912 (mSortState.direction == nsSortState_natural && mSortState.isContainerRDFSeq))
1914 // because numChildren gets modified
1915 PRInt32 realNumChildren = numChildren;
1916 nsIContent *child = nsnull;
1918 // rjc says: determine where static XUL ends and generated XUL/RDF begins
1919 PRInt32 staticCount = 0;
1921 nsAutoString staticValue;
1922 aContainer->GetAttr(kNameSpaceID_None, nsGkAtoms::staticHint, staticValue);
1923 if (!staticValue.IsEmpty())
1925 // found "static" XUL element count hint
1926 PRInt32 strErr = 0;
1927 staticCount = staticValue.ToInteger(&strErr);
1928 if (strErr)
1929 staticCount = 0;
1930 } else {
1931 // compute the "static" XUL element count
1932 for (PRUint32 childLoop = 0; childLoop < numChildren; ++childLoop) {
1933 child = aContainer->GetChildAt(childLoop);
1934 if (nsContentUtils::HasNonEmptyAttr(child, kNameSpaceID_None,
1935 nsGkAtoms::_template))
1936 break;
1937 else
1938 ++staticCount;
1941 if (mSortState.sortStaticsLast) {
1942 // indicate that static XUL comes after RDF-generated content by
1943 // making negative
1944 staticCount = -staticCount;
1947 // save the "static" XUL element count hint
1948 nsAutoString valueStr;
1949 valueStr.AppendInt(staticCount);
1950 aContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::staticHint, valueStr, PR_FALSE);
1953 if (staticCount <= 0) {
1954 numChildren += staticCount;
1955 staticCount = 0;
1956 } else if (staticCount > (PRInt32)numChildren) {
1957 staticCount = numChildren;
1958 numChildren -= staticCount;
1961 // figure out where to insert the node when a sort order is being imposed
1962 if (numChildren > 0) {
1963 nsIContent *temp;
1964 PRInt32 direction;
1966 // rjc says: The following is an implementation of a fairly optimal
1967 // binary search insertion sort... with interpolation at either end-point.
1969 if (mSortState.lastWasFirst) {
1970 child = aContainer->GetChildAt(staticCount);
1971 temp = child;
1972 rv = CompareResultToNode(aResult, temp, &direction);
1973 if (direction < 0) {
1974 aContainer->InsertChildAt(aNode, staticCount, aNotify);
1975 childAdded = PR_TRUE;
1976 } else
1977 mSortState.lastWasFirst = PR_FALSE;
1978 } else if (mSortState.lastWasLast) {
1979 child = aContainer->GetChildAt(realNumChildren - 1);
1980 temp = child;
1981 rv = CompareResultToNode(aResult, temp, &direction);
1982 if (direction > 0) {
1983 aContainer->InsertChildAt(aNode, realNumChildren, aNotify);
1984 childAdded = PR_TRUE;
1985 } else
1986 mSortState.lastWasLast = PR_FALSE;
1989 PRInt32 left = staticCount + 1, right = realNumChildren, x;
1990 while (!childAdded && right >= left) {
1991 x = (left + right) / 2;
1992 child = aContainer->GetChildAt(x - 1);
1993 temp = child;
1995 rv = CompareResultToNode(aResult, temp, &direction);
1996 if ((x == left && direction < 0) ||
1997 (x == right && direction >= 0) ||
1998 left == right)
2000 PRInt32 thePos = (direction > 0 ? x : x - 1);
2001 aContainer->InsertChildAt(aNode, thePos, aNotify);
2002 childAdded = PR_TRUE;
2004 mSortState.lastWasFirst = (thePos == staticCount);
2005 mSortState.lastWasLast = (thePos >= realNumChildren);
2007 break;
2009 if (direction < 0)
2010 right = x - 1;
2011 else
2012 left = x + 1;
2017 // if the child hasn't been inserted yet, just add it at the end. Note
2018 // that an append isn't done as there may be static content afterwards.
2019 if (!childAdded)
2020 aContainer->InsertChildAt(aNode, numChildren, aNotify);
2022 return NS_OK;