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
4 : *
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/
9 : *
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.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
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.
21 : *
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>
28 : *
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.
40 : *
41 : * ***** END LICENSE BLOCK ***** */
42 :
43 : #include "mozilla/Util.h"
44 :
45 : #include "nsContentCID.h"
46 : #include "nsIDocument.h"
47 : #include "nsIDOMNodeList.h"
48 : #include "nsIDOMXULDocument.h"
49 : #include "nsINodeInfo.h"
50 : #include "nsIServiceManager.h"
51 : #include "nsIXULDocument.h"
52 :
53 : #include "nsContentSupportMap.h"
54 : #include "nsRDFConMemberTestNode.h"
55 : #include "nsRDFPropertyTestNode.h"
56 : #include "nsXULSortService.h"
57 : #include "nsTemplateRule.h"
58 : #include "nsTemplateMap.h"
59 : #include "nsTArray.h"
60 : #include "nsXPIDLString.h"
61 : #include "nsGkAtoms.h"
62 : #include "nsXULContentUtils.h"
63 : #include "nsXULElement.h"
64 : #include "nsXULTemplateBuilder.h"
65 : #include "nsNodeInfoManager.h"
66 : #include "nsContentCreatorFunctions.h"
67 : #include "nsContentUtils.h"
68 : #include "nsAttrName.h"
69 : #include "nsNodeUtils.h"
70 : #include "mozAutoDocUpdate.h"
71 :
72 : #include "jsapi.h"
73 : #include "pldhash.h"
74 : #include "rdf.h"
75 :
76 : using namespace mozilla;
77 : using namespace mozilla::dom;
78 :
79 : //----------------------------------------------------------------------
80 : //
81 : // Return values for EnsureElementHasGenericChild()
82 : //
83 : #define NS_ELEMENT_GOT_CREATED NS_RDF_NO_VALUE
84 : #define NS_ELEMENT_WAS_THERE NS_OK
85 :
86 : //----------------------------------------------------------------------
87 : //
88 : // nsXULContentBuilder
89 : //
90 :
91 : /**
92 : * The content builder generates DOM nodes from a template. The actual content
93 : * generation is done entirely inside BuildContentFromTemplate.
94 : *
95 : * Content generation is centered around the generation node (the node with
96 : * uri="?member" on it). Nodes above the generation node are unique and
97 : * generated only once. BuildContentFromTemplate will be passed the unique
98 : * flag as an argument for content at this point and will recurse until it
99 : * finds the generation node.
100 : *
101 : * Once the generation node has been found, the results for that content node
102 : * are added to the content map, stored in mContentSupportMap.
103 : *
104 : * If recursion is allowed, generation continues, where the generation node
105 : * becomes the container to insert into.
106 : */
107 : class nsXULContentBuilder : public nsXULTemplateBuilder
108 0 : {
109 : public:
110 : // nsIXULTemplateBuilder interface
111 : NS_IMETHOD CreateContents(nsIContent* aElement, bool aForceCreation);
112 :
113 : NS_IMETHOD HasGeneratedContent(nsIRDFResource* aResource,
114 : nsIAtom* aTag,
115 : bool* aGenerated);
116 :
117 : NS_IMETHOD GetResultForContent(nsIDOMElement* aContent,
118 : nsIXULTemplateResult** aResult);
119 :
120 : // nsIMutationObserver interface
121 : NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
122 : NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
123 :
124 : protected:
125 : friend nsresult
126 : NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult);
127 :
128 : nsXULContentBuilder();
129 :
130 0 : void Traverse(nsCycleCollectionTraversalCallback &cb) const
131 : {
132 0 : mSortState.Traverse(cb);
133 0 : }
134 :
135 : virtual void Uninit(bool aIsFinal);
136 :
137 : // Implementation methods
138 : nsresult
139 : OpenContainer(nsIContent* aElement);
140 :
141 : nsresult
142 : CloseContainer(nsIContent* aElement);
143 :
144 : /**
145 : * Build content from a template for a given result. This will be called
146 : * recursively or on demand and will be called for every node in the
147 : * generated content tree.
148 : */
149 : nsresult
150 : BuildContentFromTemplate(nsIContent *aTemplateNode,
151 : nsIContent *aResourceNode,
152 : nsIContent *aRealNode,
153 : bool aIsUnique,
154 : bool aIsSelfReference,
155 : nsIXULTemplateResult* aChild,
156 : bool aNotify,
157 : nsTemplateMatch* aMatch,
158 : nsIContent** aContainer,
159 : PRInt32* aNewIndexInContainer);
160 :
161 : /**
162 : * Copy the attributes from the template node to the node generated
163 : * from it, performing any substitutions.
164 : *
165 : * @param aTemplateNode node within template
166 : * @param aRealNode generated node to set attibutes upon
167 : * @param aResult result to look up variable->value bindings in
168 : * @param aNotify true to notify of DOM changes
169 : */
170 : nsresult
171 : CopyAttributesToElement(nsIContent* aTemplateNode,
172 : nsIContent* aRealNode,
173 : nsIXULTemplateResult* aResult,
174 : bool aNotify);
175 :
176 : /**
177 : * Add any necessary persistent attributes (persist="...") from the
178 : * local store to a generated node.
179 : *
180 : * @param aTemplateNode node within template
181 : * @param aRealNode generated node to set persisted attibutes upon
182 : * @param aResult result to look up variable->value bindings in
183 : */
184 : nsresult
185 : AddPersistentAttributes(nsIContent* aTemplateNode,
186 : nsIXULTemplateResult* aResult,
187 : nsIContent* aRealNode);
188 :
189 : /**
190 : * Recalculate any attributes that have variable references. This will
191 : * be called when a binding has been changed to update the attributes.
192 : * The attributes are copied from the node aTemplateNode in the template
193 : * to the generated node aRealNode, using the values from the result
194 : * aResult. This method will operate recursively.
195 : *
196 : * @param aTemplateNode node within template
197 : * @param aRealNode generated node to set attibutes upon
198 : * @param aResult result to look up variable->value bindings in
199 : */
200 : nsresult
201 : SynchronizeUsingTemplate(nsIContent *aTemplateNode,
202 : nsIContent* aRealNode,
203 : nsIXULTemplateResult* aResult);
204 :
205 : /**
206 : * Remove the generated node aContent from the DOM and the hashtables
207 : * used by the content builder.
208 : */
209 : nsresult
210 : RemoveMember(nsIContent* aContent);
211 :
212 : /**
213 : * Create the appropriate generated content for aElement, by calling
214 : * CreateContainerContents.
215 : *
216 : * @param aElement element to generate content inside
217 : * @param aForceCreation true to force creation for closed items such as menus
218 : */
219 : nsresult
220 : CreateTemplateAndContainerContents(nsIContent* aElement,
221 : bool aForceCreation);
222 :
223 : /**
224 : * Generate the results for a template, by calling
225 : * CreateContainerContentsForQuerySet for each queryset.
226 : *
227 : * @param aElement element to generate content inside
228 : * @param aResult reference point for query
229 : * @param aForceCreation true to force creation for closed items such as menus
230 : * @param aNotify true to notify of DOM changes as each element is inserted
231 : * @param aNotifyAtEnd notify at the end of all DOM changes
232 : */
233 : nsresult
234 : CreateContainerContents(nsIContent* aElement,
235 : nsIXULTemplateResult* aResult,
236 : bool aForceCreation,
237 : bool aNotify,
238 : bool aNotifyAtEnd);
239 :
240 : /**
241 : * Generate the results for a query.
242 : *
243 : * @param aElement element to generate content inside
244 : * @param aResult reference point for query
245 : * @param aNotify true to notify of DOM changes
246 : * @param aContainer container content was added inside
247 : * @param aNewIndexInContainer index with container in which content was added
248 : */
249 : nsresult
250 : CreateContainerContentsForQuerySet(nsIContent* aElement,
251 : nsIXULTemplateResult* aResult,
252 : bool aNotify,
253 : nsTemplateQuerySet* aQuerySet,
254 : nsIContent** aContainer,
255 : PRInt32* aNewIndexInContainer);
256 :
257 : /**
258 : * Check if an element with a particular tag exists with a container.
259 : * If it is not present, append a new element with that tag into the
260 : * container.
261 : *
262 : * @param aParent parent container
263 : * @param aNameSpaceID namespace of tag to locate or create
264 : * @param aTag tag to locate or create
265 : * @param aNotify true to notify of DOM changes
266 : * @param aResult set to the found or created node.
267 : */
268 : nsresult
269 : EnsureElementHasGenericChild(nsIContent* aParent,
270 : PRInt32 aNameSpaceID,
271 : nsIAtom* aTag,
272 : bool aNotify,
273 : nsIContent** aResult);
274 :
275 : bool
276 : IsOpen(nsIContent* aElement);
277 :
278 : nsresult
279 : RemoveGeneratedContent(nsIContent* aElement);
280 :
281 : nsresult
282 : GetElementsForResult(nsIXULTemplateResult* aResult,
283 : nsCOMArray<nsIContent>& aElements);
284 :
285 : nsresult
286 : CreateElement(PRInt32 aNameSpaceID,
287 : nsIAtom* aTag,
288 : nsIContent** aResult);
289 :
290 : /**
291 : * Set the container and empty attributes on a node. If
292 : * aIgnoreNonContainers is true, then the element is not changed
293 : * for non-containers. Otherwise, the container attribute will be set to
294 : * false.
295 : *
296 : * @param aElement element to set attributes on
297 : * @param aResult result to use to determine state of attributes
298 : * @param aIgnoreNonContainers true to not change for non-containers
299 : * @param aNotify true to notify of DOM changes
300 : */
301 : nsresult
302 : SetContainerAttrs(nsIContent *aElement,
303 : nsIXULTemplateResult* aResult,
304 : bool aIgnoreNonContainers,
305 : bool aNotify);
306 :
307 : virtual nsresult
308 : RebuildAll();
309 :
310 : // GetInsertionLocations, ReplaceMatch and SynchronizeResult are inherited
311 : // from nsXULTemplateBuilder
312 :
313 : /**
314 : * Return true if the result can be inserted into the template as
315 : * generated content. For the content builder, aLocations will be set
316 : * to the list of containers where the content should be inserted.
317 : */
318 : virtual bool
319 : GetInsertionLocations(nsIXULTemplateResult* aOldResult,
320 : nsCOMArray<nsIContent>** aLocations);
321 :
322 : /**
323 : * Remove the content associated with aOldResult which no longer matches,
324 : * and/or generate content for a new match.
325 : */
326 : virtual nsresult
327 : ReplaceMatch(nsIXULTemplateResult* aOldResult,
328 : nsTemplateMatch* aNewMatch,
329 : nsTemplateRule* aNewMatchRule,
330 : void *aContext);
331 :
332 : /**
333 : * Synchronize a result bindings with the generated content for that
334 : * result. This will be called as a result of the template builder's
335 : * ResultBindingChanged method.
336 : */
337 : virtual nsresult
338 : SynchronizeResult(nsIXULTemplateResult* aResult);
339 :
340 : /**
341 : * Compare a result to a content node. If the generated content for the
342 : * result should come before aContent, set aSortOrder to -1. If it should
343 : * come after, set sortOrder to 1. If both are equal, set to 0.
344 : */
345 : nsresult
346 : CompareResultToNode(nsIXULTemplateResult* aResult, nsIContent* aContent,
347 : PRInt32* aSortOrder);
348 :
349 : /**
350 : * Insert a generated node into the container where it should go according
351 : * to the current sort. aNode is the generated content node and aResult is
352 : * the result for the generated node.
353 : */
354 : nsresult
355 : InsertSortedNode(nsIContent* aContainer,
356 : nsIContent* aNode,
357 : nsIXULTemplateResult* aResult,
358 : bool aNotify);
359 :
360 : /**
361 : * Maintains a mapping between elements in the DOM and the matches
362 : * that they support.
363 : */
364 : nsContentSupportMap mContentSupportMap;
365 :
366 : /**
367 : * Maintains a mapping from an element in the DOM to the template
368 : * element that it was created from.
369 : */
370 : nsTemplateMap mTemplateMap;
371 :
372 : /**
373 : * Information about the currently active sort
374 : */
375 : nsSortState mSortState;
376 : };
377 :
378 : nsresult
379 0 : NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult)
380 : {
381 0 : NS_PRECONDITION(aOuter == nsnull, "no aggregation");
382 0 : if (aOuter)
383 0 : return NS_ERROR_NO_AGGREGATION;
384 :
385 : nsresult rv;
386 0 : nsXULContentBuilder* result = new nsXULContentBuilder();
387 0 : if (!result)
388 0 : return NS_ERROR_OUT_OF_MEMORY;
389 :
390 0 : NS_ADDREF(result); // stabilize
391 :
392 0 : rv = result->InitGlobals();
393 :
394 0 : if (NS_SUCCEEDED(rv))
395 0 : rv = result->QueryInterface(aIID, aResult);
396 :
397 0 : NS_RELEASE(result);
398 0 : return rv;
399 : }
400 :
401 0 : nsXULContentBuilder::nsXULContentBuilder()
402 : {
403 0 : mSortState.initialized = false;
404 0 : }
405 :
406 : void
407 0 : nsXULContentBuilder::Uninit(bool aIsFinal)
408 : {
409 0 : if (! aIsFinal && mRoot) {
410 0 : nsresult rv = RemoveGeneratedContent(mRoot);
411 0 : if (NS_FAILED(rv))
412 0 : return;
413 : }
414 :
415 : // Nuke the content support map completely.
416 0 : mContentSupportMap.Clear();
417 0 : mTemplateMap.Clear();
418 :
419 0 : mSortState.initialized = false;
420 :
421 0 : nsXULTemplateBuilder::Uninit(aIsFinal);
422 : }
423 :
424 : nsresult
425 0 : nsXULContentBuilder::BuildContentFromTemplate(nsIContent *aTemplateNode,
426 : nsIContent *aResourceNode,
427 : nsIContent *aRealNode,
428 : bool aIsUnique,
429 : bool aIsSelfReference,
430 : nsIXULTemplateResult* aChild,
431 : bool aNotify,
432 : nsTemplateMatch* aMatch,
433 : nsIContent** aContainer,
434 : PRInt32* aNewIndexInContainer)
435 : {
436 : // This is the mother lode. Here is where we grovel through an
437 : // element in the template, copying children from the template
438 : // into the "real" content tree, performing substitution as we go
439 : // by looking stuff up using the results.
440 : //
441 : // |aTemplateNode| is the element in the "template tree", whose
442 : // children we will duplicate and move into the "real" content
443 : // tree.
444 : //
445 : // |aResourceNode| is the element in the "real" content tree that
446 : // has the "id" attribute set to an result's id. This is
447 : // not directly used here, but rather passed down to the XUL
448 : // sort service to perform container-level sort.
449 : //
450 : // |aRealNode| is the element in the "real" content tree to which
451 : // the new elements will be copied.
452 : //
453 : // |aIsUnique| is set to "true" so long as content has been
454 : // "unique" (or "above" the resource element) so far in the
455 : // template.
456 : //
457 : // |aIsSelfReference| should be set to "true" for cases where
458 : // the reference and member variables are the same, indicating
459 : // that the generated node is the same as the reference point,
460 : // so generation should not recurse, or else an infinite loop
461 : // would occur.
462 : //
463 : // |aChild| is the result for which we are building content.
464 : //
465 : // |aNotify| is set to "true" if content should be constructed
466 : // "noisily"; that is, whether the document observers should be
467 : // notified when new content is added to the content model.
468 : //
469 : // |aContainer| is an out parameter that will be set to the first
470 : // container element in the "real" content tree to which content
471 : // was appended.
472 : //
473 : // |aNewIndexInContainer| is an out parameter that will be set to
474 : // the index in aContainer at which new content is first
475 : // constructed.
476 : //
477 : // If |aNotify| is "false", then |aContainer| and
478 : // |aNewIndexInContainer| are used to determine where in the
479 : // content model new content is constructed. This allows a single
480 : // notification to be propagated to document observers.
481 : //
482 :
483 : nsresult rv;
484 :
485 : #ifdef PR_LOGGING
486 0 : if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) {
487 0 : PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
488 : ("nsXULContentBuilder::BuildContentFromTemplate (is unique: %d)",
489 : aIsUnique));
490 :
491 0 : nsAutoString id;
492 0 : aChild->GetId(id);
493 :
494 0 : PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
495 : ("Tags: [Template: %s Resource: %s Real: %s] for id %s",
496 : nsAtomCString(aTemplateNode->Tag()).get(),
497 : nsAtomCString(aResourceNode->Tag()).get(),
498 : nsAtomCString(aRealNode->Tag()).get(), NS_ConvertUTF16toUTF8(id).get()));
499 : }
500 : #endif
501 :
502 : // Iterate through all of the template children, constructing
503 : // "real" content model nodes for each "template" child.
504 0 : for (nsIContent* tmplKid = aTemplateNode->GetFirstChild();
505 : tmplKid;
506 0 : tmplKid = tmplKid->GetNextSibling()) {
507 :
508 0 : PRInt32 nameSpaceID = tmplKid->GetNameSpaceID();
509 :
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|.
513 : //
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.
518 : //
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|.
523 : //
524 : // For example, in a <tree> template:
525 : //
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>
538 : //
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.
545 : //
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 0 : bool isGenerationElement = false;
550 0 : bool isUnique = aIsUnique;
551 :
552 : {
553 : // We identify the resource element by presence of a
554 : // "uri='rdf:*'" attribute. (We also support the older
555 : // "uri='...'" syntax.)
556 0 : if (tmplKid->HasAttr(kNameSpaceID_None, nsGkAtoms::uri) && aMatch->IsActive()) {
557 0 : isGenerationElement = true;
558 0 : isUnique = false;
559 : }
560 : }
561 :
562 0 : nsIAtom *tag = tmplKid->Tag();
563 :
564 : #ifdef PR_LOGGING
565 0 : if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) {
566 0 : PR_LOG(gXULTemplateLog, PR_LOG_DEBUG,
567 : ("xultemplate[%p] building %s %s %s",
568 : this, nsAtomCString(tag).get(),
569 : (isGenerationElement ? "[resource]" : ""),
570 : (isUnique ? "[unique]" : "")));
571 : }
572 : #endif
573 :
574 : // Set to true if the child we're trying to create now
575 : // already existed in the content model.
576 0 : bool realKidAlreadyExisted = false;
577 :
578 0 : nsCOMPtr<nsIContent> realKid;
579 0 : if (isUnique) {
580 : // The content is "unique"; that is, we haven't descended
581 : // far enough into the template to hit the generation
582 : // element yet. |EnsureElementHasGenericChild()| will
583 : // conditionally create the element iff it isn't there
584 : // already.
585 0 : rv = EnsureElementHasGenericChild(aRealNode, nameSpaceID, tag, aNotify, getter_AddRefs(realKid));
586 0 : if (NS_FAILED(rv))
587 0 : return rv;
588 :
589 0 : if (rv == NS_ELEMENT_WAS_THERE) {
590 0 : realKidAlreadyExisted = true;
591 : }
592 : else {
593 : // Potentially remember the index of this element as the first
594 : // element that we've generated. Note that we remember
595 : // this -before- we recurse!
596 0 : if (aContainer && !*aContainer) {
597 0 : *aContainer = aRealNode;
598 0 : NS_ADDREF(*aContainer);
599 :
600 0 : PRUint32 indx = aRealNode->GetChildCount();
601 :
602 : // Since EnsureElementHasGenericChild() added us, make
603 : // sure to subtract one for our real index.
604 0 : *aNewIndexInContainer = indx - 1;
605 : }
606 : }
607 :
608 : // Recurse until we get to the resource element. Since
609 : // -we're- unique, assume that our child will be
610 : // unique. The check for the "resource" element at the top
611 : // of the function will trip this to |false| as soon as we
612 : // encounter it.
613 : rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, true,
614 : aIsSelfReference, aChild, aNotify, aMatch,
615 0 : aContainer, aNewIndexInContainer);
616 :
617 0 : if (NS_FAILED(rv))
618 0 : return rv;
619 : }
620 0 : else if (isGenerationElement) {
621 : // It's the "resource" element. Create a new element using
622 : // the namespace ID and tag from the template element.
623 0 : rv = CreateElement(nameSpaceID, tag, getter_AddRefs(realKid));
624 0 : if (NS_FAILED(rv))
625 0 : return rv;
626 :
627 : // Add the resource element to the content support map so
628 : // we can remove the match based on the content node later.
629 0 : mContentSupportMap.Put(realKid, aMatch);
630 :
631 : // Assign the element an 'id' attribute using result's id
632 0 : nsAutoString id;
633 0 : rv = aChild->GetId(id);
634 0 : if (NS_FAILED(rv))
635 0 : return rv;
636 :
637 0 : rv = realKid->SetAttr(kNameSpaceID_None, nsGkAtoms::id, id, false);
638 0 : if (NS_FAILED(rv))
639 0 : return rv;
640 :
641 : // Set up the element's 'container' and 'empty' attributes.
642 0 : SetContainerAttrs(realKid, aChild, true, false);
643 : }
644 0 : else if (tag == nsGkAtoms::textnode &&
645 : nameSpaceID == kNameSpaceID_XUL) {
646 : // <xul:text value="..."> is replaced by text of the
647 : // actual value of the 'rdf:resource' attribute for the
648 : // given node.
649 : // SynchronizeUsingTemplate contains code used to update textnodes,
650 : // so make sure to modify both when changing this
651 : PRUnichar attrbuf[128];
652 0 : nsFixedString attrValue(attrbuf, ArrayLength(attrbuf), 0);
653 0 : tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
654 0 : if (!attrValue.IsEmpty()) {
655 0 : nsAutoString value;
656 0 : rv = SubstituteText(aChild, attrValue, value);
657 0 : if (NS_FAILED(rv)) return rv;
658 :
659 0 : nsCOMPtr<nsIContent> content;
660 0 : rv = NS_NewTextNode(getter_AddRefs(content),
661 0 : mRoot->NodeInfo()->NodeInfoManager());
662 0 : if (NS_FAILED(rv)) return rv;
663 :
664 0 : content->SetText(value, false);
665 :
666 0 : rv = aRealNode->AppendChildTo(content, aNotify);
667 0 : if (NS_FAILED(rv)) return rv;
668 :
669 : // XXX Don't bother remembering text nodes as the
670 : // first element we've generated?
671 0 : }
672 : }
673 0 : else if (tmplKid->IsNodeOfType(nsINode::eTEXT)) {
674 0 : nsCOMPtr<nsIDOMNode> tmplTextNode = do_QueryInterface(tmplKid);
675 0 : if (!tmplTextNode) {
676 0 : NS_ERROR("textnode not implementing nsIDOMNode??");
677 0 : return NS_ERROR_FAILURE;
678 : }
679 0 : nsCOMPtr<nsIDOMNode> clonedNode;
680 0 : tmplTextNode->CloneNode(false, 1, getter_AddRefs(clonedNode));
681 0 : nsCOMPtr<nsIContent> clonedContent = do_QueryInterface(clonedNode);
682 0 : if (!clonedContent) {
683 0 : NS_ERROR("failed to clone textnode");
684 0 : return NS_ERROR_FAILURE;
685 : }
686 0 : rv = aRealNode->AppendChildTo(clonedContent, aNotify);
687 0 : if (NS_FAILED(rv)) return rv;
688 : }
689 : else {
690 : // It's just a generic element. Create it!
691 0 : rv = CreateElement(nameSpaceID, tag, getter_AddRefs(realKid));
692 0 : if (NS_FAILED(rv)) return rv;
693 : }
694 :
695 0 : if (realKid && !realKidAlreadyExisted) {
696 : // Potentially remember the index of this element as the
697 : // first element that we've generated.
698 0 : if (aContainer && !*aContainer) {
699 0 : *aContainer = aRealNode;
700 0 : NS_ADDREF(*aContainer);
701 :
702 0 : PRUint32 indx = aRealNode->GetChildCount();
703 :
704 : // Since we haven't inserted any content yet, our new
705 : // index in the container will be the current count of
706 : // elements in the container.
707 0 : *aNewIndexInContainer = indx;
708 : }
709 :
710 : // Remember the template kid from which we created the
711 : // real kid. This allows us to sync back up with the
712 : // template to incrementally build content.
713 0 : mTemplateMap.Put(realKid, tmplKid);
714 :
715 0 : rv = CopyAttributesToElement(tmplKid, realKid, aChild, false);
716 0 : if (NS_FAILED(rv)) return rv;
717 :
718 : // Add any persistent attributes
719 0 : if (isGenerationElement) {
720 0 : rv = AddPersistentAttributes(tmplKid, aChild, realKid);
721 0 : if (NS_FAILED(rv)) return rv;
722 : }
723 :
724 : // the unique content recurses up above. Also, don't recurse if
725 : // this is a self reference (a reference to the same resource)
726 : // or we'll end up regenerating the same content.
727 0 : if (!aIsSelfReference && !isUnique) {
728 : // this call creates the content inside the generation node,
729 : // for example the label below:
730 : // <vbox uri="?">
731 : // <label value="?title"/>
732 : // </vbox>
733 : rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, false,
734 : false, aChild, false, aMatch,
735 : nsnull /* don't care */,
736 0 : nsnull /* don't care */);
737 0 : if (NS_FAILED(rv)) return rv;
738 :
739 0 : if (isGenerationElement) {
740 : // build the next level of children
741 : rv = CreateContainerContents(realKid, aChild, false,
742 0 : false, false);
743 0 : if (NS_FAILED(rv)) return rv;
744 : }
745 : }
746 :
747 : // We'll _already_ have added the unique elements; but if
748 : // it's -not- unique, then use the XUL sort service now to
749 : // append the element to the content model.
750 0 : if (! isUnique) {
751 0 : rv = NS_ERROR_UNEXPECTED;
752 :
753 0 : if (isGenerationElement)
754 0 : rv = InsertSortedNode(aRealNode, realKid, aChild, aNotify);
755 :
756 0 : if (NS_FAILED(rv)) {
757 0 : rv = aRealNode->AppendChildTo(realKid, aNotify);
758 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "unable to insert element");
759 : }
760 : }
761 : }
762 : }
763 :
764 0 : return NS_OK;
765 : }
766 :
767 : nsresult
768 0 : nsXULContentBuilder::CopyAttributesToElement(nsIContent* aTemplateNode,
769 : nsIContent* aRealNode,
770 : nsIXULTemplateResult* aResult,
771 : bool aNotify)
772 : {
773 : nsresult rv;
774 :
775 : // Copy all attributes from the template to the new element
776 0 : PRUint32 numAttribs = aTemplateNode->GetAttrCount();
777 :
778 0 : for (PRUint32 attr = 0; attr < numAttribs; attr++) {
779 0 : const nsAttrName* name = aTemplateNode->GetAttrNameAt(attr);
780 0 : PRInt32 attribNameSpaceID = name->NamespaceID();
781 : // Hold a strong reference here so that the atom doesn't go away
782 : // during UnsetAttr.
783 0 : nsCOMPtr<nsIAtom> attribName = name->LocalName();
784 :
785 : // XXXndeakin ignore namespaces until bug 321182 is fixed
786 0 : if (attribName != nsGkAtoms::id && attribName != nsGkAtoms::uri) {
787 : // Create a buffer here, because there's a chance that an
788 : // attribute in the template is going to be an RDF URI, which is
789 : // usually longish.
790 : PRUnichar attrbuf[128];
791 0 : nsFixedString attribValue(attrbuf, ArrayLength(attrbuf), 0);
792 0 : aTemplateNode->GetAttr(attribNameSpaceID, attribName, attribValue);
793 0 : if (!attribValue.IsEmpty()) {
794 0 : nsAutoString value;
795 0 : rv = SubstituteText(aResult, attribValue, value);
796 0 : if (NS_FAILED(rv))
797 0 : return rv;
798 :
799 : // if the string is empty after substitutions, remove the
800 : // attribute
801 0 : if (!value.IsEmpty()) {
802 : rv = aRealNode->SetAttr(attribNameSpaceID,
803 : attribName,
804 : name->GetPrefix(),
805 : value,
806 0 : aNotify);
807 : }
808 : else {
809 : rv = aRealNode->UnsetAttr(attribNameSpaceID,
810 : attribName,
811 0 : aNotify);
812 : }
813 :
814 0 : if (NS_FAILED(rv))
815 0 : return rv;
816 : }
817 : }
818 : }
819 :
820 0 : return NS_OK;
821 : }
822 :
823 : nsresult
824 0 : nsXULContentBuilder::AddPersistentAttributes(nsIContent* aTemplateNode,
825 : nsIXULTemplateResult* aResult,
826 : nsIContent* aRealNode)
827 : {
828 0 : if (!mRoot)
829 0 : return NS_OK;
830 :
831 0 : nsCOMPtr<nsIRDFResource> resource;
832 0 : nsresult rv = GetResultResource(aResult, getter_AddRefs(resource));
833 0 : NS_ENSURE_SUCCESS(rv, rv);
834 :
835 0 : nsAutoString attribute, persist;
836 0 : aTemplateNode->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
837 :
838 0 : while (!persist.IsEmpty()) {
839 0 : attribute.Truncate();
840 :
841 0 : PRInt32 offset = persist.FindCharInSet(" ,");
842 0 : if (offset > 0) {
843 0 : persist.Left(attribute, offset);
844 0 : persist.Cut(0, offset + 1);
845 : }
846 : else {
847 0 : attribute = persist;
848 0 : persist.Truncate();
849 : }
850 :
851 0 : attribute.Trim(" ");
852 :
853 0 : if (attribute.IsEmpty())
854 0 : break;
855 :
856 0 : nsCOMPtr<nsIAtom> tag;
857 : PRInt32 nameSpaceID;
858 :
859 : nsCOMPtr<nsINodeInfo> ni =
860 0 : aTemplateNode->GetExistingAttrNameFromQName(attribute);
861 0 : if (ni) {
862 0 : tag = ni->NameAtom();
863 0 : nameSpaceID = ni->NamespaceID();
864 : }
865 : else {
866 0 : tag = do_GetAtom(attribute);
867 0 : NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
868 :
869 0 : nameSpaceID = kNameSpaceID_None;
870 : }
871 :
872 0 : nsCOMPtr<nsIRDFResource> property;
873 0 : rv = nsXULContentUtils::GetResource(nameSpaceID, tag, getter_AddRefs(property));
874 0 : NS_ENSURE_SUCCESS(rv, rv);
875 :
876 0 : nsCOMPtr<nsIRDFNode> target;
877 0 : rv = mDB->GetTarget(resource, property, true, getter_AddRefs(target));
878 0 : NS_ENSURE_SUCCESS(rv, rv);
879 :
880 0 : if (! target)
881 0 : continue;
882 :
883 0 : nsCOMPtr<nsIRDFLiteral> value = do_QueryInterface(target);
884 0 : NS_ASSERTION(value != nsnull, "unable to stomach that sort of node");
885 0 : if (! value)
886 0 : continue;
887 :
888 : const PRUnichar* valueStr;
889 0 : rv = value->GetValueConst(&valueStr);
890 0 : NS_ENSURE_SUCCESS(rv, rv);
891 :
892 0 : rv = aRealNode->SetAttr(nameSpaceID, tag, nsDependentString(valueStr),
893 0 : false);
894 0 : NS_ENSURE_SUCCESS(rv, rv);
895 : }
896 :
897 0 : return NS_OK;
898 : }
899 :
900 : nsresult
901 0 : nsXULContentBuilder::SynchronizeUsingTemplate(nsIContent* aTemplateNode,
902 : nsIContent* aRealElement,
903 : nsIXULTemplateResult* aResult)
904 : {
905 : // check all attributes on the template node; if they reference a resource,
906 : // update the equivalent attribute on the content node
907 : nsresult rv;
908 0 : rv = CopyAttributesToElement(aTemplateNode, aRealElement, aResult, true);
909 0 : if (NS_FAILED(rv))
910 0 : return rv;
911 :
912 0 : PRUint32 count = aTemplateNode->GetChildCount();
913 :
914 0 : for (PRUint32 loop = 0; loop < count; ++loop) {
915 0 : nsIContent *tmplKid = aTemplateNode->GetChildAt(loop);
916 :
917 0 : if (! tmplKid)
918 0 : break;
919 :
920 0 : nsIContent *realKid = aRealElement->GetChildAt(loop);
921 0 : if (! realKid)
922 0 : break;
923 :
924 : // check for text nodes and update them accordingly.
925 : // This code is similar to that in BuildContentFromTemplate
926 0 : if (tmplKid->NodeInfo()->Equals(nsGkAtoms::textnode,
927 0 : kNameSpaceID_XUL)) {
928 : PRUnichar attrbuf[128];
929 0 : nsFixedString attrValue(attrbuf, ArrayLength(attrbuf), 0);
930 0 : tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
931 0 : if (!attrValue.IsEmpty()) {
932 0 : nsAutoString value;
933 0 : rv = SubstituteText(aResult, attrValue, value);
934 0 : if (NS_FAILED(rv)) return rv;
935 0 : realKid->SetText(value, true);
936 : }
937 : }
938 :
939 0 : rv = SynchronizeUsingTemplate(tmplKid, realKid, aResult);
940 0 : if (NS_FAILED(rv)) return rv;
941 : }
942 :
943 0 : return NS_OK;
944 : }
945 :
946 : nsresult
947 0 : nsXULContentBuilder::RemoveMember(nsIContent* aContent)
948 : {
949 0 : nsCOMPtr<nsIContent> parent = aContent->GetParent();
950 0 : if (parent) {
951 0 : PRInt32 pos = parent->IndexOf(aContent);
952 :
953 0 : NS_ASSERTION(pos >= 0, "parent doesn't think this child has an index");
954 0 : if (pos < 0) return NS_OK;
955 :
956 : // Note: RemoveChildAt sets |child|'s document to null so that
957 : // it'll get knocked out of the XUL doc's resource-to-element
958 : // map.
959 0 : nsresult rv = parent->RemoveChildAt(pos, true);
960 0 : if (NS_FAILED(rv)) return rv;
961 : }
962 :
963 : // Remove from the content support map.
964 0 : mContentSupportMap.Remove(aContent);
965 :
966 : // Remove from the template map
967 0 : mTemplateMap.Remove(aContent);
968 :
969 0 : return NS_OK;
970 : }
971 :
972 : nsresult
973 0 : nsXULContentBuilder::CreateTemplateAndContainerContents(nsIContent* aElement,
974 : bool aForceCreation)
975 : {
976 : // Generate both 1) the template content for the current element,
977 : // and 2) recursive subcontent (if the current element refers to a
978 : // container result).
979 :
980 0 : PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
981 : ("nsXULContentBuilder::CreateTemplateAndContainerContents start - flags: %d",
982 : mFlags));
983 :
984 0 : if (! mQueryProcessor)
985 0 : return NS_OK;
986 :
987 : // for the root element, get the ref attribute and generate content
988 0 : if (aElement == mRoot) {
989 0 : if (! mRootResult) {
990 0 : nsAutoString ref;
991 0 : mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, ref);
992 :
993 0 : if (! ref.IsEmpty()) {
994 0 : nsresult rv = mQueryProcessor->TranslateRef(mDataSource, ref,
995 0 : getter_AddRefs(mRootResult));
996 0 : if (NS_FAILED(rv))
997 0 : return rv;
998 : }
999 : }
1000 :
1001 0 : if (mRootResult) {
1002 : CreateContainerContents(aElement, mRootResult, aForceCreation,
1003 0 : false, true);
1004 : }
1005 : }
1006 0 : else if (!(mFlags & eDontRecurse)) {
1007 : // The content map will contain the generation elements (the ones that
1008 : // are given ids) and only those elements, so get the reference point
1009 : // from the corresponding match.
1010 0 : nsTemplateMatch *match = nsnull;
1011 0 : if (mContentSupportMap.Get(aElement, &match))
1012 : CreateContainerContents(aElement, match->mResult, aForceCreation,
1013 0 : false, true);
1014 : }
1015 :
1016 0 : PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
1017 : ("nsXULContentBuilder::CreateTemplateAndContainerContents end"));
1018 :
1019 0 : return NS_OK;
1020 : }
1021 :
1022 : nsresult
1023 0 : nsXULContentBuilder::CreateContainerContents(nsIContent* aElement,
1024 : nsIXULTemplateResult* aResult,
1025 : bool aForceCreation,
1026 : bool aNotify,
1027 : bool aNotifyAtEnd)
1028 : {
1029 0 : if (!aForceCreation && !IsOpen(aElement))
1030 0 : return NS_OK;
1031 :
1032 : // don't generate children if recursion or child processing isn't allowed
1033 0 : if (aResult != mRootResult) {
1034 0 : if (mFlags & eDontRecurse)
1035 0 : return NS_OK;
1036 :
1037 : bool mayProcessChildren;
1038 0 : nsresult rv = aResult->GetMayProcessChildren(&mayProcessChildren);
1039 0 : if (NS_FAILED(rv) || !mayProcessChildren)
1040 0 : return rv;
1041 : }
1042 :
1043 0 : nsCOMPtr<nsIRDFResource> refResource;
1044 0 : GetResultResource(aResult, getter_AddRefs(refResource));
1045 0 : if (! refResource)
1046 0 : return NS_ERROR_FAILURE;
1047 :
1048 : // Avoid re-entrant builds for the same resource.
1049 0 : if (IsActivated(refResource))
1050 0 : return NS_OK;
1051 :
1052 0 : ActivationEntry entry(refResource, &mTop);
1053 :
1054 : // Compile the rules now, if they haven't been already.
1055 0 : if (! mQueriesCompiled) {
1056 0 : nsresult rv = CompileQueries();
1057 0 : if (NS_FAILED(rv))
1058 0 : return rv;
1059 : }
1060 :
1061 0 : if (mQuerySets.Length() == 0)
1062 0 : return NS_OK;
1063 :
1064 : // See if the element's templates contents have been generated:
1065 : // this prevents a re-entrant call from triggering another
1066 : // generation.
1067 0 : nsXULElement *xulcontent = nsXULElement::FromContent(aElement);
1068 0 : if (xulcontent) {
1069 0 : if (xulcontent->GetTemplateGenerated())
1070 0 : return NS_OK;
1071 :
1072 : // Now mark the element's contents as being generated so that
1073 : // any re-entrant calls don't trigger an infinite recursion.
1074 0 : xulcontent->SetTemplateGenerated();
1075 : }
1076 :
1077 0 : PRInt32 newIndexInContainer = -1;
1078 0 : nsIContent* container = nsnull;
1079 :
1080 0 : PRInt32 querySetCount = mQuerySets.Length();
1081 :
1082 0 : for (PRInt32 r = 0; r < querySetCount; r++) {
1083 0 : nsTemplateQuerySet* queryset = mQuerySets[r];
1084 :
1085 0 : nsIAtom* tag = queryset->GetTag();
1086 0 : if (tag && tag != aElement->Tag())
1087 0 : continue;
1088 :
1089 : CreateContainerContentsForQuerySet(aElement, aResult, aNotify, queryset,
1090 0 : &container, &newIndexInContainer);
1091 : }
1092 :
1093 0 : if (aNotifyAtEnd && container) {
1094 0 : MOZ_AUTO_DOC_UPDATE(container->GetCurrentDoc(), UPDATE_CONTENT_MODEL,
1095 : true);
1096 : nsNodeUtils::ContentAppended(container,
1097 0 : container->GetChildAt(newIndexInContainer),
1098 0 : newIndexInContainer);
1099 : }
1100 :
1101 0 : NS_IF_RELEASE(container);
1102 :
1103 0 : return NS_OK;
1104 : }
1105 :
1106 : nsresult
1107 0 : nsXULContentBuilder::CreateContainerContentsForQuerySet(nsIContent* aElement,
1108 : nsIXULTemplateResult* aResult,
1109 : bool aNotify,
1110 : nsTemplateQuerySet* aQuerySet,
1111 : nsIContent** aContainer,
1112 : PRInt32* aNewIndexInContainer)
1113 : {
1114 : #ifdef PR_LOGGING
1115 0 : if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) {
1116 0 : nsAutoString id;
1117 0 : aResult->GetId(id);
1118 0 : PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
1119 : ("nsXULContentBuilder::CreateContainerContentsForQuerySet start for ref %s\n",
1120 : NS_ConvertUTF16toUTF8(id).get()));
1121 : }
1122 : #endif
1123 :
1124 0 : if (! mQueryProcessor)
1125 0 : return NS_OK;
1126 :
1127 0 : nsCOMPtr<nsISimpleEnumerator> results;
1128 0 : nsresult rv = mQueryProcessor->GenerateResults(mDataSource, aResult,
1129 : aQuerySet->mCompiledQuery,
1130 0 : getter_AddRefs(results));
1131 0 : if (NS_FAILED(rv) || !results)
1132 0 : return rv;
1133 :
1134 : bool hasMoreResults;
1135 0 : rv = results->HasMoreElements(&hasMoreResults);
1136 :
1137 0 : for (; NS_SUCCEEDED(rv) && hasMoreResults;
1138 0 : rv = results->HasMoreElements(&hasMoreResults)) {
1139 0 : nsCOMPtr<nsISupports> nr;
1140 0 : rv = results->GetNext(getter_AddRefs(nr));
1141 0 : if (NS_FAILED(rv))
1142 0 : return rv;
1143 :
1144 0 : nsCOMPtr<nsIXULTemplateResult> nextresult = do_QueryInterface(nr);
1145 0 : if (!nextresult)
1146 0 : return NS_ERROR_UNEXPECTED;
1147 :
1148 0 : nsCOMPtr<nsIRDFResource> resultid;
1149 0 : rv = GetResultResource(nextresult, getter_AddRefs(resultid));
1150 0 : if (NS_FAILED(rv))
1151 0 : return rv;
1152 :
1153 0 : if (!resultid)
1154 0 : continue;
1155 :
1156 : nsTemplateMatch *newmatch =
1157 0 : nsTemplateMatch::Create(mPool, aQuerySet->Priority(),
1158 0 : nextresult, aElement);
1159 0 : if (!newmatch)
1160 0 : return NS_ERROR_OUT_OF_MEMORY;
1161 :
1162 : // check if there is already an existing match. If so, a previous
1163 : // query already generated content so the match is just added to the
1164 : // end of the set of matches.
1165 :
1166 0 : bool generateContent = true;
1167 :
1168 0 : nsTemplateMatch* prevmatch = nsnull;
1169 0 : nsTemplateMatch* existingmatch = nsnull;
1170 0 : nsTemplateMatch* removematch = nsnull;
1171 0 : if (mMatchMap.Get(resultid, &existingmatch)){
1172 : // check if there is an existing match that matched a rule
1173 0 : while (existingmatch) {
1174 : // break out once we've reached a query in the list with a
1175 : // higher priority, as the new match list is sorted by
1176 : // priority, and the new match should be inserted here
1177 0 : PRInt32 priority = existingmatch->QuerySetPriority();
1178 0 : if (priority > aQuerySet->Priority())
1179 0 : break;
1180 :
1181 : // skip over non-matching containers
1182 0 : if (existingmatch->GetContainer() == aElement) {
1183 : // if the same priority is already found, replace it. This can happen
1184 : // when a container is removed and readded
1185 0 : if (priority == aQuerySet->Priority()) {
1186 0 : removematch = existingmatch;
1187 0 : break;
1188 : }
1189 :
1190 0 : if (existingmatch->IsActive())
1191 0 : generateContent = false;
1192 : }
1193 :
1194 0 : prevmatch = existingmatch;
1195 0 : existingmatch = existingmatch->mNext;
1196 : }
1197 : }
1198 :
1199 0 : if (removematch) {
1200 : // remove the generated content for the existing match
1201 0 : rv = ReplaceMatch(removematch->mResult, nsnull, nsnull, aElement);
1202 0 : if (NS_FAILED(rv))
1203 0 : return rv;
1204 :
1205 0 : if (mFlags & eLoggingEnabled)
1206 0 : OutputMatchToLog(resultid, removematch, false);
1207 : }
1208 :
1209 0 : if (generateContent) {
1210 : // find the rule that matches. If none match, the content does not
1211 : // need to be generated
1212 :
1213 : PRInt16 ruleindex;
1214 0 : nsTemplateRule* matchedrule = nsnull;
1215 : rv = DetermineMatchedRule(aElement, nextresult, aQuerySet,
1216 0 : &matchedrule, &ruleindex);
1217 0 : if (NS_FAILED(rv)) {
1218 0 : nsTemplateMatch::Destroy(mPool, newmatch, false);
1219 0 : return rv;
1220 : }
1221 :
1222 0 : if (matchedrule) {
1223 : rv = newmatch->RuleMatched(aQuerySet, matchedrule,
1224 0 : ruleindex, nextresult);
1225 0 : if (NS_FAILED(rv)) {
1226 0 : nsTemplateMatch::Destroy(mPool, newmatch, false);
1227 0 : return rv;
1228 : }
1229 :
1230 : // Grab the template node
1231 0 : nsCOMPtr<nsIContent> action = matchedrule->GetAction();
1232 : BuildContentFromTemplate(action, aElement, aElement, true,
1233 0 : mRefVariable == matchedrule->GetMemberVariable(),
1234 : nextresult, aNotify, newmatch,
1235 0 : aContainer, aNewIndexInContainer);
1236 : }
1237 : }
1238 :
1239 0 : if (mFlags & eLoggingEnabled)
1240 0 : OutputMatchToLog(resultid, newmatch, true);
1241 :
1242 0 : if (prevmatch) {
1243 0 : prevmatch->mNext = newmatch;
1244 : }
1245 0 : else if (!mMatchMap.Put(resultid, newmatch)) {
1246 0 : nsTemplateMatch::Destroy(mPool, newmatch, true);
1247 0 : return NS_ERROR_OUT_OF_MEMORY;
1248 : }
1249 :
1250 0 : if (removematch) {
1251 0 : newmatch->mNext = removematch->mNext;
1252 0 : nsTemplateMatch::Destroy(mPool, removematch, true);
1253 : }
1254 : else {
1255 0 : newmatch->mNext = existingmatch;
1256 : }
1257 : }
1258 :
1259 0 : return rv;
1260 : }
1261 :
1262 : nsresult
1263 0 : nsXULContentBuilder::EnsureElementHasGenericChild(nsIContent* parent,
1264 : PRInt32 nameSpaceID,
1265 : nsIAtom* tag,
1266 : bool aNotify,
1267 : nsIContent** result)
1268 : {
1269 : nsresult rv;
1270 :
1271 0 : rv = nsXULContentUtils::FindChildByTag(parent, nameSpaceID, tag, result);
1272 0 : if (NS_FAILED(rv))
1273 0 : return rv;
1274 :
1275 0 : if (rv == NS_RDF_NO_VALUE) {
1276 : // we need to construct a new child element.
1277 0 : nsCOMPtr<nsIContent> element;
1278 :
1279 0 : rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
1280 0 : if (NS_FAILED(rv))
1281 0 : return rv;
1282 :
1283 : // XXX Note that the notification ensures we won't batch insertions! This could be bad! - Dave
1284 0 : rv = parent->AppendChildTo(element, aNotify);
1285 0 : if (NS_FAILED(rv))
1286 0 : return rv;
1287 :
1288 0 : *result = element;
1289 0 : NS_ADDREF(*result);
1290 0 : return NS_ELEMENT_GOT_CREATED;
1291 : }
1292 : else {
1293 0 : return NS_ELEMENT_WAS_THERE;
1294 : }
1295 : }
1296 :
1297 : bool
1298 0 : nsXULContentBuilder::IsOpen(nsIContent* aElement)
1299 : {
1300 : // Determine if this is a <treeitem> or <menu> element
1301 0 : if (!aElement->IsXUL())
1302 0 : return true;
1303 :
1304 : // XXXhyatt Use the XBL service to obtain a base tag.
1305 0 : nsIAtom *tag = aElement->Tag();
1306 0 : if (tag == nsGkAtoms::menu ||
1307 : tag == nsGkAtoms::menubutton ||
1308 : tag == nsGkAtoms::toolbarbutton ||
1309 : tag == nsGkAtoms::button ||
1310 : tag == nsGkAtoms::treeitem)
1311 : return aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
1312 0 : nsGkAtoms::_true, eCaseMatters);
1313 0 : return true;
1314 : }
1315 :
1316 : nsresult
1317 0 : nsXULContentBuilder::RemoveGeneratedContent(nsIContent* aElement)
1318 : {
1319 : // Keep a queue of "ungenerated" elements that we have to probe
1320 : // for generated content.
1321 0 : nsAutoTArray<nsIContent*, 8> ungenerated;
1322 0 : if (ungenerated.AppendElement(aElement) == nsnull)
1323 0 : return NS_ERROR_OUT_OF_MEMORY;
1324 :
1325 : PRUint32 count;
1326 0 : while (0 != (count = ungenerated.Length())) {
1327 : // Pull the next "ungenerated" element off the queue.
1328 0 : PRUint32 last = count - 1;
1329 0 : nsCOMPtr<nsIContent> element = ungenerated[last];
1330 0 : ungenerated.RemoveElementAt(last);
1331 :
1332 0 : PRUint32 i = element->GetChildCount();
1333 :
1334 0 : while (i-- > 0) {
1335 0 : nsCOMPtr<nsIContent> child = element->GetChildAt(i);
1336 :
1337 : // Optimize for the <template> element, because we *know*
1338 : // it won't have any generated content: there's no reason
1339 : // to even check this subtree.
1340 : // XXX should this check |child| rather than |element|? Otherwise
1341 : // it should be moved outside the inner loop. Bug 297290.
1342 0 : if (element->NodeInfo()->Equals(nsGkAtoms::_template,
1343 0 : kNameSpaceID_XUL) ||
1344 0 : !element->IsElement())
1345 0 : continue;
1346 :
1347 : // If the element is in the template map, then we
1348 : // assume it's been generated and nuke it.
1349 0 : nsCOMPtr<nsIContent> tmpl;
1350 0 : mTemplateMap.GetTemplateFor(child, getter_AddRefs(tmpl));
1351 :
1352 0 : if (! tmpl) {
1353 : // No 'template' attribute, so this must not have been
1354 : // generated. We'll need to examine its kids.
1355 0 : if (ungenerated.AppendElement(child) == nsnull)
1356 0 : return NS_ERROR_OUT_OF_MEMORY;
1357 0 : continue;
1358 : }
1359 :
1360 : // If we get here, it's "generated". Bye bye!
1361 0 : element->RemoveChildAt(i, true);
1362 :
1363 : // Remove this and any children from the content support map.
1364 0 : mContentSupportMap.Remove(child);
1365 :
1366 : // Remove from the template map
1367 0 : mTemplateMap.Remove(child);
1368 : }
1369 : }
1370 :
1371 0 : return NS_OK;
1372 : }
1373 :
1374 : nsresult
1375 0 : nsXULContentBuilder::GetElementsForResult(nsIXULTemplateResult* aResult,
1376 : nsCOMArray<nsIContent>& aElements)
1377 : {
1378 : // if the root has been removed from the document, just return
1379 : // since there won't be any generated content any more
1380 0 : nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetDocument());
1381 0 : if (! xuldoc)
1382 0 : return NS_OK;
1383 :
1384 0 : nsAutoString id;
1385 0 : aResult->GetId(id);
1386 :
1387 0 : xuldoc->GetElementsForID(id, aElements);
1388 :
1389 0 : return NS_OK;
1390 : }
1391 :
1392 : nsresult
1393 0 : nsXULContentBuilder::CreateElement(PRInt32 aNameSpaceID,
1394 : nsIAtom* aTag,
1395 : nsIContent** aResult)
1396 : {
1397 0 : nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
1398 0 : NS_ASSERTION(doc != nsnull, "not initialized");
1399 0 : if (! doc)
1400 0 : return NS_ERROR_NOT_INITIALIZED;
1401 :
1402 0 : nsCOMPtr<nsIContent> result;
1403 : nsCOMPtr<nsINodeInfo> nodeInfo =
1404 : doc->NodeInfoManager()->GetNodeInfo(aTag, nsnull, aNameSpaceID,
1405 0 : nsIDOMNode::ELEMENT_NODE);
1406 :
1407 0 : nsresult rv = NS_NewElement(getter_AddRefs(result), nodeInfo.forget(),
1408 0 : NOT_FROM_PARSER);
1409 0 : if (NS_FAILED(rv))
1410 0 : return rv;
1411 :
1412 0 : result.forget(aResult);
1413 0 : return NS_OK;
1414 : }
1415 :
1416 : nsresult
1417 0 : nsXULContentBuilder::SetContainerAttrs(nsIContent *aElement,
1418 : nsIXULTemplateResult* aResult,
1419 : bool aIgnoreNonContainers,
1420 : bool aNotify)
1421 : {
1422 0 : NS_PRECONDITION(aResult != nsnull, "null ptr");
1423 0 : if (! aResult)
1424 0 : return NS_ERROR_NULL_POINTER;
1425 :
1426 : bool iscontainer;
1427 0 : aResult->GetIsContainer(&iscontainer);
1428 :
1429 0 : if (aIgnoreNonContainers && !iscontainer)
1430 0 : return NS_OK;
1431 :
1432 0 : NS_NAMED_LITERAL_STRING(true_, "true");
1433 0 : NS_NAMED_LITERAL_STRING(false_, "false");
1434 :
1435 : const nsAString& newcontainer =
1436 0 : iscontainer ? true_ : false_;
1437 :
1438 : aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::container,
1439 0 : newcontainer, aNotify);
1440 :
1441 0 : if (iscontainer && !(mFlags & eDontTestEmpty)) {
1442 : bool isempty;
1443 0 : aResult->GetIsEmpty(&isempty);
1444 :
1445 : const nsAString& newempty =
1446 0 : (iscontainer && isempty) ? true_ : false_;
1447 :
1448 : aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::empty,
1449 0 : newempty, aNotify);
1450 : }
1451 :
1452 0 : return NS_OK;
1453 : }
1454 :
1455 :
1456 : //----------------------------------------------------------------------
1457 : //
1458 : // nsIXULTemplateBuilder methods
1459 : //
1460 :
1461 : NS_IMETHODIMP
1462 0 : nsXULContentBuilder::CreateContents(nsIContent* aElement, bool aForceCreation)
1463 : {
1464 0 : NS_PRECONDITION(aElement != nsnull, "null ptr");
1465 0 : if (! aElement)
1466 0 : return NS_ERROR_NULL_POINTER;
1467 :
1468 : // don't build contents for closed elements. aForceCreation will be true
1469 : // when a menu is about to be opened, so the content should be built anyway.
1470 0 : if (!aForceCreation && !IsOpen(aElement))
1471 0 : return NS_OK;
1472 :
1473 0 : return CreateTemplateAndContainerContents(aElement, aForceCreation);
1474 : }
1475 :
1476 : NS_IMETHODIMP
1477 0 : nsXULContentBuilder::HasGeneratedContent(nsIRDFResource* aResource,
1478 : nsIAtom* aTag,
1479 : bool* aGenerated)
1480 : {
1481 0 : *aGenerated = false;
1482 0 : NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
1483 0 : NS_ENSURE_STATE(mRootResult);
1484 :
1485 0 : nsCOMPtr<nsIRDFResource> rootresource;
1486 0 : nsresult rv = mRootResult->GetResource(getter_AddRefs(rootresource));
1487 0 : if (NS_FAILED(rv))
1488 0 : return rv;
1489 :
1490 : // the root resource is always acceptable
1491 0 : if (aResource == rootresource) {
1492 0 : if (!aTag || mRoot->Tag() == aTag)
1493 0 : *aGenerated = true;
1494 : }
1495 : else {
1496 : const char* uri;
1497 0 : aResource->GetValueConst(&uri);
1498 :
1499 0 : NS_ConvertUTF8toUTF16 refID(uri);
1500 :
1501 : // just return if the node is no longer in a document
1502 0 : nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetDocument());
1503 0 : if (! xuldoc)
1504 0 : return NS_OK;
1505 :
1506 0 : nsCOMArray<nsIContent> elements;
1507 0 : xuldoc->GetElementsForID(refID, elements);
1508 :
1509 0 : PRUint32 cnt = elements.Count();
1510 :
1511 0 : for (PRInt32 i = PRInt32(cnt) - 1; i >= 0; --i) {
1512 0 : nsCOMPtr<nsIContent> content = elements.SafeObjectAt(i);
1513 :
1514 0 : do {
1515 : nsTemplateMatch* match;
1516 0 : if (content == mRoot || mContentSupportMap.Get(content, &match)) {
1517 : // If we've got a tag, check it to ensure we're consistent.
1518 0 : if (!aTag || content->Tag() == aTag) {
1519 0 : *aGenerated = true;
1520 0 : return NS_OK;
1521 : }
1522 : }
1523 :
1524 0 : content = content->GetParent();
1525 0 : } while (content);
1526 : }
1527 : }
1528 :
1529 0 : return NS_OK;
1530 : }
1531 :
1532 : NS_IMETHODIMP
1533 0 : nsXULContentBuilder::GetResultForContent(nsIDOMElement* aElement,
1534 : nsIXULTemplateResult** aResult)
1535 : {
1536 0 : NS_ENSURE_ARG_POINTER(aElement);
1537 0 : NS_ENSURE_ARG_POINTER(aResult);
1538 :
1539 0 : nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
1540 0 : if (content == mRoot) {
1541 0 : *aResult = mRootResult;
1542 : }
1543 : else {
1544 0 : nsTemplateMatch *match = nsnull;
1545 0 : if (mContentSupportMap.Get(content, &match))
1546 0 : *aResult = match->mResult;
1547 : else
1548 0 : *aResult = nsnull;
1549 : }
1550 :
1551 0 : NS_IF_ADDREF(*aResult);
1552 0 : return NS_OK;
1553 : }
1554 :
1555 : //----------------------------------------------------------------------
1556 : //
1557 : // nsIDocumentObserver methods
1558 : //
1559 :
1560 : void
1561 0 : nsXULContentBuilder::AttributeChanged(nsIDocument* aDocument,
1562 : Element* aElement,
1563 : PRInt32 aNameSpaceID,
1564 : nsIAtom* aAttribute,
1565 : PRInt32 aModType)
1566 : {
1567 0 : nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1568 :
1569 : // Handle "open" and "close" cases. We do this handling before
1570 : // we've notified the observer, so that content is already created
1571 : // for the frame system to walk.
1572 0 : if (aElement->GetNameSpaceID() == kNameSpaceID_XUL &&
1573 : aAttribute == nsGkAtoms::open) {
1574 : // We're on a XUL tag, and an ``open'' attribute changed.
1575 0 : if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
1576 0 : nsGkAtoms::_true, eCaseMatters))
1577 0 : OpenContainer(aElement);
1578 : else
1579 0 : CloseContainer(aElement);
1580 : }
1581 :
1582 0 : if ((aNameSpaceID == kNameSpaceID_XUL) &&
1583 : ((aAttribute == nsGkAtoms::sort) ||
1584 : (aAttribute == nsGkAtoms::sortDirection) ||
1585 : (aAttribute == nsGkAtoms::sortResource) ||
1586 : (aAttribute == nsGkAtoms::sortResource2)))
1587 0 : mSortState.initialized = false;
1588 :
1589 : // Pass along to the generic template builder.
1590 : nsXULTemplateBuilder::AttributeChanged(aDocument, aElement, aNameSpaceID,
1591 0 : aAttribute, aModType);
1592 0 : }
1593 :
1594 : void
1595 0 : nsXULContentBuilder::NodeWillBeDestroyed(const nsINode* aNode)
1596 : {
1597 0 : nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1598 : // Break circular references
1599 0 : mContentSupportMap.Clear();
1600 :
1601 0 : nsXULTemplateBuilder::NodeWillBeDestroyed(aNode);
1602 0 : }
1603 :
1604 :
1605 : //----------------------------------------------------------------------
1606 : //
1607 : // nsXULTemplateBuilder methods
1608 : //
1609 :
1610 : bool
1611 0 : nsXULContentBuilder::GetInsertionLocations(nsIXULTemplateResult* aResult,
1612 : nsCOMArray<nsIContent>** aLocations)
1613 : {
1614 0 : *aLocations = nsnull;
1615 :
1616 0 : nsAutoString ref;
1617 0 : nsresult rv = aResult->GetBindingFor(mRefVariable, ref);
1618 0 : if (NS_FAILED(rv))
1619 0 : return false;
1620 :
1621 0 : nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetDocument());
1622 0 : if (! xuldoc)
1623 0 : return false;
1624 :
1625 0 : *aLocations = new nsCOMArray<nsIContent>;
1626 0 : NS_ENSURE_TRUE(*aLocations, false);
1627 :
1628 0 : xuldoc->GetElementsForID(ref, **aLocations);
1629 0 : PRUint32 count = (*aLocations)->Count();
1630 :
1631 0 : bool found = false;
1632 :
1633 0 : for (PRUint32 t = 0; t < count; t++) {
1634 0 : nsCOMPtr<nsIContent> content = (*aLocations)->SafeObjectAt(t);
1635 :
1636 : nsTemplateMatch* refmatch;
1637 0 : 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 0 : nsXULElement *xulcontent = nsXULElement::FromContent(content);
1644 0 : if (!xulcontent || xulcontent->GetTemplateGenerated()) {
1645 0 : found = true;
1646 0 : continue;
1647 : }
1648 : }
1649 :
1650 : // clear the item in the list since we don't want to insert there
1651 0 : (*aLocations)->ReplaceObjectAt(nsnull, t);
1652 : }
1653 :
1654 0 : return found;
1655 : }
1656 :
1657 : nsresult
1658 0 : nsXULContentBuilder::ReplaceMatch(nsIXULTemplateResult* aOldResult,
1659 : nsTemplateMatch* aNewMatch,
1660 : nsTemplateRule* aNewMatchRule,
1661 : void *aContext)
1662 :
1663 : {
1664 : nsresult rv;
1665 0 : nsIContent* content = static_cast<nsIContent*>(aContext);
1666 :
1667 : // update the container attributes for the match
1668 0 : if (content) {
1669 0 : nsAutoString ref;
1670 0 : if (aNewMatch)
1671 0 : rv = aNewMatch->mResult->GetBindingFor(mRefVariable, ref);
1672 : else
1673 0 : rv = aOldResult->GetBindingFor(mRefVariable, ref);
1674 0 : if (NS_FAILED(rv))
1675 0 : return rv;
1676 :
1677 0 : if (!ref.IsEmpty()) {
1678 0 : nsCOMPtr<nsIXULTemplateResult> refResult;
1679 0 : rv = GetResultForId(ref, getter_AddRefs(refResult));
1680 0 : if (NS_FAILED(rv))
1681 0 : return rv;
1682 :
1683 0 : if (refResult)
1684 0 : SetContainerAttrs(content, refResult, false, true);
1685 : }
1686 : }
1687 :
1688 0 : if (aOldResult) {
1689 0 : nsCOMArray<nsIContent> elements;
1690 0 : rv = GetElementsForResult(aOldResult, elements);
1691 0 : if (NS_FAILED(rv))
1692 0 : return rv;
1693 :
1694 0 : PRUint32 count = elements.Count();
1695 :
1696 0 : for (PRInt32 e = PRInt32(count) - 1; e >= 0; --e) {
1697 0 : nsCOMPtr<nsIContent> child = elements.SafeObjectAt(e);
1698 :
1699 : nsTemplateMatch* match;
1700 0 : if (mContentSupportMap.Get(child, &match)) {
1701 0 : if (content == match->GetContainer())
1702 0 : RemoveMember(child);
1703 : }
1704 : }
1705 : }
1706 :
1707 0 : if (aNewMatch) {
1708 0 : nsCOMPtr<nsIContent> action = aNewMatchRule->GetAction();
1709 : return BuildContentFromTemplate(action, content, content, true,
1710 0 : mRefVariable == aNewMatchRule->GetMemberVariable(),
1711 : aNewMatch->mResult, true, aNewMatch,
1712 0 : nsnull, nsnull);
1713 : }
1714 :
1715 0 : return NS_OK;
1716 : }
1717 :
1718 :
1719 : nsresult
1720 0 : nsXULContentBuilder::SynchronizeResult(nsIXULTemplateResult* aResult)
1721 : {
1722 0 : nsCOMArray<nsIContent> elements;
1723 0 : GetElementsForResult(aResult, elements);
1724 :
1725 0 : PRUint32 cnt = elements.Count();
1726 :
1727 0 : for (PRInt32 i = PRInt32(cnt) - 1; i >= 0; --i) {
1728 0 : nsCOMPtr<nsIContent> element = elements.SafeObjectAt(i);
1729 :
1730 : nsTemplateMatch* match;
1731 0 : if (! mContentSupportMap.Get(element, &match))
1732 0 : continue;
1733 :
1734 0 : nsCOMPtr<nsIContent> templateNode;
1735 0 : mTemplateMap.GetTemplateFor(element, getter_AddRefs(templateNode));
1736 :
1737 0 : NS_ASSERTION(templateNode, "couldn't find template node for element");
1738 0 : if (! templateNode)
1739 0 : continue;
1740 :
1741 : // this node was created by a XUL template, so update it accordingly
1742 0 : SynchronizeUsingTemplate(templateNode, element, aResult);
1743 : }
1744 :
1745 0 : return NS_OK;
1746 : }
1747 :
1748 : //----------------------------------------------------------------------
1749 : //
1750 : // Implementation methods
1751 : //
1752 :
1753 : nsresult
1754 0 : nsXULContentBuilder::OpenContainer(nsIContent* aElement)
1755 : {
1756 0 : if (aElement != mRoot) {
1757 0 : if (mFlags & eDontRecurse)
1758 0 : return NS_OK;
1759 :
1760 0 : bool rightBuilder = false;
1761 :
1762 0 : nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aElement->GetDocument());
1763 0 : if (! xuldoc)
1764 0 : return NS_OK;
1765 :
1766 : // See if we're responsible for this element
1767 0 : nsIContent* content = aElement;
1768 0 : do {
1769 0 : nsCOMPtr<nsIXULTemplateBuilder> builder;
1770 0 : xuldoc->GetTemplateBuilderFor(content, getter_AddRefs(builder));
1771 0 : if (builder) {
1772 0 : if (builder == this)
1773 0 : rightBuilder = true;
1774 : break;
1775 : }
1776 :
1777 0 : content = content->GetParent();
1778 : } while (content);
1779 :
1780 0 : if (! rightBuilder)
1781 0 : return NS_OK;
1782 : }
1783 :
1784 0 : CreateTemplateAndContainerContents(aElement, false);
1785 :
1786 0 : return NS_OK;
1787 : }
1788 :
1789 : nsresult
1790 0 : nsXULContentBuilder::CloseContainer(nsIContent* aElement)
1791 : {
1792 0 : return NS_OK;
1793 : }
1794 :
1795 : nsresult
1796 0 : nsXULContentBuilder::RebuildAll()
1797 : {
1798 0 : NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
1799 :
1800 : // Bail out early if we are being torn down.
1801 0 : nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
1802 0 : if (!doc)
1803 0 : return NS_OK;
1804 :
1805 0 : if (mQueriesCompiled)
1806 0 : Uninit(false);
1807 :
1808 0 : nsresult rv = CompileQueries();
1809 0 : if (NS_FAILED(rv))
1810 0 : return rv;
1811 :
1812 0 : if (mQuerySets.Length() == 0)
1813 0 : return NS_OK;
1814 :
1815 0 : nsXULElement *xulcontent = nsXULElement::FromContent(mRoot);
1816 0 : if (xulcontent)
1817 0 : xulcontent->ClearTemplateGenerated();
1818 :
1819 : // Now, regenerate both the template- and container-generated
1820 : // contents for the current element...
1821 0 : CreateTemplateAndContainerContents(mRoot, false);
1822 :
1823 0 : return NS_OK;
1824 : }
1825 :
1826 : /**** Sorting Methods ****/
1827 :
1828 : nsresult
1829 0 : nsXULContentBuilder::CompareResultToNode(nsIXULTemplateResult* aResult,
1830 : nsIContent* aContent,
1831 : PRInt32* aSortOrder)
1832 : {
1833 0 : NS_ASSERTION(aSortOrder, "CompareResultToNode: null out param aSortOrder");
1834 :
1835 0 : *aSortOrder = 0;
1836 :
1837 0 : nsTemplateMatch *match = nsnull;
1838 0 : if (!mContentSupportMap.Get(aContent, &match)) {
1839 0 : *aSortOrder = mSortState.sortStaticsLast ? -1 : 1;
1840 0 : return NS_OK;
1841 : }
1842 :
1843 0 : if (!mQueryProcessor)
1844 0 : return NS_OK;
1845 :
1846 0 : if (mSortState.direction == nsSortState_natural) {
1847 : // sort in natural order
1848 0 : nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
1849 : nsnull, mSortState.sortHints,
1850 0 : aSortOrder);
1851 0 : NS_ENSURE_SUCCESS(rv, rv);
1852 : }
1853 : else {
1854 : // iterate over each sort key and compare. If the nodes are equal,
1855 : // continue to compare using the next sort key. If not equal, stop.
1856 0 : PRInt32 length = mSortState.sortKeys.Count();
1857 0 : for (PRInt32 t = 0; t < length; t++) {
1858 0 : nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
1859 : mSortState.sortKeys[t],
1860 0 : mSortState.sortHints, aSortOrder);
1861 0 : NS_ENSURE_SUCCESS(rv, rv);
1862 :
1863 0 : if (*aSortOrder)
1864 0 : break;
1865 : }
1866 : }
1867 :
1868 : // flip the sort order if performing a descending sorting
1869 0 : if (mSortState.direction == nsSortState_descending)
1870 0 : *aSortOrder = -*aSortOrder;
1871 :
1872 0 : return NS_OK;
1873 : }
1874 :
1875 : nsresult
1876 0 : nsXULContentBuilder::InsertSortedNode(nsIContent* aContainer,
1877 : nsIContent* aNode,
1878 : nsIXULTemplateResult* aResult,
1879 : bool aNotify)
1880 : {
1881 : nsresult rv;
1882 :
1883 0 : if (!mSortState.initialized) {
1884 0 : nsAutoString sort, sortDirection, sortHints;
1885 0 : mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
1886 0 : mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, sortDirection);
1887 0 : mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, sortHints);
1888 0 : sortDirection.AppendLiteral(" ");
1889 0 : sortDirection += sortHints;
1890 : rv = XULSortServiceImpl::InitializeSortState(mRoot, aContainer,
1891 0 : sort, sortDirection, &mSortState);
1892 0 : NS_ENSURE_SUCCESS(rv, rv);
1893 : }
1894 :
1895 : // when doing a natural sort, items will typically be sorted according to
1896 : // the order they appear in the datasource. For RDF, cache whether the
1897 : // reference parent is an RDF Seq. That way, the items can be sorted in the
1898 : // order they are in the Seq.
1899 0 : mSortState.isContainerRDFSeq = false;
1900 0 : if (mSortState.direction == nsSortState_natural) {
1901 0 : nsCOMPtr<nsISupports> ref;
1902 0 : nsresult rv = aResult->GetBindingObjectFor(mRefVariable, getter_AddRefs(ref));
1903 0 : NS_ENSURE_SUCCESS(rv, rv);
1904 :
1905 0 : nsCOMPtr<nsIRDFResource> container = do_QueryInterface(ref);
1906 :
1907 0 : if (container) {
1908 0 : rv = gRDFContainerUtils->IsSeq(mDB, container, &mSortState.isContainerRDFSeq);
1909 0 : NS_ENSURE_SUCCESS(rv, rv);
1910 : }
1911 : }
1912 :
1913 0 : bool childAdded = false;
1914 0 : PRUint32 numChildren = aContainer->GetChildCount();
1915 :
1916 0 : if (mSortState.direction != nsSortState_natural ||
1917 : (mSortState.direction == nsSortState_natural && mSortState.isContainerRDFSeq))
1918 : {
1919 : // because numChildren gets modified
1920 0 : PRInt32 realNumChildren = numChildren;
1921 0 : nsIContent *child = nsnull;
1922 :
1923 : // rjc says: determine where static XUL ends and generated XUL/RDF begins
1924 0 : PRInt32 staticCount = 0;
1925 :
1926 0 : nsAutoString staticValue;
1927 0 : aContainer->GetAttr(kNameSpaceID_None, nsGkAtoms::staticHint, staticValue);
1928 0 : if (!staticValue.IsEmpty())
1929 : {
1930 : // found "static" XUL element count hint
1931 0 : PRInt32 strErr = 0;
1932 0 : staticCount = staticValue.ToInteger(&strErr);
1933 0 : if (strErr)
1934 0 : staticCount = 0;
1935 : } else {
1936 : // compute the "static" XUL element count
1937 0 : for (nsIContent* child = aContainer->GetFirstChild();
1938 : child;
1939 0 : child = child->GetNextSibling()) {
1940 :
1941 0 : if (nsContentUtils::HasNonEmptyAttr(child, kNameSpaceID_None,
1942 0 : nsGkAtoms::_template))
1943 0 : break;
1944 : else
1945 0 : ++staticCount;
1946 : }
1947 :
1948 0 : if (mSortState.sortStaticsLast) {
1949 : // indicate that static XUL comes after RDF-generated content by
1950 : // making negative
1951 0 : staticCount = -staticCount;
1952 : }
1953 :
1954 : // save the "static" XUL element count hint
1955 0 : nsAutoString valueStr;
1956 0 : valueStr.AppendInt(staticCount);
1957 0 : aContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::staticHint, valueStr, false);
1958 : }
1959 :
1960 0 : if (staticCount <= 0) {
1961 0 : numChildren += staticCount;
1962 0 : staticCount = 0;
1963 0 : } else if (staticCount > (PRInt32)numChildren) {
1964 0 : staticCount = numChildren;
1965 0 : numChildren -= staticCount;
1966 : }
1967 :
1968 : // figure out where to insert the node when a sort order is being imposed
1969 0 : if (numChildren > 0) {
1970 : nsIContent *temp;
1971 : PRInt32 direction;
1972 :
1973 : // rjc says: The following is an implementation of a fairly optimal
1974 : // binary search insertion sort... with interpolation at either end-point.
1975 :
1976 0 : if (mSortState.lastWasFirst) {
1977 0 : child = aContainer->GetChildAt(staticCount);
1978 0 : temp = child;
1979 0 : rv = CompareResultToNode(aResult, temp, &direction);
1980 0 : if (direction < 0) {
1981 0 : aContainer->InsertChildAt(aNode, staticCount, aNotify);
1982 0 : childAdded = true;
1983 : } else
1984 0 : mSortState.lastWasFirst = false;
1985 0 : } else if (mSortState.lastWasLast) {
1986 0 : child = aContainer->GetChildAt(realNumChildren - 1);
1987 0 : temp = child;
1988 0 : rv = CompareResultToNode(aResult, temp, &direction);
1989 0 : if (direction > 0) {
1990 0 : aContainer->InsertChildAt(aNode, realNumChildren, aNotify);
1991 0 : childAdded = true;
1992 : } else
1993 0 : mSortState.lastWasLast = false;
1994 : }
1995 :
1996 0 : PRInt32 left = staticCount + 1, right = realNumChildren, x;
1997 0 : while (!childAdded && right >= left) {
1998 0 : x = (left + right) / 2;
1999 0 : child = aContainer->GetChildAt(x - 1);
2000 0 : temp = child;
2001 :
2002 0 : rv = CompareResultToNode(aResult, temp, &direction);
2003 0 : if ((x == left && direction < 0) ||
2004 : (x == right && direction >= 0) ||
2005 : left == right)
2006 : {
2007 0 : PRInt32 thePos = (direction > 0 ? x : x - 1);
2008 0 : aContainer->InsertChildAt(aNode, thePos, aNotify);
2009 0 : childAdded = true;
2010 :
2011 0 : mSortState.lastWasFirst = (thePos == staticCount);
2012 0 : mSortState.lastWasLast = (thePos >= realNumChildren);
2013 :
2014 0 : break;
2015 : }
2016 0 : if (direction < 0)
2017 0 : right = x - 1;
2018 : else
2019 0 : left = x + 1;
2020 : }
2021 : }
2022 : }
2023 :
2024 : // if the child hasn't been inserted yet, just add it at the end. Note
2025 : // that an append isn't done as there may be static content afterwards.
2026 0 : if (!childAdded)
2027 0 : aContainer->InsertChildAt(aNode, numChildren, aNotify);
2028 :
2029 0 : return NS_OK;
2030 : }
|