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