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
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.
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"
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
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
106 // nsIXULTemplateBuilder interface
107 NS_IMETHOD
CreateContents(nsIContent
* aElement
, PRBool aForceCreation
);
109 NS_IMETHOD
HasGeneratedContent(nsIRDFResource
* aResource
,
113 NS_IMETHOD
GetResultForContent(nsIDOMElement
* aContent
,
114 nsIXULTemplateResult
** aResult
);
116 // nsIMutationObserver interface
117 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
118 NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
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
135 OpenContainer(nsIContent
* aElement
);
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.
146 BuildContentFromTemplate(nsIContent
*aTemplateNode
,
147 nsIContent
*aResourceNode
,
148 nsIContent
*aRealNode
,
150 PRBool aIsSelfReference
,
151 nsIXULTemplateResult
* aChild
,
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
167 CopyAttributesToElement(nsIContent
* aTemplateNode
,
168 nsIContent
* aRealNode
,
169 nsIXULTemplateResult
* aResult
,
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
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
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.
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
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
230 CreateContainerContents(nsIContent
* aElement
,
231 nsIXULTemplateResult
* aResult
,
232 PRBool aForceCreation
,
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
246 CreateContainerContentsForQuerySet(nsIContent
* aElement
,
247 nsIXULTemplateResult
* aResult
,
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
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.
265 EnsureElementHasGenericChild(nsIContent
* aParent
,
266 PRInt32 aNameSpaceID
,
269 nsIContent
** aResult
);
272 IsOpen(nsIContent
* aElement
);
275 RemoveGeneratedContent(nsIContent
* aElement
);
278 GetElementsForResult(nsIXULTemplateResult
* aResult
,
279 nsCOMArray
<nsIContent
>& aElements
);
282 CreateElement(PRInt32 aNameSpaceID
,
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
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
298 SetContainerAttrs(nsIContent
*aElement
,
299 nsIXULTemplateResult
* aResult
,
300 PRBool aIgnoreNonContainers
,
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.
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.
323 ReplaceMatch(nsIXULTemplateResult
* aOldResult
,
324 nsTemplateMatch
* aNewMatch
,
325 nsTemplateRule
* aNewMatchRule
,
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.
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.
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.
351 InsertSortedNode(nsIContent
* aContainer
,
353 nsIXULTemplateResult
* aResult
,
357 * Maintains a mapping between elements in the DOM and the matches
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
;
375 NS_NewXULContentBuilder(nsISupports
* aOuter
, REFNSIID aIID
, void** aResult
)
377 NS_PRECONDITION(aOuter
== nsnull
, "no aggregation");
379 return NS_ERROR_NO_AGGREGATION
;
382 nsXULContentBuilder
* result
= new nsXULContentBuilder();
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
);
397 nsXULContentBuilder::nsXULContentBuilder()
399 mSortState
.initialized
= PR_FALSE
;
403 nsXULContentBuilder::Uninit(PRBool aIsFinal
)
405 if (! aIsFinal
&& mRoot
) {
406 nsresult rv
= RemoveGeneratedContent(mRoot
);
411 // Nuke the content support map completely.
412 mContentSupportMap
.Clear();
413 mTemplateMap
.Clear();
415 mSortState
.initialized
= PR_FALSE
;
417 nsXULTemplateBuilder::Uninit(aIsFinal
);
421 nsXULContentBuilder::BuildContentFromTemplate(nsIContent
*aTemplateNode
,
422 nsIContent
*aResourceNode
,
423 nsIContent
*aRealNode
,
425 PRBool aIsSelfReference
,
426 nsIXULTemplateResult
* aChild
,
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
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
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
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
469 // |aNewIndexInContainer| is an out parameter that will be set to
470 // the index in aContainer at which new content is first
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.
482 if (PR_LOG_TEST(gXULTemplateLog
, PR_LOG_DEBUG
)) {
483 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
484 ("nsXULContentBuilder::BuildContentFromTemplate (is unique: %d)",
487 const char *tmpln
, *resn
, *realn
;
488 aTemplateNode
->Tag()->GetUTF8String(&tmpln
);
489 aResourceNode
->Tag()->GetUTF8String(&resn
);
490 aRealNode
->Tag()->GetUTF8String(&realn
);
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()));
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
524 // For example, in a <tree> template:
528 // <treechildren> [1]
529 // <treeitem uri="rdf:*"> [2]
531 // <treecell value="rdf:urn:foo" /> [4]
532 // <treecell value="rdf:urn:bar" /> [5]
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
;
562 nsIAtom
*tag
= tmplKid
->Tag();
565 if (PR_LOG_TEST(gXULTemplateLog
, PR_LOG_DEBUG
)) {
567 tag
->GetUTF8String(&tagname
);
568 PR_LOG(gXULTemplateLog
, PR_LOG_DEBUG
,
569 ("xultemplate[%p] building %s %s %s",
571 (isGenerationElement
? "[resource]" : ""),
572 (isUnique
? "[unique]" : "")));
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
;
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
587 rv
= EnsureElementHasGenericChild(aRealNode
, nameSpaceID
, tag
, aNotify
, getter_AddRefs(realKid
));
591 if (rv
== NS_ELEMENT_WAS_THERE
) {
592 realKidAlreadyExisted
= PR_TRUE
;
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
615 rv
= BuildContentFromTemplate(tmplKid
, aResourceNode
, realKid
, PR_TRUE
,
616 aIsSelfReference
, aChild
, aNotify
, aMatch
,
617 aContainer
, aNewIndexInContainer
);
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
));
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
635 rv
= aChild
->GetId(id
);
639 rv
= realKid
->SetAttr(kNameSpaceID_None
, nsGkAtoms::id
, id
, PR_FALSE
);
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());
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
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()) {
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
);
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
;
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:
744 // <label value="?title"/>
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
755 rv
= CreateContainerContents(realKid
, aChild
, 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.
765 rv
= NS_ERROR_UNEXPECTED
;
767 if (isGenerationElement
)
768 rv
= InsertSortedNode(aRealNode
, realKid
, aChild
, aNotify
);
771 rv
= aRealNode
->AppendChildTo(realKid
, aNotify
);
772 NS_ASSERTION(NS_SUCCEEDED(rv
), "unable to insert element");
782 nsXULContentBuilder::CopyAttributesToElement(nsIContent
* aTemplateNode
,
783 nsIContent
* aRealNode
,
784 nsIXULTemplateResult
* aResult
,
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
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
804 PRUnichar attrbuf
[128];
805 nsFixedString
attribValue(attrbuf
, NS_ARRAY_LENGTH(attrbuf
), 0);
806 aTemplateNode
->GetAttr(attribNameSpaceID
, attribName
, attribValue
);
807 if (!attribValue
.IsEmpty()) {
809 rv
= SubstituteText(aResult
, attribValue
, value
);
813 // if the string is empty after substitutions, remove the
815 if (!value
.IsEmpty()) {
816 rv
= aRealNode
->SetAttr(attribNameSpaceID
,
823 rv
= aRealNode
->UnsetAttr(attribNameSpaceID
,
838 nsXULContentBuilder::AddPersistentAttributes(nsIContent
* aTemplateNode
,
839 nsIXULTemplateResult
* aResult
,
840 nsIContent
* aRealNode
)
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(" ,");
857 persist
.Left(attribute
, offset
);
858 persist
.Cut(0, offset
+ 1);
867 if (attribute
.IsEmpty())
870 nsCOMPtr
<nsIAtom
> tag
;
873 nsCOMPtr
<nsINodeInfo
> ni
=
874 aTemplateNode
->GetExistingAttrNameFromQName(attribute
);
876 tag
= ni
->NameAtom();
877 nameSpaceID
= ni
->NamespaceID();
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
);
897 nsCOMPtr
<nsIRDFLiteral
> value
= do_QueryInterface(target
);
898 NS_ASSERTION(value
!= nsnull
, "unable to stomach that sort of node");
902 const PRUnichar
* valueStr
;
903 rv
= value
->GetValueConst(&valueStr
);
904 NS_ENSURE_SUCCESS(rv
, rv
);
906 rv
= aRealNode
->SetAttr(nameSpaceID
, tag
, nsDependentString(valueStr
),
908 NS_ENSURE_SUCCESS(rv
, rv
);
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
922 rv
= CopyAttributesToElement(aTemplateNode
, aRealElement
, aResult
, PR_TRUE
);
926 PRUint32 count
= aTemplateNode
->GetChildCount();
928 for (PRUint32 loop
= 0; loop
< count
; ++loop
) {
929 nsIContent
*tmplKid
= aTemplateNode
->GetChildAt(loop
);
934 nsIContent
*realKid
= aRealElement
->GetChildAt(loop
);
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
,
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()) {
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
;
961 nsXULContentBuilder::RemoveMember(nsIContent
* aContent
)
963 nsCOMPtr
<nsIContent
> parent
= aContent
->GetParent();
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
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
);
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",
998 if (! mQueryProcessor
)
1001 // for the root element, get the ref attribute and generate content
1002 if (aElement
== mRoot
) {
1003 if (! mRootResult
) {
1005 mRoot
->GetAttr(kNameSpaceID_None
, nsGkAtoms::ref
, ref
);
1007 if (! ref
.IsEmpty()) {
1008 nsresult rv
= mQueryProcessor
->TranslateRef(mDataSource
, ref
,
1009 getter_AddRefs(mRootResult
));
1016 CreateContainerContents(aElement
, mRootResult
, aForceCreation
,
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
)
1032 CreateContainerContents(aElement
, match
->mResult
, aForceCreation
,
1037 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
1038 ("nsXULContentBuilder::CreateTemplateAndContainerContents end"));
1044 nsXULContentBuilder::CreateContainerContents(nsIContent
* aElement
,
1045 nsIXULTemplateResult
* aResult
,
1046 PRBool aForceCreation
,
1048 PRBool aNotifyAtEnd
)
1050 if (!aForceCreation
&& !IsOpen(aElement
))
1053 nsCOMPtr
<nsIRDFResource
> refResource
;
1054 GetResultResource(aResult
, getter_AddRefs(refResource
));
1056 return NS_ERROR_FAILURE
;
1058 // Avoid re-entrant builds for the same resource.
1059 if (IsActivated(refResource
))
1062 ActivationEntry
entry(refResource
, &mTop
);
1064 // Compile the rules now, if they haven't been already.
1065 if (! mQueriesCompiled
) {
1066 nsresult rv
= CompileQueries();
1071 if (mQuerySets
.Length() == 0)
1074 // See if the element's templates contents have been generated:
1075 // this prevents a re-entrant call from triggering another
1077 nsXULElement
*xulcontent
= nsXULElement::FromContent(aElement
);
1079 if (xulcontent
->GetTemplateGenerated())
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())
1099 CreateContainerContentsForQuerySet(aElement
, aResult
, aNotify
, queryset
,
1100 &container
, &newIndexInContainer
);
1103 if (aNotifyAtEnd
&& container
) {
1104 MOZ_AUTO_DOC_UPDATE(container
->GetCurrentDoc(), UPDATE_CONTENT_MODEL
,
1106 nsNodeUtils::ContentAppended(container
, newIndexInContainer
);
1109 NS_IF_RELEASE(container
);
1115 nsXULContentBuilder::CreateContainerContentsForQuerySet(nsIContent
* aElement
,
1116 nsIXULTemplateResult
* aResult
,
1118 nsTemplateQuerySet
* aQuerySet
,
1119 nsIContent
** aContainer
,
1120 PRInt32
* aNewIndexInContainer
)
1123 if (PR_LOG_TEST(gXULTemplateLog
, PR_LOG_DEBUG
)) {
1126 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
1127 ("nsXULContentBuilder::CreateContainerContentsForQuerySet start for ref %s\n",
1128 NS_ConvertUTF16toUTF8(id
).get()));
1132 if (! mQueryProcessor
)
1135 nsCOMPtr
<nsISimpleEnumerator
> results
;
1136 nsresult rv
= mQueryProcessor
->GenerateResults(mDataSource
, aResult
,
1137 aQuerySet
->mCompiledQuery
,
1138 getter_AddRefs(results
));
1139 if (NS_FAILED(rv
) || !results
)
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
));
1152 nsCOMPtr
<nsIXULTemplateResult
> nextresult
= do_QueryInterface(nr
);
1154 return NS_ERROR_UNEXPECTED
;
1156 nsCOMPtr
<nsIRDFResource
> resultid
;
1157 rv
= GetResultResource(nextresult
, getter_AddRefs(resultid
));
1164 nsTemplateMatch
*newmatch
=
1165 nsTemplateMatch::Create(mPool
, aQuerySet
->Priority(),
1166 nextresult
, aElement
);
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())
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
;
1198 if (existingmatch
->IsActive())
1199 generateContent
= PR_FALSE
;
1202 prevmatch
= existingmatch
;
1203 existingmatch
= existingmatch
->mNext
;
1208 // remove the generated content for the existing match
1209 rv
= ReplaceMatch(removematch
->mResult
, nsnull
, nsnull
, aElement
);
1214 if (generateContent
) {
1215 // find the rule that matches. If none match, the content does not
1216 // need to be generated
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
);
1228 rv
= newmatch
->RuleMatched(aQuerySet
, matchedrule
,
1229 ruleindex
, nextresult
);
1230 if (NS_FAILED(rv
)) {
1231 nsTemplateMatch::Destroy(mPool
, newmatch
, PR_FALSE
);
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
);
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
;
1253 newmatch
->mNext
= removematch
->mNext
;
1254 nsTemplateMatch::Destroy(mPool
, removematch
, PR_TRUE
);
1257 newmatch
->mNext
= existingmatch
;
1265 nsXULContentBuilder::EnsureElementHasGenericChild(nsIContent
* parent
,
1266 PRInt32 nameSpaceID
,
1269 nsIContent
** result
)
1273 rv
= nsXULContentUtils::FindChildByTag(parent
, nameSpaceID
, tag
, result
);
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
));
1285 // XXX Note that the notification ensures we won't batch insertions! This could be bad! - Dave
1286 rv
= parent
->AppendChildTo(element
, aNotify
);
1292 return NS_ELEMENT_GOT_CREATED
;
1295 return NS_ELEMENT_WAS_THERE
;
1300 nsXULContentBuilder::IsOpen(nsIContent
* aElement
)
1302 // Determine if this is a <treeitem> or <menu> element
1303 if (!aElement
->IsNodeOfType(nsINode::eXUL
))
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
);
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
;
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();
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
))
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
));
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
;
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
);
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());
1389 return xuldoc
->GetElementsForID(id
, aElements
);
1393 nsXULContentBuilder::CreateElement(PRInt32 aNameSpaceID
,
1395 nsIContent
** aResult
)
1397 nsCOMPtr
<nsIDocument
> doc
= mRoot
->GetDocument();
1398 NS_ASSERTION(doc
!= nsnull
, "not initialized");
1400 return NS_ERROR_NOT_INITIALIZED
;
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
,
1414 NS_ADDREF(*aResult
);
1419 nsXULContentBuilder::SetContainerAttrs(nsIContent
*aElement
,
1420 nsIXULTemplateResult
* aResult
,
1421 PRBool aIgnoreNonContainers
,
1424 NS_PRECONDITION(aResult
!= nsnull
, "null ptr");
1426 return NS_ERROR_NULL_POINTER
;
1429 aResult
->GetIsContainer(&iscontainer
);
1431 if (aIgnoreNonContainers
&& !iscontainer
)
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
)) {
1445 aResult
->GetIsEmpty(&isempty
);
1447 const nsAString
& newempty
=
1448 (iscontainer
&& isempty
) ? true_
: false_
;
1450 aElement
->SetAttr(kNameSpaceID_None
, nsGkAtoms::empty
,
1458 //----------------------------------------------------------------------
1460 // nsIXULTemplateBuilder methods
1464 nsXULContentBuilder::CreateContents(nsIContent
* aElement
, PRBool aForceCreation
)
1466 NS_PRECONDITION(aElement
!= nsnull
, "null ptr");
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
))
1475 return CreateTemplateAndContainerContents(aElement
, aForceCreation
);
1479 nsXULContentBuilder::HasGeneratedContent(nsIRDFResource
* aResource
,
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
));
1492 // the root resource is always acceptable
1493 if (aResource
== rootresource
) {
1494 if (!aTag
|| mRoot
->Tag() == aTag
)
1495 *aGenerated
= PR_TRUE
;
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());
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
);
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
;
1526 content
= content
->GetParent();
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
;
1546 nsTemplateMatch
*match
= nsnull
;
1547 if (mContentSupportMap
.Get(content
, &match
))
1548 *aResult
= match
->mResult
;
1553 NS_IF_ADDREF(*aResult
);
1557 //----------------------------------------------------------------------
1559 // nsIDocumentObserver methods
1563 nsXULContentBuilder::AttributeChanged(nsIDocument
* aDocument
,
1564 nsIContent
* aContent
,
1565 PRInt32 aNameSpaceID
,
1566 nsIAtom
* aAttribute
,
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
);
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
);
1596 nsXULContentBuilder::NodeWillBeDestroyed(const nsINode
* aNode
)
1598 // Break circular references
1599 mContentSupportMap
.Clear();
1601 nsXULTemplateBuilder::NodeWillBeDestroyed(aNode
);
1605 //----------------------------------------------------------------------
1607 // nsXULTemplateBuilder methods
1611 nsXULContentBuilder::GetInsertionLocations(nsIXULTemplateResult
* aResult
,
1612 nsCOMArray
<nsIContent
>** aLocations
)
1614 *aLocations
= nsnull
;
1617 nsresult rv
= aResult
->GetBindingFor(mRefVariable
, ref
);
1621 nsCOMPtr
<nsIXULDocument
> xuldoc
= do_QueryInterface(mRoot
->GetDocument());
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
1643 nsXULElement
*xulcontent
= nsXULElement::FromContent(content
);
1644 if (!xulcontent
|| xulcontent
->GetTemplateGenerated()) {
1650 // clear the item in the list since we don't want to insert there
1651 (*aLocations
)->ReplaceObjectAt(nsnull
, t
);
1658 nsXULContentBuilder::ReplaceMatch(nsIXULTemplateResult
* aOldResult
,
1659 nsTemplateMatch
* aNewMatch
,
1660 nsTemplateRule
* aNewMatchRule
,
1665 nsIContent
* content
= static_cast<nsIContent
*>(aContext
);
1667 // update the container attributes for the match
1671 rv
= aNewMatch
->mResult
->GetBindingFor(mRefVariable
, ref
);
1673 rv
= aOldResult
->GetBindingFor(mRefVariable
, ref
);
1677 if (!ref
.IsEmpty()) {
1678 nsCOMPtr
<nsIXULTemplateResult
> refResult
;
1679 rv
= GetResultForId(ref
, getter_AddRefs(refResult
));
1684 SetContainerAttrs(content
, refResult
, PR_FALSE
, PR_TRUE
);
1689 nsCOMArray
<nsIContent
> elements
;
1690 rv
= GetElementsForResult(aOldResult
, elements
);
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
);
1708 nsCOMPtr
<nsIContent
> action
= aNewMatchRule
->GetAction();
1709 return BuildContentFromTemplate(action
, content
, content
, PR_TRUE
,
1710 mRefVariable
== aNewMatchRule
->GetMemberVariable(),
1711 aNewMatch
->mResult
, PR_TRUE
, aNewMatch
,
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
))
1734 nsCOMPtr
<nsIContent
> templateNode
;
1735 mTemplateMap
.GetTemplateFor(element
, getter_AddRefs(templateNode
));
1737 NS_ASSERTION(templateNode
, "couldn't find template node for element");
1741 // this node was created by a XUL template, so update it accordingly
1742 SynchronizeUsingTemplate(templateNode
, element
, aResult
);
1748 //----------------------------------------------------------------------
1750 // Implementation methods
1754 nsXULContentBuilder::OpenContainer(nsIContent
* aElement
)
1756 if (aElement
!= mRoot
) {
1757 if (mFlags
& eDontRecurse
)
1760 PRBool rightBuilder
= PR_FALSE
;
1762 nsCOMPtr
<nsIXULDocument
> xuldoc
= do_QueryInterface(aElement
->GetDocument());
1766 // See if we're responsible for this element
1767 nsIContent
* content
= aElement
;
1769 nsCOMPtr
<nsIXULTemplateBuilder
> builder
;
1770 xuldoc
->GetTemplateBuilderFor(content
, getter_AddRefs(builder
));
1772 if (builder
== this)
1773 rightBuilder
= PR_TRUE
;
1777 content
= content
->GetParent();
1784 CreateTemplateAndContainerContents(aElement
, PR_FALSE
);
1790 nsXULContentBuilder::CloseContainer(nsIContent
* aElement
)
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();
1805 if (mQueriesCompiled
)
1808 nsresult rv
= CompileQueries();
1812 if (mQuerySets
.Length() == 0)
1815 nsXULElement
*xulcontent
= nsXULElement::FromContent(mRoot
);
1817 xulcontent
->ClearTemplateGenerated();
1819 // Now, regenerate both the template- and container-generated
1820 // contents for the current element...
1821 CreateTemplateAndContainerContents(mRoot
, PR_FALSE
);
1826 /**** Sorting Methods ****/
1829 nsXULContentBuilder::CompareResultToNode(nsIXULTemplateResult
* aResult
,
1830 nsIContent
* aContent
,
1831 PRInt32
* aSortOrder
)
1833 NS_ASSERTION(aSortOrder
, "CompareResultToNode: null out param aSortOrder");
1837 nsTemplateMatch
*match
= nsnull
;
1838 if (!mContentSupportMap
.Get(aContent
, &match
)) {
1839 *aSortOrder
= mSortState
.sortStaticsLast
? -1 : 1;
1843 if (!mQueryProcessor
)
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
);
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
);
1866 // flip the sort order if performing a descending sorting
1867 if (mSortState
.direction
== nsSortState_descending
)
1868 *aSortOrder
= -*aSortOrder
;
1874 nsXULContentBuilder::InsertSortedNode(nsIContent
* aContainer
,
1876 nsIXULTemplateResult
* aResult
,
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
);
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
1927 staticCount
= staticValue
.ToInteger(&strErr
);
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
))
1941 if (mSortState
.sortStaticsLast
) {
1942 // indicate that static XUL comes after RDF-generated content by
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
;
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) {
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
);
1972 rv
= CompareResultToNode(aResult
, temp
, &direction
);
1973 if (direction
< 0) {
1974 aContainer
->InsertChildAt(aNode
, staticCount
, aNotify
);
1975 childAdded
= PR_TRUE
;
1977 mSortState
.lastWasFirst
= PR_FALSE
;
1978 } else if (mSortState
.lastWasLast
) {
1979 child
= aContainer
->GetChildAt(realNumChildren
- 1);
1981 rv
= CompareResultToNode(aResult
, temp
, &direction
);
1982 if (direction
> 0) {
1983 aContainer
->InsertChildAt(aNode
, realNumChildren
, aNotify
);
1984 childAdded
= PR_TRUE
;
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);
1995 rv
= CompareResultToNode(aResult
, temp
, &direction
);
1996 if ((x
== left
&& direction
< 0) ||
1997 (x
== right
&& direction
>= 0) ||
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
);
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.
2020 aContainer
->InsertChildAt(aNode
, numChildren
, aNotify
);