Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / content / xul / templates / src / nsXULTemplateBuilder.cpp
blobbcd7290084f2d140ed659e31d49e972ab3fa9999
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Robert Churchill <rjc@netscape.com>
24 * David Hyatt <hyatt@netscape.com>
25 * Chris Waterson <waterson@netscape.com>
26 * Pierre Phaneuf <pp@ludusdesign.com>
27 * Joe Hewitt <hewitt@netscape.com>
28 * Neil Deakin <enndeakin@sympatico.ca>
29 * Laurent Jouanneau <laurent.jouanneau@disruptive-innovations.com>
31 * Alternatively, the contents of this file may be used under the terms of
32 * either of the GNU General Public License Version 2 or later (the "GPL"),
33 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
34 * in which case the provisions of the GPL or the LGPL are applicable instead
35 * of those above. If you wish to allow use of your version of this file only
36 * under the terms of either the GPL or the LGPL, and not to allow others to
37 * use your version of this file under the terms of the MPL, indicate your
38 * decision by deleting the provisions above and replace them with the notice
39 * and other provisions required by the GPL or the LGPL. If you do not delete
40 * the provisions above, a recipient may use your version of this file under
41 * the terms of any one of the MPL, the GPL or the LGPL.
43 * ***** END LICENSE BLOCK ***** */
47 Builds content from a datasource using the XUL <template> tag.
49 TO DO
51 . Fix ContentTagTest's location in the network construction
53 To turn on logging for this module, set:
55 NSPR_LOG_MODULES nsXULTemplateBuilder:5
59 #include "nsCOMPtr.h"
60 #include "nsCRT.h"
61 #include "nsFixedSizeAllocator.h"
62 #include "nsIContent.h"
63 #include "nsIDOMElement.h"
64 #include "nsIDOMNode.h"
65 #include "nsIDOMDocument.h"
66 #include "nsIDOMXMLDocument.h"
67 #include "nsIPrivateDOMImplementation.h"
68 #include "nsIDOMXULElement.h"
69 #include "nsIDocument.h"
70 #include "nsBindingManager.h"
71 #include "nsIDOMNodeList.h"
72 #include "nsINameSpaceManager.h"
73 #include "nsIObserverService.h"
74 #include "nsIRDFCompositeDataSource.h"
75 #include "nsIRDFInferDataSource.h"
76 #include "nsIRDFContainerUtils.h"
77 #include "nsIXULDocument.h"
78 #include "nsIXULTemplateBuilder.h"
79 #include "nsIXULBuilderListener.h"
80 #include "nsIRDFRemoteDataSource.h"
81 #include "nsIRDFService.h"
82 #include "nsIScriptGlobalObject.h"
83 #include "nsIServiceManager.h"
84 #include "nsISimpleEnumerator.h"
85 #include "nsIMutableArray.h"
86 #include "nsIURL.h"
87 #include "nsIXPConnect.h"
88 #include "nsContentCID.h"
89 #include "nsRDFCID.h"
90 #include "nsXULContentUtils.h"
91 #include "nsString.h"
92 #include "nsVoidArray.h"
93 #include "nsXPIDLString.h"
94 #include "nsWhitespaceTokenizer.h"
95 #include "nsGkAtoms.h"
96 #include "nsXULElement.h"
97 #include "jsapi.h"
98 #include "prlog.h"
99 #include "rdf.h"
100 #include "pldhash.h"
101 #include "plhash.h"
102 #include "nsIDOMClassInfo.h"
103 #include "nsPIDOMWindow.h"
105 #include "nsNetUtil.h"
106 #include "nsXULTemplateBuilder.h"
107 #include "nsXULTemplateQueryProcessorRDF.h"
108 #include "nsXULTemplateQueryProcessorXML.h"
109 #include "nsXULTemplateQueryProcessorStorage.h"
111 //----------------------------------------------------------------------
113 static NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID);
114 static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
116 //----------------------------------------------------------------------
118 // nsXULTemplateBuilder
121 nsrefcnt nsXULTemplateBuilder::gRefCnt = 0;
122 nsIRDFService* nsXULTemplateBuilder::gRDFService;
123 nsIRDFContainerUtils* nsXULTemplateBuilder::gRDFContainerUtils;
124 nsIScriptSecurityManager* nsXULTemplateBuilder::gScriptSecurityManager;
125 nsIPrincipal* nsXULTemplateBuilder::gSystemPrincipal;
126 nsIObserverService* nsXULTemplateBuilder::gObserverService;
128 #ifdef PR_LOGGING
129 PRLogModuleInfo* gXULTemplateLog;
130 #endif
132 #define NS_QUERY_PROCESSOR_CONTRACTID_PREFIX "@mozilla.org/xul/xul-query-processor;1?name="
134 //----------------------------------------------------------------------
136 // nsXULTemplateBuilder methods
139 nsXULTemplateBuilder::nsXULTemplateBuilder(void)
140 : mQueriesCompiled(PR_FALSE),
141 mFlags(0),
142 mTop(nsnull),
143 mObservedDocument(nsnull)
147 static PLDHashOperator
148 DestroyMatchList(nsISupports* aKey, nsTemplateMatch* aMatch, void* aContext)
150 nsFixedSizeAllocator* pool = static_cast<nsFixedSizeAllocator *>(aContext);
152 // delete all the matches in the list
153 while (aMatch) {
154 nsTemplateMatch* next = aMatch->mNext;
155 nsTemplateMatch::Destroy(*pool, aMatch, PR_TRUE);
156 aMatch = next;
159 return PL_DHASH_NEXT;
162 nsXULTemplateBuilder::~nsXULTemplateBuilder(void)
164 if (--gRefCnt == 0) {
165 NS_IF_RELEASE(gRDFService);
166 NS_IF_RELEASE(gRDFContainerUtils);
167 NS_IF_RELEASE(gSystemPrincipal);
168 NS_IF_RELEASE(gScriptSecurityManager);
169 NS_IF_RELEASE(gObserverService);
172 Uninit(PR_TRUE);
176 nsresult
177 nsXULTemplateBuilder::InitGlobals()
179 nsresult rv;
181 if (gRefCnt++ == 0) {
182 // Initialize the global shared reference to the service
183 // manager and get some shared resource objects.
184 rv = CallGetService(kRDFServiceCID, &gRDFService);
185 if (NS_FAILED(rv))
186 return rv;
188 rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils);
189 if (NS_FAILED(rv))
190 return rv;
192 rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID,
193 &gScriptSecurityManager);
194 if (NS_FAILED(rv))
195 return rv;
197 rv = gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal);
198 if (NS_FAILED(rv))
199 return rv;
201 rv = CallGetService(NS_OBSERVERSERVICE_CONTRACTID, &gObserverService);
202 if (NS_FAILED(rv))
203 return rv;
206 #ifdef PR_LOGGING
207 if (! gXULTemplateLog)
208 gXULTemplateLog = PR_NewLogModule("nsXULTemplateBuilder");
209 #endif
211 if (!mMatchMap.IsInitialized() && !mMatchMap.Init())
212 return NS_ERROR_OUT_OF_MEMORY;
214 const size_t bucketsizes[] = { sizeof(nsTemplateMatch) };
215 return mPool.Init("nsXULTemplateBuilder", bucketsizes, 1, 256);
219 void
220 nsXULTemplateBuilder::Uninit(PRBool aIsFinal)
222 if (mObservedDocument && aIsFinal) {
223 gObserverService->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
224 mObservedDocument->RemoveObserver(this);
225 mObservedDocument = nsnull;
228 if (mQueryProcessor)
229 mQueryProcessor->Done();
231 for (PRInt32 q = mQuerySets.Length() - 1; q >= 0; q--) {
232 nsTemplateQuerySet* qs = mQuerySets[q];
233 delete qs;
236 mQuerySets.Clear();
238 mMatchMap.EnumerateRead(DestroyMatchList, &mPool);
239 mMatchMap.Clear();
241 mRootResult = nsnull;
242 mRefVariable = nsnull;
243 mMemberVariable = nsnull;
245 mQueriesCompiled = PR_FALSE;
248 static PLDHashOperator
249 TraverseMatchList(nsISupports* aKey, nsTemplateMatch* aMatch, void* aContext)
251 nsCycleCollectionTraversalCallback *cb =
252 static_cast<nsCycleCollectionTraversalCallback*>(aContext);
254 cb->NoteXPCOMChild(aKey);
255 nsTemplateMatch* match = aMatch;
256 while (match) {
257 cb->NoteXPCOMChild(match->GetContainer());
258 cb->NoteXPCOMChild(match->mResult);
259 match = match->mNext;
262 return PL_DHASH_NEXT;
265 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULTemplateBuilder)
266 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULTemplateBuilder)
267 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDataSource)
268 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDB)
269 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCompDB)
270 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
271 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULTemplateBuilder)
272 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDataSource)
273 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDB)
274 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCompDB)
275 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRoot)
276 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRootResult)
277 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mListeners)
278 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mQueryProcessor)
279 if (tmp->mMatchMap.IsInitialized())
280 tmp->mMatchMap.EnumerateRead(TraverseMatchList, &cb);
282 PRUint32 i, count = tmp->mQuerySets.Length();
283 for (i = 0; i < count; ++i) {
284 nsTemplateQuerySet *set = tmp->mQuerySets[i];
285 cb.NoteXPCOMChild(set->mQueryNode);
286 cb.NoteXPCOMChild(set->mCompiledQuery);
287 PRUint16 j, rulesCount = set->RuleCount();
288 for (j = 0; j < rulesCount; ++j) {
289 set->GetRuleAt(j)->Traverse(cb);
293 tmp->Traverse(cb);
294 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
296 NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsXULTemplateBuilder,
297 nsIXULTemplateBuilder)
298 NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS(nsXULTemplateBuilder,
299 nsIXULTemplateBuilder)
301 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULTemplateBuilder)
302 NS_INTERFACE_MAP_ENTRY(nsIXULTemplateBuilder)
303 NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
304 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
305 NS_INTERFACE_MAP_ENTRY(nsIObserver)
306 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULTemplateBuilder)
307 NS_INTERFACE_MAP_ENTRY_DOM_CLASSINFO(XULTemplateBuilder)
308 NS_INTERFACE_MAP_END
310 //----------------------------------------------------------------------
312 // nsIXULTemplateBuilder methods
315 NS_IMETHODIMP
316 nsXULTemplateBuilder::GetRoot(nsIDOMElement** aResult)
318 if (mRoot) {
319 return CallQueryInterface(mRoot, aResult);
321 *aResult = nsnull;
322 return NS_OK;
325 NS_IMETHODIMP
326 nsXULTemplateBuilder::GetDatasource(nsISupports** aResult)
328 if (mCompDB)
329 NS_ADDREF(*aResult = mCompDB);
330 else
331 NS_IF_ADDREF(*aResult = mDataSource);
332 return NS_OK;
335 NS_IMETHODIMP
336 nsXULTemplateBuilder::SetDatasource(nsISupports* aResult)
338 mDataSource = aResult;
339 mCompDB = do_QueryInterface(mDataSource);
341 return Rebuild();
344 NS_IMETHODIMP
345 nsXULTemplateBuilder::GetDatabase(nsIRDFCompositeDataSource** aResult)
347 NS_IF_ADDREF(*aResult = mCompDB);
348 return NS_OK;
351 NS_IMETHODIMP
352 nsXULTemplateBuilder::GetQueryProcessor(nsIXULTemplateQueryProcessor** aResult)
354 NS_IF_ADDREF(*aResult = mQueryProcessor.get());
355 return NS_OK;
358 NS_IMETHODIMP
359 nsXULTemplateBuilder::AddRuleFilter(nsIDOMNode* aRule, nsIXULTemplateRuleFilter* aFilter)
361 if (!aRule || !aFilter)
362 return NS_ERROR_NULL_POINTER;
364 // a custom rule filter may be added, one for each rule. If a new one is
365 // added, it replaces the old one. Look for the right rule and set its
366 // filter
368 PRInt32 count = mQuerySets.Length();
369 for (PRInt32 q = 0; q < count; q++) {
370 nsTemplateQuerySet* queryset = mQuerySets[q];
372 PRInt16 rulecount = queryset->RuleCount();
373 for (PRInt16 r = 0; r < rulecount; r++) {
374 nsTemplateRule* rule = queryset->GetRuleAt(r);
376 nsCOMPtr<nsIDOMNode> rulenode;
377 rule->GetRuleNode(getter_AddRefs(rulenode));
378 if (aRule == rulenode) {
379 rule->SetRuleFilter(aFilter);
380 return NS_OK;
385 return NS_OK;
388 NS_IMETHODIMP
389 nsXULTemplateBuilder::Rebuild()
391 PRInt32 i;
393 for (i = mListeners.Count() - 1; i >= 0; --i) {
394 mListeners[i]->WillRebuild(this);
397 nsresult rv = RebuildAll();
399 for (i = mListeners.Count() - 1; i >= 0; --i) {
400 mListeners[i]->DidRebuild(this);
403 return rv;
406 NS_IMETHODIMP
407 nsXULTemplateBuilder::Refresh()
409 nsresult rv;
411 if (!mCompDB)
412 return NS_ERROR_FAILURE;
414 nsCOMPtr<nsISimpleEnumerator> dslist;
415 rv = mCompDB->GetDataSources(getter_AddRefs(dslist));
416 NS_ENSURE_SUCCESS(rv, rv);
418 PRBool hasMore;
419 nsCOMPtr<nsISupports> next;
420 nsCOMPtr<nsIRDFRemoteDataSource> rds;
422 while(NS_SUCCEEDED(dslist->HasMoreElements(&hasMore)) && hasMore) {
423 dslist->GetNext(getter_AddRefs(next));
424 if (next && (rds = do_QueryInterface(next))) {
425 rds->Refresh(PR_FALSE);
429 // XXXbsmedberg: it would be kinda nice to install an async nsIRDFXMLSink
430 // observer and call rebuild() once the load is complete. See bug 254600.
432 return NS_OK;
435 NS_IMETHODIMP
436 nsXULTemplateBuilder::Init(nsIContent* aElement)
438 NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
439 mRoot = aElement;
441 nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
442 NS_ASSERTION(doc, "element has no document");
443 if (! doc)
444 return NS_ERROR_UNEXPECTED;
446 PRBool shouldDelay;
447 nsresult rv = LoadDataSources(doc, &shouldDelay);
449 if (NS_SUCCEEDED(rv)) {
450 // Add ourselves as a document observer
451 doc->AddObserver(this);
453 mObservedDocument = doc;
454 gObserverService->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC,
455 PR_FALSE);
458 return rv;
461 NS_IMETHODIMP
462 nsXULTemplateBuilder::CreateContents(nsIContent* aElement, PRBool aForceCreation)
464 return NS_OK;
467 NS_IMETHODIMP
468 nsXULTemplateBuilder::HasGeneratedContent(nsIRDFResource* aResource,
469 nsIAtom* aTag,
470 PRBool* aGenerated)
472 *aGenerated = PR_FALSE;
473 return NS_OK;
476 NS_IMETHODIMP
477 nsXULTemplateBuilder::AddResult(nsIXULTemplateResult* aResult,
478 nsIDOMNode* aQueryNode)
480 NS_ENSURE_ARG_POINTER(aResult);
481 NS_ENSURE_ARG_POINTER(aQueryNode);
483 return UpdateResult(nsnull, aResult, aQueryNode);
486 NS_IMETHODIMP
487 nsXULTemplateBuilder::RemoveResult(nsIXULTemplateResult* aResult)
489 NS_ENSURE_ARG_POINTER(aResult);
491 return UpdateResult(aResult, nsnull, nsnull);
494 NS_IMETHODIMP
495 nsXULTemplateBuilder::ReplaceResult(nsIXULTemplateResult* aOldResult,
496 nsIXULTemplateResult* aNewResult,
497 nsIDOMNode* aQueryNode)
499 NS_ENSURE_ARG_POINTER(aOldResult);
500 NS_ENSURE_ARG_POINTER(aNewResult);
501 NS_ENSURE_ARG_POINTER(aQueryNode);
503 // just remove the old result and then add a new result separately
505 nsresult rv = UpdateResult(aOldResult, nsnull, nsnull);
506 if (NS_FAILED(rv))
507 return rv;
509 return UpdateResult(nsnull, aNewResult, aQueryNode);
512 nsresult
513 nsXULTemplateBuilder::UpdateResult(nsIXULTemplateResult* aOldResult,
514 nsIXULTemplateResult* aNewResult,
515 nsIDOMNode* aQueryNode)
517 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
518 ("nsXULTemplateBuilder::UpdateResult %p %p %p",
519 aOldResult, aNewResult, aQueryNode));
521 // get the containers where content may be inserted. If
522 // GetInsertionLocations returns false, no container has generated
523 // any content yet so new content should not be generated either. This
524 // will be false if the result applies to content that is in a closed menu
525 // or treeitem for example.
527 nsAutoPtr<nsCOMArray<nsIContent> > insertionPoints;
528 PRBool mayReplace = GetInsertionLocations(aOldResult ? aOldResult : aNewResult,
529 getter_Transfers(insertionPoints));
530 if (! mayReplace)
531 return NS_OK;
533 nsresult rv = NS_OK;
535 nsCOMPtr<nsIRDFResource> oldId, newId;
536 nsTemplateQuerySet* queryset = nsnull;
538 if (aOldResult) {
539 rv = GetResultResource(aOldResult, getter_AddRefs(oldId));
540 if (NS_FAILED(rv))
541 return rv;
543 // Ignore re-entrant builds for content that is currently in our
544 // activation stack.
545 if (IsActivated(oldId))
546 return NS_OK;
549 if (aNewResult) {
550 rv = GetResultResource(aNewResult, getter_AddRefs(newId));
551 if (NS_FAILED(rv))
552 return rv;
554 // skip results that don't have ids
555 if (! newId)
556 return NS_OK;
558 // Ignore re-entrant builds for content that is currently in our
559 // activation stack.
560 if (IsActivated(newId))
561 return NS_OK;
563 // look for the queryset associated with the supplied query node
564 nsCOMPtr<nsIContent> querycontent = do_QueryInterface(aQueryNode);
566 PRInt32 count = mQuerySets.Length();
567 for (PRInt32 q = 0; q < count; q++) {
568 nsTemplateQuerySet* qs = mQuerySets[q];
569 if (qs->mQueryNode == querycontent) {
570 queryset = qs;
571 break;
575 if (! queryset)
576 return NS_OK;
579 if (insertionPoints) {
580 // iterate over each insertion point and add or remove the result from
581 // that container
582 PRUint32 count = insertionPoints->Count();
583 for (PRUint32 t = 0; t < count; t++) {
584 nsCOMPtr<nsIContent> insertionPoint = insertionPoints->SafeObjectAt(t);
585 if (insertionPoint) {
586 rv = UpdateResultInContainer(aOldResult, aNewResult, queryset,
587 oldId, newId, insertionPoint);
588 if (NS_FAILED(rv))
589 return rv;
593 else {
594 // The tree builder doesn't use insertion points, so no insertion
595 // points will be set. In this case, just update the one result.
596 rv = UpdateResultInContainer(aOldResult, aNewResult, queryset,
597 oldId, newId, nsnull);
600 return NS_OK;
603 nsresult
604 nsXULTemplateBuilder::UpdateResultInContainer(nsIXULTemplateResult* aOldResult,
605 nsIXULTemplateResult* aNewResult,
606 nsTemplateQuerySet* aQuerySet,
607 nsIRDFResource* aOldId,
608 nsIRDFResource* aNewId,
609 nsIContent* aInsertionPoint)
611 // This method takes a result that no longer applies (aOldResult) and
612 // replaces it with a new result (aNewResult). Either may be null
613 // indicating to just remove a result or add a new one without replacing.
615 // Matches are stored in the hashtable mMatchMap, keyed by result id. If
616 // there is more than one query, or the same id is found in different
617 // containers, the values in the hashtable will be a linked list of all
618 // the matches for that id. The matches are sorted according to the
619 // queries they are associated with. Matches for earlier queries in the
620 // template take priority over matches from later queries. The priority
621 // for a match is determined from the match's QuerySetPriority method.
622 // The first query has a priority 0, and higher numbers are for later
623 // queries with successively higher priorities. Thus, a match takes
624 // precedence if it has a lower priority than another. If there is only
625 // one query or container, then the match doesn't have any linked items.
627 // Matches are nsTemplateMatch objects. They are wrappers around
628 // nsIXULTemplateResult result objects and are created with
629 // nsTemplateMatch::Create below. The aQuerySet argument specifies which
630 // query the match is associated with.
632 // When a result id exists in multiple containers, the match's mContainer
633 // field is set to the container it corresponds to. The aInsertionPoint
634 // argument specifies which container is being updated. Even though they
635 // are stored in the same linked list as other matches of the same id, the
636 // matches for different containers are treated separately. They are only
637 // stored in the same hashtable to avoid a more complex data structure, as
638 // the use of the same id in multiple containers isn't a common occurance.
640 // Only one match with a given id per container is active at a time. When
641 // a match is active, content is generated for it. When a match is
642 // inactive, content is not generated for it. A match becomes active if
643 // another match with the same id and container with a lower priority
644 // isn't already active, and the match has a rule or conditions clause
645 // which evaluates to true. The former is checked by comparing the value
646 // of the QuerySetPriority method of the match with earlier matches. The
647 // latter is checked with the DetermineMatchedRule method.
649 // Naturally, if a match with a lower priority is active, it overrides
650 // the new match, so the new match is hooked up into the match linked
651 // list as inactive, and no content is generated for it. If a match with a
652 // higher priority is active, and the new match's conditions evaluate
653 // to true, then this existing match with the higher priority needs to have
654 // its generated content removed and replaced with the new match's
655 // generated content.
657 // Similar situations apply when removing an existing match. If the match
658 // is active, the existing generated content will need to be removed, and
659 // a match of higher priority that is revealed may become active and need
660 // to have content generated.
662 // Content removal and generation is done by the ReplaceMatch method which
663 // is overridden for the content builder and tree builder to update the
664 // generated output for each type.
666 // The code below handles all of the various cases and ensures that the
667 // match lists are maintained properly.
669 nsresult rv = NS_OK;
670 PRInt16 ruleindex;
671 nsTemplateRule* matchedrule = nsnull;
673 // Indicates that the old match was active and must have its content
674 // removed
675 PRBool oldMatchWasActive = PR_FALSE;
677 // acceptedmatch will be set to a new match that has to have new content
678 // generated for it. If a new match doesn't need to have content
679 // generated, (because for example, a match with a lower priority
680 // already applies), then acceptedmatch will be null, but the match will
681 // be still hooked up into the chain, since it may become active later
682 // as other results are updated.
683 nsTemplateMatch* acceptedmatch = nsnull;
685 // When aOldResult is specified, removematch will be set to the
686 // corresponding match. This match needs to be deleted as it no longer
687 // applies. However, removedmatch will be null when aOldResult is null, or
688 // when no match was found corresponding to aOldResult.
689 nsTemplateMatch* removedmatch = nsnull;
691 // These will be set when aNewResult is specified indicating to add a
692 // result, but will end up replacing an existing match. The former
693 // indicates a match being replaced that was active and had content
694 // generated for it, while the latter indicates a match that wasn't active
695 // and just needs to be deleted. Both may point to different matches. For
696 // example, if the new match becomes active, replacing an inactive match,
697 // the inactive match will need to be deleted. However, if another match
698 // with a higher priority is active, the new match will override it, so
699 // content will need to be generated for the new match and removed for
700 // this existing active match.
701 nsTemplateMatch* replacedmatch = nsnull, * replacedmatchtodelete = nsnull;
703 if (aOldResult) {
704 nsTemplateMatch* firstmatch;
705 if (mMatchMap.Get(aOldId, &firstmatch)) {
706 nsTemplateMatch* oldmatch = firstmatch;
707 nsTemplateMatch* prevmatch = nsnull;
709 // look for the right match if there was more than one
710 while (oldmatch && (oldmatch->mResult != aOldResult)) {
711 prevmatch = oldmatch;
712 oldmatch = oldmatch->mNext;
715 if (oldmatch) {
716 nsTemplateMatch* findmatch = oldmatch->mNext;
718 // Keep a reference so that linked list can be hooked up at
719 // the end in case an error occurs.
720 nsTemplateMatch* nextmatch = findmatch;
722 if (oldmatch->IsActive()) {
723 // Indicate that the old match was active so its content
724 // will be removed later.
725 oldMatchWasActive = PR_TRUE;
727 // The match being removed is the active match, so scan
728 // through the later matches to determine if one should
729 // now become the active match.
730 while (findmatch) {
731 // only other matches with the same container should
732 // now match, leave other containers alone
733 if (findmatch->GetContainer() == aInsertionPoint) {
734 nsTemplateQuerySet* qs =
735 mQuerySets[findmatch->QuerySetPriority()];
737 DetermineMatchedRule(aInsertionPoint, findmatch->mResult,
738 qs, &matchedrule, &ruleindex);
740 if (matchedrule) {
741 rv = findmatch->RuleMatched(qs,
742 matchedrule, ruleindex,
743 findmatch->mResult);
744 if (NS_FAILED(rv))
745 return rv;
747 acceptedmatch = findmatch;
748 break;
752 findmatch = findmatch->mNext;
756 if (oldmatch == firstmatch) {
757 // the match to remove is at the beginning
758 if (oldmatch->mNext) {
759 if (!mMatchMap.Put(aOldId, oldmatch->mNext))
760 return NS_ERROR_OUT_OF_MEMORY;
762 else {
763 mMatchMap.Remove(aOldId);
767 if (prevmatch)
768 prevmatch->mNext = nextmatch;
770 removedmatch = oldmatch;
775 if (aNewResult) {
776 // only allow a result to be inserted into containers with a matching tag
777 nsIAtom* tag = aQuerySet->GetTag();
778 if (aInsertionPoint && tag && tag != aInsertionPoint->Tag())
779 return NS_OK;
781 PRInt32 findpriority = aQuerySet->Priority();
783 nsTemplateMatch *newmatch =
784 nsTemplateMatch::Create(mPool, findpriority,
785 aNewResult, aInsertionPoint);
786 if (!newmatch)
787 return NS_ERROR_OUT_OF_MEMORY;
789 nsTemplateMatch* firstmatch;
790 if (mMatchMap.Get(aNewId, &firstmatch)) {
791 PRBool hasEarlierActiveMatch = PR_FALSE;
793 // Scan through the existing matches to find where the new one
794 // should be inserted. oldmatch will be set to the old match for
795 // the same query and prevmatch will be set to the match before it.
796 nsTemplateMatch* prevmatch = nsnull;
797 nsTemplateMatch* oldmatch = firstmatch;
798 while (oldmatch) {
799 // Break out once we've reached a query in the list with a
800 // lower priority. The new match will be inserted at this
801 // location so that the match list is sorted by priority.
802 PRInt32 priority = oldmatch->QuerySetPriority();
803 if (priority > findpriority) {
804 oldmatch = nsnull;
805 break;
808 // look for matches that belong in the same container
809 if (oldmatch->GetContainer() == aInsertionPoint) {
810 if (priority == findpriority)
811 break;
813 // If a match with a lower priority is active, the new
814 // match can't replace it.
815 if (oldmatch->IsActive())
816 hasEarlierActiveMatch = PR_TRUE;
819 prevmatch = oldmatch;
820 oldmatch = oldmatch->mNext;
823 // At this point, oldmatch will either be null, or set to a match
824 // with the same container and priority. If set, oldmatch will
825 // need to be replaced by newmatch.
827 if (oldmatch)
828 newmatch->mNext = oldmatch->mNext;
829 else if (prevmatch)
830 newmatch->mNext = prevmatch->mNext;
831 else
832 newmatch->mNext = firstmatch;
834 // hasEarlierActiveMatch will be set to true if a match with a
835 // lower priority was found. The new match won't replace it in
836 // this case. If hasEarlierActiveMatch is false, then the new match
837 // may be become active if it matches one of the rules, and will
838 // generate output. It's also possible however, that a match with
839 // the same priority already exists, which means that the new match
840 // will replace the old one. In this case, oldmatch will be set to
841 // the old match. The content for the old match must be removed and
842 // content for the new match generated in its place.
843 if (! hasEarlierActiveMatch) {
844 // If the old match was the active match, set replacedmatch to
845 // indicate that it needs its content removed.
846 if (oldmatch) {
847 if (oldmatch->IsActive())
848 replacedmatch = oldmatch;
849 replacedmatchtodelete = oldmatch;
852 // check if the new result matches the rules
853 rv = DetermineMatchedRule(aInsertionPoint, newmatch->mResult,
854 aQuerySet, &matchedrule, &ruleindex);
855 if (NS_FAILED(rv)) {
856 nsTemplateMatch::Destroy(mPool, newmatch, PR_FALSE);
857 return rv;
860 if (matchedrule) {
861 rv = newmatch->RuleMatched(aQuerySet,
862 matchedrule, ruleindex,
863 newmatch->mResult);
864 if (NS_FAILED(rv)) {
865 nsTemplateMatch::Destroy(mPool, newmatch, PR_FALSE);
866 return rv;
869 // acceptedmatch may have been set in the block handling
870 // aOldResult earlier. If so, we would only get here when
871 // that match has a higher priority than this new match.
872 // As only one match can have content generated for it, it
873 // is OK to set acceptedmatch here to the new match,
874 // ignoring the other one.
875 acceptedmatch = newmatch;
877 // Clear the matched state of the later results for the
878 // same container.
879 nsTemplateMatch* clearmatch = newmatch->mNext;
880 while (clearmatch) {
881 if (clearmatch->GetContainer() == aInsertionPoint &&
882 clearmatch->IsActive()) {
883 clearmatch->SetInactive();
884 // Replacedmatch should be null here. If not, it
885 // means that two matches were active which isn't
886 // a valid state
887 NS_ASSERTION(!replacedmatch,
888 "replaced match already set");
889 replacedmatch = clearmatch;
890 break;
892 clearmatch = clearmatch->mNext;
895 else if (oldmatch && oldmatch->IsActive()) {
896 // The result didn't match the rules, so look for a later
897 // one. However, only do this if the old match was the
898 // active match.
899 newmatch = newmatch->mNext;
900 while (newmatch) {
901 if (newmatch->GetContainer() == aInsertionPoint) {
902 rv = DetermineMatchedRule(aInsertionPoint, newmatch->mResult,
903 aQuerySet, &matchedrule, &ruleindex);
904 if (NS_FAILED(rv)) {
905 nsTemplateMatch::Destroy(mPool, newmatch,
906 PR_FALSE);
907 return rv;
910 if (matchedrule) {
911 rv = newmatch->RuleMatched(aQuerySet,
912 matchedrule, ruleindex,
913 newmatch->mResult);
914 if (NS_FAILED(rv)) {
915 nsTemplateMatch::Destroy(mPool, newmatch,
916 PR_FALSE);
917 return rv;
920 acceptedmatch = newmatch;
921 break;
925 newmatch = newmatch->mNext;
929 // put the match in the map if there isn't a previous match
930 if (! prevmatch) {
931 if (!mMatchMap.Put(aNewId, newmatch)) {
932 // The match may have already matched a rule above, so
933 // HasBeenRemoved should be called to indicate that it
934 // is being removed again.
935 nsTemplateMatch::Destroy(mPool, newmatch, PR_TRUE);
936 return rv;
941 // hook up the match last in case an error occurs
942 if (prevmatch)
943 prevmatch->mNext = newmatch;
945 else {
946 // The id is not used in the hashtable yet so create a new match
947 // and add it to the hashtable.
948 rv = DetermineMatchedRule(aInsertionPoint, aNewResult,
949 aQuerySet, &matchedrule, &ruleindex);
950 if (NS_FAILED(rv)) {
951 nsTemplateMatch::Destroy(mPool, newmatch, PR_FALSE);
952 return rv;
955 if (matchedrule) {
956 rv = newmatch->RuleMatched(aQuerySet, matchedrule,
957 ruleindex, aNewResult);
958 if (NS_FAILED(rv)) {
959 nsTemplateMatch::Destroy(mPool, newmatch, PR_FALSE);
960 return rv;
963 acceptedmatch = newmatch;
966 if (!mMatchMap.Put(aNewId, newmatch)) {
967 nsTemplateMatch::Destroy(mPool, newmatch, PR_TRUE);
968 return NS_ERROR_OUT_OF_MEMORY;
973 // The ReplaceMatch method is builder specific and removes the generated
974 // content for a match.
976 // Remove the content for a match that was active and needs to be replaced.
977 if (replacedmatch)
978 rv = ReplaceMatch(replacedmatch->mResult, nsnull, nsnull,
979 aInsertionPoint);
981 // remove a match that needs to be deleted.
982 if (replacedmatchtodelete)
983 nsTemplateMatch::Destroy(mPool, replacedmatchtodelete, PR_TRUE);
985 // If the old match was active, the content for it needs to be removed.
986 // If the old match was not active, it shouldn't have had any content,
987 // so just pass null to ReplaceMatch. If acceptedmatch was set, then
988 // content needs to be generated for a new match.
989 if (oldMatchWasActive || acceptedmatch)
990 rv = ReplaceMatch(oldMatchWasActive ? aOldResult : nsnull,
991 acceptedmatch, matchedrule, aInsertionPoint);
993 // delete the old match that was replaced
994 if (removedmatch)
995 nsTemplateMatch::Destroy(mPool, removedmatch, PR_TRUE);
997 return rv;
1000 NS_IMETHODIMP
1001 nsXULTemplateBuilder::ResultBindingChanged(nsIXULTemplateResult* aResult)
1003 // A binding update is used when only the values of the bindings have
1004 // changed, so the same rule still applies. Just synchronize the content.
1005 // The new result will have the new values.
1006 NS_ENSURE_ARG_POINTER(aResult);
1008 return SynchronizeResult(aResult);
1011 NS_IMETHODIMP
1012 nsXULTemplateBuilder::GetRootResult(nsIXULTemplateResult** aResult)
1014 *aResult = mRootResult;
1015 NS_IF_ADDREF(*aResult);
1016 return NS_OK;
1019 NS_IMETHODIMP
1020 nsXULTemplateBuilder::GetResultForId(const nsAString& aId,
1021 nsIXULTemplateResult** aResult)
1023 if (aId.IsEmpty())
1024 return NS_ERROR_INVALID_ARG;
1026 nsCOMPtr<nsIRDFResource> resource;
1027 gRDFService->GetUnicodeResource(aId, getter_AddRefs(resource));
1029 *aResult = nsnull;
1031 nsTemplateMatch* match;
1032 if (mMatchMap.Get(resource, &match)) {
1033 // find the active match
1034 while (match) {
1035 if (match->IsActive()) {
1036 *aResult = match->mResult;
1037 NS_IF_ADDREF(*aResult);
1038 break;
1040 match = match->mNext;
1044 return NS_OK;
1047 NS_IMETHODIMP
1048 nsXULTemplateBuilder::GetResultForContent(nsIDOMElement* aContent,
1049 nsIXULTemplateResult** aResult)
1051 *aResult = nsnull;
1052 return NS_OK;
1055 NS_IMETHODIMP
1056 nsXULTemplateBuilder::AddListener(nsIXULBuilderListener* aListener)
1058 NS_ENSURE_ARG(aListener);
1060 if (!mListeners.AppendObject(aListener))
1061 return NS_ERROR_OUT_OF_MEMORY;
1063 return NS_OK;
1066 NS_IMETHODIMP
1067 nsXULTemplateBuilder::RemoveListener(nsIXULBuilderListener* aListener)
1069 NS_ENSURE_ARG(aListener);
1071 mListeners.RemoveObject(aListener);
1073 return NS_OK;
1076 NS_IMETHODIMP
1077 nsXULTemplateBuilder::Observe(nsISupports* aSubject,
1078 const char* aTopic,
1079 const PRUnichar* aData)
1081 // Uuuuber hack to clean up circular references that the cycle collector
1082 // doesn't know about. See bug 394514.
1083 if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) {
1084 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject);
1085 if (window) {
1086 nsCOMPtr<nsIDocument> doc =
1087 do_QueryInterface(window->GetExtantDocument());
1088 if (doc && doc == mObservedDocument)
1089 NodeWillBeDestroyed(doc);
1092 return NS_OK;
1094 //----------------------------------------------------------------------
1096 // nsIDocumentOberver interface
1099 void
1100 nsXULTemplateBuilder::AttributeChanged(nsIDocument* aDocument,
1101 nsIContent* aContent,
1102 PRInt32 aNameSpaceID,
1103 nsIAtom* aAttribute,
1104 PRInt32 aModType,
1105 PRUint32 aStateMask)
1107 if (aContent == mRoot && aNameSpaceID == kNameSpaceID_None) {
1108 // Check for a change to the 'ref' attribute on an atom, in which
1109 // case we may need to nuke and rebuild the entire content model
1110 // beneath the element.
1111 if (aAttribute == nsGkAtoms::ref)
1112 Rebuild();
1114 // Check for a change to the 'datasources' attribute. If so, setup
1115 // mDB by parsing the new value and rebuild.
1116 else if (aAttribute == nsGkAtoms::datasources) {
1117 Uninit(PR_FALSE); // Reset results
1119 PRBool shouldDelay;
1120 LoadDataSources(aDocument, &shouldDelay);
1121 if (!shouldDelay)
1122 Rebuild();
1127 void
1128 nsXULTemplateBuilder::ContentRemoved(nsIDocument* aDocument,
1129 nsIContent* aContainer,
1130 nsIContent* aChild,
1131 PRInt32 aIndexInContainer)
1133 if (mRoot && nsContentUtils::ContentIsDescendantOf(mRoot, aChild)) {
1134 nsRefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this);
1136 if (mQueryProcessor)
1137 mQueryProcessor->Done();
1139 // use false since content is going away anyway
1140 Uninit(PR_FALSE);
1142 aDocument->RemoveObserver(this);
1144 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument);
1145 if (xuldoc)
1146 xuldoc->SetTemplateBuilderFor(mRoot, nsnull);
1148 // clear the template state when removing content so that template
1149 // content will be regenerated again if the content is reinserted
1150 nsXULElement *xulcontent = nsXULElement::FromContent(mRoot);
1151 if (xulcontent)
1152 xulcontent->ClearTemplateGenerated();
1154 mDB = nsnull;
1155 mCompDB = nsnull;
1156 mRoot = nsnull;
1157 mDataSource = nsnull;
1161 void
1162 nsXULTemplateBuilder::NodeWillBeDestroyed(const nsINode* aNode)
1164 // The call to RemoveObserver could release the last reference to
1165 // |this|, so hold another reference.
1166 nsRefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this);
1168 // Break circular references
1169 if (mQueryProcessor)
1170 mQueryProcessor->Done();
1172 mDataSource = nsnull;
1173 mDB = nsnull;
1174 mCompDB = nsnull;
1175 mRoot = nsnull;
1177 Uninit(PR_TRUE);
1183 //----------------------------------------------------------------------
1185 // Implementation methods
1188 nsresult
1189 nsXULTemplateBuilder::LoadDataSources(nsIDocument* aDocument,
1190 PRBool* aShouldDelayBuilding)
1192 NS_PRECONDITION(mRoot != nsnull, "not initialized");
1194 nsresult rv;
1195 PRBool isRDFQuery = PR_FALSE;
1197 // we'll set these again later, after we create a new composite ds
1198 mDB = nsnull;
1199 mCompDB = nsnull;
1200 mDataSource = nsnull;
1202 *aShouldDelayBuilding = PR_FALSE;
1204 nsAutoString datasources;
1205 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::datasources, datasources);
1207 nsAutoString querytype;
1208 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::querytype, querytype);
1210 // create the query processor. The querytype attribute on the root element
1211 // may be used to create one of a specific type.
1213 // XXX should non-chrome be restricted to specific names?
1214 if (querytype.IsEmpty())
1215 querytype.AssignLiteral("rdf");
1217 if (querytype.EqualsLiteral("rdf")) {
1218 isRDFQuery = PR_TRUE;
1219 mQueryProcessor = new nsXULTemplateQueryProcessorRDF();
1220 NS_ENSURE_TRUE(mQueryProcessor, NS_ERROR_OUT_OF_MEMORY);
1222 else if (querytype.EqualsLiteral("xml")) {
1223 mQueryProcessor = new nsXULTemplateQueryProcessorXML();
1224 NS_ENSURE_TRUE(mQueryProcessor, NS_ERROR_OUT_OF_MEMORY);
1226 else if (querytype.EqualsLiteral("storage")) {
1227 mQueryProcessor = new nsXULTemplateQueryProcessorStorage();
1228 NS_ENSURE_TRUE(mQueryProcessor, NS_ERROR_OUT_OF_MEMORY);
1230 else {
1231 nsCAutoString cid(NS_QUERY_PROCESSOR_CONTRACTID_PREFIX);
1232 AppendUTF16toUTF8(querytype, cid);
1233 mQueryProcessor = do_CreateInstance(cid.get(), &rv);
1234 // XXXndeakin log an error here - bug 321169
1235 NS_ENSURE_TRUE(mQueryProcessor, rv);
1238 rv = LoadDataSourceUrls(aDocument, datasources,
1239 isRDFQuery, aShouldDelayBuilding);
1240 NS_ENSURE_SUCCESS(rv, rv);
1242 // Now set the database on the element, so that script writers can
1243 // access it.
1244 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument);
1245 if (xuldoc)
1246 xuldoc->SetTemplateBuilderFor(mRoot, this);
1248 if (!mRoot->IsNodeOfType(nsINode::eXUL)) {
1249 // Hmm. This must be an HTML element. Try to set it as a
1250 // JS property "by hand".
1251 InitHTMLTemplateRoot();
1254 return NS_OK;
1257 nsresult
1258 nsXULTemplateBuilder::LoadDataSourceUrls(nsIDocument* aDocument,
1259 const nsAString& aDataSources,
1260 PRBool aIsRDFQuery,
1261 PRBool* aShouldDelayBuilding)
1263 // Grab the doc's principal...
1264 nsIPrincipal *docPrincipal = aDocument->NodePrincipal();
1266 NS_ASSERTION(docPrincipal == mRoot->NodePrincipal(),
1267 "Principal mismatch? Which one to use?");
1269 PRBool isTrusted = PR_FALSE;
1270 nsresult rv = IsSystemPrincipal(docPrincipal, &isTrusted);
1271 NS_ENSURE_SUCCESS(rv, rv);
1273 // Parse datasources: they are assumed to be a whitespace
1274 // separated list of URIs; e.g.,
1276 // rdf:bookmarks rdf:history http://foo.bar.com/blah.cgi?baz=9
1278 nsIURI *docurl = aDocument->GetDocumentURI();
1280 nsCOMPtr<nsIMutableArray> uriList = do_CreateInstance(NS_ARRAY_CONTRACTID);
1281 if (!uriList)
1282 return NS_ERROR_FAILURE;
1284 nsAutoString datasources(aDataSources);
1285 PRUint32 first = 0;
1286 while (1) {
1287 while (first < datasources.Length() && nsCRT::IsAsciiSpace(datasources.CharAt(first)))
1288 ++first;
1290 if (first >= datasources.Length())
1291 break;
1293 PRUint32 last = first;
1294 while (last < datasources.Length() && !nsCRT::IsAsciiSpace(datasources.CharAt(last)))
1295 ++last;
1297 nsAutoString uriStr;
1298 datasources.Mid(uriStr, first, last - first);
1299 first = last + 1;
1301 // A special 'dummy' datasource
1302 if (uriStr.EqualsLiteral("rdf:null"))
1303 continue;
1305 if (uriStr.CharAt(0) == '#') {
1306 // ok, the datasource is certainly a node of the current document
1307 nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(aDocument);
1308 nsCOMPtr<nsIDOMElement> dsnode;
1310 domdoc->GetElementById(Substring(uriStr, 1),
1311 getter_AddRefs(dsnode));
1313 if (dsnode)
1314 uriList->AppendElement(dsnode, PR_FALSE);
1315 continue;
1318 // N.B. that `failure' (e.g., because it's an unknown
1319 // protocol) leaves uriStr unaltered.
1320 NS_MakeAbsoluteURI(uriStr, uriStr, docurl);
1322 nsCOMPtr<nsIURI> uri;
1323 rv = NS_NewURI(getter_AddRefs(uri), uriStr);
1324 if (NS_FAILED(rv) || !uri)
1325 continue; // Necko will barf if our URI is weird
1327 nsCOMPtr<nsIPrincipal> principal;
1328 if (!isTrusted) {
1329 // Our document is untrusted, so check to see if we can
1330 // load the datasource that they've asked for.
1332 rv = gScriptSecurityManager->GetCodebasePrincipal(uri, getter_AddRefs(principal));
1333 NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get codebase principal");
1334 NS_ENSURE_SUCCESS(rv, rv);
1336 PRBool same;
1337 rv = docPrincipal->Equals(principal, &same);
1338 NS_ASSERTION(NS_SUCCEEDED(rv), "unable to test same origin");
1339 NS_ENSURE_SUCCESS(rv, rv);
1341 if (! same)
1342 continue;
1344 // If we get here, we've run the gauntlet, and the
1345 // datasource's URI has the same origin as our
1346 // document. Let it load!
1349 uriList->AppendElement(uri, PR_FALSE);
1352 nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(mRoot);
1353 rv = mQueryProcessor->GetDatasource(uriList,
1354 rootNode,
1355 isTrusted,
1356 this,
1357 aShouldDelayBuilding,
1358 getter_AddRefs(mDataSource));
1359 NS_ENSURE_SUCCESS(rv, rv);
1362 if (aIsRDFQuery && mDataSource) {
1363 // check if we were given an inference engine type
1364 nsCOMPtr<nsIRDFInferDataSource> inferDB = do_QueryInterface(mDataSource);
1365 if (inferDB) {
1366 nsCOMPtr<nsIRDFDataSource> ds;
1367 inferDB->GetBaseDataSource(getter_AddRefs(ds));
1368 if (ds)
1369 mCompDB = do_QueryInterface(ds);
1372 if (!mCompDB)
1373 mCompDB = do_QueryInterface(mDataSource);
1375 mDB = do_QueryInterface(mDataSource);
1378 if (!mDB && isTrusted) {
1379 gRDFService->GetDataSource("rdf:local-store", getter_AddRefs(mDB));
1382 return NS_OK;
1385 nsresult
1386 nsXULTemplateBuilder::InitHTMLTemplateRoot()
1388 // Use XPConnect and the JS APIs to whack mDB and this as the
1389 // 'database' and 'builder' properties onto aElement.
1390 nsresult rv;
1392 nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
1393 NS_ASSERTION(doc, "no document");
1394 if (! doc)
1395 return NS_ERROR_UNEXPECTED;
1397 nsIScriptGlobalObject *global = doc->GetScriptGlobalObject();
1398 if (! global)
1399 return NS_ERROR_UNEXPECTED;
1401 JSObject *scope = global->GetGlobalJSObject();
1403 nsIScriptContext *context = global->GetContext();
1404 if (! context)
1405 return NS_ERROR_UNEXPECTED;
1407 JSContext* jscontext = reinterpret_cast<JSContext*>(context->GetNativeContext());
1408 NS_ASSERTION(context != nsnull, "no jscontext");
1409 if (! jscontext)
1410 return NS_ERROR_UNEXPECTED;
1412 JSAutoRequest ar(jscontext);
1414 nsIXPConnect *xpc = nsContentUtils::XPConnect();
1416 JSObject* jselement = nsnull;
1418 nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
1419 rv = xpc->WrapNative(jscontext, scope, mRoot, NS_GET_IID(nsIDOMElement),
1420 getter_AddRefs(wrapper));
1421 NS_ENSURE_SUCCESS(rv, rv);
1423 rv = wrapper->GetJSObject(&jselement);
1424 NS_ENSURE_SUCCESS(rv, rv);
1426 if (mDB) {
1427 // database
1428 rv = xpc->WrapNative(jscontext, scope, mDB,
1429 NS_GET_IID(nsIRDFCompositeDataSource),
1430 getter_AddRefs(wrapper));
1431 NS_ENSURE_SUCCESS(rv, rv);
1433 JSObject* jsobj;
1434 rv = wrapper->GetJSObject(&jsobj);
1435 NS_ENSURE_SUCCESS(rv, rv);
1437 jsval jsdatabase = OBJECT_TO_JSVAL(jsobj);
1439 PRBool ok;
1440 ok = JS_SetProperty(jscontext, jselement, "database", &jsdatabase);
1441 NS_ASSERTION(ok, "unable to set database property");
1442 if (! ok)
1443 return NS_ERROR_FAILURE;
1447 // builder
1448 nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
1449 rv = xpc->WrapNative(jscontext, jselement,
1450 static_cast<nsIXULTemplateBuilder*>(this),
1451 NS_GET_IID(nsIXULTemplateBuilder),
1452 getter_AddRefs(wrapper));
1453 NS_ENSURE_SUCCESS(rv, rv);
1455 JSObject* jsobj;
1456 rv = wrapper->GetJSObject(&jsobj);
1457 NS_ENSURE_SUCCESS(rv, rv);
1459 jsval jsbuilder = OBJECT_TO_JSVAL(jsobj);
1461 PRBool ok;
1462 ok = JS_SetProperty(jscontext, jselement, "builder", &jsbuilder);
1463 if (! ok)
1464 return NS_ERROR_FAILURE;
1467 return NS_OK;
1470 nsresult
1471 nsXULTemplateBuilder::DetermineMatchedRule(nsIContent *aContainer,
1472 nsIXULTemplateResult* aResult,
1473 nsTemplateQuerySet* aQuerySet,
1474 nsTemplateRule** aMatchedRule,
1475 PRInt16 *aRuleIndex)
1477 // iterate through the rules and look for one that the result matches
1478 PRInt16 count = aQuerySet->RuleCount();
1479 for (PRInt16 r = 0; r < count; r++) {
1480 nsTemplateRule* rule = aQuerySet->GetRuleAt(r);
1481 // If a tag was specified, it must match the tag of the container
1482 // where content is being inserted.
1483 nsIAtom* tag = rule->GetTag();
1484 if ((!aContainer || !tag || tag == aContainer->Tag()) &&
1485 rule->CheckMatch(aResult)) {
1486 *aMatchedRule = rule;
1487 *aRuleIndex = r;
1488 return NS_OK;
1492 *aRuleIndex = -1;
1493 *aMatchedRule = nsnull;
1494 return NS_OK;
1497 void
1498 nsXULTemplateBuilder::ParseAttribute(const nsAString& aAttributeValue,
1499 void (*aVariableCallback)(nsXULTemplateBuilder*, const nsAString&, void*),
1500 void (*aTextCallback)(nsXULTemplateBuilder*, const nsAString&, void*),
1501 void* aClosure)
1503 nsAString::const_iterator done_parsing;
1504 aAttributeValue.EndReading(done_parsing);
1506 nsAString::const_iterator iter;
1507 aAttributeValue.BeginReading(iter);
1509 nsAString::const_iterator mark(iter), backup(iter);
1511 for (; iter != done_parsing; backup = ++iter) {
1512 // A variable is either prefixed with '?' (in the extended
1513 // syntax) or "rdf:" (in the simple syntax).
1514 PRBool isvar;
1515 if (*iter == PRUnichar('?') && (++iter != done_parsing)) {
1516 isvar = PR_TRUE;
1518 else if ((*iter == PRUnichar('r') && (++iter != done_parsing)) &&
1519 (*iter == PRUnichar('d') && (++iter != done_parsing)) &&
1520 (*iter == PRUnichar('f') && (++iter != done_parsing)) &&
1521 (*iter == PRUnichar(':') && (++iter != done_parsing))) {
1522 isvar = PR_TRUE;
1524 else {
1525 isvar = PR_FALSE;
1528 if (! isvar) {
1529 // It's not a variable, or we ran off the end of the
1530 // string after the initial variable prefix. Since we may
1531 // have slurped down some characters before realizing that
1532 // fact, back up to the point where we started.
1533 iter = backup;
1534 continue;
1536 else if (backup != mark && aTextCallback) {
1537 // Okay, we've found a variable, and there's some vanilla
1538 // text that's been buffered up. Flush it.
1539 (*aTextCallback)(this, Substring(mark, backup), aClosure);
1542 if (*iter == PRUnichar('?')) {
1543 // Well, it was not really a variable, but "??". We use one
1544 // question mark (the second one, actually) literally.
1545 mark = iter;
1546 continue;
1549 // Construct a substring that is the symbol we need to look up
1550 // in the rule's symbol table. The symbol is terminated by a
1551 // space character, a caret, or the end of the string,
1552 // whichever comes first.
1553 nsAString::const_iterator first(backup);
1555 PRUnichar c = 0;
1556 while (iter != done_parsing) {
1557 c = *iter;
1558 if ((c == PRUnichar(' ')) || (c == PRUnichar('^')))
1559 break;
1561 ++iter;
1564 nsAString::const_iterator last(iter);
1566 // Back up so we don't consume the terminating character
1567 // *unless* the terminating character was a caret: the caret
1568 // means "concatenate with no space in between".
1569 if (c != PRUnichar('^'))
1570 --iter;
1572 (*aVariableCallback)(this, Substring(first, last), aClosure);
1573 mark = iter;
1574 ++mark;
1577 if (backup != mark && aTextCallback) {
1578 // If there's any text left over, then fire the text callback
1579 (*aTextCallback)(this, Substring(mark, backup), aClosure);
1584 struct NS_STACK_CLASS SubstituteTextClosure {
1585 SubstituteTextClosure(nsIXULTemplateResult* aResult, nsAString& aString)
1586 : result(aResult), str(aString) {}
1588 // some datasources are lazily initialized or modified while values are
1589 // being retrieved, causing results to be removed. Due to this, hold a
1590 // strong reference to the result.
1591 nsCOMPtr<nsIXULTemplateResult> result;
1592 nsAString& str;
1595 nsresult
1596 nsXULTemplateBuilder::SubstituteText(nsIXULTemplateResult* aResult,
1597 const nsAString& aAttributeValue,
1598 nsAString& aString)
1600 // See if it's the special value "..."
1601 if (aAttributeValue.EqualsLiteral("...")) {
1602 aResult->GetId(aString);
1603 return NS_OK;
1606 // Reasonable guess at how big it should be
1607 aString.SetCapacity(aAttributeValue.Length());
1609 SubstituteTextClosure closure(aResult, aString);
1610 ParseAttribute(aAttributeValue,
1611 SubstituteTextReplaceVariable,
1612 SubstituteTextAppendText,
1613 &closure);
1615 return NS_OK;
1619 void
1620 nsXULTemplateBuilder::SubstituteTextAppendText(nsXULTemplateBuilder* aThis,
1621 const nsAString& aText,
1622 void* aClosure)
1624 // Append aString to the closure's result
1625 SubstituteTextClosure* c = static_cast<SubstituteTextClosure*>(aClosure);
1626 c->str.Append(aText);
1629 void
1630 nsXULTemplateBuilder::SubstituteTextReplaceVariable(nsXULTemplateBuilder* aThis,
1631 const nsAString& aVariable,
1632 void* aClosure)
1634 // Substitute the value for the variable and append to the
1635 // closure's result.
1636 SubstituteTextClosure* c = static_cast<SubstituteTextClosure*>(aClosure);
1638 nsAutoString replacementText;
1640 // The symbol "rdf:*" is special, and means "this guy's URI"
1641 if (aVariable.EqualsLiteral("rdf:*")){
1642 c->result->GetId(replacementText);
1644 else {
1645 // Got a variable; get the value it's assigned to
1646 nsCOMPtr<nsIAtom> var = do_GetAtom(aVariable);
1647 c->result->GetBindingFor(var, replacementText);
1650 c->str += replacementText;
1653 PRBool
1654 nsXULTemplateBuilder::IsTemplateElement(nsIContent* aContent)
1656 return aContent->NodeInfo()->Equals(nsGkAtoms::_template,
1657 kNameSpaceID_XUL);
1660 nsresult
1661 nsXULTemplateBuilder::GetTemplateRoot(nsIContent** aResult)
1663 NS_PRECONDITION(mRoot != nsnull, "not initialized");
1664 if (! mRoot)
1665 return NS_ERROR_NOT_INITIALIZED;
1667 // First, check and see if the root has a template attribute. This
1668 // allows a template to be specified "out of line"; e.g.,
1670 // <window>
1671 // <foo template="MyTemplate">...</foo>
1672 // <template id="MyTemplate">...</template>
1673 // </window>
1675 nsAutoString templateID;
1676 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::_template, templateID);
1678 if (! templateID.IsEmpty()) {
1679 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mRoot->GetDocument());
1680 if (! domDoc)
1681 return NS_OK;
1683 nsCOMPtr<nsIDOMElement> domElement;
1684 domDoc->GetElementById(templateID, getter_AddRefs(domElement));
1686 if (domElement)
1687 return CallQueryInterface(domElement, aResult);
1690 #if 1 // XXX hack to workaround bug with XBL insertion/removal?
1692 // If root node has no template attribute, then look for a child
1693 // node which is a template tag
1694 PRUint32 count = mRoot->GetChildCount();
1696 for (PRUint32 i = 0; i < count; ++i) {
1697 nsIContent *child = mRoot->GetChildAt(i);
1699 if (IsTemplateElement(child)) {
1700 NS_ADDREF(*aResult = child);
1701 return NS_OK;
1705 #endif
1707 // If we couldn't find a real child, look through the anonymous
1708 // kids, too.
1709 nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
1710 if (! doc)
1711 return NS_OK;
1713 nsCOMPtr<nsIDOMNodeList> kids;
1714 doc->BindingManager()->GetXBLChildNodesFor(mRoot, getter_AddRefs(kids));
1716 if (kids) {
1717 PRUint32 length;
1718 kids->GetLength(&length);
1720 for (PRUint32 i = 0; i < length; ++i) {
1721 nsCOMPtr<nsIDOMNode> node;
1722 kids->Item(i, getter_AddRefs(node));
1723 if (! node)
1724 continue;
1726 nsCOMPtr<nsIContent> child = do_QueryInterface(node);
1728 if (IsTemplateElement(child)) {
1729 NS_ADDREF(*aResult = child.get());
1730 return NS_OK;
1735 *aResult = nsnull;
1736 return NS_OK;
1739 nsresult
1740 nsXULTemplateBuilder::CompileQueries()
1742 nsCOMPtr<nsIContent> tmpl;
1743 GetTemplateRoot(getter_AddRefs(tmpl));
1744 if (! tmpl)
1745 return NS_OK;
1747 if (! mRoot)
1748 return NS_ERROR_NOT_INITIALIZED;
1750 // Determine if there are any special settings we need to observe
1751 mFlags = 0;
1753 nsAutoString flags;
1754 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::flags, flags);
1756 // if the dont-test-empty flag is set, containers should not be checked to
1757 // see if they are empty. If dont-recurse is set, then don't process the
1758 // template recursively and only show one level of results.
1759 nsWhitespaceTokenizer tokenizer(flags);
1760 while (tokenizer.hasMoreTokens()) {
1761 const nsDependentSubstring& token(tokenizer.nextToken());
1762 if (token.EqualsLiteral("dont-test-empty"))
1763 mFlags |= eDontTestEmpty;
1764 else if (token.EqualsLiteral("dont-recurse"))
1765 mFlags |= eDontRecurse;
1768 nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot);
1769 nsresult rv =
1770 mQueryProcessor->InitializeForBuilding(mDataSource, this, rootnode);
1771 if (NS_FAILED(rv))
1772 return rv;
1774 // Set the "container" and "member" variables, if the user has specified
1775 // them. The container variable may be specified with the container
1776 // attribute on the <template> and the member variable may be specified
1777 // using the member attribute or the value of the uri attribute inside the
1778 // first action body in the template. If not specified, the container
1779 // variable defaults to '?uri' and the member variable defaults to '?' or
1780 // 'rdf:*' for simple queries.
1782 // For RDF queries, the container variable may also be set via the
1783 // <content> tag.
1785 nsAutoString containervar;
1786 tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::container, containervar);
1788 if (containervar.IsEmpty())
1789 mRefVariable = do_GetAtom("?uri");
1790 else
1791 mRefVariable = do_GetAtom(containervar);
1793 nsAutoString membervar;
1794 tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::member, membervar);
1796 if (membervar.IsEmpty())
1797 mMemberVariable = nsnull;
1798 else
1799 mMemberVariable = do_GetAtom(membervar);
1801 nsTemplateQuerySet* queryset = new nsTemplateQuerySet(0);
1802 if (!queryset)
1803 return NS_ERROR_OUT_OF_MEMORY;
1805 if (!mQuerySets.AppendElement(queryset)) {
1806 delete queryset;
1807 return NS_ERROR_OUT_OF_MEMORY;
1810 PRBool canUseTemplate = PR_FALSE;
1811 PRInt32 priority = 0;
1812 rv = CompileTemplate(tmpl, queryset, PR_FALSE, &priority, &canUseTemplate);
1814 if (NS_FAILED(rv) || !canUseTemplate) {
1815 for (PRInt32 q = mQuerySets.Length() - 1; q >= 0; q--) {
1816 nsTemplateQuerySet* qs = mQuerySets[q];
1817 delete qs;
1819 mQuerySets.Clear();
1822 mQueriesCompiled = PR_TRUE;
1824 return NS_OK;
1827 nsresult
1828 nsXULTemplateBuilder::CompileTemplate(nsIContent* aTemplate,
1829 nsTemplateQuerySet* aQuerySet,
1830 PRBool aIsQuerySet,
1831 PRInt32* aPriority,
1832 PRBool* aCanUseTemplate)
1834 NS_ASSERTION(aQuerySet, "No queryset supplied");
1836 // XXXndeakin log syntax errors
1838 nsresult rv = NS_OK;
1840 PRBool isQuerySetMode = PR_FALSE;
1841 PRBool hasQuerySet = PR_FALSE, hasRule = PR_FALSE, hasQuery = PR_FALSE;
1843 PRUint32 count = aTemplate->GetChildCount();
1845 for (PRUint32 i = 0; i < count; i++) {
1846 nsIContent *rulenode = aTemplate->GetChildAt(i);
1847 nsINodeInfo *ni = rulenode->NodeInfo();
1849 // don't allow more queries than can be supported
1850 if (*aPriority == PR_INT16_MAX)
1851 return NS_ERROR_FAILURE;
1853 // XXXndeakin queryset isn't a good name for this tag since it only
1854 // ever contains one query
1855 if (!aIsQuerySet && ni->Equals(nsGkAtoms::queryset, kNameSpaceID_XUL)) {
1856 if (hasRule || hasQuery)
1857 continue;
1859 isQuerySetMode = PR_TRUE;
1861 // only create a queryset for those after the first since the
1862 // first one is always created by CompileQueries
1863 if (hasQuerySet) {
1864 aQuerySet = new nsTemplateQuerySet(++*aPriority);
1865 if (!aQuerySet)
1866 return NS_ERROR_OUT_OF_MEMORY;
1868 // once the queryset is appended to the mQuerySets list, it
1869 // will be removed by CompileQueries if an error occurs
1870 if (!mQuerySets.AppendElement(aQuerySet)) {
1871 delete aQuerySet;
1872 return NS_ERROR_OUT_OF_MEMORY;
1876 hasQuerySet = PR_TRUE;
1878 rv = CompileTemplate(rulenode, aQuerySet, PR_TRUE, aPriority, aCanUseTemplate);
1879 if (NS_FAILED(rv))
1880 return rv;
1883 // once a queryset is used, everything must be a queryset
1884 if (isQuerySetMode)
1885 continue;
1887 if (ni->Equals(nsGkAtoms::rule, kNameSpaceID_XUL)) {
1888 nsCOMPtr<nsIContent> action;
1889 nsXULContentUtils::FindChildByTag(rulenode,
1890 kNameSpaceID_XUL,
1891 nsGkAtoms::action,
1892 getter_AddRefs(action));
1894 if (action){
1895 nsCOMPtr<nsIAtom> memberVariable;
1896 DetermineMemberVariable(action, getter_AddRefs(memberVariable));
1897 if (! memberVariable) continue;
1899 if (hasQuery) {
1900 nsCOMPtr<nsIAtom> tag;
1901 DetermineRDFQueryRef(aQuerySet->mQueryNode,
1902 getter_AddRefs(tag));
1903 if (tag)
1904 aQuerySet->SetTag(tag);
1906 if (! aQuerySet->mCompiledQuery) {
1907 nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode));
1909 rv = mQueryProcessor->CompileQuery(this, query,
1910 mRefVariable, memberVariable,
1911 getter_AddRefs(aQuerySet->mCompiledQuery));
1912 if (NS_FAILED(rv))
1913 return rv;
1916 if (aQuerySet->mCompiledQuery) {
1917 rv = CompileExtendedQuery(rulenode, action, memberVariable,
1918 aQuerySet);
1919 if (NS_FAILED(rv))
1920 return rv;
1922 *aCanUseTemplate = PR_TRUE;
1925 else {
1926 // backwards-compatible RDF template syntax where there is
1927 // an <action> node but no <query> node. In this case,
1928 // use the conditions as if it was the query.
1930 nsCOMPtr<nsIContent> conditions;
1931 nsXULContentUtils::FindChildByTag(rulenode,
1932 kNameSpaceID_XUL,
1933 nsGkAtoms::conditions,
1934 getter_AddRefs(conditions));
1936 if (conditions) {
1937 // create a new queryset if one hasn't been created already
1938 if (hasQuerySet) {
1939 aQuerySet = new nsTemplateQuerySet(++*aPriority);
1940 if (! aQuerySet)
1941 return NS_ERROR_OUT_OF_MEMORY;
1943 if (!mQuerySets.AppendElement(aQuerySet)) {
1944 delete aQuerySet;
1945 return NS_ERROR_OUT_OF_MEMORY;
1949 nsCOMPtr<nsIAtom> tag;
1950 DetermineRDFQueryRef(conditions, getter_AddRefs(tag));
1951 if (tag)
1952 aQuerySet->SetTag(tag);
1954 hasQuerySet = PR_TRUE;
1956 nsCOMPtr<nsIDOMNode> conditionsnode(do_QueryInterface(conditions));
1958 aQuerySet->mQueryNode = conditions;
1959 rv = mQueryProcessor->CompileQuery(this, conditionsnode,
1960 mRefVariable,
1961 memberVariable,
1962 getter_AddRefs(aQuerySet->mCompiledQuery));
1963 if (NS_FAILED(rv))
1964 return rv;
1966 if (aQuerySet->mCompiledQuery) {
1967 rv = CompileExtendedQuery(rulenode, action, memberVariable,
1968 aQuerySet);
1969 if (NS_FAILED(rv))
1970 return rv;
1972 *aCanUseTemplate = PR_TRUE;
1977 else {
1978 if (hasQuery)
1979 continue;
1981 // a new queryset must always be created in this case
1982 if (hasQuerySet) {
1983 aQuerySet = new nsTemplateQuerySet(++*aPriority);
1984 if (! aQuerySet)
1985 return NS_ERROR_OUT_OF_MEMORY;
1987 if (!mQuerySets.AppendElement(aQuerySet)) {
1988 delete aQuerySet;
1989 return NS_ERROR_OUT_OF_MEMORY;
1993 hasQuerySet = PR_TRUE;
1995 rv = CompileSimpleQuery(rulenode, aQuerySet, aCanUseTemplate);
1996 if (NS_FAILED(rv))
1997 return rv;
2000 hasRule = PR_TRUE;
2002 else if (ni->Equals(nsGkAtoms::query, kNameSpaceID_XUL)) {
2003 if (hasQuery)
2004 continue;
2006 aQuerySet->mQueryNode = rulenode;
2007 hasQuery = PR_TRUE;
2009 else if (ni->Equals(nsGkAtoms::action, kNameSpaceID_XUL)) {
2010 // the query must appear before the action
2011 if (! hasQuery)
2012 continue;
2014 nsCOMPtr<nsIAtom> tag;
2015 DetermineRDFQueryRef(aQuerySet->mQueryNode, getter_AddRefs(tag));
2016 if (tag)
2017 aQuerySet->SetTag(tag);
2019 nsCOMPtr<nsIAtom> memberVariable;
2020 DetermineMemberVariable(rulenode, getter_AddRefs(memberVariable));
2021 if (! memberVariable) continue;
2023 nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode));
2025 rv = mQueryProcessor->CompileQuery(this, query,
2026 mRefVariable, memberVariable,
2027 getter_AddRefs(aQuerySet->mCompiledQuery));
2029 if (aQuerySet->mCompiledQuery) {
2030 nsTemplateRule* rule = new nsTemplateRule(aTemplate, rulenode, aQuerySet);
2031 if (! rule)
2032 return NS_ERROR_OUT_OF_MEMORY;
2034 rv = aQuerySet->AddRule(rule);
2035 if (NS_FAILED(rv)) {
2036 delete rule;
2037 return rv;
2040 rule->SetVars(mRefVariable, memberVariable);
2042 *aCanUseTemplate = PR_TRUE;
2044 return NS_OK;
2049 if (! hasRule && ! hasQuery && ! hasQuerySet) {
2050 // if no rules are specified in the template, then the contents of the
2051 // <template> tag are the one-and-only template.
2052 rv = CompileSimpleQuery(aTemplate, aQuerySet, aCanUseTemplate);
2055 return rv;
2058 nsresult
2059 nsXULTemplateBuilder::CompileExtendedQuery(nsIContent* aRuleElement,
2060 nsIContent* aActionElement,
2061 nsIAtom* aMemberVariable,
2062 nsTemplateQuerySet* aQuerySet)
2064 // Compile an "extended" <template> rule. An extended rule may have
2065 // a <conditions> child, an <action> child, and a <bindings> child.
2066 nsresult rv;
2068 nsTemplateRule* rule = new nsTemplateRule(aRuleElement, aActionElement, aQuerySet);
2069 if (! rule)
2070 return NS_ERROR_OUT_OF_MEMORY;
2072 nsCOMPtr<nsIContent> conditions;
2073 nsXULContentUtils::FindChildByTag(aRuleElement,
2074 kNameSpaceID_XUL,
2075 nsGkAtoms::conditions,
2076 getter_AddRefs(conditions));
2078 // allow the conditions to be placed directly inside the rule
2079 if (!conditions)
2080 conditions = aRuleElement;
2082 rv = CompileConditions(rule, conditions);
2083 // If the rule compilation failed, then we have to bail.
2084 if (NS_FAILED(rv)) {
2085 delete rule;
2086 return rv;
2089 rv = aQuerySet->AddRule(rule);
2090 if (NS_FAILED(rv)) {
2091 delete rule;
2092 return rv;
2095 rule->SetVars(mRefVariable, aMemberVariable);
2097 // If we've got bindings, add 'em.
2098 nsCOMPtr<nsIContent> bindings;
2099 nsXULContentUtils::FindChildByTag(aRuleElement,
2100 kNameSpaceID_XUL,
2101 nsGkAtoms::bindings,
2102 getter_AddRefs(bindings));
2104 // allow bindings to be placed directly inside rule
2105 if (!bindings)
2106 bindings = aRuleElement;
2108 rv = CompileBindings(rule, bindings);
2109 NS_ENSURE_SUCCESS(rv, rv);
2111 return NS_OK;
2114 nsresult
2115 nsXULTemplateBuilder::DetermineMemberVariable(nsIContent* aActionElement,
2116 nsIAtom** aMemberVariable)
2118 // If the member variable hasn't already been specified, then
2119 // grovel over <action> to find it. We'll use the first one
2120 // that we find in a breadth-first search.
2122 if (mMemberVariable) {
2123 *aMemberVariable = mMemberVariable;
2124 NS_IF_ADDREF(*aMemberVariable);
2126 else {
2127 *aMemberVariable = nsnull;
2129 nsCOMArray<nsIContent> unvisited;
2131 if (!unvisited.AppendObject(aActionElement))
2132 return NS_ERROR_OUT_OF_MEMORY;
2134 while (unvisited.Count()) {
2135 nsIContent* next = unvisited[0];
2136 unvisited.RemoveObjectAt(0);
2138 nsAutoString uri;
2139 next->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri);
2141 if (!uri.IsEmpty() && uri[0] == PRUnichar('?')) {
2142 // Found it.
2143 *aMemberVariable = NS_NewAtom(uri);
2144 break;
2147 // otherwise, append the children to the unvisited list: this
2148 // results in a breadth-first search.
2149 PRUint32 count = next->GetChildCount();
2151 for (PRUint32 i = 0; i < count; ++i) {
2152 nsIContent *child = next->GetChildAt(i);
2154 if (!unvisited.AppendObject(child))
2155 return NS_ERROR_OUT_OF_MEMORY;
2160 return NS_OK;
2163 void
2164 nsXULTemplateBuilder::DetermineRDFQueryRef(nsIContent* aQueryElement, nsIAtom** aTag)
2166 // check for a tag
2167 nsCOMPtr<nsIContent> content;
2168 nsXULContentUtils::FindChildByTag(aQueryElement,
2169 kNameSpaceID_XUL,
2170 nsGkAtoms::content,
2171 getter_AddRefs(content));
2173 if (! content) {
2174 // look for older treeitem syntax as well
2175 nsXULContentUtils::FindChildByTag(aQueryElement,
2176 kNameSpaceID_XUL,
2177 nsGkAtoms::treeitem,
2178 getter_AddRefs(content));
2181 if (content) {
2182 nsAutoString uri;
2183 content->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri);
2185 if (!uri.IsEmpty())
2186 mRefVariable = do_GetAtom(uri);
2188 nsAutoString tag;
2189 content->GetAttr(kNameSpaceID_None, nsGkAtoms::tag, tag);
2191 if (!tag.IsEmpty())
2192 *aTag = NS_NewAtom(tag);
2196 nsresult
2197 nsXULTemplateBuilder::CompileSimpleQuery(nsIContent* aRuleElement,
2198 nsTemplateQuerySet* aQuerySet,
2199 PRBool* aCanUseTemplate)
2201 // compile a simple query, which is a query with no <query> or
2202 // <conditions>. This means that a default query is used.
2203 nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aRuleElement));
2205 nsCOMPtr<nsIAtom> memberVariable;
2206 if (mMemberVariable)
2207 memberVariable = mMemberVariable;
2208 else
2209 memberVariable = do_GetAtom("rdf:*");
2211 // since there is no <query> node for a simple query, the query node will
2212 // be either the <rule> node if multiple rules are used, or the <template> node.
2213 aQuerySet->mQueryNode = aRuleElement;
2214 nsresult rv = mQueryProcessor->CompileQuery(this, query,
2215 mRefVariable, memberVariable,
2216 getter_AddRefs(aQuerySet->mCompiledQuery));
2217 if (NS_FAILED(rv))
2218 return rv;
2220 if (! aQuerySet->mCompiledQuery) {
2221 *aCanUseTemplate = PR_FALSE;
2222 return NS_OK;
2225 nsTemplateRule* rule = new nsTemplateRule(aRuleElement, aRuleElement, aQuerySet);
2226 if (! rule)
2227 return NS_ERROR_OUT_OF_MEMORY;
2229 rv = aQuerySet->AddRule(rule);
2230 if (NS_FAILED(rv)) {
2231 delete rule;
2232 return rv;
2235 rule->SetVars(mRefVariable, memberVariable);
2237 nsAutoString tag;
2238 aRuleElement->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag);
2240 if (!tag.IsEmpty()) {
2241 nsCOMPtr<nsIAtom> tagatom = do_GetAtom(tag);
2242 aQuerySet->SetTag(tagatom);
2245 *aCanUseTemplate = PR_TRUE;
2247 return AddSimpleRuleBindings(rule, aRuleElement);
2250 nsresult
2251 nsXULTemplateBuilder::CompileConditions(nsTemplateRule* aRule,
2252 nsIContent* aCondition)
2254 nsAutoString tag;
2255 aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag);
2257 if (!tag.IsEmpty()) {
2258 nsCOMPtr<nsIAtom> tagatom = do_GetAtom(tag);
2259 aRule->SetTag(tagatom);
2262 PRUint32 count = aCondition->GetChildCount();
2264 nsTemplateCondition* currentCondition = nsnull;
2266 for (PRUint32 i = 0; i < count; i++) {
2267 nsIContent *node = aCondition->GetChildAt(i);
2269 if (node->NodeInfo()->Equals(nsGkAtoms::where, kNameSpaceID_XUL)) {
2270 nsresult rv = CompileWhereCondition(aRule, node, &currentCondition);
2271 if (NS_FAILED(rv))
2272 return rv;
2276 return NS_OK;
2279 nsresult
2280 nsXULTemplateBuilder::CompileWhereCondition(nsTemplateRule* aRule,
2281 nsIContent* aCondition,
2282 nsTemplateCondition** aCurrentCondition)
2284 // Compile a <where> condition, which must be of the form:
2286 // <where subject="?var1|string" rel="relation" value="?var2|string" />
2288 // The value of rel may be:
2289 // equal - subject must be equal to object
2290 // notequal - subject must not be equal to object
2291 // less - subject must be less than object
2292 // greater - subject must be greater than object
2293 // startswith - subject must start with object
2294 // endswith - subject must end with object
2295 // contains - subject must contain object
2296 // Comparisons are done as strings unless the subject is an integer.
2298 // subject
2299 nsAutoString subject;
2300 aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject);
2301 if (subject.IsEmpty())
2302 return NS_OK;
2304 nsCOMPtr<nsIAtom> svar;
2305 if (subject[0] == PRUnichar('?'))
2306 svar = do_GetAtom(subject);
2308 nsAutoString relstring;
2309 aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, relstring);
2310 if (relstring.IsEmpty())
2311 return NS_OK;
2313 // object
2314 nsAutoString value;
2315 aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
2316 if (value.IsEmpty())
2317 return NS_OK;
2319 // multiple
2320 PRBool shouldMultiple =
2321 aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::multiple,
2322 nsGkAtoms::_true, eCaseMatters);
2324 nsCOMPtr<nsIAtom> vvar;
2325 if (!shouldMultiple && (value[0] == PRUnichar('?'))) {
2326 vvar = do_GetAtom(value);
2329 // ignorecase
2330 PRBool shouldIgnoreCase =
2331 aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorecase,
2332 nsGkAtoms::_true, eCaseMatters);
2334 // negate
2335 PRBool shouldNegate =
2336 aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::negate,
2337 nsGkAtoms::_true, eCaseMatters);
2339 nsTemplateCondition* condition;
2341 if (svar && vvar) {
2342 condition = new nsTemplateCondition(svar, relstring, vvar,
2343 shouldIgnoreCase, shouldNegate);
2345 else if (svar && !value.IsEmpty()) {
2346 condition = new nsTemplateCondition(svar, relstring, value,
2347 shouldIgnoreCase, shouldNegate, shouldMultiple);
2349 else if (vvar) {
2350 condition = new nsTemplateCondition(subject, relstring, vvar,
2351 shouldIgnoreCase, shouldNegate);
2353 else {
2354 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
2355 ("xultemplate[%p] on <where> test, expected at least one variable", this));
2356 return NS_OK;
2359 if (! condition)
2360 return NS_ERROR_OUT_OF_MEMORY;
2362 if (*aCurrentCondition) {
2363 (*aCurrentCondition)->SetNext(condition);
2365 else {
2366 aRule->SetCondition(condition);
2369 *aCurrentCondition = condition;
2371 return NS_OK;
2374 nsresult
2375 nsXULTemplateBuilder::CompileBindings(nsTemplateRule* aRule, nsIContent* aBindings)
2377 // Add an extended rule's bindings.
2378 nsresult rv;
2380 PRUint32 count = aBindings->GetChildCount();
2382 for (PRUint32 i = 0; i < count; ++i) {
2383 nsIContent *binding = aBindings->GetChildAt(i);
2385 if (binding->NodeInfo()->Equals(nsGkAtoms::binding,
2386 kNameSpaceID_XUL)) {
2387 rv = CompileBinding(aRule, binding);
2389 else {
2390 #ifdef PR_LOGGING
2391 nsAutoString tagstr;
2392 binding->NodeInfo()->GetQualifiedName(tagstr);
2394 nsCAutoString tagstrC;
2395 tagstrC.AssignWithConversion(tagstr);
2396 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
2397 ("xultemplate[%p] unrecognized binding <%s>",
2398 this, tagstrC.get()));
2399 #endif
2401 continue;
2404 if (NS_FAILED(rv))
2405 return rv;
2408 aRule->AddBindingsToQueryProcessor(mQueryProcessor);
2410 return NS_OK;
2414 nsresult
2415 nsXULTemplateBuilder::CompileBinding(nsTemplateRule* aRule,
2416 nsIContent* aBinding)
2418 // Compile a <binding> "condition", which must be of the form:
2420 // <binding subject="?var1"
2421 // predicate="resource"
2422 // object="?var2" />
2424 // XXXwaterson Some day it would be cool to allow the 'predicate'
2425 // to be bound to a variable.
2427 // subject
2428 nsAutoString subject;
2429 aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject);
2431 if (subject.IsEmpty()) {
2432 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
2433 ("xultemplate[%p] <binding> requires `subject'", this));
2435 return NS_OK;
2438 nsCOMPtr<nsIAtom> svar;
2439 if (subject[0] == PRUnichar('?')) {
2440 svar = do_GetAtom(subject);
2442 else {
2443 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
2444 ("xultemplate[%p] <binding> requires `subject' to be a variable", this));
2446 return NS_OK;
2449 // predicate
2450 nsAutoString predicate;
2451 aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::predicate, predicate);
2452 if (predicate.IsEmpty()) {
2453 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
2454 ("xultemplate[%p] <binding> requires `predicate'", this));
2456 return NS_OK;
2459 // object
2460 nsAutoString object;
2461 aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::object, object);
2463 if (object.IsEmpty()) {
2464 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
2465 ("xultemplate[%p] <binding> requires `object'", this));
2467 return NS_OK;
2470 nsCOMPtr<nsIAtom> ovar;
2471 if (object[0] == PRUnichar('?')) {
2472 ovar = do_GetAtom(object);
2474 else {
2475 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
2476 ("xultemplate[%p] <binding> requires `object' to be a variable", this));
2478 return NS_OK;
2481 return aRule->AddBinding(svar, predicate, ovar);
2484 nsresult
2485 nsXULTemplateBuilder::AddSimpleRuleBindings(nsTemplateRule* aRule,
2486 nsIContent* aElement)
2488 // Crawl the content tree of a "simple" rule, adding a variable
2489 // assignment for any attribute whose value is "rdf:".
2491 nsAutoVoidArray elements;
2493 if (!elements.AppendElement(aElement))
2494 return NS_ERROR_OUT_OF_MEMORY;
2496 while (elements.Count()) {
2497 // Pop the next element off the stack
2498 PRUint32 i = (PRUint32)(elements.Count() - 1);
2499 nsIContent* element = static_cast<nsIContent*>(elements[i]);
2500 elements.RemoveElementAt(i);
2502 // Iterate through its attributes, looking for substitutions
2503 // that we need to add as bindings.
2504 PRUint32 count = element->GetAttrCount();
2506 for (i = 0; i < count; ++i) {
2507 const nsAttrName* name = element->GetAttrNameAt(i);
2509 if (!name->Equals(nsGkAtoms::id, kNameSpaceID_None) &&
2510 !name->Equals(nsGkAtoms::uri, kNameSpaceID_None)) {
2511 nsAutoString value;
2512 element->GetAttr(name->NamespaceID(), name->LocalName(), value);
2514 // Scan the attribute for variables, adding a binding for
2515 // each one.
2516 ParseAttribute(value, AddBindingsFor, nsnull, aRule);
2520 // Push kids onto the stack, and search them next.
2521 count = element->GetChildCount();
2523 while (count-- > 0) {
2524 if (!elements.AppendElement(element->GetChildAt(count)))
2525 return NS_ERROR_OUT_OF_MEMORY;
2529 aRule->AddBindingsToQueryProcessor(mQueryProcessor);
2531 return NS_OK;
2534 void
2535 nsXULTemplateBuilder::AddBindingsFor(nsXULTemplateBuilder* aThis,
2536 const nsAString& aVariable,
2537 void* aClosure)
2539 // We should *only* be recieving "rdf:"-style variables. Make
2540 // sure...
2541 if (!StringBeginsWith(aVariable, NS_LITERAL_STRING("rdf:")))
2542 return;
2544 nsTemplateRule* rule = static_cast<nsTemplateRule*>(aClosure);
2546 nsCOMPtr<nsIAtom> var = do_GetAtom(aVariable);
2548 // Strip it down to the raw RDF property by clobbering the "rdf:"
2549 // prefix
2550 nsAutoString property;
2551 property.Assign(Substring(aVariable, PRUint32(4), aVariable.Length() - 4));
2553 if (! rule->HasBinding(rule->GetMemberVariable(), property, var))
2554 // In the simple syntax, the binding is always from the
2555 // member variable, through the property, to the target.
2556 rule->AddBinding(rule->GetMemberVariable(), property, var);
2560 nsresult
2561 nsXULTemplateBuilder::IsSystemPrincipal(nsIPrincipal *principal, PRBool *result)
2563 if (!gSystemPrincipal)
2564 return NS_ERROR_UNEXPECTED;
2566 *result = (principal == gSystemPrincipal);
2567 return NS_OK;
2570 PRBool
2571 nsXULTemplateBuilder::IsActivated(nsIRDFResource *aResource)
2573 for (ActivationEntry *entry = mTop;
2574 entry != nsnull;
2575 entry = entry->mPrevious) {
2576 if (entry->mResource == aResource)
2577 return PR_TRUE;
2579 return PR_FALSE;
2582 nsresult
2583 nsXULTemplateBuilder::GetResultResource(nsIXULTemplateResult* aResult,
2584 nsIRDFResource** aResource)
2586 // get the resource for a result by checking its resource property. If it
2587 // is not set, check the id. This allows non-chrome implementations to
2588 // avoid having to use RDF.
2589 nsresult rv = aResult->GetResource(aResource);
2590 if (NS_FAILED(rv))
2591 return rv;
2593 if (! *aResource) {
2594 nsAutoString id;
2595 rv = aResult->GetId(id);
2596 if (NS_FAILED(rv))
2597 return rv;
2599 return gRDFService->GetUnicodeResource(id, aResource);
2602 return rv;