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, ¤tCondition);
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