|
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 NSPR_LOG_MODULES nsXULTemplateBuilder:5 |
|
17 |
|
18 */ |
|
19 |
|
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" |
|
73 |
|
74 using namespace mozilla::dom; |
|
75 using namespace mozilla; |
|
76 |
|
77 //---------------------------------------------------------------------- |
|
78 // |
|
79 // nsXULTemplateBuilder |
|
80 // |
|
81 |
|
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; |
|
88 |
|
89 #ifdef PR_LOGGING |
|
90 PRLogModuleInfo* gXULTemplateLog; |
|
91 #endif |
|
92 |
|
93 #define NS_QUERY_PROCESSOR_CONTRACTID_PREFIX "@mozilla.org/xul/xul-query-processor;1?name=" |
|
94 |
|
95 //---------------------------------------------------------------------- |
|
96 // |
|
97 // nsXULTemplateBuilder methods |
|
98 // |
|
99 |
|
100 nsXULTemplateBuilder::nsXULTemplateBuilder(void) |
|
101 : mQueriesCompiled(false), |
|
102 mFlags(0), |
|
103 mTop(nullptr), |
|
104 mObservedDocument(nullptr) |
|
105 { |
|
106 MOZ_COUNT_CTOR(nsXULTemplateBuilder); |
|
107 } |
|
108 |
|
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 } |
|
118 |
|
119 return PL_DHASH_REMOVE; |
|
120 } |
|
121 |
|
122 nsXULTemplateBuilder::~nsXULTemplateBuilder(void) |
|
123 { |
|
124 Uninit(true); |
|
125 |
|
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 } |
|
133 |
|
134 MOZ_COUNT_DTOR(nsXULTemplateBuilder); |
|
135 } |
|
136 |
|
137 |
|
138 nsresult |
|
139 nsXULTemplateBuilder::InitGlobals() |
|
140 { |
|
141 nsresult rv; |
|
142 |
|
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; |
|
150 |
|
151 NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); |
|
152 rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils); |
|
153 if (NS_FAILED(rv)) |
|
154 return rv; |
|
155 |
|
156 rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, |
|
157 &gScriptSecurityManager); |
|
158 if (NS_FAILED(rv)) |
|
159 return rv; |
|
160 |
|
161 rv = gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal); |
|
162 if (NS_FAILED(rv)) |
|
163 return rv; |
|
164 |
|
165 rv = CallGetService(NS_OBSERVERSERVICE_CONTRACTID, &gObserverService); |
|
166 if (NS_FAILED(rv)) |
|
167 return rv; |
|
168 } |
|
169 |
|
170 #ifdef PR_LOGGING |
|
171 if (! gXULTemplateLog) |
|
172 gXULTemplateLog = PR_NewLogModule("nsXULTemplateBuilder"); |
|
173 #endif |
|
174 |
|
175 return NS_OK; |
|
176 } |
|
177 |
|
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 } |
|
185 |
|
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 } |
|
194 |
|
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 } |
|
202 |
|
203 mQuerySets.Clear(); |
|
204 |
|
205 mMatchMap.Enumerate(DestroyMatchList, nullptr); |
|
206 |
|
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 } |
|
212 |
|
213 void |
|
214 nsXULTemplateBuilder::Uninit(bool aIsFinal) |
|
215 { |
|
216 if (mObservedDocument && aIsFinal) { |
|
217 StopObserving(); |
|
218 } |
|
219 |
|
220 if (mQueryProcessor) |
|
221 mQueryProcessor->Done(); |
|
222 |
|
223 CleanUp(aIsFinal); |
|
224 |
|
225 mRootResult = nullptr; |
|
226 mRefVariable = nullptr; |
|
227 mMemberVariable = nullptr; |
|
228 |
|
229 mQueriesCompiled = false; |
|
230 } |
|
231 |
|
232 static PLDHashOperator |
|
233 TraverseMatchList(nsISupports* aKey, nsTemplateMatch* aMatch, void* aContext) |
|
234 { |
|
235 nsCycleCollectionTraversalCallback *cb = |
|
236 static_cast<nsCycleCollectionTraversalCallback*>(aContext); |
|
237 |
|
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 } |
|
245 |
|
246 return PL_DHASH_NEXT; |
|
247 } |
|
248 |
|
249 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULTemplateBuilder) |
|
250 |
|
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 } |
|
271 |
|
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 |
|
294 |
|
295 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULTemplateBuilder) |
|
296 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULTemplateBuilder) |
|
297 |
|
298 DOMCI_DATA(XULTemplateBuilder, nsXULTemplateBuilder) |
|
299 |
|
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 |
|
308 |
|
309 //---------------------------------------------------------------------- |
|
310 // |
|
311 // nsIXULTemplateBuilder methods |
|
312 // |
|
313 |
|
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 } |
|
323 |
|
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 } |
|
333 |
|
334 NS_IMETHODIMP |
|
335 nsXULTemplateBuilder::SetDatasource(nsISupports* aResult) |
|
336 { |
|
337 mDataSource = aResult; |
|
338 mCompDB = do_QueryInterface(mDataSource); |
|
339 |
|
340 return Rebuild(); |
|
341 } |
|
342 |
|
343 NS_IMETHODIMP |
|
344 nsXULTemplateBuilder::GetDatabase(nsIRDFCompositeDataSource** aResult) |
|
345 { |
|
346 NS_IF_ADDREF(*aResult = mCompDB); |
|
347 return NS_OK; |
|
348 } |
|
349 |
|
350 NS_IMETHODIMP |
|
351 nsXULTemplateBuilder::GetQueryProcessor(nsIXULTemplateQueryProcessor** aResult) |
|
352 { |
|
353 NS_IF_ADDREF(*aResult = mQueryProcessor.get()); |
|
354 return NS_OK; |
|
355 } |
|
356 |
|
357 NS_IMETHODIMP |
|
358 nsXULTemplateBuilder::AddRuleFilter(nsIDOMNode* aRule, nsIXULTemplateRuleFilter* aFilter) |
|
359 { |
|
360 if (!aRule || !aFilter) |
|
361 return NS_ERROR_NULL_POINTER; |
|
362 |
|
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 |
|
366 |
|
367 int32_t count = mQuerySets.Length(); |
|
368 for (int32_t q = 0; q < count; q++) { |
|
369 nsTemplateQuerySet* queryset = mQuerySets[q]; |
|
370 |
|
371 int16_t rulecount = queryset->RuleCount(); |
|
372 for (int16_t r = 0; r < rulecount; r++) { |
|
373 nsTemplateRule* rule = queryset->GetRuleAt(r); |
|
374 |
|
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 } |
|
383 |
|
384 return NS_OK; |
|
385 } |
|
386 |
|
387 NS_IMETHODIMP |
|
388 nsXULTemplateBuilder::Rebuild() |
|
389 { |
|
390 int32_t i; |
|
391 |
|
392 for (i = mListeners.Count() - 1; i >= 0; --i) { |
|
393 mListeners[i]->WillRebuild(this); |
|
394 } |
|
395 |
|
396 nsresult rv = RebuildAll(); |
|
397 |
|
398 for (i = mListeners.Count() - 1; i >= 0; --i) { |
|
399 mListeners[i]->DidRebuild(this); |
|
400 } |
|
401 |
|
402 return rv; |
|
403 } |
|
404 |
|
405 NS_IMETHODIMP |
|
406 nsXULTemplateBuilder::Refresh() |
|
407 { |
|
408 nsresult rv; |
|
409 |
|
410 if (!mCompDB) |
|
411 return NS_ERROR_FAILURE; |
|
412 |
|
413 nsCOMPtr<nsISimpleEnumerator> dslist; |
|
414 rv = mCompDB->GetDataSources(getter_AddRefs(dslist)); |
|
415 NS_ENSURE_SUCCESS(rv, rv); |
|
416 |
|
417 bool hasMore; |
|
418 nsCOMPtr<nsISupports> next; |
|
419 nsCOMPtr<nsIRDFRemoteDataSource> rds; |
|
420 |
|
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 } |
|
427 |
|
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. |
|
430 |
|
431 return NS_OK; |
|
432 } |
|
433 |
|
434 NS_IMETHODIMP |
|
435 nsXULTemplateBuilder::Init(nsIContent* aElement) |
|
436 { |
|
437 NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); |
|
438 mRoot = aElement; |
|
439 |
|
440 nsCOMPtr<nsIDocument> doc = mRoot->GetDocument(); |
|
441 NS_ASSERTION(doc, "element has no document"); |
|
442 if (! doc) |
|
443 return NS_ERROR_UNEXPECTED; |
|
444 |
|
445 bool shouldDelay; |
|
446 nsresult rv = LoadDataSources(doc, &shouldDelay); |
|
447 |
|
448 if (NS_SUCCEEDED(rv)) { |
|
449 StartObserving(doc); |
|
450 } |
|
451 |
|
452 return rv; |
|
453 } |
|
454 |
|
455 NS_IMETHODIMP |
|
456 nsXULTemplateBuilder::CreateContents(nsIContent* aElement, bool aForceCreation) |
|
457 { |
|
458 return NS_OK; |
|
459 } |
|
460 |
|
461 NS_IMETHODIMP |
|
462 nsXULTemplateBuilder::HasGeneratedContent(nsIRDFResource* aResource, |
|
463 nsIAtom* aTag, |
|
464 bool* aGenerated) |
|
465 { |
|
466 *aGenerated = false; |
|
467 return NS_OK; |
|
468 } |
|
469 |
|
470 NS_IMETHODIMP |
|
471 nsXULTemplateBuilder::AddResult(nsIXULTemplateResult* aResult, |
|
472 nsIDOMNode* aQueryNode) |
|
473 { |
|
474 NS_ENSURE_ARG_POINTER(aResult); |
|
475 NS_ENSURE_ARG_POINTER(aQueryNode); |
|
476 |
|
477 return UpdateResult(nullptr, aResult, aQueryNode); |
|
478 } |
|
479 |
|
480 NS_IMETHODIMP |
|
481 nsXULTemplateBuilder::RemoveResult(nsIXULTemplateResult* aResult) |
|
482 { |
|
483 NS_ENSURE_ARG_POINTER(aResult); |
|
484 |
|
485 return UpdateResult(aResult, nullptr, nullptr); |
|
486 } |
|
487 |
|
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); |
|
496 |
|
497 // just remove the old result and then add a new result separately |
|
498 |
|
499 nsresult rv = UpdateResult(aOldResult, nullptr, nullptr); |
|
500 if (NS_FAILED(rv)) |
|
501 return rv; |
|
502 |
|
503 return UpdateResult(nullptr, aNewResult, aQueryNode); |
|
504 } |
|
505 |
|
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)); |
|
514 |
|
515 if (!mRoot || !mQueriesCompiled) |
|
516 return NS_OK; |
|
517 |
|
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. |
|
523 |
|
524 nsAutoPtr<nsCOMArray<nsIContent> > insertionPoints; |
|
525 bool mayReplace = GetInsertionLocations(aOldResult ? aOldResult : aNewResult, |
|
526 getter_Transfers(insertionPoints)); |
|
527 if (! mayReplace) |
|
528 return NS_OK; |
|
529 |
|
530 nsresult rv = NS_OK; |
|
531 |
|
532 nsCOMPtr<nsIRDFResource> oldId, newId; |
|
533 nsTemplateQuerySet* queryset = nullptr; |
|
534 |
|
535 if (aOldResult) { |
|
536 rv = GetResultResource(aOldResult, getter_AddRefs(oldId)); |
|
537 if (NS_FAILED(rv)) |
|
538 return rv; |
|
539 |
|
540 // Ignore re-entrant builds for content that is currently in our |
|
541 // activation stack. |
|
542 if (IsActivated(oldId)) |
|
543 return NS_OK; |
|
544 } |
|
545 |
|
546 if (aNewResult) { |
|
547 rv = GetResultResource(aNewResult, getter_AddRefs(newId)); |
|
548 if (NS_FAILED(rv)) |
|
549 return rv; |
|
550 |
|
551 // skip results that don't have ids |
|
552 if (! newId) |
|
553 return NS_OK; |
|
554 |
|
555 // Ignore re-entrant builds for content that is currently in our |
|
556 // activation stack. |
|
557 if (IsActivated(newId)) |
|
558 return NS_OK; |
|
559 |
|
560 // look for the queryset associated with the supplied query node |
|
561 nsCOMPtr<nsIContent> querycontent = do_QueryInterface(aQueryNode); |
|
562 |
|
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 } |
|
571 |
|
572 if (! queryset) |
|
573 return NS_OK; |
|
574 } |
|
575 |
|
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 } |
|
596 |
|
597 return NS_OK; |
|
598 } |
|
599 |
|
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. |
|
665 |
|
666 nsresult rv = NS_OK; |
|
667 int16_t ruleindex; |
|
668 nsTemplateRule* matchedrule = nullptr; |
|
669 |
|
670 // Indicates that the old match was active and must have its content |
|
671 // removed |
|
672 bool oldMatchWasActive = false; |
|
673 |
|
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; |
|
681 |
|
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; |
|
687 |
|
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; |
|
699 |
|
700 if (aOldResult) { |
|
701 nsTemplateMatch* firstmatch; |
|
702 if (mMatchMap.Get(aOldId, &firstmatch)) { |
|
703 nsTemplateMatch* oldmatch = firstmatch; |
|
704 nsTemplateMatch* prevmatch = nullptr; |
|
705 |
|
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 } |
|
711 |
|
712 if (oldmatch) { |
|
713 nsTemplateMatch* findmatch = oldmatch->mNext; |
|
714 |
|
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; |
|
718 |
|
719 if (oldmatch->IsActive()) { |
|
720 // Indicate that the old match was active so its content |
|
721 // will be removed later. |
|
722 oldMatchWasActive = true; |
|
723 |
|
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()]; |
|
733 |
|
734 DetermineMatchedRule(aInsertionPoint, findmatch->mResult, |
|
735 qs, &matchedrule, &ruleindex); |
|
736 |
|
737 if (matchedrule) { |
|
738 rv = findmatch->RuleMatched(qs, |
|
739 matchedrule, ruleindex, |
|
740 findmatch->mResult); |
|
741 if (NS_FAILED(rv)) |
|
742 return rv; |
|
743 |
|
744 acceptedmatch = findmatch; |
|
745 break; |
|
746 } |
|
747 } |
|
748 |
|
749 findmatch = findmatch->mNext; |
|
750 } |
|
751 } |
|
752 |
|
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 } |
|
762 |
|
763 if (prevmatch) |
|
764 prevmatch->mNext = nextmatch; |
|
765 |
|
766 removedmatch = oldmatch; |
|
767 if (mFlags & eLoggingEnabled) |
|
768 OutputMatchToLog(aOldId, removedmatch, false); |
|
769 } |
|
770 } |
|
771 } |
|
772 |
|
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; |
|
779 |
|
780 int32_t findpriority = aQuerySet->Priority(); |
|
781 |
|
782 newmatch = nsTemplateMatch::Create(findpriority, |
|
783 aNewResult, aInsertionPoint); |
|
784 if (!newmatch) |
|
785 return NS_ERROR_OUT_OF_MEMORY; |
|
786 |
|
787 nsTemplateMatch* firstmatch; |
|
788 if (mMatchMap.Get(aNewId, &firstmatch)) { |
|
789 bool hasEarlierActiveMatch = false; |
|
790 |
|
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 } |
|
805 |
|
806 // look for matches that belong in the same container |
|
807 if (oldmatch->GetContainer() == aInsertionPoint) { |
|
808 if (priority == findpriority) |
|
809 break; |
|
810 |
|
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 } |
|
816 |
|
817 prevmatch = oldmatch; |
|
818 oldmatch = oldmatch->mNext; |
|
819 } |
|
820 |
|
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. |
|
824 |
|
825 if (oldmatch) |
|
826 newmatch->mNext = oldmatch->mNext; |
|
827 else if (prevmatch) |
|
828 newmatch->mNext = prevmatch->mNext; |
|
829 else |
|
830 newmatch->mNext = firstmatch; |
|
831 |
|
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 } |
|
849 |
|
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 } |
|
857 |
|
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 } |
|
866 |
|
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; |
|
874 |
|
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 } |
|
906 |
|
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 } |
|
915 |
|
916 acceptedmatch = newmatch; |
|
917 break; |
|
918 } |
|
919 } |
|
920 |
|
921 newmatch = newmatch->mNext; |
|
922 } |
|
923 } |
|
924 |
|
925 // put the match in the map if there isn't a previous match |
|
926 if (! prevmatch) { |
|
927 mMatchMap.Put(aNewId, newmatch); |
|
928 } |
|
929 } |
|
930 |
|
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 } |
|
944 |
|
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 } |
|
952 |
|
953 acceptedmatch = newmatch; |
|
954 } |
|
955 |
|
956 mMatchMap.Put(aNewId, newmatch); |
|
957 } |
|
958 } |
|
959 |
|
960 // The ReplaceMatch method is builder specific and removes the generated |
|
961 // content for a match. |
|
962 |
|
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); |
|
967 |
|
968 if (mFlags & eLoggingEnabled) |
|
969 OutputMatchToLog(aNewId, replacedmatch, false); |
|
970 } |
|
971 |
|
972 // remove a match that needs to be deleted. |
|
973 if (replacedmatchtodelete) |
|
974 nsTemplateMatch::Destroy(replacedmatchtodelete, true); |
|
975 |
|
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); |
|
983 |
|
984 // delete the old match that was replaced |
|
985 if (removedmatch) |
|
986 nsTemplateMatch::Destroy(removedmatch, true); |
|
987 |
|
988 if (mFlags & eLoggingEnabled && newmatch) |
|
989 OutputMatchToLog(aNewId, newmatch, true); |
|
990 |
|
991 return rv; |
|
992 } |
|
993 |
|
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); |
|
1001 |
|
1002 if (!mRoot || !mQueriesCompiled) |
|
1003 return NS_OK; |
|
1004 |
|
1005 return SynchronizeResult(aResult); |
|
1006 } |
|
1007 |
|
1008 NS_IMETHODIMP |
|
1009 nsXULTemplateBuilder::GetRootResult(nsIXULTemplateResult** aResult) |
|
1010 { |
|
1011 *aResult = mRootResult; |
|
1012 NS_IF_ADDREF(*aResult); |
|
1013 return NS_OK; |
|
1014 } |
|
1015 |
|
1016 NS_IMETHODIMP |
|
1017 nsXULTemplateBuilder::GetResultForId(const nsAString& aId, |
|
1018 nsIXULTemplateResult** aResult) |
|
1019 { |
|
1020 if (aId.IsEmpty()) |
|
1021 return NS_ERROR_INVALID_ARG; |
|
1022 |
|
1023 nsCOMPtr<nsIRDFResource> resource; |
|
1024 gRDFService->GetUnicodeResource(aId, getter_AddRefs(resource)); |
|
1025 |
|
1026 *aResult = nullptr; |
|
1027 |
|
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 } |
|
1040 |
|
1041 return NS_OK; |
|
1042 } |
|
1043 |
|
1044 NS_IMETHODIMP |
|
1045 nsXULTemplateBuilder::GetResultForContent(nsIDOMElement* aContent, |
|
1046 nsIXULTemplateResult** aResult) |
|
1047 { |
|
1048 *aResult = nullptr; |
|
1049 return NS_OK; |
|
1050 } |
|
1051 |
|
1052 NS_IMETHODIMP |
|
1053 nsXULTemplateBuilder::AddListener(nsIXULBuilderListener* aListener) |
|
1054 { |
|
1055 NS_ENSURE_ARG(aListener); |
|
1056 |
|
1057 if (!mListeners.AppendObject(aListener)) |
|
1058 return NS_ERROR_OUT_OF_MEMORY; |
|
1059 |
|
1060 return NS_OK; |
|
1061 } |
|
1062 |
|
1063 NS_IMETHODIMP |
|
1064 nsXULTemplateBuilder::RemoveListener(nsIXULBuilderListener* aListener) |
|
1065 { |
|
1066 NS_ENSURE_ARG(aListener); |
|
1067 |
|
1068 mListeners.RemoveObject(aListener); |
|
1069 |
|
1070 return NS_OK; |
|
1071 } |
|
1072 |
|
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 // |
|
1094 |
|
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)); |
|
1109 |
|
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 } |
|
1118 |
|
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); |
|
1128 |
|
1129 if (mQueryProcessor) |
|
1130 mQueryProcessor->Done(); |
|
1131 |
|
1132 // Pass false to Uninit since content is going away anyway |
|
1133 nsContentUtils::AddScriptRunner( |
|
1134 NS_NewRunnableMethod(this, &nsXULTemplateBuilder::UninitFalse)); |
|
1135 |
|
1136 MOZ_ASSERT(aDocument == mObservedDocument); |
|
1137 StopObserving(); |
|
1138 |
|
1139 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument); |
|
1140 if (xuldoc) |
|
1141 xuldoc->SetTemplateBuilderFor(mRoot, nullptr); |
|
1142 |
|
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(); |
|
1148 |
|
1149 CleanUp(true); |
|
1150 |
|
1151 mDB = nullptr; |
|
1152 mCompDB = nullptr; |
|
1153 mDataSource = nullptr; |
|
1154 } |
|
1155 } |
|
1156 |
|
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); |
|
1163 |
|
1164 // Break circular references |
|
1165 if (mQueryProcessor) |
|
1166 mQueryProcessor->Done(); |
|
1167 |
|
1168 mDataSource = nullptr; |
|
1169 mDB = nullptr; |
|
1170 mCompDB = nullptr; |
|
1171 |
|
1172 nsContentUtils::AddScriptRunner( |
|
1173 NS_NewRunnableMethod(this, &nsXULTemplateBuilder::UninitTrue)); |
|
1174 } |
|
1175 |
|
1176 |
|
1177 |
|
1178 |
|
1179 //---------------------------------------------------------------------- |
|
1180 // |
|
1181 // Implementation methods |
|
1182 // |
|
1183 |
|
1184 nsresult |
|
1185 nsXULTemplateBuilder::LoadDataSources(nsIDocument* aDocument, |
|
1186 bool* aShouldDelayBuilding) |
|
1187 { |
|
1188 NS_PRECONDITION(mRoot != nullptr, "not initialized"); |
|
1189 |
|
1190 nsresult rv; |
|
1191 bool isRDFQuery = false; |
|
1192 |
|
1193 // we'll set these again later, after we create a new composite ds |
|
1194 mDB = nullptr; |
|
1195 mCompDB = nullptr; |
|
1196 mDataSource = nullptr; |
|
1197 |
|
1198 *aShouldDelayBuilding = false; |
|
1199 |
|
1200 nsAutoString datasources; |
|
1201 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::datasources, datasources); |
|
1202 |
|
1203 nsAutoString querytype; |
|
1204 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::querytype, querytype); |
|
1205 |
|
1206 // create the query processor. The querytype attribute on the root element |
|
1207 // may be used to create one of a specific type. |
|
1208 |
|
1209 // XXX should non-chrome be restricted to specific names? |
|
1210 if (querytype.IsEmpty()) |
|
1211 querytype.AssignLiteral("rdf"); |
|
1212 |
|
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); |
|
1230 |
|
1231 if (!mQueryProcessor) { |
|
1232 nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_INVALID_QUERYPROCESSOR); |
|
1233 return rv; |
|
1234 } |
|
1235 } |
|
1236 |
|
1237 rv = LoadDataSourceUrls(aDocument, datasources, |
|
1238 isRDFQuery, aShouldDelayBuilding); |
|
1239 NS_ENSURE_SUCCESS(rv, rv); |
|
1240 |
|
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); |
|
1246 |
|
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 } |
|
1252 |
|
1253 return NS_OK; |
|
1254 } |
|
1255 |
|
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(); |
|
1264 |
|
1265 NS_ASSERTION(docPrincipal == mRoot->NodePrincipal(), |
|
1266 "Principal mismatch? Which one to use?"); |
|
1267 |
|
1268 bool isTrusted = false; |
|
1269 nsresult rv = IsSystemPrincipal(docPrincipal, &isTrusted); |
|
1270 NS_ENSURE_SUCCESS(rv, rv); |
|
1271 |
|
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(); |
|
1278 |
|
1279 nsCOMPtr<nsIMutableArray> uriList = do_CreateInstance(NS_ARRAY_CONTRACTID); |
|
1280 if (!uriList) |
|
1281 return NS_ERROR_FAILURE; |
|
1282 |
|
1283 nsAutoString datasources(aDataSources); |
|
1284 uint32_t first = 0; |
|
1285 while (1) { |
|
1286 while (first < datasources.Length() && nsCRT::IsAsciiSpace(datasources.CharAt(first))) |
|
1287 ++first; |
|
1288 |
|
1289 if (first >= datasources.Length()) |
|
1290 break; |
|
1291 |
|
1292 uint32_t last = first; |
|
1293 while (last < datasources.Length() && !nsCRT::IsAsciiSpace(datasources.CharAt(last))) |
|
1294 ++last; |
|
1295 |
|
1296 nsAutoString uriStr; |
|
1297 datasources.Mid(uriStr, first, last - first); |
|
1298 first = last + 1; |
|
1299 |
|
1300 // A special 'dummy' datasource |
|
1301 if (uriStr.EqualsLiteral("rdf:null")) |
|
1302 continue; |
|
1303 |
|
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; |
|
1308 |
|
1309 domdoc->GetElementById(Substring(uriStr, 1), |
|
1310 getter_AddRefs(dsnode)); |
|
1311 |
|
1312 if (dsnode) |
|
1313 uriList->AppendElement(dsnode, false); |
|
1314 continue; |
|
1315 } |
|
1316 |
|
1317 // N.B. that `failure' (e.g., because it's an unknown |
|
1318 // protocol) leaves uriStr unaltered. |
|
1319 NS_MakeAbsoluteURI(uriStr, uriStr, docurl); |
|
1320 |
|
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 |
|
1325 |
|
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; |
|
1330 |
|
1331 uriList->AppendElement(uri, false); |
|
1332 } |
|
1333 |
|
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); |
|
1342 |
|
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 } |
|
1352 |
|
1353 if (!mCompDB) |
|
1354 mCompDB = do_QueryInterface(mDataSource); |
|
1355 |
|
1356 mDB = do_QueryInterface(mDataSource); |
|
1357 } |
|
1358 |
|
1359 if (!mDB && isTrusted) { |
|
1360 gRDFService->GetDataSource("rdf:local-store", getter_AddRefs(mDB)); |
|
1361 } |
|
1362 |
|
1363 return NS_OK; |
|
1364 } |
|
1365 |
|
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; |
|
1372 |
|
1373 nsCOMPtr<nsIDocument> doc = mRoot->GetDocument(); |
|
1374 NS_ASSERTION(doc, "no document"); |
|
1375 if (! doc) |
|
1376 return NS_ERROR_UNEXPECTED; |
|
1377 |
|
1378 nsCOMPtr<nsIScriptGlobalObject> global = |
|
1379 do_QueryInterface(doc->GetWindow()); |
|
1380 if (! global) |
|
1381 return NS_ERROR_UNEXPECTED; |
|
1382 |
|
1383 nsCOMPtr<nsIGlobalObject> innerWin = |
|
1384 do_QueryInterface(doc->GetInnerWindow()); |
|
1385 |
|
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(); |
|
1390 |
|
1391 JS::Rooted<JS::Value> v(jscontext); |
|
1392 rv = nsContentUtils::WrapNative(jscontext, mRoot, mRoot, &v); |
|
1393 NS_ENSURE_SUCCESS(rv, rv); |
|
1394 |
|
1395 JS::Rooted<JSObject*> jselement(jscontext, JSVAL_TO_OBJECT(v)); |
|
1396 |
|
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); |
|
1404 |
|
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 } |
|
1410 |
|
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); |
|
1419 |
|
1420 bool ok = JS_SetProperty(jscontext, jselement, "builder", jsbuilder); |
|
1421 if (! ok) |
|
1422 return NS_ERROR_FAILURE; |
|
1423 } |
|
1424 |
|
1425 return NS_OK; |
|
1426 } |
|
1427 |
|
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 } |
|
1449 |
|
1450 *aRuleIndex = -1; |
|
1451 *aMatchedRule = nullptr; |
|
1452 return NS_OK; |
|
1453 } |
|
1454 |
|
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); |
|
1463 |
|
1464 nsAString::const_iterator iter; |
|
1465 aAttributeValue.BeginReading(iter); |
|
1466 |
|
1467 nsAString::const_iterator mark(iter), backup(iter); |
|
1468 |
|
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 } |
|
1485 |
|
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 } |
|
1499 |
|
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 } |
|
1506 |
|
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); |
|
1512 |
|
1513 char16_t c = 0; |
|
1514 while (iter != done_parsing) { |
|
1515 c = *iter; |
|
1516 if ((c == char16_t(' ')) || (c == char16_t('^'))) |
|
1517 break; |
|
1518 |
|
1519 ++iter; |
|
1520 } |
|
1521 |
|
1522 nsAString::const_iterator last(iter); |
|
1523 |
|
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; |
|
1529 |
|
1530 (*aVariableCallback)(this, Substring(first, last), aClosure); |
|
1531 mark = iter; |
|
1532 ++mark; |
|
1533 } |
|
1534 |
|
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 } |
|
1540 |
|
1541 |
|
1542 struct MOZ_STACK_CLASS SubstituteTextClosure { |
|
1543 SubstituteTextClosure(nsIXULTemplateResult* aResult, nsAString& aString) |
|
1544 : result(aResult), str(aString) {} |
|
1545 |
|
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 }; |
|
1552 |
|
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 } |
|
1563 |
|
1564 // Reasonable guess at how big it should be |
|
1565 aString.SetCapacity(aAttributeValue.Length()); |
|
1566 |
|
1567 SubstituteTextClosure closure(aResult, aString); |
|
1568 ParseAttribute(aAttributeValue, |
|
1569 SubstituteTextReplaceVariable, |
|
1570 SubstituteTextAppendText, |
|
1571 &closure); |
|
1572 |
|
1573 return NS_OK; |
|
1574 } |
|
1575 |
|
1576 |
|
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 } |
|
1586 |
|
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); |
|
1595 |
|
1596 nsAutoString replacementText; |
|
1597 |
|
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 } |
|
1607 |
|
1608 c->str += replacementText; |
|
1609 } |
|
1610 |
|
1611 bool |
|
1612 nsXULTemplateBuilder::IsTemplateElement(nsIContent* aContent) |
|
1613 { |
|
1614 return aContent->NodeInfo()->Equals(nsGkAtoms::_template, |
|
1615 kNameSpaceID_XUL); |
|
1616 } |
|
1617 |
|
1618 nsresult |
|
1619 nsXULTemplateBuilder::GetTemplateRoot(nsIContent** aResult) |
|
1620 { |
|
1621 NS_PRECONDITION(mRoot != nullptr, "not initialized"); |
|
1622 if (! mRoot) |
|
1623 return NS_ERROR_NOT_INITIALIZED; |
|
1624 |
|
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); |
|
1635 |
|
1636 if (! templateID.IsEmpty()) { |
|
1637 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mRoot->GetDocument()); |
|
1638 if (! domDoc) |
|
1639 return NS_OK; |
|
1640 |
|
1641 nsCOMPtr<nsIDOMElement> domElement; |
|
1642 domDoc->GetElementById(templateID, getter_AddRefs(domElement)); |
|
1643 |
|
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 } |
|
1653 |
|
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()) { |
|
1659 |
|
1660 if (IsTemplateElement(child)) { |
|
1661 NS_ADDREF(*aResult = child); |
|
1662 return NS_OK; |
|
1663 } |
|
1664 } |
|
1665 |
|
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 } |
|
1678 |
|
1679 *aResult = nullptr; |
|
1680 return NS_OK; |
|
1681 } |
|
1682 |
|
1683 nsresult |
|
1684 nsXULTemplateBuilder::CompileQueries() |
|
1685 { |
|
1686 nsCOMPtr<nsIContent> tmpl; |
|
1687 GetTemplateRoot(getter_AddRefs(tmpl)); |
|
1688 if (! tmpl) |
|
1689 return NS_OK; |
|
1690 |
|
1691 if (! mRoot) |
|
1692 return NS_ERROR_NOT_INITIALIZED; |
|
1693 |
|
1694 // Determine if there are any special settings we need to observe |
|
1695 mFlags = 0; |
|
1696 |
|
1697 nsAutoString flags; |
|
1698 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::flags, flags); |
|
1699 |
|
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 } |
|
1715 |
|
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 |
|
1721 |
|
1722 nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot); |
|
1723 nsresult rv = |
|
1724 mQueryProcessor->InitializeForBuilding(mDataSource, this, rootnode); |
|
1725 if (NS_FAILED(rv)) |
|
1726 return rv; |
|
1727 |
|
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. |
|
1735 |
|
1736 // For RDF queries, the container variable may also be set via the |
|
1737 // <content> tag. |
|
1738 |
|
1739 nsAutoString containervar; |
|
1740 tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::container, containervar); |
|
1741 |
|
1742 if (containervar.IsEmpty()) |
|
1743 mRefVariable = do_GetAtom("?uri"); |
|
1744 else |
|
1745 mRefVariable = do_GetAtom(containervar); |
|
1746 |
|
1747 nsAutoString membervar; |
|
1748 tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::member, membervar); |
|
1749 |
|
1750 if (membervar.IsEmpty()) |
|
1751 mMemberVariable = nullptr; |
|
1752 else |
|
1753 mMemberVariable = do_GetAtom(membervar); |
|
1754 |
|
1755 nsTemplateQuerySet* queryset = new nsTemplateQuerySet(0); |
|
1756 if (!queryset) |
|
1757 return NS_ERROR_OUT_OF_MEMORY; |
|
1758 |
|
1759 if (!mQuerySets.AppendElement(queryset)) { |
|
1760 delete queryset; |
|
1761 return NS_ERROR_OUT_OF_MEMORY; |
|
1762 } |
|
1763 |
|
1764 bool canUseTemplate = false; |
|
1765 int32_t priority = 0; |
|
1766 rv = CompileTemplate(tmpl, queryset, false, &priority, &canUseTemplate); |
|
1767 |
|
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 } |
|
1775 |
|
1776 mQueriesCompiled = true; |
|
1777 |
|
1778 return NS_OK; |
|
1779 } |
|
1780 |
|
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"); |
|
1789 |
|
1790 nsresult rv = NS_OK; |
|
1791 |
|
1792 bool isQuerySetMode = false; |
|
1793 bool hasQuerySet = false, hasRule = false, hasQuery = false; |
|
1794 |
|
1795 for (nsIContent* rulenode = aTemplate->GetFirstChild(); |
|
1796 rulenode; |
|
1797 rulenode = rulenode->GetNextSibling()) { |
|
1798 |
|
1799 nsINodeInfo *ni = rulenode->NodeInfo(); |
|
1800 |
|
1801 // don't allow more queries than can be supported |
|
1802 if (*aPriority == INT16_MAX) |
|
1803 return NS_ERROR_FAILURE; |
|
1804 |
|
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 } |
|
1812 |
|
1813 isQuerySetMode = true; |
|
1814 |
|
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; |
|
1821 |
|
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 } |
|
1829 |
|
1830 hasQuerySet = true; |
|
1831 |
|
1832 rv = CompileTemplate(rulenode, aQuerySet, true, aPriority, aCanUseTemplate); |
|
1833 if (NS_FAILED(rv)) |
|
1834 return rv; |
|
1835 } |
|
1836 |
|
1837 // once a queryset is used, everything must be a queryset |
|
1838 if (isQuerySetMode) |
|
1839 continue; |
|
1840 |
|
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)); |
|
1847 |
|
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 } |
|
1857 |
|
1858 if (hasQuery) { |
|
1859 nsCOMPtr<nsIAtom> tag; |
|
1860 DetermineRDFQueryRef(aQuerySet->mQueryNode, |
|
1861 getter_AddRefs(tag)); |
|
1862 if (tag) |
|
1863 aQuerySet->SetTag(tag); |
|
1864 |
|
1865 if (! aQuerySet->mCompiledQuery) { |
|
1866 nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode)); |
|
1867 |
|
1868 rv = mQueryProcessor->CompileQuery(this, query, |
|
1869 mRefVariable, memberVariable, |
|
1870 getter_AddRefs(aQuerySet->mCompiledQuery)); |
|
1871 if (NS_FAILED(rv)) |
|
1872 return rv; |
|
1873 } |
|
1874 |
|
1875 if (aQuerySet->mCompiledQuery) { |
|
1876 rv = CompileExtendedQuery(rulenode, action, memberVariable, |
|
1877 aQuerySet); |
|
1878 if (NS_FAILED(rv)) |
|
1879 return rv; |
|
1880 |
|
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. |
|
1888 |
|
1889 nsCOMPtr<nsIContent> conditions; |
|
1890 nsXULContentUtils::FindChildByTag(rulenode, |
|
1891 kNameSpaceID_XUL, |
|
1892 nsGkAtoms::conditions, |
|
1893 getter_AddRefs(conditions)); |
|
1894 |
|
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; |
|
1901 |
|
1902 if (!mQuerySets.AppendElement(aQuerySet)) { |
|
1903 delete aQuerySet; |
|
1904 return NS_ERROR_OUT_OF_MEMORY; |
|
1905 } |
|
1906 } |
|
1907 |
|
1908 nsCOMPtr<nsIAtom> tag; |
|
1909 DetermineRDFQueryRef(conditions, getter_AddRefs(tag)); |
|
1910 if (tag) |
|
1911 aQuerySet->SetTag(tag); |
|
1912 |
|
1913 hasQuerySet = true; |
|
1914 |
|
1915 nsCOMPtr<nsIDOMNode> conditionsnode(do_QueryInterface(conditions)); |
|
1916 |
|
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; |
|
1924 |
|
1925 if (aQuerySet->mCompiledQuery) { |
|
1926 rv = CompileExtendedQuery(rulenode, action, memberVariable, |
|
1927 aQuerySet); |
|
1928 if (NS_FAILED(rv)) |
|
1929 return rv; |
|
1930 |
|
1931 *aCanUseTemplate = true; |
|
1932 } |
|
1933 } |
|
1934 } |
|
1935 } |
|
1936 else { |
|
1937 if (hasQuery) |
|
1938 continue; |
|
1939 |
|
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; |
|
1945 |
|
1946 if (!mQuerySets.AppendElement(aQuerySet)) { |
|
1947 delete aQuerySet; |
|
1948 return NS_ERROR_OUT_OF_MEMORY; |
|
1949 } |
|
1950 } |
|
1951 |
|
1952 hasQuerySet = true; |
|
1953 |
|
1954 rv = CompileSimpleQuery(rulenode, aQuerySet, aCanUseTemplate); |
|
1955 if (NS_FAILED(rv)) |
|
1956 return rv; |
|
1957 } |
|
1958 |
|
1959 hasRule = true; |
|
1960 } |
|
1961 else if (ni->Equals(nsGkAtoms::query, kNameSpaceID_XUL)) { |
|
1962 if (hasQuery) |
|
1963 continue; |
|
1964 |
|
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; |
|
1972 |
|
1973 nsCOMPtr<nsIAtom> tag; |
|
1974 DetermineRDFQueryRef(aQuerySet->mQueryNode, getter_AddRefs(tag)); |
|
1975 if (tag) |
|
1976 aQuerySet->SetTag(tag); |
|
1977 |
|
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 } |
|
1986 |
|
1987 nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode)); |
|
1988 |
|
1989 rv = mQueryProcessor->CompileQuery(this, query, |
|
1990 mRefVariable, memberVariable, |
|
1991 getter_AddRefs(aQuerySet->mCompiledQuery)); |
|
1992 |
|
1993 if (aQuerySet->mCompiledQuery) { |
|
1994 nsTemplateRule* rule = aQuerySet->NewRule(aTemplate, rulenode, aQuerySet); |
|
1995 if (! rule) |
|
1996 return NS_ERROR_OUT_OF_MEMORY; |
|
1997 |
|
1998 rule->SetVars(mRefVariable, memberVariable); |
|
1999 |
|
2000 *aCanUseTemplate = true; |
|
2001 |
|
2002 return NS_OK; |
|
2003 } |
|
2004 } |
|
2005 } |
|
2006 |
|
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 } |
|
2012 |
|
2013 return rv; |
|
2014 } |
|
2015 |
|
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; |
|
2025 |
|
2026 nsTemplateRule* rule = aQuerySet->NewRule(aRuleElement, aActionElement, aQuerySet); |
|
2027 if (! rule) |
|
2028 return NS_ERROR_OUT_OF_MEMORY; |
|
2029 |
|
2030 nsCOMPtr<nsIContent> conditions; |
|
2031 nsXULContentUtils::FindChildByTag(aRuleElement, |
|
2032 kNameSpaceID_XUL, |
|
2033 nsGkAtoms::conditions, |
|
2034 getter_AddRefs(conditions)); |
|
2035 |
|
2036 // allow the conditions to be placed directly inside the rule |
|
2037 if (!conditions) |
|
2038 conditions = aRuleElement; |
|
2039 |
|
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 } |
|
2046 |
|
2047 rule->SetVars(mRefVariable, aMemberVariable); |
|
2048 |
|
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)); |
|
2055 |
|
2056 // allow bindings to be placed directly inside rule |
|
2057 if (!bindings) |
|
2058 bindings = aRuleElement; |
|
2059 |
|
2060 rv = CompileBindings(rule, bindings); |
|
2061 NS_ENSURE_SUCCESS(rv, rv); |
|
2062 |
|
2063 return NS_OK; |
|
2064 } |
|
2065 |
|
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 } |
|
2079 |
|
2080 nsCOMPtr<nsIAtom> result = DetermineMemberVariable(child); |
|
2081 if (result) { |
|
2082 return result.forget(); |
|
2083 } |
|
2084 } |
|
2085 |
|
2086 return nullptr; |
|
2087 } |
|
2088 |
|
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)); |
|
2098 |
|
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 } |
|
2106 |
|
2107 if (content) { |
|
2108 nsAutoString uri; |
|
2109 content->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri); |
|
2110 |
|
2111 if (!uri.IsEmpty()) |
|
2112 mRefVariable = do_GetAtom(uri); |
|
2113 |
|
2114 nsAutoString tag; |
|
2115 content->GetAttr(kNameSpaceID_None, nsGkAtoms::tag, tag); |
|
2116 |
|
2117 if (!tag.IsEmpty()) |
|
2118 *aTag = NS_NewAtom(tag).take(); |
|
2119 } |
|
2120 } |
|
2121 |
|
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)); |
|
2130 |
|
2131 nsCOMPtr<nsIAtom> memberVariable; |
|
2132 if (mMemberVariable) |
|
2133 memberVariable = mMemberVariable; |
|
2134 else |
|
2135 memberVariable = do_GetAtom("rdf:*"); |
|
2136 |
|
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; |
|
2145 |
|
2146 if (! aQuerySet->mCompiledQuery) { |
|
2147 *aCanUseTemplate = false; |
|
2148 return NS_OK; |
|
2149 } |
|
2150 |
|
2151 nsTemplateRule* rule = aQuerySet->NewRule(aRuleElement, aRuleElement, aQuerySet); |
|
2152 if (! rule) |
|
2153 return NS_ERROR_OUT_OF_MEMORY; |
|
2154 |
|
2155 rule->SetVars(mRefVariable, memberVariable); |
|
2156 |
|
2157 nsAutoString tag; |
|
2158 aRuleElement->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag); |
|
2159 |
|
2160 if (!tag.IsEmpty()) { |
|
2161 nsCOMPtr<nsIAtom> tagatom = do_GetAtom(tag); |
|
2162 aQuerySet->SetTag(tagatom); |
|
2163 } |
|
2164 |
|
2165 *aCanUseTemplate = true; |
|
2166 |
|
2167 return AddSimpleRuleBindings(rule, aRuleElement); |
|
2168 } |
|
2169 |
|
2170 nsresult |
|
2171 nsXULTemplateBuilder::CompileConditions(nsTemplateRule* aRule, |
|
2172 nsIContent* aCondition) |
|
2173 { |
|
2174 nsAutoString tag; |
|
2175 aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag); |
|
2176 |
|
2177 if (!tag.IsEmpty()) { |
|
2178 nsCOMPtr<nsIAtom> tagatom = do_GetAtom(tag); |
|
2179 aRule->SetTag(tagatom); |
|
2180 } |
|
2181 |
|
2182 nsTemplateCondition* currentCondition = nullptr; |
|
2183 |
|
2184 for (nsIContent* node = aCondition->GetFirstChild(); |
|
2185 node; |
|
2186 node = node->GetNextSibling()) { |
|
2187 |
|
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 } |
|
2194 |
|
2195 return NS_OK; |
|
2196 } |
|
2197 |
|
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. |
|
2216 |
|
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 } |
|
2224 |
|
2225 nsCOMPtr<nsIAtom> svar; |
|
2226 if (subject[0] == char16_t('?')) |
|
2227 svar = do_GetAtom(subject); |
|
2228 |
|
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 } |
|
2235 |
|
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 } |
|
2243 |
|
2244 // multiple |
|
2245 bool shouldMultiple = |
|
2246 aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::multiple, |
|
2247 nsGkAtoms::_true, eCaseMatters); |
|
2248 |
|
2249 nsCOMPtr<nsIAtom> vvar; |
|
2250 if (!shouldMultiple && (value[0] == char16_t('?'))) { |
|
2251 vvar = do_GetAtom(value); |
|
2252 } |
|
2253 |
|
2254 // ignorecase |
|
2255 bool shouldIgnoreCase = |
|
2256 aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorecase, |
|
2257 nsGkAtoms::_true, eCaseMatters); |
|
2258 |
|
2259 // negate |
|
2260 bool shouldNegate = |
|
2261 aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::negate, |
|
2262 nsGkAtoms::_true, eCaseMatters); |
|
2263 |
|
2264 nsTemplateCondition* condition; |
|
2265 |
|
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 } |
|
2282 |
|
2283 if (! condition) |
|
2284 return NS_ERROR_OUT_OF_MEMORY; |
|
2285 |
|
2286 if (*aCurrentCondition) { |
|
2287 (*aCurrentCondition)->SetNext(condition); |
|
2288 } |
|
2289 else { |
|
2290 aRule->SetCondition(condition); |
|
2291 } |
|
2292 |
|
2293 *aCurrentCondition = condition; |
|
2294 |
|
2295 return NS_OK; |
|
2296 } |
|
2297 |
|
2298 nsresult |
|
2299 nsXULTemplateBuilder::CompileBindings(nsTemplateRule* aRule, nsIContent* aBindings) |
|
2300 { |
|
2301 // Add an extended rule's bindings. |
|
2302 nsresult rv; |
|
2303 |
|
2304 for (nsIContent* binding = aBindings->GetFirstChild(); |
|
2305 binding; |
|
2306 binding = binding->GetNextSibling()) { |
|
2307 |
|
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 } |
|
2315 |
|
2316 aRule->AddBindingsToQueryProcessor(mQueryProcessor); |
|
2317 |
|
2318 return NS_OK; |
|
2319 } |
|
2320 |
|
2321 |
|
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. |
|
2334 |
|
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 } |
|
2342 |
|
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 } |
|
2351 |
|
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 } |
|
2359 |
|
2360 // object |
|
2361 nsAutoString object; |
|
2362 aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::object, object); |
|
2363 |
|
2364 if (object.IsEmpty()) { |
|
2365 nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT); |
|
2366 return NS_OK; |
|
2367 } |
|
2368 |
|
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 } |
|
2377 |
|
2378 return aRule->AddBinding(svar, predicate, ovar); |
|
2379 } |
|
2380 |
|
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:". |
|
2387 |
|
2388 nsAutoTArray<nsIContent*, 8> elements; |
|
2389 |
|
2390 if (elements.AppendElement(aElement) == nullptr) |
|
2391 return NS_ERROR_OUT_OF_MEMORY; |
|
2392 |
|
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); |
|
2398 |
|
2399 // Iterate through its attributes, looking for substitutions |
|
2400 // that we need to add as bindings. |
|
2401 uint32_t count = element->GetAttrCount(); |
|
2402 |
|
2403 for (i = 0; i < count; ++i) { |
|
2404 const nsAttrName* name = element->GetAttrNameAt(i); |
|
2405 |
|
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); |
|
2410 |
|
2411 // Scan the attribute for variables, adding a binding for |
|
2412 // each one. |
|
2413 ParseAttribute(value, AddBindingsFor, nullptr, aRule); |
|
2414 } |
|
2415 } |
|
2416 |
|
2417 // Push kids onto the stack, and search them next. |
|
2418 for (nsIContent* child = element->GetLastChild(); |
|
2419 child; |
|
2420 child = child->GetPreviousSibling()) { |
|
2421 |
|
2422 if (!elements.AppendElement(child)) |
|
2423 return NS_ERROR_OUT_OF_MEMORY; |
|
2424 } |
|
2425 } |
|
2426 |
|
2427 aRule->AddBindingsToQueryProcessor(mQueryProcessor); |
|
2428 |
|
2429 return NS_OK; |
|
2430 } |
|
2431 |
|
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; |
|
2441 |
|
2442 nsTemplateRule* rule = static_cast<nsTemplateRule*>(aClosure); |
|
2443 |
|
2444 nsCOMPtr<nsIAtom> var = do_GetAtom(aVariable); |
|
2445 |
|
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)); |
|
2450 |
|
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 } |
|
2456 |
|
2457 |
|
2458 nsresult |
|
2459 nsXULTemplateBuilder::IsSystemPrincipal(nsIPrincipal *principal, bool *result) |
|
2460 { |
|
2461 if (!gSystemPrincipal) |
|
2462 return NS_ERROR_UNEXPECTED; |
|
2463 |
|
2464 *result = (principal == gSystemPrincipal); |
|
2465 return NS_OK; |
|
2466 } |
|
2467 |
|
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 } |
|
2479 |
|
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; |
|
2490 |
|
2491 if (! *aResource) { |
|
2492 nsAutoString id; |
|
2493 rv = aResult->GetId(id); |
|
2494 if (NS_FAILED(rv)) |
|
2495 return rv; |
|
2496 |
|
2497 return gRDFService->GetUnicodeResource(id, aResource); |
|
2498 } |
|
2499 |
|
2500 return rv; |
|
2501 } |
|
2502 |
|
2503 |
|
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; |
|
2511 |
|
2512 nsAutoString msg; |
|
2513 |
|
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 } |
|
2521 |
|
2522 nsAutoString refstring; |
|
2523 aMatch->mResult->GetBindingFor(mRefVariable, refstring); |
|
2524 if (!refstring.IsEmpty()) { |
|
2525 msg.AppendLiteral(" using ref "); |
|
2526 msg.Append(refstring); |
|
2527 } |
|
2528 |
|
2529 msg.AppendLiteral("\n "); |
|
2530 |
|
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 } |
|
2544 |
|
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 } |
|
2591 |
|
2592 nsAutoString idstring; |
|
2593 nsXULContentUtils::GetTextForNode(aId, idstring); |
|
2594 msg.AppendLiteral(": "); |
|
2595 msg.Append(idstring); |
|
2596 |
|
2597 nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); |
|
2598 if (cs) |
|
2599 cs->LogStringMessage(msg.get()); |
|
2600 } |