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