1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 /*
7 
8   Builds content from a datasource using the XUL <template> tag.
9 
10   TO DO
11 
12   . Fix ContentTagTest's location in the network construction
13 
14   To turn on logging for this module, set:
15 
16     MOZ_LOG=nsXULTemplateBuilder:5
17 
18  */
19 
20 #include "nsAutoPtr.h"
21 #include "nsCOMPtr.h"
22 #include "nsCRT.h"
23 #include "nsIContent.h"
24 #include "nsIDOMElement.h"
25 #include "nsIDOMNode.h"
26 #include "nsIDOMDocument.h"
27 #include "nsIDOMXULElement.h"
28 #include "nsIDocument.h"
29 #include "nsBindingManager.h"
30 #include "nsIDOMNodeList.h"
31 #include "nsIObserverService.h"
32 #include "nsIRDFCompositeDataSource.h"
33 #include "nsIRDFInferDataSource.h"
34 #include "nsIRDFContainerUtils.h"
35 #include "nsIXULDocument.h"
36 #include "nsIXULTemplateBuilder.h"
37 #include "nsIXULBuilderListener.h"
38 #include "nsIRDFRemoteDataSource.h"
39 #include "nsIRDFService.h"
40 #include "nsIScriptContext.h"
41 #include "nsIScriptGlobalObject.h"
42 #include "nsIServiceManager.h"
43 #include "nsISimpleEnumerator.h"
44 #include "nsIMutableArray.h"
45 #include "nsIURL.h"
46 #include "nsIXPConnect.h"
47 #include "nsContentCID.h"
48 #include "nsNameSpaceManager.h"
49 #include "nsRDFCID.h"
50 #include "nsXULContentUtils.h"
51 #include "nsString.h"
52 #include "nsTArray.h"
53 #include "nsXPIDLString.h"
54 #include "nsWhitespaceTokenizer.h"
55 #include "nsGkAtoms.h"
56 #include "nsXULElement.h"
57 #include "jsapi.h"
58 #include "mozilla/Logging.h"
59 #include "rdf.h"
60 #include "PLDHashTable.h"
61 #include "plhash.h"
62 #include "nsDOMClassInfoID.h"
63 #include "nsPIDOMWindow.h"
64 #include "nsIConsoleService.h"
65 #include "nsNetUtil.h"
66 #include "nsXULTemplateBuilder.h"
67 #include "nsXULTemplateQueryProcessorRDF.h"
68 #include "nsXULTemplateQueryProcessorXML.h"
69 #include "nsXULTemplateQueryProcessorStorage.h"
70 #include "nsContentUtils.h"
71 #include "ChildIterator.h"
72 #include "mozilla/dom/ScriptSettings.h"
73 #include "nsGlobalWindow.h"
74 
75 using namespace mozilla::dom;
76 using namespace mozilla;
77 
78 //----------------------------------------------------------------------
79 //
80 // nsXULTemplateBuilder
81 //
82 
83 nsrefcnt                  nsXULTemplateBuilder::gRefCnt = 0;
84 nsIRDFService*            nsXULTemplateBuilder::gRDFService;
85 nsIRDFContainerUtils*     nsXULTemplateBuilder::gRDFContainerUtils;
86 nsIScriptSecurityManager* nsXULTemplateBuilder::gScriptSecurityManager;
87 nsIPrincipal*             nsXULTemplateBuilder::gSystemPrincipal;
88 nsIObserverService*       nsXULTemplateBuilder::gObserverService;
89 
90 LazyLogModule gXULTemplateLog("nsXULTemplateBuilder");
91 
92 #define NS_QUERY_PROCESSOR_CONTRACTID_PREFIX "@mozilla.org/xul/xul-query-processor;1?name="
93 
94 //----------------------------------------------------------------------
95 //
96 // nsXULTemplateBuilder methods
97 //
98 
nsXULTemplateBuilder(void)99 nsXULTemplateBuilder::nsXULTemplateBuilder(void)
100     : mQueriesCompiled(false),
101       mFlags(0),
102       mTop(nullptr),
103       mObservedDocument(nullptr)
104 {
105     MOZ_COUNT_CTOR(nsXULTemplateBuilder);
106 }
107 
108 void
DestroyMatchMap()109 nsXULTemplateBuilder::DestroyMatchMap()
110 {
111     for (auto iter = mMatchMap.Iter(); !iter.Done(); iter.Next()) {
112         nsTemplateMatch*& match = iter.Data();
113         // delete all the matches in the list
114         while (match) {
115             nsTemplateMatch* next = match->mNext;
116             nsTemplateMatch::Destroy(match, true);
117             match = next;
118         }
119 
120         iter.Remove();
121     }
122 }
123 
~nsXULTemplateBuilder(void)124 nsXULTemplateBuilder::~nsXULTemplateBuilder(void)
125 {
126     Uninit(true);
127 
128     if (--gRefCnt == 0) {
129         NS_IF_RELEASE(gRDFService);
130         NS_IF_RELEASE(gRDFContainerUtils);
131         NS_IF_RELEASE(gSystemPrincipal);
132         NS_IF_RELEASE(gScriptSecurityManager);
133         NS_IF_RELEASE(gObserverService);
134     }
135 
136     MOZ_COUNT_DTOR(nsXULTemplateBuilder);
137 }
138 
139 
140 nsresult
InitGlobals()141 nsXULTemplateBuilder::InitGlobals()
142 {
143     nsresult rv;
144 
145     if (gRefCnt++ == 0) {
146         // Initialize the global shared reference to the service
147         // manager and get some shared resource objects.
148         NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
149         rv = CallGetService(kRDFServiceCID, &gRDFService);
150         if (NS_FAILED(rv))
151             return rv;
152 
153         NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID);
154         rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils);
155         if (NS_FAILED(rv))
156             return rv;
157 
158         rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID,
159                             &gScriptSecurityManager);
160         if (NS_FAILED(rv))
161             return rv;
162 
163         rv = gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal);
164         if (NS_FAILED(rv))
165             return rv;
166 
167         rv = CallGetService(NS_OBSERVERSERVICE_CONTRACTID, &gObserverService);
168         if (NS_FAILED(rv))
169             return rv;
170     }
171 
172     return NS_OK;
173 }
174 
175 void
StartObserving(nsIDocument * aDocument)176 nsXULTemplateBuilder::StartObserving(nsIDocument* aDocument)
177 {
178     aDocument->AddObserver(this);
179     mObservedDocument = aDocument;
180     gObserverService->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, false);
181 }
182 
183 void
StopObserving()184 nsXULTemplateBuilder::StopObserving()
185 {
186     MOZ_ASSERT(mObservedDocument);
187     mObservedDocument->RemoveObserver(this);
188     mObservedDocument = nullptr;
189     gObserverService->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
190 }
191 
192 void
CleanUp(bool aIsFinal)193 nsXULTemplateBuilder::CleanUp(bool aIsFinal)
194 {
195     for (int32_t q = mQuerySets.Length() - 1; q >= 0; q--) {
196         nsTemplateQuerySet* qs = mQuerySets[q];
197         delete qs;
198     }
199 
200     mQuerySets.Clear();
201 
202     DestroyMatchMap();
203 
204     // Setting mQueryProcessor to null will close connections. This would be
205     // handled by the cycle collector, but we want to close them earlier.
206     if (aIsFinal)
207         mQueryProcessor = nullptr;
208 }
209 
210 void
Uninit(bool aIsFinal)211 nsXULTemplateBuilder::Uninit(bool aIsFinal)
212 {
213     if (mObservedDocument && aIsFinal) {
214         StopObserving();
215     }
216 
217     if (mQueryProcessor)
218         mQueryProcessor->Done();
219 
220     CleanUp(aIsFinal);
221 
222     mRootResult = nullptr;
223     mRefVariable = nullptr;
224     mMemberVariable = nullptr;
225 
226     mQueriesCompiled = false;
227 }
228 
229 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULTemplateBuilder)
230 
231 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULTemplateBuilder)
232     NS_IMPL_CYCLE_COLLECTION_UNLINK(mDataSource)
233     NS_IMPL_CYCLE_COLLECTION_UNLINK(mDB)
234     NS_IMPL_CYCLE_COLLECTION_UNLINK(mCompDB)
235     NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot)
236     NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootResult)
237     NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners)
238     NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueryProcessor)
239     tmp->DestroyMatchMap();
240     for (uint32_t i = 0; i < tmp->mQuerySets.Length(); ++i) {
241         nsTemplateQuerySet* qs = tmp->mQuerySets[i];
242         delete qs;
243     }
244     tmp->mQuerySets.Clear();
245 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
246 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULTemplateBuilder)
247     if (tmp->mObservedDocument && !cb.WantAllTraces()) {
248         // The global observer service holds us alive.
249         return NS_SUCCESS_INTERRUPTED_TRAVERSE;
250     }
251 
252     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDataSource)
253     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDB)
254     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCompDB)
255     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
256     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootResult)
257     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners)
258     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueryProcessor)
259 
260     for (auto iter = tmp->mMatchMap.Iter(); !iter.Done(); iter.Next()) {
261         cb.NoteXPCOMChild(iter.Key());
262         nsTemplateMatch* match = iter.UserData();
263         while (match) {
264             cb.NoteXPCOMChild(match->GetContainer());
265             cb.NoteXPCOMChild(match->mResult);
266             match = match->mNext;
267         }
268     }
269 
270     {
271       uint32_t i, count = tmp->mQuerySets.Length();
272       for (i = 0; i < count; ++i) {
273         nsTemplateQuerySet *set = tmp->mQuerySets[i];
274         cb.NoteXPCOMChild(set->mQueryNode);
275         cb.NoteXPCOMChild(set->mCompiledQuery);
276         uint16_t j, rulesCount = set->RuleCount();
277         for (j = 0; j < rulesCount; ++j) {
278           set->GetRuleAt(j)->Traverse(cb);
279         }
280       }
281     }
282     tmp->Traverse(cb);
283 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
284 
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULTemplateBuilder)285 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULTemplateBuilder)
286 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULTemplateBuilder)
287 
288 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULTemplateBuilder)
289   NS_INTERFACE_MAP_ENTRY(nsIXULTemplateBuilder)
290   NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
291   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
292   NS_INTERFACE_MAP_ENTRY(nsIObserver)
293   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULTemplateBuilder)
294   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULTemplateBuilder)
295 NS_INTERFACE_MAP_END
296 
297 //----------------------------------------------------------------------
298 //
299 // nsIXULTemplateBuilder methods
300 //
301 
302 NS_IMETHODIMP
303 nsXULTemplateBuilder::GetRoot(nsIDOMElement** aResult)
304 {
305     if (mRoot) {
306         return CallQueryInterface(mRoot, aResult);
307     }
308     *aResult = nullptr;
309     return NS_OK;
310 }
311 
312 NS_IMETHODIMP
GetDatasource(nsISupports ** aResult)313 nsXULTemplateBuilder::GetDatasource(nsISupports** aResult)
314 {
315     if (mCompDB)
316         NS_ADDREF(*aResult = mCompDB);
317     else
318         NS_IF_ADDREF(*aResult = mDataSource);
319     return NS_OK;
320 }
321 
322 NS_IMETHODIMP
SetDatasource(nsISupports * aResult)323 nsXULTemplateBuilder::SetDatasource(nsISupports* aResult)
324 {
325     mDataSource = aResult;
326     mCompDB = do_QueryInterface(mDataSource);
327 
328     return Rebuild();
329 }
330 
331 NS_IMETHODIMP
GetDatabase(nsIRDFCompositeDataSource ** aResult)332 nsXULTemplateBuilder::GetDatabase(nsIRDFCompositeDataSource** aResult)
333 {
334     NS_IF_ADDREF(*aResult = mCompDB);
335     return NS_OK;
336 }
337 
338 NS_IMETHODIMP
GetQueryProcessor(nsIXULTemplateQueryProcessor ** aResult)339 nsXULTemplateBuilder::GetQueryProcessor(nsIXULTemplateQueryProcessor** aResult)
340 {
341     NS_IF_ADDREF(*aResult = mQueryProcessor.get());
342     return NS_OK;
343 }
344 
345 NS_IMETHODIMP
AddRuleFilter(nsIDOMNode * aRule,nsIXULTemplateRuleFilter * aFilter)346 nsXULTemplateBuilder::AddRuleFilter(nsIDOMNode* aRule, nsIXULTemplateRuleFilter* aFilter)
347 {
348     if (!aRule || !aFilter)
349         return NS_ERROR_NULL_POINTER;
350 
351     // a custom rule filter may be added, one for each rule. If a new one is
352     // added, it replaces the old one. Look for the right rule and set its
353     // filter
354 
355     int32_t count = mQuerySets.Length();
356     for (int32_t q = 0; q < count; q++) {
357         nsTemplateQuerySet* queryset = mQuerySets[q];
358 
359         int16_t rulecount = queryset->RuleCount();
360         for (int16_t r = 0; r < rulecount; r++) {
361             nsTemplateRule* rule = queryset->GetRuleAt(r);
362 
363             nsCOMPtr<nsIDOMNode> rulenode;
364             rule->GetRuleNode(getter_AddRefs(rulenode));
365             if (aRule == rulenode) {
366                 rule->SetRuleFilter(aFilter);
367                 return NS_OK;
368             }
369         }
370     }
371 
372     return NS_OK;
373 }
374 
375 NS_IMETHODIMP
Rebuild()376 nsXULTemplateBuilder::Rebuild()
377 {
378     int32_t i;
379 
380     for (i = mListeners.Count() - 1; i >= 0; --i) {
381         mListeners[i]->WillRebuild(this);
382     }
383 
384     nsresult rv = RebuildAll();
385 
386     for (i = mListeners.Count() - 1; i >= 0; --i) {
387         mListeners[i]->DidRebuild(this);
388     }
389 
390     return rv;
391 }
392 
393 NS_IMETHODIMP
Refresh()394 nsXULTemplateBuilder::Refresh()
395 {
396     nsresult rv;
397 
398     if (!mCompDB)
399         return NS_ERROR_FAILURE;
400 
401     nsCOMPtr<nsISimpleEnumerator> dslist;
402     rv = mCompDB->GetDataSources(getter_AddRefs(dslist));
403     NS_ENSURE_SUCCESS(rv, rv);
404 
405     bool hasMore;
406     nsCOMPtr<nsISupports> next;
407     nsCOMPtr<nsIRDFRemoteDataSource> rds;
408 
409     while(NS_SUCCEEDED(dslist->HasMoreElements(&hasMore)) && hasMore) {
410         dslist->GetNext(getter_AddRefs(next));
411         if (next && (rds = do_QueryInterface(next))) {
412             rds->Refresh(false);
413         }
414     }
415 
416     // XXXbsmedberg: it would be kinda nice to install an async nsIRDFXMLSink
417     // observer and call rebuild() once the load is complete. See bug 254600.
418 
419     return NS_OK;
420 }
421 
422 NS_IMETHODIMP
Init(nsIContent * aElement)423 nsXULTemplateBuilder::Init(nsIContent* aElement)
424 {
425     NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
426     mRoot = aElement;
427 
428     nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc();
429     NS_ASSERTION(doc, "element has no document");
430     if (! doc)
431         return NS_ERROR_UNEXPECTED;
432 
433     bool shouldDelay;
434     nsresult rv = LoadDataSources(doc, &shouldDelay);
435 
436     if (NS_SUCCEEDED(rv)) {
437         StartObserving(doc);
438     }
439 
440     return rv;
441 }
442 
443 NS_IMETHODIMP
CreateContents(nsIContent * aElement,bool aForceCreation)444 nsXULTemplateBuilder::CreateContents(nsIContent* aElement, bool aForceCreation)
445 {
446     return NS_OK;
447 }
448 
449 NS_IMETHODIMP
HasGeneratedContent(nsIRDFResource * aResource,nsIAtom * aTag,bool * aGenerated)450 nsXULTemplateBuilder::HasGeneratedContent(nsIRDFResource* aResource,
451                                           nsIAtom* aTag,
452                                           bool* aGenerated)
453 {
454     *aGenerated = false;
455     return NS_OK;
456 }
457 
458 NS_IMETHODIMP
AddResult(nsIXULTemplateResult * aResult,nsIDOMNode * aQueryNode)459 nsXULTemplateBuilder::AddResult(nsIXULTemplateResult* aResult,
460                                 nsIDOMNode* aQueryNode)
461 {
462     NS_ENSURE_ARG_POINTER(aResult);
463     NS_ENSURE_ARG_POINTER(aQueryNode);
464 
465     return UpdateResult(nullptr, aResult, aQueryNode);
466 }
467 
468 NS_IMETHODIMP
RemoveResult(nsIXULTemplateResult * aResult)469 nsXULTemplateBuilder::RemoveResult(nsIXULTemplateResult* aResult)
470 {
471     NS_ENSURE_ARG_POINTER(aResult);
472 
473     return UpdateResult(aResult, nullptr, nullptr);
474 }
475 
476 NS_IMETHODIMP
ReplaceResult(nsIXULTemplateResult * aOldResult,nsIXULTemplateResult * aNewResult,nsIDOMNode * aQueryNode)477 nsXULTemplateBuilder::ReplaceResult(nsIXULTemplateResult* aOldResult,
478                                     nsIXULTemplateResult* aNewResult,
479                                     nsIDOMNode* aQueryNode)
480 {
481     NS_ENSURE_ARG_POINTER(aOldResult);
482     NS_ENSURE_ARG_POINTER(aNewResult);
483     NS_ENSURE_ARG_POINTER(aQueryNode);
484 
485     // just remove the old result and then add a new result separately
486 
487     nsresult rv = UpdateResult(aOldResult, nullptr, nullptr);
488     if (NS_FAILED(rv))
489         return rv;
490 
491     return UpdateResult(nullptr, aNewResult, aQueryNode);
492 }
493 
494 nsresult
UpdateResult(nsIXULTemplateResult * aOldResult,nsIXULTemplateResult * aNewResult,nsIDOMNode * aQueryNode)495 nsXULTemplateBuilder::UpdateResult(nsIXULTemplateResult* aOldResult,
496                                    nsIXULTemplateResult* aNewResult,
497                                    nsIDOMNode* aQueryNode)
498 {
499     MOZ_LOG(gXULTemplateLog, LogLevel::Info,
500            ("nsXULTemplateBuilder::UpdateResult %p %p %p",
501            aOldResult, aNewResult, aQueryNode));
502 
503     if (!mRoot || !mQueriesCompiled)
504       return NS_OK;
505 
506     // get the containers where content may be inserted. If
507     // GetInsertionLocations returns false, no container has generated
508     // any content yet so new content should not be generated either. This
509     // will be false if the result applies to content that is in a closed menu
510     // or treeitem for example.
511 
512     nsAutoPtr<nsCOMArray<nsIContent> > insertionPoints;
513     bool mayReplace = GetInsertionLocations(aOldResult ? aOldResult : aNewResult,
514                                               getter_Transfers(insertionPoints));
515     if (! mayReplace)
516         return NS_OK;
517 
518     nsresult rv = NS_OK;
519 
520     nsCOMPtr<nsIRDFResource> oldId, newId;
521     nsTemplateQuerySet* queryset = nullptr;
522 
523     if (aOldResult) {
524         rv = GetResultResource(aOldResult, getter_AddRefs(oldId));
525         if (NS_FAILED(rv))
526             return rv;
527 
528         // Ignore re-entrant builds for content that is currently in our
529         // activation stack.
530         if (IsActivated(oldId))
531             return NS_OK;
532     }
533 
534     if (aNewResult) {
535         rv = GetResultResource(aNewResult, getter_AddRefs(newId));
536         if (NS_FAILED(rv))
537             return rv;
538 
539         // skip results that don't have ids
540         if (! newId)
541             return NS_OK;
542 
543         // Ignore re-entrant builds for content that is currently in our
544         // activation stack.
545         if (IsActivated(newId))
546             return NS_OK;
547 
548         // look for the queryset associated with the supplied query node
549         nsCOMPtr<nsIContent> querycontent = do_QueryInterface(aQueryNode);
550 
551         int32_t count = mQuerySets.Length();
552         for (int32_t q = 0; q < count; q++) {
553             nsTemplateQuerySet* qs = mQuerySets[q];
554             if (qs->mQueryNode == querycontent) {
555                 queryset = qs;
556                 break;
557             }
558         }
559 
560         if (! queryset)
561             return NS_OK;
562     }
563 
564     if (insertionPoints) {
565         // iterate over each insertion point and add or remove the result from
566         // that container
567         uint32_t count = insertionPoints->Count();
568         for (uint32_t t = 0; t < count; t++) {
569             nsCOMPtr<nsIContent> insertionPoint = insertionPoints->SafeObjectAt(t);
570             if (insertionPoint) {
571                 rv = UpdateResultInContainer(aOldResult, aNewResult, queryset,
572                                              oldId, newId, insertionPoint);
573                 if (NS_FAILED(rv))
574                     return rv;
575             }
576         }
577     }
578     else {
579         // The tree builder doesn't use insertion points, so no insertion
580         // points will be set. In this case, just update the one result.
581         rv = UpdateResultInContainer(aOldResult, aNewResult, queryset,
582                                      oldId, newId, nullptr);
583     }
584 
585     return NS_OK;
586 }
587 
588 nsresult
UpdateResultInContainer(nsIXULTemplateResult * aOldResult,nsIXULTemplateResult * aNewResult,nsTemplateQuerySet * aQuerySet,nsIRDFResource * aOldId,nsIRDFResource * aNewId,nsIContent * aInsertionPoint)589 nsXULTemplateBuilder::UpdateResultInContainer(nsIXULTemplateResult* aOldResult,
590                                               nsIXULTemplateResult* aNewResult,
591                                               nsTemplateQuerySet* aQuerySet,
592                                               nsIRDFResource* aOldId,
593                                               nsIRDFResource* aNewId,
594                                               nsIContent* aInsertionPoint)
595 {
596     // This method takes a result that no longer applies (aOldResult) and
597     // replaces it with a new result (aNewResult). Either may be null
598     // indicating to just remove a result or add a new one without replacing.
599     //
600     // Matches are stored in the hashtable mMatchMap, keyed by result id. If
601     // there is more than one query, or the same id is found in different
602     // containers, the values in the hashtable will be a linked list of all
603     // the matches for that id. The matches are sorted according to the
604     // queries they are associated with. Matches for earlier queries in the
605     // template take priority over matches from later queries. The priority
606     // for a match is determined from the match's QuerySetPriority method.
607     // The first query has a priority 0, and higher numbers are for later
608     // queries with successively higher priorities. Thus, a match takes
609     // precedence if it has a lower priority than another. If there is only
610     // one query or container, then the match doesn't have any linked items.
611     //
612     // Matches are nsTemplateMatch objects. They are wrappers around
613     // nsIXULTemplateResult result objects and are created with
614     // nsTemplateMatch::Create below. The aQuerySet argument specifies which
615     // query the match is associated with.
616     //
617     // When a result id exists in multiple containers, the match's mContainer
618     // field is set to the container it corresponds to. The aInsertionPoint
619     // argument specifies which container is being updated. Even though they
620     // are stored in the same linked list as other matches of the same id, the
621     // matches for different containers are treated separately. They are only
622     // stored in the same hashtable to avoid a more complex data structure, as
623     // the use of the same id in multiple containers isn't a common occurance.
624     //
625     // Only one match with a given id per container is active at a time. When
626     // a match is active, content is generated for it. When a match is
627     // inactive, content is not generated for it. A match becomes active if
628     // another match with the same id and container with a lower priority
629     // isn't already active, and the match has a rule or conditions clause
630     // which evaluates to true. The former is checked by comparing the value
631     // of the QuerySetPriority method of the match with earlier matches. The
632     // latter is checked with the DetermineMatchedRule method.
633     //
634     // Naturally, if a match with a lower priority is active, it overrides
635     // the new match, so the new match is hooked up into the match linked
636     // list as inactive, and no content is generated for it. If a match with a
637     // higher priority is active, and the new match's conditions evaluate
638     // to true, then this existing match with the higher priority needs to have
639     // its generated content removed and replaced with the new match's
640     // generated content.
641     //
642     // Similar situations apply when removing an existing match. If the match
643     // is active, the existing generated content will need to be removed, and
644     // a match of higher priority that is revealed may become active and need
645     // to have content generated.
646     //
647     // Content removal and generation is done by the ReplaceMatch method which
648     // is overridden for the content builder and tree builder to update the
649     // generated output for each type.
650     //
651     // The code below handles all of the various cases and ensures that the
652     // match lists are maintained properly.
653 
654     nsresult rv = NS_OK;
655     int16_t ruleindex;
656     nsTemplateRule* matchedrule = nullptr;
657 
658     // Indicates that the old match was active and must have its content
659     // removed
660     bool oldMatchWasActive = false;
661 
662     // acceptedmatch will be set to a new match that has to have new content
663     // generated for it. If a new match doesn't need to have content
664     // generated, (because for example, a match with a lower priority
665     // already applies), then acceptedmatch will be null, but the match will
666     // be still hooked up into the chain, since it may become active later
667     // as other results are updated.
668     nsTemplateMatch* acceptedmatch = nullptr;
669 
670     // When aOldResult is specified, removematch will be set to the
671     // corresponding match. This match needs to be deleted as it no longer
672     // applies. However, removedmatch will be null when aOldResult is null, or
673     // when no match was found corresponding to aOldResult.
674     nsTemplateMatch* removedmatch = nullptr;
675 
676     // These will be set when aNewResult is specified indicating to add a
677     // result, but will end up replacing an existing match. The former
678     // indicates a match being replaced that was active and had content
679     // generated for it, while the latter indicates a match that wasn't active
680     // and just needs to be deleted. Both may point to different matches. For
681     // example, if the new match becomes active, replacing an inactive match,
682     // the inactive match will need to be deleted. However, if another match
683     // with a higher priority is active, the new match will override it, so
684     // content will need to be generated for the new match and removed for
685     // this existing active match.
686     nsTemplateMatch* replacedmatch = nullptr;
687     nsTemplateMatch* replacedmatchtodelete = nullptr;
688 
689     if (aOldResult) {
690         nsTemplateMatch* firstmatch;
691         if (mMatchMap.Get(aOldId, &firstmatch)) {
692             nsTemplateMatch* oldmatch = firstmatch;
693             nsTemplateMatch* prevmatch = nullptr;
694 
695             // look for the right match if there was more than one
696             while (oldmatch && (oldmatch->mResult != aOldResult)) {
697                 prevmatch = oldmatch;
698                 oldmatch = oldmatch->mNext;
699             }
700 
701             if (oldmatch) {
702                 nsTemplateMatch* findmatch = oldmatch->mNext;
703 
704                 // Keep a reference so that linked list can be hooked up at
705                 // the end in case an error occurs.
706                 nsTemplateMatch* nextmatch = findmatch;
707 
708                 if (oldmatch->IsActive()) {
709                     // Indicate that the old match was active so its content
710                     // will be removed later.
711                     oldMatchWasActive = true;
712 
713                     // The match being removed is the active match, so scan
714                     // through the later matches to determine if one should
715                     // now become the active match.
716                     while (findmatch) {
717                         // only other matches with the same container should
718                         // now match, leave other containers alone
719                         if (findmatch->GetContainer() == aInsertionPoint) {
720                             nsTemplateQuerySet* qs =
721                                 mQuerySets[findmatch->QuerySetPriority()];
722 
723                             DetermineMatchedRule(aInsertionPoint, findmatch->mResult,
724                                                  qs, &matchedrule, &ruleindex);
725 
726                             if (matchedrule) {
727                                 rv = findmatch->RuleMatched(qs,
728                                                             matchedrule, ruleindex,
729                                                             findmatch->mResult);
730                                 if (NS_FAILED(rv))
731                                     return rv;
732 
733                                 acceptedmatch = findmatch;
734                                 break;
735                             }
736                         }
737 
738                         findmatch = findmatch->mNext;
739                     }
740                 }
741 
742                 if (oldmatch == firstmatch) {
743                     // the match to remove is at the beginning
744                     if (oldmatch->mNext) {
745                         mMatchMap.Put(aOldId, oldmatch->mNext);
746                     }
747                     else {
748                         mMatchMap.Remove(aOldId);
749                     }
750                 }
751 
752                 if (prevmatch)
753                     prevmatch->mNext = nextmatch;
754 
755                 removedmatch = oldmatch;
756                 if (mFlags & eLoggingEnabled)
757                     OutputMatchToLog(aOldId, removedmatch, false);
758             }
759         }
760     }
761 
762     nsTemplateMatch *newmatch = nullptr;
763     if (aNewResult) {
764         // only allow a result to be inserted into containers with a matching tag
765         nsIAtom* tag = aQuerySet->GetTag();
766         if (aInsertionPoint && tag &&
767             tag != aInsertionPoint->NodeInfo()->NameAtom())
768             return NS_OK;
769 
770         int32_t findpriority = aQuerySet->Priority();
771 
772         newmatch = nsTemplateMatch::Create(findpriority,
773                                            aNewResult, aInsertionPoint);
774         if (!newmatch)
775             return NS_ERROR_OUT_OF_MEMORY;
776 
777         nsTemplateMatch* firstmatch;
778         if (mMatchMap.Get(aNewId, &firstmatch)) {
779             bool hasEarlierActiveMatch = false;
780 
781             // Scan through the existing matches to find where the new one
782             // should be inserted. oldmatch will be set to the old match for
783             // the same query and prevmatch will be set to the match before it.
784             nsTemplateMatch* prevmatch = nullptr;
785             nsTemplateMatch* oldmatch = firstmatch;
786             while (oldmatch) {
787                 // Break out once we've reached a query in the list with a
788                 // lower priority. The new match will be inserted at this
789                 // location so that the match list is sorted by priority.
790                 int32_t priority = oldmatch->QuerySetPriority();
791                 if (priority > findpriority) {
792                     oldmatch = nullptr;
793                     break;
794                 }
795 
796                 // look for matches that belong in the same container
797                 if (oldmatch->GetContainer() == aInsertionPoint) {
798                     if (priority == findpriority)
799                         break;
800 
801                     // If a match with a lower priority is active, the new
802                     // match can't replace it.
803                     if (oldmatch->IsActive())
804                         hasEarlierActiveMatch = true;
805                 }
806 
807                 prevmatch = oldmatch;
808                 oldmatch = oldmatch->mNext;
809             }
810 
811             // At this point, oldmatch will either be null, or set to a match
812             // with the same container and priority. If set, oldmatch will
813             // need to be replaced by newmatch.
814 
815             if (oldmatch)
816                 newmatch->mNext = oldmatch->mNext;
817             else if (prevmatch)
818                 newmatch->mNext = prevmatch->mNext;
819             else
820                 newmatch->mNext = firstmatch;
821 
822             // hasEarlierActiveMatch will be set to true if a match with a
823             // lower priority was found. The new match won't replace it in
824             // this case. If hasEarlierActiveMatch is false, then the new match
825             // may be become active if it matches one of the rules, and will
826             // generate output. It's also possible however, that a match with
827             // the same priority already exists, which means that the new match
828             // will replace the old one. In this case, oldmatch will be set to
829             // the old match. The content for the old match must be removed and
830             // content for the new match generated in its place.
831             if (! hasEarlierActiveMatch) {
832                 // If the old match was the active match, set replacedmatch to
833                 // indicate that it needs its content removed.
834                 if (oldmatch) {
835                     if (oldmatch->IsActive())
836                         replacedmatch = oldmatch;
837                     replacedmatchtodelete = oldmatch;
838                 }
839 
840                 // check if the new result matches the rules
841                 rv = DetermineMatchedRule(aInsertionPoint, newmatch->mResult,
842                                           aQuerySet, &matchedrule, &ruleindex);
843                 if (NS_FAILED(rv)) {
844                     nsTemplateMatch::Destroy(newmatch, false);
845                     return rv;
846                 }
847 
848                 if (matchedrule) {
849                     rv = newmatch->RuleMatched(aQuerySet,
850                                                matchedrule, ruleindex,
851                                                newmatch->mResult);
852                     if (NS_FAILED(rv)) {
853                         nsTemplateMatch::Destroy(newmatch, false);
854                         return rv;
855                     }
856 
857                     // acceptedmatch may have been set in the block handling
858                     // aOldResult earlier. If so, we would only get here when
859                     // that match has a higher priority than this new match.
860                     // As only one match can have content generated for it, it
861                     // is OK to set acceptedmatch here to the new match,
862                     // ignoring the other one.
863                     acceptedmatch = newmatch;
864 
865                     // Clear the matched state of the later results for the
866                     // same container.
867                     nsTemplateMatch* clearmatch = newmatch->mNext;
868                     while (clearmatch) {
869                         if (clearmatch->GetContainer() == aInsertionPoint &&
870                             clearmatch->IsActive()) {
871                             clearmatch->SetInactive();
872                             // Replacedmatch should be null here. If not, it
873                             // means that two matches were active which isn't
874                             // a valid state
875                             NS_ASSERTION(!replacedmatch,
876                                          "replaced match already set");
877                             replacedmatch = clearmatch;
878                             break;
879                         }
880                         clearmatch = clearmatch->mNext;
881                     }
882                 }
883                 else if (oldmatch && oldmatch->IsActive()) {
884                     // The result didn't match the rules, so look for a later
885                     // one. However, only do this if the old match was the
886                     // active match.
887                     newmatch = newmatch->mNext;
888                     while (newmatch) {
889                         if (newmatch->GetContainer() == aInsertionPoint) {
890                             rv = DetermineMatchedRule(aInsertionPoint, newmatch->mResult,
891                                                       aQuerySet, &matchedrule, &ruleindex);
892                             if (NS_FAILED(rv)) {
893                                 nsTemplateMatch::Destroy(newmatch, false);
894                                 return rv;
895                             }
896 
897                             if (matchedrule) {
898                                 rv = newmatch->RuleMatched(aQuerySet,
899                                                            matchedrule, ruleindex,
900                                                            newmatch->mResult);
901                                 if (NS_FAILED(rv)) {
902                                     nsTemplateMatch::Destroy(newmatch, false);
903                                     return rv;
904                                 }
905 
906                                 acceptedmatch = newmatch;
907                                 break;
908                             }
909                         }
910 
911                         newmatch = newmatch->mNext;
912                     }
913                 }
914 
915                 // put the match in the map if there isn't a previous match
916                 if (! prevmatch) {
917                     mMatchMap.Put(aNewId, newmatch);
918                 }
919             }
920 
921             // hook up the match last in case an error occurs
922             if (prevmatch)
923                 prevmatch->mNext = newmatch;
924         }
925         else {
926             // The id is not used in the hashtable yet so create a new match
927             // and add it to the hashtable.
928             rv = DetermineMatchedRule(aInsertionPoint, aNewResult,
929                                       aQuerySet, &matchedrule, &ruleindex);
930             if (NS_FAILED(rv)) {
931                 nsTemplateMatch::Destroy(newmatch, false);
932                 return rv;
933             }
934 
935             if (matchedrule) {
936                 rv = newmatch->RuleMatched(aQuerySet, matchedrule,
937                                            ruleindex, aNewResult);
938                 if (NS_FAILED(rv)) {
939                     nsTemplateMatch::Destroy(newmatch, false);
940                     return rv;
941                 }
942 
943                 acceptedmatch = newmatch;
944             }
945 
946             mMatchMap.Put(aNewId, newmatch);
947         }
948     }
949 
950     // The ReplaceMatch method is builder specific and removes the generated
951     // content for a match.
952 
953     // Remove the content for a match that was active and needs to be replaced.
954     if (replacedmatch) {
955         rv = ReplaceMatch(replacedmatch->mResult, nullptr, nullptr,
956                           aInsertionPoint);
957 
958         if (mFlags & eLoggingEnabled)
959             OutputMatchToLog(aNewId, replacedmatch, false);
960     }
961 
962     // remove a match that needs to be deleted.
963     if (replacedmatchtodelete)
964         nsTemplateMatch::Destroy(replacedmatchtodelete, true);
965 
966     // If the old match was active, the content for it needs to be removed.
967     // If the old match was not active, it shouldn't have had any content,
968     // so just pass null to ReplaceMatch. If acceptedmatch was set, then
969     // content needs to be generated for a new match.
970     if (oldMatchWasActive || acceptedmatch)
971         rv = ReplaceMatch(oldMatchWasActive ? aOldResult : nullptr,
972                           acceptedmatch, matchedrule, aInsertionPoint);
973 
974     // delete the old match that was replaced
975     if (removedmatch)
976         nsTemplateMatch::Destroy(removedmatch, true);
977 
978     if (mFlags & eLoggingEnabled && newmatch)
979         OutputMatchToLog(aNewId, newmatch, true);
980 
981     return rv;
982 }
983 
984 NS_IMETHODIMP
ResultBindingChanged(nsIXULTemplateResult * aResult)985 nsXULTemplateBuilder::ResultBindingChanged(nsIXULTemplateResult* aResult)
986 {
987     // A binding update is used when only the values of the bindings have
988     // changed, so the same rule still applies. Just synchronize the content.
989     // The new result will have the new values.
990     NS_ENSURE_ARG_POINTER(aResult);
991 
992     if (!mRoot || !mQueriesCompiled)
993       return NS_OK;
994 
995     return SynchronizeResult(aResult);
996 }
997 
998 NS_IMETHODIMP
GetRootResult(nsIXULTemplateResult ** aResult)999 nsXULTemplateBuilder::GetRootResult(nsIXULTemplateResult** aResult)
1000 {
1001   *aResult = mRootResult;
1002   NS_IF_ADDREF(*aResult);
1003   return NS_OK;
1004 }
1005 
1006 NS_IMETHODIMP
GetResultForId(const nsAString & aId,nsIXULTemplateResult ** aResult)1007 nsXULTemplateBuilder::GetResultForId(const nsAString& aId,
1008                                      nsIXULTemplateResult** aResult)
1009 {
1010     if (aId.IsEmpty())
1011         return NS_ERROR_INVALID_ARG;
1012 
1013     nsCOMPtr<nsIRDFResource> resource;
1014     gRDFService->GetUnicodeResource(aId, getter_AddRefs(resource));
1015 
1016     *aResult = nullptr;
1017 
1018     nsTemplateMatch* match;
1019     if (mMatchMap.Get(resource, &match)) {
1020         // find the active match
1021         while (match) {
1022             if (match->IsActive()) {
1023                 *aResult = match->mResult;
1024                 NS_IF_ADDREF(*aResult);
1025                 break;
1026             }
1027             match = match->mNext;
1028         }
1029     }
1030 
1031     return NS_OK;
1032 }
1033 
1034 NS_IMETHODIMP
GetResultForContent(nsIDOMElement * aContent,nsIXULTemplateResult ** aResult)1035 nsXULTemplateBuilder::GetResultForContent(nsIDOMElement* aContent,
1036                                           nsIXULTemplateResult** aResult)
1037 {
1038     *aResult = nullptr;
1039     return NS_OK;
1040 }
1041 
1042 NS_IMETHODIMP
AddListener(nsIXULBuilderListener * aListener)1043 nsXULTemplateBuilder::AddListener(nsIXULBuilderListener* aListener)
1044 {
1045     NS_ENSURE_ARG(aListener);
1046 
1047     if (!mListeners.AppendObject(aListener))
1048         return NS_ERROR_OUT_OF_MEMORY;
1049 
1050     return NS_OK;
1051 }
1052 
1053 NS_IMETHODIMP
RemoveListener(nsIXULBuilderListener * aListener)1054 nsXULTemplateBuilder::RemoveListener(nsIXULBuilderListener* aListener)
1055 {
1056     NS_ENSURE_ARG(aListener);
1057 
1058     mListeners.RemoveObject(aListener);
1059 
1060     return NS_OK;
1061 }
1062 
1063 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)1064 nsXULTemplateBuilder::Observe(nsISupports* aSubject,
1065                               const char* aTopic,
1066                               const char16_t* aData)
1067 {
1068     // Uuuuber hack to clean up circular references that the cycle collector
1069     // doesn't know about. See bug 394514.
1070     if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) {
1071         if (nsCOMPtr<mozIDOMWindow> window = do_QueryInterface(aSubject)) {
1072             nsCOMPtr<nsIDocument> doc =
1073                 nsPIDOMWindowInner::From(window)->GetExtantDoc();
1074             if (doc && doc == mObservedDocument)
1075                 NodeWillBeDestroyed(doc);
1076         }
1077     }
1078     return NS_OK;
1079 }
1080 //----------------------------------------------------------------------
1081 //
1082 // nsIDocumentOberver interface
1083 //
1084 
1085 void
AttributeChanged(nsIDocument * aDocument,Element * aElement,int32_t aNameSpaceID,nsIAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)1086 nsXULTemplateBuilder::AttributeChanged(nsIDocument* aDocument,
1087                                        Element*     aElement,
1088                                        int32_t      aNameSpaceID,
1089                                        nsIAtom*     aAttribute,
1090                                        int32_t      aModType,
1091                                        const nsAttrValue* aOldValue)
1092 {
1093     if (aElement == mRoot && aNameSpaceID == kNameSpaceID_None) {
1094         // Check for a change to the 'ref' attribute on an atom, in which
1095         // case we may need to nuke and rebuild the entire content model
1096         // beneath the element.
1097         if (aAttribute == nsGkAtoms::ref)
1098             nsContentUtils::AddScriptRunner(
1099                 NewRunnableMethod(this, &nsXULTemplateBuilder::RunnableRebuild));
1100 
1101         // Check for a change to the 'datasources' attribute. If so, setup
1102         // mDB by parsing the new value and rebuild.
1103         else if (aAttribute == nsGkAtoms::datasources) {
1104             nsContentUtils::AddScriptRunner(
1105                 NewRunnableMethod(this, &nsXULTemplateBuilder::RunnableLoadAndRebuild));
1106         }
1107     }
1108 }
1109 
1110 void
ContentRemoved(nsIDocument * aDocument,nsIContent * aContainer,nsIContent * aChild,int32_t aIndexInContainer,nsIContent * aPreviousSibling)1111 nsXULTemplateBuilder::ContentRemoved(nsIDocument* aDocument,
1112                                      nsIContent* aContainer,
1113                                      nsIContent* aChild,
1114                                      int32_t aIndexInContainer,
1115                                      nsIContent* aPreviousSibling)
1116 {
1117     if (mRoot && nsContentUtils::ContentIsDescendantOf(mRoot, aChild)) {
1118         RefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this);
1119 
1120         if (mQueryProcessor)
1121             mQueryProcessor->Done();
1122 
1123         // Pass false to Uninit since content is going away anyway
1124         nsContentUtils::AddScriptRunner(
1125             NewRunnableMethod(this, &nsXULTemplateBuilder::UninitFalse));
1126 
1127         MOZ_ASSERT(aDocument == mObservedDocument);
1128         StopObserving();
1129 
1130         nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument);
1131         if (xuldoc)
1132             xuldoc->SetTemplateBuilderFor(mRoot, nullptr);
1133 
1134         // clear the template state when removing content so that template
1135         // content will be regenerated again if the content is reinserted
1136         nsXULElement *xulcontent = nsXULElement::FromContent(mRoot);
1137         if (xulcontent)
1138             xulcontent->ClearTemplateGenerated();
1139 
1140         CleanUp(true);
1141 
1142         mDB = nullptr;
1143         mCompDB = nullptr;
1144         mDataSource = nullptr;
1145     }
1146 }
1147 
1148 void
NodeWillBeDestroyed(const nsINode * aNode)1149 nsXULTemplateBuilder::NodeWillBeDestroyed(const nsINode* aNode)
1150 {
1151     // The call to RemoveObserver could release the last reference to
1152     // |this|, so hold another reference.
1153     RefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this);
1154 
1155     // Break circular references
1156     if (mQueryProcessor)
1157         mQueryProcessor->Done();
1158 
1159     mDataSource = nullptr;
1160     mDB = nullptr;
1161     mCompDB = nullptr;
1162 
1163     nsContentUtils::AddScriptRunner(
1164         NewRunnableMethod(this, &nsXULTemplateBuilder::UninitTrue));
1165 }
1166 
1167 
1168 
1169 
1170 //----------------------------------------------------------------------
1171 //
1172 // Implementation methods
1173 //
1174 
1175 nsresult
LoadDataSources(nsIDocument * aDocument,bool * aShouldDelayBuilding)1176 nsXULTemplateBuilder::LoadDataSources(nsIDocument* aDocument,
1177                                       bool* aShouldDelayBuilding)
1178 {
1179     NS_PRECONDITION(mRoot != nullptr, "not initialized");
1180 
1181     nsresult rv;
1182     bool isRDFQuery = false;
1183 
1184     // we'll set these again later, after we create a new composite ds
1185     mDB = nullptr;
1186     mCompDB = nullptr;
1187     mDataSource = nullptr;
1188 
1189     *aShouldDelayBuilding = false;
1190 
1191     nsAutoString datasources;
1192     mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::datasources, datasources);
1193 
1194     nsAutoString querytype;
1195     mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::querytype, querytype);
1196 
1197     // create the query processor. The querytype attribute on the root element
1198     // may be used to create one of a specific type.
1199 
1200     // XXX should non-chrome be restricted to specific names?
1201     if (querytype.IsEmpty())
1202         querytype.AssignLiteral("rdf");
1203 
1204     if (querytype.EqualsLiteral("rdf")) {
1205         isRDFQuery = true;
1206         mQueryProcessor = new nsXULTemplateQueryProcessorRDF();
1207     }
1208     else if (querytype.EqualsLiteral("xml")) {
1209         mQueryProcessor = new nsXULTemplateQueryProcessorXML();
1210     }
1211     else if (querytype.EqualsLiteral("storage")) {
1212         mQueryProcessor = new nsXULTemplateQueryProcessorStorage();
1213     }
1214     else {
1215         nsAutoCString cid(NS_QUERY_PROCESSOR_CONTRACTID_PREFIX);
1216         AppendUTF16toUTF8(querytype, cid);
1217         mQueryProcessor = do_CreateInstance(cid.get(), &rv);
1218 
1219         if (!mQueryProcessor) {
1220             nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_INVALID_QUERYPROCESSOR);
1221             return rv;
1222         }
1223     }
1224 
1225     rv = LoadDataSourceUrls(aDocument, datasources,
1226                             isRDFQuery, aShouldDelayBuilding);
1227     NS_ENSURE_SUCCESS(rv, rv);
1228 
1229     // Now set the database on the element, so that script writers can
1230     // access it.
1231     nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument);
1232     if (xuldoc)
1233         xuldoc->SetTemplateBuilderFor(mRoot, this);
1234 
1235     if (!mRoot->IsXULElement()) {
1236         // Hmm. This must be an HTML element. Try to set it as a
1237         // JS property "by hand".
1238         InitHTMLTemplateRoot();
1239     }
1240 
1241     return NS_OK;
1242 }
1243 
1244 nsresult
LoadDataSourceUrls(nsIDocument * aDocument,const nsAString & aDataSources,bool aIsRDFQuery,bool * aShouldDelayBuilding)1245 nsXULTemplateBuilder::LoadDataSourceUrls(nsIDocument* aDocument,
1246                                          const nsAString& aDataSources,
1247                                          bool aIsRDFQuery,
1248                                          bool* aShouldDelayBuilding)
1249 {
1250     // Grab the doc's principal...
1251     nsIPrincipal *docPrincipal = aDocument->NodePrincipal();
1252 
1253     NS_ASSERTION(docPrincipal == mRoot->NodePrincipal(),
1254                  "Principal mismatch?  Which one to use?");
1255 
1256     bool isTrusted = false;
1257     nsresult rv = IsSystemPrincipal(docPrincipal, &isTrusted);
1258     NS_ENSURE_SUCCESS(rv, rv);
1259 
1260     // Parse datasources: they are assumed to be a whitespace
1261     // separated list of URIs; e.g.,
1262     //
1263     //     rdf:bookmarks rdf:history http://foo.bar.com/blah.cgi?baz=9
1264     //
1265     nsIURI *docurl = aDocument->GetDocumentURI();
1266 
1267     nsCOMPtr<nsIMutableArray> uriList = do_CreateInstance(NS_ARRAY_CONTRACTID);
1268     if (!uriList)
1269         return NS_ERROR_FAILURE;
1270 
1271     nsAutoString datasources(aDataSources);
1272     uint32_t first = 0;
1273     while (1) {
1274         while (first < datasources.Length() && nsCRT::IsAsciiSpace(datasources.CharAt(first)))
1275             ++first;
1276 
1277         if (first >= datasources.Length())
1278             break;
1279 
1280         uint32_t last = first;
1281         while (last < datasources.Length() && !nsCRT::IsAsciiSpace(datasources.CharAt(last)))
1282             ++last;
1283 
1284         nsAutoString uriStr;
1285         datasources.Mid(uriStr, first, last - first);
1286         first = last + 1;
1287 
1288         // A special 'dummy' datasource
1289         if (uriStr.EqualsLiteral("rdf:null"))
1290             continue;
1291 
1292         if (uriStr.CharAt(0) == '#') {
1293             // ok, the datasource is certainly a node of the current document
1294             nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(aDocument);
1295             nsCOMPtr<nsIDOMElement> dsnode;
1296 
1297             domdoc->GetElementById(Substring(uriStr, 1),
1298                                    getter_AddRefs(dsnode));
1299 
1300             if (dsnode)
1301                 uriList->AppendElement(dsnode, false);
1302             continue;
1303         }
1304 
1305         // N.B. that `failure' (e.g., because it's an unknown
1306         // protocol) leaves uriStr unaltered.
1307         NS_MakeAbsoluteURI(uriStr, uriStr, docurl);
1308 
1309         nsCOMPtr<nsIURI> uri;
1310         rv = NS_NewURI(getter_AddRefs(uri), uriStr);
1311         if (NS_FAILED(rv) || !uri)
1312             continue; // Necko will barf if our URI is weird
1313 
1314         // don't add the uri to the list if the document is not allowed to
1315         // load it
1316         if (!isTrusted && NS_FAILED(docPrincipal->CheckMayLoad(uri, true, false)))
1317           continue;
1318 
1319         uriList->AppendElement(uri, false);
1320     }
1321 
1322     nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(mRoot);
1323     rv = mQueryProcessor->GetDatasource(uriList,
1324                                         rootNode,
1325                                         isTrusted,
1326                                         this,
1327                                         aShouldDelayBuilding,
1328                                         getter_AddRefs(mDataSource));
1329     NS_ENSURE_SUCCESS(rv, rv);
1330 
1331     if (aIsRDFQuery && mDataSource) {
1332         // check if we were given an inference engine type
1333         nsCOMPtr<nsIRDFInferDataSource> inferDB = do_QueryInterface(mDataSource);
1334         if (inferDB) {
1335             nsCOMPtr<nsIRDFDataSource> ds;
1336             inferDB->GetBaseDataSource(getter_AddRefs(ds));
1337             if (ds)
1338                 mCompDB = do_QueryInterface(ds);
1339         }
1340 
1341         if (!mCompDB)
1342             mCompDB = do_QueryInterface(mDataSource);
1343 
1344         mDB = do_QueryInterface(mDataSource);
1345     }
1346 
1347     if (!mDB && isTrusted) {
1348         gRDFService->GetDataSource("rdf:local-store", getter_AddRefs(mDB));
1349     }
1350 
1351     return NS_OK;
1352 }
1353 
1354 nsresult
InitHTMLTemplateRoot()1355 nsXULTemplateBuilder::InitHTMLTemplateRoot()
1356 {
1357     // Use XPConnect and the JS APIs to whack mDB and this as the
1358     // 'database' and 'builder' properties onto aElement.
1359     nsresult rv;
1360 
1361     nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc();
1362     NS_ASSERTION(doc, "no document");
1363     if (! doc)
1364         return NS_ERROR_UNEXPECTED;
1365 
1366     nsCOMPtr<nsIScriptGlobalObject> global =
1367       do_QueryInterface(doc->GetWindow());
1368     if (! global)
1369         return NS_ERROR_UNEXPECTED;
1370 
1371     nsCOMPtr<nsIGlobalObject> innerWin =
1372         do_QueryInterface(doc->GetInnerWindow());
1373 
1374     // We are going to run script via JS_SetProperty, so we need a script entry
1375     // point, but as this is XUL related it does not appear in the HTML spec.
1376     AutoEntryScript aes(innerWin, "nsXULTemplateBuilder creation", true);
1377     JSContext* jscontext = aes.cx();
1378 
1379     JS::Rooted<JS::Value> v(jscontext);
1380     rv = nsContentUtils::WrapNative(jscontext, mRoot, mRoot, &v);
1381     NS_ENSURE_SUCCESS(rv, rv);
1382 
1383     JS::Rooted<JSObject*> jselement(jscontext, v.toObjectOrNull());
1384 
1385     if (mDB) {
1386         // database
1387         JS::Rooted<JS::Value> jsdatabase(jscontext);
1388         rv = nsContentUtils::WrapNative(jscontext, mDB,
1389                                         &NS_GET_IID(nsIRDFCompositeDataSource),
1390                                         &jsdatabase);
1391         NS_ENSURE_SUCCESS(rv, rv);
1392 
1393         bool ok = JS_SetProperty(jscontext, jselement, "database", jsdatabase);
1394         NS_ASSERTION(ok, "unable to set database property");
1395         if (! ok)
1396             return NS_ERROR_FAILURE;
1397     }
1398 
1399     {
1400         // builder
1401         JS::Rooted<JS::Value> jsbuilder(jscontext);
1402         rv = nsContentUtils::WrapNative(jscontext,
1403                                         static_cast<nsIXULTemplateBuilder*>(this),
1404                                         &NS_GET_IID(nsIXULTemplateBuilder),
1405                                         &jsbuilder);
1406         NS_ENSURE_SUCCESS(rv, rv);
1407 
1408         bool ok = JS_SetProperty(jscontext, jselement, "builder", jsbuilder);
1409         if (! ok)
1410             return NS_ERROR_FAILURE;
1411     }
1412 
1413     return NS_OK;
1414 }
1415 
1416 nsresult
DetermineMatchedRule(nsIContent * aContainer,nsIXULTemplateResult * aResult,nsTemplateQuerySet * aQuerySet,nsTemplateRule ** aMatchedRule,int16_t * aRuleIndex)1417 nsXULTemplateBuilder::DetermineMatchedRule(nsIContent *aContainer,
1418                                            nsIXULTemplateResult* aResult,
1419                                            nsTemplateQuerySet* aQuerySet,
1420                                            nsTemplateRule** aMatchedRule,
1421                                            int16_t *aRuleIndex)
1422 {
1423     // iterate through the rules and look for one that the result matches
1424     int16_t count = aQuerySet->RuleCount();
1425     for (int16_t r = 0; r < count; r++) {
1426         nsTemplateRule* rule = aQuerySet->GetRuleAt(r);
1427         // If a tag was specified, it must match the tag of the container
1428         // where content is being inserted.
1429         nsIAtom* tag = rule->GetTag();
1430         if ((!aContainer || !tag ||
1431              tag == aContainer->NodeInfo()->NameAtom()) &&
1432             rule->CheckMatch(aResult)) {
1433             *aMatchedRule = rule;
1434             *aRuleIndex = r;
1435             return NS_OK;
1436         }
1437     }
1438 
1439     *aRuleIndex = -1;
1440     *aMatchedRule = nullptr;
1441     return NS_OK;
1442 }
1443 
1444 void
ParseAttribute(const nsAString & aAttributeValue,void (* aVariableCallback)(nsXULTemplateBuilder *,const nsAString &,void *),void (* aTextCallback)(nsXULTemplateBuilder *,const nsAString &,void *),void * aClosure)1445 nsXULTemplateBuilder::ParseAttribute(const nsAString& aAttributeValue,
1446                                      void (*aVariableCallback)(nsXULTemplateBuilder*, const nsAString&, void*),
1447                                      void (*aTextCallback)(nsXULTemplateBuilder*, const nsAString&, void*),
1448                                      void* aClosure)
1449 {
1450     nsAString::const_iterator done_parsing;
1451     aAttributeValue.EndReading(done_parsing);
1452 
1453     nsAString::const_iterator iter;
1454     aAttributeValue.BeginReading(iter);
1455 
1456     nsAString::const_iterator mark(iter), backup(iter);
1457 
1458     for (; iter != done_parsing; backup = ++iter) {
1459         // A variable is either prefixed with '?' (in the extended
1460         // syntax) or "rdf:" (in the simple syntax).
1461         bool isvar;
1462         if (*iter == char16_t('?') && (++iter != done_parsing)) {
1463             isvar = true;
1464         }
1465         else if ((*iter == char16_t('r') && (++iter != done_parsing)) &&
1466                  (*iter == char16_t('d') && (++iter != done_parsing)) &&
1467                  (*iter == char16_t('f') && (++iter != done_parsing)) &&
1468                  (*iter == char16_t(':') && (++iter != done_parsing))) {
1469             isvar = true;
1470         }
1471         else {
1472             isvar = false;
1473         }
1474 
1475         if (! isvar) {
1476             // It's not a variable, or we ran off the end of the
1477             // string after the initial variable prefix. Since we may
1478             // have slurped down some characters before realizing that
1479             // fact, back up to the point where we started.
1480             iter = backup;
1481             continue;
1482         }
1483         else if (backup != mark && aTextCallback) {
1484             // Okay, we've found a variable, and there's some vanilla
1485             // text that's been buffered up. Flush it.
1486             (*aTextCallback)(this, Substring(mark, backup), aClosure);
1487         }
1488 
1489         if (*iter == char16_t('?')) {
1490             // Well, it was not really a variable, but "??". We use one
1491             // question mark (the second one, actually) literally.
1492             mark = iter;
1493             continue;
1494         }
1495 
1496         // Construct a substring that is the symbol we need to look up
1497         // in the rule's symbol table. The symbol is terminated by a
1498         // space character, a caret, or the end of the string,
1499         // whichever comes first.
1500         nsAString::const_iterator first(backup);
1501 
1502         char16_t c = 0;
1503         while (iter != done_parsing) {
1504             c = *iter;
1505             if ((c == char16_t(' ')) || (c == char16_t('^')))
1506                 break;
1507 
1508             ++iter;
1509         }
1510 
1511         nsAString::const_iterator last(iter);
1512 
1513         // Back up so we don't consume the terminating character
1514         // *unless* the terminating character was a caret: the caret
1515         // means "concatenate with no space in between".
1516         if (c != char16_t('^'))
1517             --iter;
1518 
1519         (*aVariableCallback)(this, Substring(first, last), aClosure);
1520         mark = iter;
1521         ++mark;
1522     }
1523 
1524     if (backup != mark && aTextCallback) {
1525         // If there's any text left over, then fire the text callback
1526         (*aTextCallback)(this, Substring(mark, backup), aClosure);
1527     }
1528 }
1529 
1530 
1531 struct MOZ_STACK_CLASS SubstituteTextClosure {
SubstituteTextClosureSubstituteTextClosure1532     SubstituteTextClosure(nsIXULTemplateResult* aResult, nsAString& aString)
1533         : result(aResult), str(aString) {}
1534 
1535     // some datasources are lazily initialized or modified while values are
1536     // being retrieved, causing results to be removed. Due to this, hold a
1537     // strong reference to the result.
1538     nsCOMPtr<nsIXULTemplateResult> result;
1539     nsAString& str;
1540 };
1541 
1542 nsresult
SubstituteText(nsIXULTemplateResult * aResult,const nsAString & aAttributeValue,nsAString & aString)1543 nsXULTemplateBuilder::SubstituteText(nsIXULTemplateResult* aResult,
1544                                      const nsAString& aAttributeValue,
1545                                      nsAString& aString)
1546 {
1547     // See if it's the special value "..."
1548     if (aAttributeValue.EqualsLiteral("...")) {
1549         aResult->GetId(aString);
1550         return NS_OK;
1551     }
1552 
1553     // Reasonable guess at how big it should be
1554     aString.SetCapacity(aAttributeValue.Length());
1555 
1556     SubstituteTextClosure closure(aResult, aString);
1557     ParseAttribute(aAttributeValue,
1558                    SubstituteTextReplaceVariable,
1559                    SubstituteTextAppendText,
1560                    &closure);
1561 
1562     return NS_OK;
1563 }
1564 
1565 
1566 void
SubstituteTextAppendText(nsXULTemplateBuilder * aThis,const nsAString & aText,void * aClosure)1567 nsXULTemplateBuilder::SubstituteTextAppendText(nsXULTemplateBuilder* aThis,
1568                                                const nsAString& aText,
1569                                                void* aClosure)
1570 {
1571     // Append aString to the closure's result
1572     SubstituteTextClosure* c = static_cast<SubstituteTextClosure*>(aClosure);
1573     c->str.Append(aText);
1574 }
1575 
1576 void
SubstituteTextReplaceVariable(nsXULTemplateBuilder * aThis,const nsAString & aVariable,void * aClosure)1577 nsXULTemplateBuilder::SubstituteTextReplaceVariable(nsXULTemplateBuilder* aThis,
1578                                                     const nsAString& aVariable,
1579                                                     void* aClosure)
1580 {
1581     // Substitute the value for the variable and append to the
1582     // closure's result.
1583     SubstituteTextClosure* c = static_cast<SubstituteTextClosure*>(aClosure);
1584 
1585     nsAutoString replacementText;
1586 
1587     // The symbol "rdf:*" is special, and means "this guy's URI"
1588     if (aVariable.EqualsLiteral("rdf:*")){
1589         c->result->GetId(replacementText);
1590     }
1591     else {
1592         // Got a variable; get the value it's assigned to
1593         nsCOMPtr<nsIAtom> var = NS_Atomize(aVariable);
1594         c->result->GetBindingFor(var, replacementText);
1595     }
1596 
1597     c->str += replacementText;
1598 }
1599 
1600 bool
IsTemplateElement(nsIContent * aContent)1601 nsXULTemplateBuilder::IsTemplateElement(nsIContent* aContent)
1602 {
1603     return aContent->NodeInfo()->Equals(nsGkAtoms::_template,
1604                                         kNameSpaceID_XUL);
1605 }
1606 
1607 nsresult
GetTemplateRoot(nsIContent ** aResult)1608 nsXULTemplateBuilder::GetTemplateRoot(nsIContent** aResult)
1609 {
1610     NS_PRECONDITION(mRoot != nullptr, "not initialized");
1611     if (! mRoot)
1612         return NS_ERROR_NOT_INITIALIZED;
1613 
1614     // First, check and see if the root has a template attribute. This
1615     // allows a template to be specified "out of line"; e.g.,
1616     //
1617     //   <window>
1618     //     <foo template="MyTemplate">...</foo>
1619     //     <template id="MyTemplate">...</template>
1620     //   </window>
1621     //
1622     nsAutoString templateID;
1623     mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::_template, templateID);
1624 
1625     if (! templateID.IsEmpty()) {
1626         nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mRoot->GetComposedDoc());
1627         if (! domDoc)
1628             return NS_OK;
1629 
1630         nsCOMPtr<nsIDOMElement> domElement;
1631         domDoc->GetElementById(templateID, getter_AddRefs(domElement));
1632 
1633         if (domElement) {
1634             nsCOMPtr<nsIContent> content = do_QueryInterface(domElement);
1635             NS_ENSURE_STATE(content &&
1636                             !nsContentUtils::ContentIsDescendantOf(mRoot,
1637                                                                    content));
1638             content.forget(aResult);
1639             return NS_OK;
1640         }
1641     }
1642 
1643     // If root node has no template attribute, then look for a child
1644     // node which is a template tag.
1645     for (nsIContent* child = mRoot->GetFirstChild();
1646          child;
1647          child = child->GetNextSibling()) {
1648 
1649         if (IsTemplateElement(child)) {
1650             NS_ADDREF(*aResult = child);
1651             return NS_OK;
1652         }
1653     }
1654 
1655     // Look through the anonymous children as well. Although FlattenedChildIterator
1656     // will find a template element that has been placed in an insertion point, many
1657     // bindings do not have a specific insertion point for the template element, which
1658     // would cause it to not be part of the flattened content tree. The check above to
1659     // check the explicit children as well handles this case.
1660     FlattenedChildIterator iter(mRoot);
1661     for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
1662         if (IsTemplateElement(child)) {
1663             NS_ADDREF(*aResult = child);
1664             return NS_OK;
1665         }
1666     }
1667 
1668     *aResult = nullptr;
1669     return NS_OK;
1670 }
1671 
1672 nsresult
CompileQueries()1673 nsXULTemplateBuilder::CompileQueries()
1674 {
1675     nsCOMPtr<nsIContent> tmpl;
1676     GetTemplateRoot(getter_AddRefs(tmpl));
1677     if (! tmpl)
1678         return NS_OK;
1679 
1680     if (! mRoot)
1681         return NS_ERROR_NOT_INITIALIZED;
1682 
1683     // Determine if there are any special settings we need to observe
1684     mFlags = 0;
1685 
1686     nsAutoString flags;
1687     mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::flags, flags);
1688 
1689     // if the dont-test-empty flag is set, containers should not be checked to
1690     // see if they are empty. If dont-recurse is set, then don't process the
1691     // template recursively and only show one level of results. The logging
1692     // flag logs errors and results to the console, which is useful when
1693     // debugging templates.
1694     nsWhitespaceTokenizer tokenizer(flags);
1695     while (tokenizer.hasMoreTokens()) {
1696       const nsDependentSubstring& token(tokenizer.nextToken());
1697       if (token.EqualsLiteral("dont-test-empty"))
1698         mFlags |= eDontTestEmpty;
1699       else if (token.EqualsLiteral("dont-recurse"))
1700         mFlags |= eDontRecurse;
1701       else if (token.EqualsLiteral("logging"))
1702         mFlags |= eLoggingEnabled;
1703     }
1704 
1705     // always enable logging if the debug setting is used
1706     if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug))
1707         mFlags |= eLoggingEnabled;
1708 
1709     nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot);
1710     nsresult rv =
1711         mQueryProcessor->InitializeForBuilding(mDataSource, this, rootnode);
1712     if (NS_FAILED(rv))
1713         return rv;
1714 
1715     // Set the "container" and "member" variables, if the user has specified
1716     // them. The container variable may be specified with the container
1717     // attribute on the <template> and the member variable may be specified
1718     // using the member attribute or the value of the uri attribute inside the
1719     // first action body in the template. If not specified, the container
1720     // variable defaults to '?uri' and the member variable defaults to '?' or
1721     // 'rdf:*' for simple queries.
1722 
1723     // For RDF queries, the container variable may also be set via the
1724     // <content> tag.
1725 
1726     nsAutoString containervar;
1727     tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::container, containervar);
1728 
1729     if (containervar.IsEmpty())
1730         mRefVariable = NS_Atomize("?uri");
1731     else
1732         mRefVariable = NS_Atomize(containervar);
1733 
1734     nsAutoString membervar;
1735     tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::member, membervar);
1736 
1737     if (membervar.IsEmpty())
1738         mMemberVariable = nullptr;
1739     else
1740         mMemberVariable = NS_Atomize(membervar);
1741 
1742     nsTemplateQuerySet* queryset = new nsTemplateQuerySet(0);
1743     if (!mQuerySets.AppendElement(queryset)) {
1744         delete queryset;
1745         return NS_ERROR_OUT_OF_MEMORY;
1746     }
1747 
1748     bool canUseTemplate = false;
1749     int32_t priority = 0;
1750     rv = CompileTemplate(tmpl, queryset, false, &priority, &canUseTemplate);
1751 
1752     if (NS_FAILED(rv) || !canUseTemplate) {
1753         for (int32_t q = mQuerySets.Length() - 1; q >= 0; q--) {
1754             nsTemplateQuerySet* qs = mQuerySets[q];
1755             delete qs;
1756         }
1757         mQuerySets.Clear();
1758     }
1759 
1760     mQueriesCompiled = true;
1761 
1762     return NS_OK;
1763 }
1764 
1765 nsresult
CompileTemplate(nsIContent * aTemplate,nsTemplateQuerySet * aQuerySet,bool aIsQuerySet,int32_t * aPriority,bool * aCanUseTemplate)1766 nsXULTemplateBuilder::CompileTemplate(nsIContent* aTemplate,
1767                                       nsTemplateQuerySet* aQuerySet,
1768                                       bool aIsQuerySet,
1769                                       int32_t* aPriority,
1770                                       bool* aCanUseTemplate)
1771 {
1772     NS_ASSERTION(aQuerySet, "No queryset supplied");
1773 
1774     nsresult rv = NS_OK;
1775 
1776     bool isQuerySetMode = false;
1777     bool hasQuerySet = false, hasRule = false, hasQuery = false;
1778 
1779     for (nsIContent* rulenode = aTemplate->GetFirstChild();
1780          rulenode;
1781          rulenode = rulenode->GetNextSibling()) {
1782 
1783         mozilla::dom::NodeInfo *ni = rulenode->NodeInfo();
1784 
1785         // don't allow more queries than can be supported
1786         if (*aPriority == INT16_MAX)
1787             return NS_ERROR_FAILURE;
1788 
1789         // XXXndeakin queryset isn't a good name for this tag since it only
1790         //            ever contains one query
1791         if (!aIsQuerySet && ni->Equals(nsGkAtoms::queryset, kNameSpaceID_XUL)) {
1792             if (hasRule || hasQuery) {
1793               nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_INVALID_QUERYSET);
1794               continue;
1795             }
1796 
1797             isQuerySetMode = true;
1798 
1799             // only create a queryset for those after the first since the
1800             // first one is always created by CompileQueries
1801             if (hasQuerySet) {
1802                 aQuerySet = new nsTemplateQuerySet(++*aPriority);
1803 
1804                 // once the queryset is appended to the mQuerySets list, it
1805                 // will be removed by CompileQueries if an error occurs
1806                 if (!mQuerySets.AppendElement(aQuerySet)) {
1807                     delete aQuerySet;
1808                     return NS_ERROR_OUT_OF_MEMORY;
1809                 }
1810             }
1811 
1812             hasQuerySet = true;
1813 
1814             rv = CompileTemplate(rulenode, aQuerySet, true, aPriority, aCanUseTemplate);
1815             if (NS_FAILED(rv))
1816                 return rv;
1817         }
1818 
1819         // once a queryset is used, everything must be a queryset
1820         if (isQuerySetMode)
1821             continue;
1822 
1823         if (ni->Equals(nsGkAtoms::rule, kNameSpaceID_XUL)) {
1824             nsCOMPtr<nsIContent> action;
1825             nsXULContentUtils::FindChildByTag(rulenode,
1826                                               kNameSpaceID_XUL,
1827                                               nsGkAtoms::action,
1828                                               getter_AddRefs(action));
1829 
1830             if (action){
1831                 nsCOMPtr<nsIAtom> memberVariable = mMemberVariable;
1832                 if (!memberVariable) {
1833                     memberVariable = DetermineMemberVariable(action);
1834                     if (!memberVariable) {
1835                         nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_NO_MEMBERVAR);
1836                         continue;
1837                     }
1838                 }
1839 
1840                 if (hasQuery) {
1841                     nsCOMPtr<nsIAtom> tag;
1842                     DetermineRDFQueryRef(aQuerySet->mQueryNode,
1843                                          getter_AddRefs(tag));
1844                     if (tag)
1845                         aQuerySet->SetTag(tag);
1846 
1847                     if (! aQuerySet->mCompiledQuery) {
1848                         nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode));
1849 
1850                         rv = mQueryProcessor->CompileQuery(this, query,
1851                                                            mRefVariable, memberVariable,
1852                                                            getter_AddRefs(aQuerySet->mCompiledQuery));
1853                         if (NS_FAILED(rv))
1854                             return rv;
1855                     }
1856 
1857                     if (aQuerySet->mCompiledQuery) {
1858                         rv = CompileExtendedQuery(rulenode, action, memberVariable,
1859                                                   aQuerySet);
1860                         if (NS_FAILED(rv))
1861                             return rv;
1862 
1863                         *aCanUseTemplate = true;
1864                     }
1865                 }
1866                 else {
1867                     // backwards-compatible RDF template syntax where there is
1868                     // an <action> node but no <query> node. In this case,
1869                     // use the conditions as if it was the query.
1870 
1871                     nsCOMPtr<nsIContent> conditions;
1872                     nsXULContentUtils::FindChildByTag(rulenode,
1873                                                       kNameSpaceID_XUL,
1874                                                       nsGkAtoms::conditions,
1875                                                       getter_AddRefs(conditions));
1876 
1877                     if (conditions) {
1878                         // create a new queryset if one hasn't been created already
1879                         if (hasQuerySet) {
1880                             aQuerySet = new nsTemplateQuerySet(++*aPriority);
1881                             if (!mQuerySets.AppendElement(aQuerySet)) {
1882                                 delete aQuerySet;
1883                                 return NS_ERROR_OUT_OF_MEMORY;
1884                             }
1885                         }
1886 
1887                         nsCOMPtr<nsIAtom> tag;
1888                         DetermineRDFQueryRef(conditions, getter_AddRefs(tag));
1889                         if (tag)
1890                             aQuerySet->SetTag(tag);
1891 
1892                         hasQuerySet = true;
1893 
1894                         nsCOMPtr<nsIDOMNode> conditionsnode(do_QueryInterface(conditions));
1895 
1896                         aQuerySet->mQueryNode = conditions;
1897                         rv = mQueryProcessor->CompileQuery(this, conditionsnode,
1898                                                            mRefVariable,
1899                                                            memberVariable,
1900                                                            getter_AddRefs(aQuerySet->mCompiledQuery));
1901                         if (NS_FAILED(rv))
1902                             return rv;
1903 
1904                         if (aQuerySet->mCompiledQuery) {
1905                             rv = CompileExtendedQuery(rulenode, action, memberVariable,
1906                                                       aQuerySet);
1907                             if (NS_FAILED(rv))
1908                                 return rv;
1909 
1910                             *aCanUseTemplate = true;
1911                         }
1912                     }
1913                 }
1914             }
1915             else {
1916                 if (hasQuery)
1917                     continue;
1918 
1919                 // a new queryset must always be created in this case
1920                 if (hasQuerySet) {
1921                     aQuerySet = new nsTemplateQuerySet(++*aPriority);
1922                     if (!mQuerySets.AppendElement(aQuerySet)) {
1923                         delete aQuerySet;
1924                         return NS_ERROR_OUT_OF_MEMORY;
1925                     }
1926                 }
1927 
1928                 hasQuerySet = true;
1929 
1930                 rv = CompileSimpleQuery(rulenode, aQuerySet, aCanUseTemplate);
1931                 if (NS_FAILED(rv))
1932                     return rv;
1933             }
1934 
1935             hasRule = true;
1936         }
1937         else if (ni->Equals(nsGkAtoms::query, kNameSpaceID_XUL)) {
1938             if (hasQuery)
1939               continue;
1940 
1941             aQuerySet->mQueryNode = rulenode;
1942             hasQuery = true;
1943         }
1944         else if (ni->Equals(nsGkAtoms::action, kNameSpaceID_XUL)) {
1945             // the query must appear before the action
1946             if (! hasQuery)
1947                 continue;
1948 
1949             nsCOMPtr<nsIAtom> tag;
1950             DetermineRDFQueryRef(aQuerySet->mQueryNode, getter_AddRefs(tag));
1951             if (tag)
1952                 aQuerySet->SetTag(tag);
1953 
1954             nsCOMPtr<nsIAtom> memberVariable = mMemberVariable;
1955             if (!memberVariable) {
1956                 memberVariable = DetermineMemberVariable(rulenode);
1957                 if (!memberVariable) {
1958                     nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_NO_MEMBERVAR);
1959                     continue;
1960                 }
1961             }
1962 
1963             nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode));
1964 
1965             rv = mQueryProcessor->CompileQuery(this, query,
1966                                                mRefVariable, memberVariable,
1967                                                getter_AddRefs(aQuerySet->mCompiledQuery));
1968 
1969             if (aQuerySet->mCompiledQuery) {
1970                 nsTemplateRule* rule = aQuerySet->NewRule(aTemplate, rulenode, aQuerySet);
1971                 if (! rule)
1972                     return NS_ERROR_OUT_OF_MEMORY;
1973 
1974                 rule->SetVars(mRefVariable, memberVariable);
1975 
1976                 *aCanUseTemplate = true;
1977 
1978                 return NS_OK;
1979             }
1980         }
1981     }
1982 
1983     if (! hasRule && ! hasQuery && ! hasQuerySet) {
1984         // if no rules are specified in the template, then the contents of the
1985         // <template> tag are the one-and-only template.
1986         rv = CompileSimpleQuery(aTemplate, aQuerySet, aCanUseTemplate);
1987      }
1988 
1989     return rv;
1990 }
1991 
1992 nsresult
CompileExtendedQuery(nsIContent * aRuleElement,nsIContent * aActionElement,nsIAtom * aMemberVariable,nsTemplateQuerySet * aQuerySet)1993 nsXULTemplateBuilder::CompileExtendedQuery(nsIContent* aRuleElement,
1994                                            nsIContent* aActionElement,
1995                                            nsIAtom* aMemberVariable,
1996                                            nsTemplateQuerySet* aQuerySet)
1997 {
1998     // Compile an "extended" <template> rule. An extended rule may have
1999     // a <conditions> child, an <action> child, and a <bindings> child.
2000     nsresult rv;
2001 
2002     nsTemplateRule* rule = aQuerySet->NewRule(aRuleElement, aActionElement, aQuerySet);
2003     if (! rule)
2004          return NS_ERROR_OUT_OF_MEMORY;
2005 
2006     nsCOMPtr<nsIContent> conditions;
2007     nsXULContentUtils::FindChildByTag(aRuleElement,
2008                                       kNameSpaceID_XUL,
2009                                       nsGkAtoms::conditions,
2010                                       getter_AddRefs(conditions));
2011 
2012     // allow the conditions to be placed directly inside the rule
2013     if (!conditions)
2014         conditions = aRuleElement;
2015 
2016     rv = CompileConditions(rule, conditions);
2017     // If the rule compilation failed, then we have to bail.
2018     if (NS_FAILED(rv)) {
2019         aQuerySet->RemoveRule(rule);
2020         return rv;
2021     }
2022 
2023     rule->SetVars(mRefVariable, aMemberVariable);
2024 
2025     // If we've got bindings, add 'em.
2026     nsCOMPtr<nsIContent> bindings;
2027     nsXULContentUtils::FindChildByTag(aRuleElement,
2028                                       kNameSpaceID_XUL,
2029                                       nsGkAtoms::bindings,
2030                                       getter_AddRefs(bindings));
2031 
2032     // allow bindings to be placed directly inside rule
2033     if (!bindings)
2034         bindings = aRuleElement;
2035 
2036     rv = CompileBindings(rule, bindings);
2037     NS_ENSURE_SUCCESS(rv, rv);
2038 
2039     return NS_OK;
2040 }
2041 
2042 already_AddRefed<nsIAtom>
DetermineMemberVariable(nsIContent * aElement)2043 nsXULTemplateBuilder::DetermineMemberVariable(nsIContent* aElement)
2044 {
2045     // recursively iterate over the children looking for an element
2046     // with uri="?..."
2047     for (nsIContent* child = aElement->GetFirstChild();
2048          child;
2049          child = child->GetNextSibling()) {
2050         nsAutoString uri;
2051         child->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri);
2052         if (!uri.IsEmpty() && uri[0] == char16_t('?')) {
2053             return NS_Atomize(uri);
2054         }
2055 
2056         nsCOMPtr<nsIAtom> result = DetermineMemberVariable(child);
2057         if (result) {
2058             return result.forget();
2059         }
2060     }
2061 
2062     return nullptr;
2063 }
2064 
2065 void
DetermineRDFQueryRef(nsIContent * aQueryElement,nsIAtom ** aTag)2066 nsXULTemplateBuilder::DetermineRDFQueryRef(nsIContent* aQueryElement, nsIAtom** aTag)
2067 {
2068     // check for a tag
2069     nsCOMPtr<nsIContent> content;
2070     nsXULContentUtils::FindChildByTag(aQueryElement,
2071                                       kNameSpaceID_XUL,
2072                                       nsGkAtoms::content,
2073                                       getter_AddRefs(content));
2074 
2075     if (! content) {
2076         // look for older treeitem syntax as well
2077         nsXULContentUtils::FindChildByTag(aQueryElement,
2078                                           kNameSpaceID_XUL,
2079                                           nsGkAtoms::treeitem,
2080                                           getter_AddRefs(content));
2081     }
2082 
2083     if (content) {
2084         nsAutoString uri;
2085         content->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri);
2086 
2087         if (!uri.IsEmpty())
2088             mRefVariable = NS_Atomize(uri);
2089 
2090         nsAutoString tag;
2091         content->GetAttr(kNameSpaceID_None, nsGkAtoms::tag, tag);
2092 
2093         if (!tag.IsEmpty())
2094             *aTag = NS_Atomize(tag).take();
2095     }
2096 }
2097 
2098 nsresult
CompileSimpleQuery(nsIContent * aRuleElement,nsTemplateQuerySet * aQuerySet,bool * aCanUseTemplate)2099 nsXULTemplateBuilder::CompileSimpleQuery(nsIContent* aRuleElement,
2100                                          nsTemplateQuerySet* aQuerySet,
2101                                          bool* aCanUseTemplate)
2102 {
2103     // compile a simple query, which is a query with no <query> or
2104     // <conditions>. This means that a default query is used.
2105     nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aRuleElement));
2106 
2107     nsCOMPtr<nsIAtom> memberVariable;
2108     if (mMemberVariable)
2109         memberVariable = mMemberVariable;
2110     else
2111         memberVariable = NS_Atomize("rdf:*");
2112 
2113     // since there is no <query> node for a simple query, the query node will
2114     // be either the <rule> node if multiple rules are used, or the <template> node.
2115     aQuerySet->mQueryNode = aRuleElement;
2116     nsresult rv = mQueryProcessor->CompileQuery(this, query,
2117                                                 mRefVariable, memberVariable,
2118                                                 getter_AddRefs(aQuerySet->mCompiledQuery));
2119     if (NS_FAILED(rv))
2120         return rv;
2121 
2122     if (! aQuerySet->mCompiledQuery) {
2123         *aCanUseTemplate = false;
2124         return NS_OK;
2125     }
2126 
2127     nsTemplateRule* rule = aQuerySet->NewRule(aRuleElement, aRuleElement, aQuerySet);
2128     if (! rule)
2129         return NS_ERROR_OUT_OF_MEMORY;
2130 
2131     rule->SetVars(mRefVariable, memberVariable);
2132 
2133     nsAutoString tag;
2134     aRuleElement->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag);
2135 
2136     if (!tag.IsEmpty()) {
2137         nsCOMPtr<nsIAtom> tagatom = NS_Atomize(tag);
2138         aQuerySet->SetTag(tagatom);
2139     }
2140 
2141     *aCanUseTemplate = true;
2142 
2143     return AddSimpleRuleBindings(rule, aRuleElement);
2144 }
2145 
2146 nsresult
CompileConditions(nsTemplateRule * aRule,nsIContent * aCondition)2147 nsXULTemplateBuilder::CompileConditions(nsTemplateRule* aRule,
2148                                         nsIContent* aCondition)
2149 {
2150     nsAutoString tag;
2151     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag);
2152 
2153     if (!tag.IsEmpty()) {
2154         nsCOMPtr<nsIAtom> tagatom = NS_Atomize(tag);
2155         aRule->SetTag(tagatom);
2156     }
2157 
2158     nsTemplateCondition* currentCondition = nullptr;
2159 
2160     for (nsIContent* node = aCondition->GetFirstChild();
2161          node;
2162          node = node->GetNextSibling()) {
2163 
2164         if (node->NodeInfo()->Equals(nsGkAtoms::where, kNameSpaceID_XUL)) {
2165             nsresult rv = CompileWhereCondition(aRule, node, &currentCondition);
2166             if (NS_FAILED(rv))
2167                 return rv;
2168         }
2169     }
2170 
2171     return NS_OK;
2172 }
2173 
2174 nsresult
CompileWhereCondition(nsTemplateRule * aRule,nsIContent * aCondition,nsTemplateCondition ** aCurrentCondition)2175 nsXULTemplateBuilder::CompileWhereCondition(nsTemplateRule* aRule,
2176                                             nsIContent* aCondition,
2177                                             nsTemplateCondition** aCurrentCondition)
2178 {
2179     // Compile a <where> condition, which must be of the form:
2180     //
2181     //   <where subject="?var1|string" rel="relation" value="?var2|string" />
2182     //
2183     //    The value of rel may be:
2184     //      equal - subject must be equal to object
2185     //      notequal - subject must not be equal to object
2186     //      less - subject must be less than object
2187     //      greater - subject must be greater than object
2188     //      startswith - subject must start with object
2189     //      endswith - subject must end with object
2190     //      contains - subject must contain object
2191     //    Comparisons are done as strings unless the subject is an integer.
2192 
2193     // subject
2194     nsAutoString subject;
2195     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject);
2196     if (subject.IsEmpty()) {
2197         nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_SUBJECT);
2198         return NS_OK;
2199     }
2200 
2201     nsCOMPtr<nsIAtom> svar;
2202     if (subject[0] == char16_t('?'))
2203         svar = NS_Atomize(subject);
2204 
2205     nsAutoString relstring;
2206     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, relstring);
2207     if (relstring.IsEmpty()) {
2208         nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_RELATION);
2209         return NS_OK;
2210     }
2211 
2212     // object
2213     nsAutoString value;
2214     aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
2215     if (value.IsEmpty()) {
2216         nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_VALUE);
2217         return NS_OK;
2218     }
2219 
2220     // multiple
2221     bool shouldMultiple =
2222       aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::multiple,
2223                               nsGkAtoms::_true, eCaseMatters);
2224 
2225     nsCOMPtr<nsIAtom> vvar;
2226     if (!shouldMultiple && (value[0] == char16_t('?'))) {
2227         vvar = NS_Atomize(value);
2228     }
2229 
2230     // ignorecase
2231     bool shouldIgnoreCase =
2232       aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorecase,
2233                               nsGkAtoms::_true, eCaseMatters);
2234 
2235     // negate
2236     bool shouldNegate =
2237       aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::negate,
2238                               nsGkAtoms::_true, eCaseMatters);
2239 
2240     nsTemplateCondition* condition;
2241 
2242     if (svar && vvar) {
2243         condition = new nsTemplateCondition(svar, relstring, vvar,
2244                                             shouldIgnoreCase, shouldNegate);
2245     }
2246     else if (svar && !value.IsEmpty()) {
2247         condition = new nsTemplateCondition(svar, relstring, value,
2248                                             shouldIgnoreCase, shouldNegate, shouldMultiple);
2249     }
2250     else if (vvar) {
2251         condition = new nsTemplateCondition(subject, relstring, vvar,
2252                                             shouldIgnoreCase, shouldNegate);
2253     }
2254     else {
2255         nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_VAR);
2256         return NS_OK;
2257     }
2258 
2259     if (*aCurrentCondition) {
2260         (*aCurrentCondition)->SetNext(condition);
2261     }
2262     else {
2263         aRule->SetCondition(condition);
2264     }
2265 
2266     *aCurrentCondition = condition;
2267 
2268     return NS_OK;
2269 }
2270 
2271 nsresult
CompileBindings(nsTemplateRule * aRule,nsIContent * aBindings)2272 nsXULTemplateBuilder::CompileBindings(nsTemplateRule* aRule, nsIContent* aBindings)
2273 {
2274     // Add an extended rule's bindings.
2275     nsresult rv;
2276 
2277     for (nsIContent* binding = aBindings->GetFirstChild();
2278          binding;
2279          binding = binding->GetNextSibling()) {
2280 
2281         if (binding->NodeInfo()->Equals(nsGkAtoms::binding,
2282                                         kNameSpaceID_XUL)) {
2283             rv = CompileBinding(aRule, binding);
2284             if (NS_FAILED(rv))
2285                 return rv;
2286         }
2287     }
2288 
2289     aRule->AddBindingsToQueryProcessor(mQueryProcessor);
2290 
2291     return NS_OK;
2292 }
2293 
2294 
2295 nsresult
CompileBinding(nsTemplateRule * aRule,nsIContent * aBinding)2296 nsXULTemplateBuilder::CompileBinding(nsTemplateRule* aRule,
2297                                      nsIContent* aBinding)
2298 {
2299     // Compile a <binding> "condition", which must be of the form:
2300     //
2301     //   <binding subject="?var1"
2302     //            predicate="resource"
2303     //            object="?var2" />
2304     //
2305     // XXXwaterson Some day it would be cool to allow the 'predicate'
2306     // to be bound to a variable.
2307 
2308     // subject
2309     nsAutoString subject;
2310     aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject);
2311     if (subject.IsEmpty()) {
2312         nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_SUBJECT);
2313         return NS_OK;
2314     }
2315 
2316     nsCOMPtr<nsIAtom> svar;
2317     if (subject[0] == char16_t('?')) {
2318         svar = NS_Atomize(subject);
2319     }
2320     else {
2321         nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_SUBJECT);
2322         return NS_OK;
2323     }
2324 
2325     // predicate
2326     nsAutoString predicate;
2327     aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::predicate, predicate);
2328     if (predicate.IsEmpty()) {
2329         nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_PREDICATE);
2330         return NS_OK;
2331     }
2332 
2333     // object
2334     nsAutoString object;
2335     aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::object, object);
2336 
2337     if (object.IsEmpty()) {
2338         nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT);
2339         return NS_OK;
2340     }
2341 
2342     nsCOMPtr<nsIAtom> ovar;
2343     if (object[0] == char16_t('?')) {
2344         ovar = NS_Atomize(object);
2345     }
2346     else {
2347         nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT);
2348         return NS_OK;
2349     }
2350 
2351     return aRule->AddBinding(svar, predicate, ovar);
2352 }
2353 
2354 nsresult
AddSimpleRuleBindings(nsTemplateRule * aRule,nsIContent * aElement)2355 nsXULTemplateBuilder::AddSimpleRuleBindings(nsTemplateRule* aRule,
2356                                             nsIContent* aElement)
2357 {
2358     // Crawl the content tree of a "simple" rule, adding a variable
2359     // assignment for any attribute whose value is "rdf:".
2360 
2361     AutoTArray<nsIContent*, 8> elements;
2362 
2363     if (elements.AppendElement(aElement) == nullptr)
2364         return NS_ERROR_OUT_OF_MEMORY;
2365 
2366     while (elements.Length()) {
2367         // Pop the next element off the stack
2368         uint32_t i = elements.Length() - 1;
2369         nsIContent* element = elements[i];
2370         elements.RemoveElementAt(i);
2371 
2372         // Iterate through its attributes, looking for substitutions
2373         // that we need to add as bindings.
2374         uint32_t count = element->GetAttrCount();
2375 
2376         for (i = 0; i < count; ++i) {
2377             const nsAttrName* name = element->GetAttrNameAt(i);
2378 
2379             if (!name->Equals(nsGkAtoms::id, kNameSpaceID_None) &&
2380                 !name->Equals(nsGkAtoms::uri, kNameSpaceID_None)) {
2381                 nsAutoString value;
2382                 element->GetAttr(name->NamespaceID(), name->LocalName(), value);
2383 
2384                 // Scan the attribute for variables, adding a binding for
2385                 // each one.
2386                 ParseAttribute(value, AddBindingsFor, nullptr, aRule);
2387             }
2388         }
2389 
2390         // Push kids onto the stack, and search them next.
2391         for (nsIContent* child = element->GetLastChild();
2392              child;
2393              child = child->GetPreviousSibling()) {
2394 
2395             if (!elements.AppendElement(child))
2396                 return NS_ERROR_OUT_OF_MEMORY;
2397         }
2398     }
2399 
2400     aRule->AddBindingsToQueryProcessor(mQueryProcessor);
2401 
2402     return NS_OK;
2403 }
2404 
2405 void
AddBindingsFor(nsXULTemplateBuilder * aThis,const nsAString & aVariable,void * aClosure)2406 nsXULTemplateBuilder::AddBindingsFor(nsXULTemplateBuilder* aThis,
2407                                      const nsAString& aVariable,
2408                                      void* aClosure)
2409 {
2410     // We should *only* be recieving "rdf:"-style variables. Make
2411     // sure...
2412     if (!StringBeginsWith(aVariable, NS_LITERAL_STRING("rdf:")))
2413         return;
2414 
2415     nsTemplateRule* rule = static_cast<nsTemplateRule*>(aClosure);
2416 
2417     nsCOMPtr<nsIAtom> var = NS_Atomize(aVariable);
2418 
2419     // Strip it down to the raw RDF property by clobbering the "rdf:"
2420     // prefix
2421     nsAutoString property;
2422     property.Assign(Substring(aVariable, uint32_t(4), aVariable.Length() - 4));
2423 
2424     if (! rule->HasBinding(rule->GetMemberVariable(), property, var))
2425         // In the simple syntax, the binding is always from the
2426         // member variable, through the property, to the target.
2427         rule->AddBinding(rule->GetMemberVariable(), property, var);
2428 }
2429 
2430 
2431 nsresult
IsSystemPrincipal(nsIPrincipal * principal,bool * result)2432 nsXULTemplateBuilder::IsSystemPrincipal(nsIPrincipal *principal, bool *result)
2433 {
2434   if (!gSystemPrincipal)
2435     return NS_ERROR_UNEXPECTED;
2436 
2437   *result = (principal == gSystemPrincipal);
2438   return NS_OK;
2439 }
2440 
2441 bool
IsActivated(nsIRDFResource * aResource)2442 nsXULTemplateBuilder::IsActivated(nsIRDFResource *aResource)
2443 {
2444     for (ActivationEntry *entry = mTop;
2445          entry != nullptr;
2446          entry = entry->mPrevious) {
2447         if (entry->mResource == aResource)
2448             return true;
2449     }
2450     return false;
2451 }
2452 
2453 nsresult
GetResultResource(nsIXULTemplateResult * aResult,nsIRDFResource ** aResource)2454 nsXULTemplateBuilder::GetResultResource(nsIXULTemplateResult* aResult,
2455                                         nsIRDFResource** aResource)
2456 {
2457     // get the resource for a result by checking its resource property. If it
2458     // is not set, check the id. This allows non-chrome implementations to
2459     // avoid having to use RDF.
2460     nsresult rv = aResult->GetResource(aResource);
2461     if (NS_FAILED(rv))
2462         return rv;
2463 
2464     if (! *aResource) {
2465         nsAutoString id;
2466         rv = aResult->GetId(id);
2467         if (NS_FAILED(rv))
2468             return rv;
2469 
2470         return gRDFService->GetUnicodeResource(id, aResource);
2471     }
2472 
2473     return rv;
2474 }
2475 
2476 
2477 void
OutputMatchToLog(nsIRDFResource * aId,nsTemplateMatch * aMatch,bool aIsNew)2478 nsXULTemplateBuilder::OutputMatchToLog(nsIRDFResource* aId,
2479                                        nsTemplateMatch* aMatch,
2480                                        bool aIsNew)
2481 {
2482     int32_t priority = aMatch->QuerySetPriority() + 1;
2483     int32_t activePriority = -1;
2484 
2485     nsAutoString msg;
2486 
2487     nsAutoString templateid;
2488     mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::id, templateid);
2489     msg.AppendLiteral("In template");
2490     if (!templateid.IsEmpty()) {
2491         msg.AppendLiteral(" with id ");
2492         msg.Append(templateid);
2493     }
2494 
2495     nsAutoString refstring;
2496     aMatch->mResult->GetBindingFor(mRefVariable, refstring);
2497     if (!refstring.IsEmpty()) {
2498         msg.AppendLiteral(" using ref ");
2499         msg.Append(refstring);
2500     }
2501 
2502     msg.AppendLiteral("\n    ");
2503 
2504     nsTemplateMatch* match = nullptr;
2505     if (mMatchMap.Get(aId, &match)){
2506         while (match) {
2507             if (match == aMatch)
2508                 break;
2509             if (match->IsActive() &&
2510                 match->GetContainer() == aMatch->GetContainer()) {
2511                 activePriority = match->QuerySetPriority() + 1;
2512                 break;
2513             }
2514             match = match->mNext;
2515         }
2516     }
2517 
2518     if (aMatch->IsActive()) {
2519         if (aIsNew) {
2520             msg.AppendLiteral("New active result for query ");
2521             msg.AppendInt(priority);
2522             msg.AppendLiteral(" matching rule ");
2523             msg.AppendInt(aMatch->RuleIndex() + 1);
2524         }
2525         else {
2526             msg.AppendLiteral("Removed active result for query ");
2527             msg.AppendInt(priority);
2528             if (activePriority > 0) {
2529                 msg.AppendLiteral(" (new active query is ");
2530                 msg.AppendInt(activePriority);
2531                 msg.Append(')');
2532             }
2533             else {
2534                 msg.AppendLiteral(" (no new active query)");
2535             }
2536         }
2537     }
2538     else {
2539         if (aIsNew) {
2540             msg.AppendLiteral("New inactive result for query ");
2541             msg.AppendInt(priority);
2542             if (activePriority > 0) {
2543                 msg.AppendLiteral(" (overridden by query ");
2544                 msg.AppendInt(activePriority);
2545                 msg.Append(')');
2546             }
2547             else {
2548                 msg.AppendLiteral(" (didn't match a rule)");
2549             }
2550         }
2551         else {
2552             msg.AppendLiteral("Removed inactive result for query ");
2553             msg.AppendInt(priority);
2554             if (activePriority > 0) {
2555                 msg.AppendLiteral(" (active query is ");
2556                 msg.AppendInt(activePriority);
2557                 msg.Append(')');
2558             }
2559             else {
2560                 msg.AppendLiteral(" (no active query)");
2561             }
2562         }
2563     }
2564 
2565     nsAutoString idstring;
2566     nsXULContentUtils::GetTextForNode(aId, idstring);
2567     msg.AppendLiteral(": ");
2568     msg.Append(idstring);
2569 
2570     nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
2571     if (cs)
2572       cs->LogStringMessage(msg.get());
2573 }
2574