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