1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/xul/templates/src/nsXULTemplateBuilder.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2600 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +/* 1.10 + 1.11 + Builds content from a datasource using the XUL <template> tag. 1.12 + 1.13 + TO DO 1.14 + 1.15 + . Fix ContentTagTest's location in the network construction 1.16 + 1.17 + To turn on logging for this module, set: 1.18 + 1.19 + NSPR_LOG_MODULES nsXULTemplateBuilder:5 1.20 + 1.21 + */ 1.22 + 1.23 +#include "nsCOMPtr.h" 1.24 +#include "nsCRT.h" 1.25 +#include "nsIContent.h" 1.26 +#include "nsIDOMElement.h" 1.27 +#include "nsIDOMNode.h" 1.28 +#include "nsIDOMDocument.h" 1.29 +#include "nsIDOMXMLDocument.h" 1.30 +#include "nsIDOMXULElement.h" 1.31 +#include "nsIDocument.h" 1.32 +#include "nsBindingManager.h" 1.33 +#include "nsIDOMNodeList.h" 1.34 +#include "nsIObserverService.h" 1.35 +#include "nsIRDFCompositeDataSource.h" 1.36 +#include "nsIRDFInferDataSource.h" 1.37 +#include "nsIRDFContainerUtils.h" 1.38 +#include "nsIXULDocument.h" 1.39 +#include "nsIXULTemplateBuilder.h" 1.40 +#include "nsIXULBuilderListener.h" 1.41 +#include "nsIRDFRemoteDataSource.h" 1.42 +#include "nsIRDFService.h" 1.43 +#include "nsIScriptContext.h" 1.44 +#include "nsIScriptGlobalObject.h" 1.45 +#include "nsIServiceManager.h" 1.46 +#include "nsISimpleEnumerator.h" 1.47 +#include "nsIMutableArray.h" 1.48 +#include "nsIURL.h" 1.49 +#include "nsIXPConnect.h" 1.50 +#include "nsContentCID.h" 1.51 +#include "nsNameSpaceManager.h" 1.52 +#include "nsRDFCID.h" 1.53 +#include "nsXULContentUtils.h" 1.54 +#include "nsString.h" 1.55 +#include "nsTArray.h" 1.56 +#include "nsXPIDLString.h" 1.57 +#include "nsWhitespaceTokenizer.h" 1.58 +#include "nsGkAtoms.h" 1.59 +#include "nsXULElement.h" 1.60 +#include "jsapi.h" 1.61 +#include "prlog.h" 1.62 +#include "rdf.h" 1.63 +#include "pldhash.h" 1.64 +#include "plhash.h" 1.65 +#include "nsDOMClassInfoID.h" 1.66 +#include "nsPIDOMWindow.h" 1.67 +#include "nsIConsoleService.h" 1.68 +#include "nsNetUtil.h" 1.69 +#include "nsXULTemplateBuilder.h" 1.70 +#include "nsXULTemplateQueryProcessorRDF.h" 1.71 +#include "nsXULTemplateQueryProcessorXML.h" 1.72 +#include "nsXULTemplateQueryProcessorStorage.h" 1.73 +#include "nsContentUtils.h" 1.74 +#include "ChildIterator.h" 1.75 +#include "mozilla/dom/ScriptSettings.h" 1.76 + 1.77 +using namespace mozilla::dom; 1.78 +using namespace mozilla; 1.79 + 1.80 +//---------------------------------------------------------------------- 1.81 +// 1.82 +// nsXULTemplateBuilder 1.83 +// 1.84 + 1.85 +nsrefcnt nsXULTemplateBuilder::gRefCnt = 0; 1.86 +nsIRDFService* nsXULTemplateBuilder::gRDFService; 1.87 +nsIRDFContainerUtils* nsXULTemplateBuilder::gRDFContainerUtils; 1.88 +nsIScriptSecurityManager* nsXULTemplateBuilder::gScriptSecurityManager; 1.89 +nsIPrincipal* nsXULTemplateBuilder::gSystemPrincipal; 1.90 +nsIObserverService* nsXULTemplateBuilder::gObserverService; 1.91 + 1.92 +#ifdef PR_LOGGING 1.93 +PRLogModuleInfo* gXULTemplateLog; 1.94 +#endif 1.95 + 1.96 +#define NS_QUERY_PROCESSOR_CONTRACTID_PREFIX "@mozilla.org/xul/xul-query-processor;1?name=" 1.97 + 1.98 +//---------------------------------------------------------------------- 1.99 +// 1.100 +// nsXULTemplateBuilder methods 1.101 +// 1.102 + 1.103 +nsXULTemplateBuilder::nsXULTemplateBuilder(void) 1.104 + : mQueriesCompiled(false), 1.105 + mFlags(0), 1.106 + mTop(nullptr), 1.107 + mObservedDocument(nullptr) 1.108 +{ 1.109 + MOZ_COUNT_CTOR(nsXULTemplateBuilder); 1.110 +} 1.111 + 1.112 +static PLDHashOperator 1.113 +DestroyMatchList(nsISupports* aKey, nsTemplateMatch*& aMatch, void* aContext) 1.114 +{ 1.115 + // delete all the matches in the list 1.116 + while (aMatch) { 1.117 + nsTemplateMatch* next = aMatch->mNext; 1.118 + nsTemplateMatch::Destroy(aMatch, true); 1.119 + aMatch = next; 1.120 + } 1.121 + 1.122 + return PL_DHASH_REMOVE; 1.123 +} 1.124 + 1.125 +nsXULTemplateBuilder::~nsXULTemplateBuilder(void) 1.126 +{ 1.127 + Uninit(true); 1.128 + 1.129 + if (--gRefCnt == 0) { 1.130 + NS_IF_RELEASE(gRDFService); 1.131 + NS_IF_RELEASE(gRDFContainerUtils); 1.132 + NS_IF_RELEASE(gSystemPrincipal); 1.133 + NS_IF_RELEASE(gScriptSecurityManager); 1.134 + NS_IF_RELEASE(gObserverService); 1.135 + } 1.136 + 1.137 + MOZ_COUNT_DTOR(nsXULTemplateBuilder); 1.138 +} 1.139 + 1.140 + 1.141 +nsresult 1.142 +nsXULTemplateBuilder::InitGlobals() 1.143 +{ 1.144 + nsresult rv; 1.145 + 1.146 + if (gRefCnt++ == 0) { 1.147 + // Initialize the global shared reference to the service 1.148 + // manager and get some shared resource objects. 1.149 + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); 1.150 + rv = CallGetService(kRDFServiceCID, &gRDFService); 1.151 + if (NS_FAILED(rv)) 1.152 + return rv; 1.153 + 1.154 + NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); 1.155 + rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils); 1.156 + if (NS_FAILED(rv)) 1.157 + return rv; 1.158 + 1.159 + rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, 1.160 + &gScriptSecurityManager); 1.161 + if (NS_FAILED(rv)) 1.162 + return rv; 1.163 + 1.164 + rv = gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal); 1.165 + if (NS_FAILED(rv)) 1.166 + return rv; 1.167 + 1.168 + rv = CallGetService(NS_OBSERVERSERVICE_CONTRACTID, &gObserverService); 1.169 + if (NS_FAILED(rv)) 1.170 + return rv; 1.171 + } 1.172 + 1.173 +#ifdef PR_LOGGING 1.174 + if (! gXULTemplateLog) 1.175 + gXULTemplateLog = PR_NewLogModule("nsXULTemplateBuilder"); 1.176 +#endif 1.177 + 1.178 + return NS_OK; 1.179 +} 1.180 + 1.181 +void 1.182 +nsXULTemplateBuilder::StartObserving(nsIDocument* aDocument) 1.183 +{ 1.184 + aDocument->AddObserver(this); 1.185 + mObservedDocument = aDocument; 1.186 + gObserverService->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, false); 1.187 +} 1.188 + 1.189 +void 1.190 +nsXULTemplateBuilder::StopObserving() 1.191 +{ 1.192 + MOZ_ASSERT(mObservedDocument); 1.193 + mObservedDocument->RemoveObserver(this); 1.194 + mObservedDocument = nullptr; 1.195 + gObserverService->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); 1.196 +} 1.197 + 1.198 +void 1.199 +nsXULTemplateBuilder::CleanUp(bool aIsFinal) 1.200 +{ 1.201 + for (int32_t q = mQuerySets.Length() - 1; q >= 0; q--) { 1.202 + nsTemplateQuerySet* qs = mQuerySets[q]; 1.203 + delete qs; 1.204 + } 1.205 + 1.206 + mQuerySets.Clear(); 1.207 + 1.208 + mMatchMap.Enumerate(DestroyMatchList, nullptr); 1.209 + 1.210 + // Setting mQueryProcessor to null will close connections. This would be 1.211 + // handled by the cycle collector, but we want to close them earlier. 1.212 + if (aIsFinal) 1.213 + mQueryProcessor = nullptr; 1.214 +} 1.215 + 1.216 +void 1.217 +nsXULTemplateBuilder::Uninit(bool aIsFinal) 1.218 +{ 1.219 + if (mObservedDocument && aIsFinal) { 1.220 + StopObserving(); 1.221 + } 1.222 + 1.223 + if (mQueryProcessor) 1.224 + mQueryProcessor->Done(); 1.225 + 1.226 + CleanUp(aIsFinal); 1.227 + 1.228 + mRootResult = nullptr; 1.229 + mRefVariable = nullptr; 1.230 + mMemberVariable = nullptr; 1.231 + 1.232 + mQueriesCompiled = false; 1.233 +} 1.234 + 1.235 +static PLDHashOperator 1.236 +TraverseMatchList(nsISupports* aKey, nsTemplateMatch* aMatch, void* aContext) 1.237 +{ 1.238 + nsCycleCollectionTraversalCallback *cb = 1.239 + static_cast<nsCycleCollectionTraversalCallback*>(aContext); 1.240 + 1.241 + cb->NoteXPCOMChild(aKey); 1.242 + nsTemplateMatch* match = aMatch; 1.243 + while (match) { 1.244 + cb->NoteXPCOMChild(match->GetContainer()); 1.245 + cb->NoteXPCOMChild(match->mResult); 1.246 + match = match->mNext; 1.247 + } 1.248 + 1.249 + return PL_DHASH_NEXT; 1.250 +} 1.251 + 1.252 +NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULTemplateBuilder) 1.253 + 1.254 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULTemplateBuilder) 1.255 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDataSource) 1.256 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDB) 1.257 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCompDB) 1.258 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot) 1.259 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootResult) 1.260 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners) 1.261 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueryProcessor) 1.262 + tmp->mMatchMap.Enumerate(DestroyMatchList, nullptr); 1.263 + for (uint32_t i = 0; i < tmp->mQuerySets.Length(); ++i) { 1.264 + nsTemplateQuerySet* qs = tmp->mQuerySets[i]; 1.265 + delete qs; 1.266 + } 1.267 + tmp->mQuerySets.Clear(); 1.268 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.269 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULTemplateBuilder) 1.270 + if (tmp->mObservedDocument && !cb.WantAllTraces()) { 1.271 + // The global observer service holds us alive. 1.272 + return NS_SUCCESS_INTERRUPTED_TRAVERSE; 1.273 + } 1.274 + 1.275 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDataSource) 1.276 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDB) 1.277 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCompDB) 1.278 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) 1.279 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootResult) 1.280 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners) 1.281 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueryProcessor) 1.282 + tmp->mMatchMap.EnumerateRead(TraverseMatchList, &cb); 1.283 + { 1.284 + uint32_t i, count = tmp->mQuerySets.Length(); 1.285 + for (i = 0; i < count; ++i) { 1.286 + nsTemplateQuerySet *set = tmp->mQuerySets[i]; 1.287 + cb.NoteXPCOMChild(set->mQueryNode); 1.288 + cb.NoteXPCOMChild(set->mCompiledQuery); 1.289 + uint16_t j, rulesCount = set->RuleCount(); 1.290 + for (j = 0; j < rulesCount; ++j) { 1.291 + set->GetRuleAt(j)->Traverse(cb); 1.292 + } 1.293 + } 1.294 + } 1.295 + tmp->Traverse(cb); 1.296 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.297 + 1.298 +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULTemplateBuilder) 1.299 +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULTemplateBuilder) 1.300 + 1.301 +DOMCI_DATA(XULTemplateBuilder, nsXULTemplateBuilder) 1.302 + 1.303 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULTemplateBuilder) 1.304 + NS_INTERFACE_MAP_ENTRY(nsIXULTemplateBuilder) 1.305 + NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver) 1.306 + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) 1.307 + NS_INTERFACE_MAP_ENTRY(nsIObserver) 1.308 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULTemplateBuilder) 1.309 + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULTemplateBuilder) 1.310 +NS_INTERFACE_MAP_END 1.311 + 1.312 +//---------------------------------------------------------------------- 1.313 +// 1.314 +// nsIXULTemplateBuilder methods 1.315 +// 1.316 + 1.317 +NS_IMETHODIMP 1.318 +nsXULTemplateBuilder::GetRoot(nsIDOMElement** aResult) 1.319 +{ 1.320 + if (mRoot) { 1.321 + return CallQueryInterface(mRoot, aResult); 1.322 + } 1.323 + *aResult = nullptr; 1.324 + return NS_OK; 1.325 +} 1.326 + 1.327 +NS_IMETHODIMP 1.328 +nsXULTemplateBuilder::GetDatasource(nsISupports** aResult) 1.329 +{ 1.330 + if (mCompDB) 1.331 + NS_ADDREF(*aResult = mCompDB); 1.332 + else 1.333 + NS_IF_ADDREF(*aResult = mDataSource); 1.334 + return NS_OK; 1.335 +} 1.336 + 1.337 +NS_IMETHODIMP 1.338 +nsXULTemplateBuilder::SetDatasource(nsISupports* aResult) 1.339 +{ 1.340 + mDataSource = aResult; 1.341 + mCompDB = do_QueryInterface(mDataSource); 1.342 + 1.343 + return Rebuild(); 1.344 +} 1.345 + 1.346 +NS_IMETHODIMP 1.347 +nsXULTemplateBuilder::GetDatabase(nsIRDFCompositeDataSource** aResult) 1.348 +{ 1.349 + NS_IF_ADDREF(*aResult = mCompDB); 1.350 + return NS_OK; 1.351 +} 1.352 + 1.353 +NS_IMETHODIMP 1.354 +nsXULTemplateBuilder::GetQueryProcessor(nsIXULTemplateQueryProcessor** aResult) 1.355 +{ 1.356 + NS_IF_ADDREF(*aResult = mQueryProcessor.get()); 1.357 + return NS_OK; 1.358 +} 1.359 + 1.360 +NS_IMETHODIMP 1.361 +nsXULTemplateBuilder::AddRuleFilter(nsIDOMNode* aRule, nsIXULTemplateRuleFilter* aFilter) 1.362 +{ 1.363 + if (!aRule || !aFilter) 1.364 + return NS_ERROR_NULL_POINTER; 1.365 + 1.366 + // a custom rule filter may be added, one for each rule. If a new one is 1.367 + // added, it replaces the old one. Look for the right rule and set its 1.368 + // filter 1.369 + 1.370 + int32_t count = mQuerySets.Length(); 1.371 + for (int32_t q = 0; q < count; q++) { 1.372 + nsTemplateQuerySet* queryset = mQuerySets[q]; 1.373 + 1.374 + int16_t rulecount = queryset->RuleCount(); 1.375 + for (int16_t r = 0; r < rulecount; r++) { 1.376 + nsTemplateRule* rule = queryset->GetRuleAt(r); 1.377 + 1.378 + nsCOMPtr<nsIDOMNode> rulenode; 1.379 + rule->GetRuleNode(getter_AddRefs(rulenode)); 1.380 + if (aRule == rulenode) { 1.381 + rule->SetRuleFilter(aFilter); 1.382 + return NS_OK; 1.383 + } 1.384 + } 1.385 + } 1.386 + 1.387 + return NS_OK; 1.388 +} 1.389 + 1.390 +NS_IMETHODIMP 1.391 +nsXULTemplateBuilder::Rebuild() 1.392 +{ 1.393 + int32_t i; 1.394 + 1.395 + for (i = mListeners.Count() - 1; i >= 0; --i) { 1.396 + mListeners[i]->WillRebuild(this); 1.397 + } 1.398 + 1.399 + nsresult rv = RebuildAll(); 1.400 + 1.401 + for (i = mListeners.Count() - 1; i >= 0; --i) { 1.402 + mListeners[i]->DidRebuild(this); 1.403 + } 1.404 + 1.405 + return rv; 1.406 +} 1.407 + 1.408 +NS_IMETHODIMP 1.409 +nsXULTemplateBuilder::Refresh() 1.410 +{ 1.411 + nsresult rv; 1.412 + 1.413 + if (!mCompDB) 1.414 + return NS_ERROR_FAILURE; 1.415 + 1.416 + nsCOMPtr<nsISimpleEnumerator> dslist; 1.417 + rv = mCompDB->GetDataSources(getter_AddRefs(dslist)); 1.418 + NS_ENSURE_SUCCESS(rv, rv); 1.419 + 1.420 + bool hasMore; 1.421 + nsCOMPtr<nsISupports> next; 1.422 + nsCOMPtr<nsIRDFRemoteDataSource> rds; 1.423 + 1.424 + while(NS_SUCCEEDED(dslist->HasMoreElements(&hasMore)) && hasMore) { 1.425 + dslist->GetNext(getter_AddRefs(next)); 1.426 + if (next && (rds = do_QueryInterface(next))) { 1.427 + rds->Refresh(false); 1.428 + } 1.429 + } 1.430 + 1.431 + // XXXbsmedberg: it would be kinda nice to install an async nsIRDFXMLSink 1.432 + // observer and call rebuild() once the load is complete. See bug 254600. 1.433 + 1.434 + return NS_OK; 1.435 +} 1.436 + 1.437 +NS_IMETHODIMP 1.438 +nsXULTemplateBuilder::Init(nsIContent* aElement) 1.439 +{ 1.440 + NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); 1.441 + mRoot = aElement; 1.442 + 1.443 + nsCOMPtr<nsIDocument> doc = mRoot->GetDocument(); 1.444 + NS_ASSERTION(doc, "element has no document"); 1.445 + if (! doc) 1.446 + return NS_ERROR_UNEXPECTED; 1.447 + 1.448 + bool shouldDelay; 1.449 + nsresult rv = LoadDataSources(doc, &shouldDelay); 1.450 + 1.451 + if (NS_SUCCEEDED(rv)) { 1.452 + StartObserving(doc); 1.453 + } 1.454 + 1.455 + return rv; 1.456 +} 1.457 + 1.458 +NS_IMETHODIMP 1.459 +nsXULTemplateBuilder::CreateContents(nsIContent* aElement, bool aForceCreation) 1.460 +{ 1.461 + return NS_OK; 1.462 +} 1.463 + 1.464 +NS_IMETHODIMP 1.465 +nsXULTemplateBuilder::HasGeneratedContent(nsIRDFResource* aResource, 1.466 + nsIAtom* aTag, 1.467 + bool* aGenerated) 1.468 +{ 1.469 + *aGenerated = false; 1.470 + return NS_OK; 1.471 +} 1.472 + 1.473 +NS_IMETHODIMP 1.474 +nsXULTemplateBuilder::AddResult(nsIXULTemplateResult* aResult, 1.475 + nsIDOMNode* aQueryNode) 1.476 +{ 1.477 + NS_ENSURE_ARG_POINTER(aResult); 1.478 + NS_ENSURE_ARG_POINTER(aQueryNode); 1.479 + 1.480 + return UpdateResult(nullptr, aResult, aQueryNode); 1.481 +} 1.482 + 1.483 +NS_IMETHODIMP 1.484 +nsXULTemplateBuilder::RemoveResult(nsIXULTemplateResult* aResult) 1.485 +{ 1.486 + NS_ENSURE_ARG_POINTER(aResult); 1.487 + 1.488 + return UpdateResult(aResult, nullptr, nullptr); 1.489 +} 1.490 + 1.491 +NS_IMETHODIMP 1.492 +nsXULTemplateBuilder::ReplaceResult(nsIXULTemplateResult* aOldResult, 1.493 + nsIXULTemplateResult* aNewResult, 1.494 + nsIDOMNode* aQueryNode) 1.495 +{ 1.496 + NS_ENSURE_ARG_POINTER(aOldResult); 1.497 + NS_ENSURE_ARG_POINTER(aNewResult); 1.498 + NS_ENSURE_ARG_POINTER(aQueryNode); 1.499 + 1.500 + // just remove the old result and then add a new result separately 1.501 + 1.502 + nsresult rv = UpdateResult(aOldResult, nullptr, nullptr); 1.503 + if (NS_FAILED(rv)) 1.504 + return rv; 1.505 + 1.506 + return UpdateResult(nullptr, aNewResult, aQueryNode); 1.507 +} 1.508 + 1.509 +nsresult 1.510 +nsXULTemplateBuilder::UpdateResult(nsIXULTemplateResult* aOldResult, 1.511 + nsIXULTemplateResult* aNewResult, 1.512 + nsIDOMNode* aQueryNode) 1.513 +{ 1.514 + PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, 1.515 + ("nsXULTemplateBuilder::UpdateResult %p %p %p", 1.516 + aOldResult, aNewResult, aQueryNode)); 1.517 + 1.518 + if (!mRoot || !mQueriesCompiled) 1.519 + return NS_OK; 1.520 + 1.521 + // get the containers where content may be inserted. If 1.522 + // GetInsertionLocations returns false, no container has generated 1.523 + // any content yet so new content should not be generated either. This 1.524 + // will be false if the result applies to content that is in a closed menu 1.525 + // or treeitem for example. 1.526 + 1.527 + nsAutoPtr<nsCOMArray<nsIContent> > insertionPoints; 1.528 + bool mayReplace = GetInsertionLocations(aOldResult ? aOldResult : aNewResult, 1.529 + getter_Transfers(insertionPoints)); 1.530 + if (! mayReplace) 1.531 + return NS_OK; 1.532 + 1.533 + nsresult rv = NS_OK; 1.534 + 1.535 + nsCOMPtr<nsIRDFResource> oldId, newId; 1.536 + nsTemplateQuerySet* queryset = nullptr; 1.537 + 1.538 + if (aOldResult) { 1.539 + rv = GetResultResource(aOldResult, getter_AddRefs(oldId)); 1.540 + if (NS_FAILED(rv)) 1.541 + return rv; 1.542 + 1.543 + // Ignore re-entrant builds for content that is currently in our 1.544 + // activation stack. 1.545 + if (IsActivated(oldId)) 1.546 + return NS_OK; 1.547 + } 1.548 + 1.549 + if (aNewResult) { 1.550 + rv = GetResultResource(aNewResult, getter_AddRefs(newId)); 1.551 + if (NS_FAILED(rv)) 1.552 + return rv; 1.553 + 1.554 + // skip results that don't have ids 1.555 + if (! newId) 1.556 + return NS_OK; 1.557 + 1.558 + // Ignore re-entrant builds for content that is currently in our 1.559 + // activation stack. 1.560 + if (IsActivated(newId)) 1.561 + return NS_OK; 1.562 + 1.563 + // look for the queryset associated with the supplied query node 1.564 + nsCOMPtr<nsIContent> querycontent = do_QueryInterface(aQueryNode); 1.565 + 1.566 + int32_t count = mQuerySets.Length(); 1.567 + for (int32_t q = 0; q < count; q++) { 1.568 + nsTemplateQuerySet* qs = mQuerySets[q]; 1.569 + if (qs->mQueryNode == querycontent) { 1.570 + queryset = qs; 1.571 + break; 1.572 + } 1.573 + } 1.574 + 1.575 + if (! queryset) 1.576 + return NS_OK; 1.577 + } 1.578 + 1.579 + if (insertionPoints) { 1.580 + // iterate over each insertion point and add or remove the result from 1.581 + // that container 1.582 + uint32_t count = insertionPoints->Count(); 1.583 + for (uint32_t t = 0; t < count; t++) { 1.584 + nsCOMPtr<nsIContent> insertionPoint = insertionPoints->SafeObjectAt(t); 1.585 + if (insertionPoint) { 1.586 + rv = UpdateResultInContainer(aOldResult, aNewResult, queryset, 1.587 + oldId, newId, insertionPoint); 1.588 + if (NS_FAILED(rv)) 1.589 + return rv; 1.590 + } 1.591 + } 1.592 + } 1.593 + else { 1.594 + // The tree builder doesn't use insertion points, so no insertion 1.595 + // points will be set. In this case, just update the one result. 1.596 + rv = UpdateResultInContainer(aOldResult, aNewResult, queryset, 1.597 + oldId, newId, nullptr); 1.598 + } 1.599 + 1.600 + return NS_OK; 1.601 +} 1.602 + 1.603 +nsresult 1.604 +nsXULTemplateBuilder::UpdateResultInContainer(nsIXULTemplateResult* aOldResult, 1.605 + nsIXULTemplateResult* aNewResult, 1.606 + nsTemplateQuerySet* aQuerySet, 1.607 + nsIRDFResource* aOldId, 1.608 + nsIRDFResource* aNewId, 1.609 + nsIContent* aInsertionPoint) 1.610 +{ 1.611 + // This method takes a result that no longer applies (aOldResult) and 1.612 + // replaces it with a new result (aNewResult). Either may be null 1.613 + // indicating to just remove a result or add a new one without replacing. 1.614 + // 1.615 + // Matches are stored in the hashtable mMatchMap, keyed by result id. If 1.616 + // there is more than one query, or the same id is found in different 1.617 + // containers, the values in the hashtable will be a linked list of all 1.618 + // the matches for that id. The matches are sorted according to the 1.619 + // queries they are associated with. Matches for earlier queries in the 1.620 + // template take priority over matches from later queries. The priority 1.621 + // for a match is determined from the match's QuerySetPriority method. 1.622 + // The first query has a priority 0, and higher numbers are for later 1.623 + // queries with successively higher priorities. Thus, a match takes 1.624 + // precedence if it has a lower priority than another. If there is only 1.625 + // one query or container, then the match doesn't have any linked items. 1.626 + // 1.627 + // Matches are nsTemplateMatch objects. They are wrappers around 1.628 + // nsIXULTemplateResult result objects and are created with 1.629 + // nsTemplateMatch::Create below. The aQuerySet argument specifies which 1.630 + // query the match is associated with. 1.631 + // 1.632 + // When a result id exists in multiple containers, the match's mContainer 1.633 + // field is set to the container it corresponds to. The aInsertionPoint 1.634 + // argument specifies which container is being updated. Even though they 1.635 + // are stored in the same linked list as other matches of the same id, the 1.636 + // matches for different containers are treated separately. They are only 1.637 + // stored in the same hashtable to avoid a more complex data structure, as 1.638 + // the use of the same id in multiple containers isn't a common occurance. 1.639 + // 1.640 + // Only one match with a given id per container is active at a time. When 1.641 + // a match is active, content is generated for it. When a match is 1.642 + // inactive, content is not generated for it. A match becomes active if 1.643 + // another match with the same id and container with a lower priority 1.644 + // isn't already active, and the match has a rule or conditions clause 1.645 + // which evaluates to true. The former is checked by comparing the value 1.646 + // of the QuerySetPriority method of the match with earlier matches. The 1.647 + // latter is checked with the DetermineMatchedRule method. 1.648 + // 1.649 + // Naturally, if a match with a lower priority is active, it overrides 1.650 + // the new match, so the new match is hooked up into the match linked 1.651 + // list as inactive, and no content is generated for it. If a match with a 1.652 + // higher priority is active, and the new match's conditions evaluate 1.653 + // to true, then this existing match with the higher priority needs to have 1.654 + // its generated content removed and replaced with the new match's 1.655 + // generated content. 1.656 + // 1.657 + // Similar situations apply when removing an existing match. If the match 1.658 + // is active, the existing generated content will need to be removed, and 1.659 + // a match of higher priority that is revealed may become active and need 1.660 + // to have content generated. 1.661 + // 1.662 + // Content removal and generation is done by the ReplaceMatch method which 1.663 + // is overridden for the content builder and tree builder to update the 1.664 + // generated output for each type. 1.665 + // 1.666 + // The code below handles all of the various cases and ensures that the 1.667 + // match lists are maintained properly. 1.668 + 1.669 + nsresult rv = NS_OK; 1.670 + int16_t ruleindex; 1.671 + nsTemplateRule* matchedrule = nullptr; 1.672 + 1.673 + // Indicates that the old match was active and must have its content 1.674 + // removed 1.675 + bool oldMatchWasActive = false; 1.676 + 1.677 + // acceptedmatch will be set to a new match that has to have new content 1.678 + // generated for it. If a new match doesn't need to have content 1.679 + // generated, (because for example, a match with a lower priority 1.680 + // already applies), then acceptedmatch will be null, but the match will 1.681 + // be still hooked up into the chain, since it may become active later 1.682 + // as other results are updated. 1.683 + nsTemplateMatch* acceptedmatch = nullptr; 1.684 + 1.685 + // When aOldResult is specified, removematch will be set to the 1.686 + // corresponding match. This match needs to be deleted as it no longer 1.687 + // applies. However, removedmatch will be null when aOldResult is null, or 1.688 + // when no match was found corresponding to aOldResult. 1.689 + nsTemplateMatch* removedmatch = nullptr; 1.690 + 1.691 + // These will be set when aNewResult is specified indicating to add a 1.692 + // result, but will end up replacing an existing match. The former 1.693 + // indicates a match being replaced that was active and had content 1.694 + // generated for it, while the latter indicates a match that wasn't active 1.695 + // and just needs to be deleted. Both may point to different matches. For 1.696 + // example, if the new match becomes active, replacing an inactive match, 1.697 + // the inactive match will need to be deleted. However, if another match 1.698 + // with a higher priority is active, the new match will override it, so 1.699 + // content will need to be generated for the new match and removed for 1.700 + // this existing active match. 1.701 + nsTemplateMatch* replacedmatch = nullptr, * replacedmatchtodelete = nullptr; 1.702 + 1.703 + if (aOldResult) { 1.704 + nsTemplateMatch* firstmatch; 1.705 + if (mMatchMap.Get(aOldId, &firstmatch)) { 1.706 + nsTemplateMatch* oldmatch = firstmatch; 1.707 + nsTemplateMatch* prevmatch = nullptr; 1.708 + 1.709 + // look for the right match if there was more than one 1.710 + while (oldmatch && (oldmatch->mResult != aOldResult)) { 1.711 + prevmatch = oldmatch; 1.712 + oldmatch = oldmatch->mNext; 1.713 + } 1.714 + 1.715 + if (oldmatch) { 1.716 + nsTemplateMatch* findmatch = oldmatch->mNext; 1.717 + 1.718 + // Keep a reference so that linked list can be hooked up at 1.719 + // the end in case an error occurs. 1.720 + nsTemplateMatch* nextmatch = findmatch; 1.721 + 1.722 + if (oldmatch->IsActive()) { 1.723 + // Indicate that the old match was active so its content 1.724 + // will be removed later. 1.725 + oldMatchWasActive = true; 1.726 + 1.727 + // The match being removed is the active match, so scan 1.728 + // through the later matches to determine if one should 1.729 + // now become the active match. 1.730 + while (findmatch) { 1.731 + // only other matches with the same container should 1.732 + // now match, leave other containers alone 1.733 + if (findmatch->GetContainer() == aInsertionPoint) { 1.734 + nsTemplateQuerySet* qs = 1.735 + mQuerySets[findmatch->QuerySetPriority()]; 1.736 + 1.737 + DetermineMatchedRule(aInsertionPoint, findmatch->mResult, 1.738 + qs, &matchedrule, &ruleindex); 1.739 + 1.740 + if (matchedrule) { 1.741 + rv = findmatch->RuleMatched(qs, 1.742 + matchedrule, ruleindex, 1.743 + findmatch->mResult); 1.744 + if (NS_FAILED(rv)) 1.745 + return rv; 1.746 + 1.747 + acceptedmatch = findmatch; 1.748 + break; 1.749 + } 1.750 + } 1.751 + 1.752 + findmatch = findmatch->mNext; 1.753 + } 1.754 + } 1.755 + 1.756 + if (oldmatch == firstmatch) { 1.757 + // the match to remove is at the beginning 1.758 + if (oldmatch->mNext) { 1.759 + mMatchMap.Put(aOldId, oldmatch->mNext); 1.760 + } 1.761 + else { 1.762 + mMatchMap.Remove(aOldId); 1.763 + } 1.764 + } 1.765 + 1.766 + if (prevmatch) 1.767 + prevmatch->mNext = nextmatch; 1.768 + 1.769 + removedmatch = oldmatch; 1.770 + if (mFlags & eLoggingEnabled) 1.771 + OutputMatchToLog(aOldId, removedmatch, false); 1.772 + } 1.773 + } 1.774 + } 1.775 + 1.776 + nsTemplateMatch *newmatch = nullptr; 1.777 + if (aNewResult) { 1.778 + // only allow a result to be inserted into containers with a matching tag 1.779 + nsIAtom* tag = aQuerySet->GetTag(); 1.780 + if (aInsertionPoint && tag && tag != aInsertionPoint->Tag()) 1.781 + return NS_OK; 1.782 + 1.783 + int32_t findpriority = aQuerySet->Priority(); 1.784 + 1.785 + newmatch = nsTemplateMatch::Create(findpriority, 1.786 + aNewResult, aInsertionPoint); 1.787 + if (!newmatch) 1.788 + return NS_ERROR_OUT_OF_MEMORY; 1.789 + 1.790 + nsTemplateMatch* firstmatch; 1.791 + if (mMatchMap.Get(aNewId, &firstmatch)) { 1.792 + bool hasEarlierActiveMatch = false; 1.793 + 1.794 + // Scan through the existing matches to find where the new one 1.795 + // should be inserted. oldmatch will be set to the old match for 1.796 + // the same query and prevmatch will be set to the match before it. 1.797 + nsTemplateMatch* prevmatch = nullptr; 1.798 + nsTemplateMatch* oldmatch = firstmatch; 1.799 + while (oldmatch) { 1.800 + // Break out once we've reached a query in the list with a 1.801 + // lower priority. The new match will be inserted at this 1.802 + // location so that the match list is sorted by priority. 1.803 + int32_t priority = oldmatch->QuerySetPriority(); 1.804 + if (priority > findpriority) { 1.805 + oldmatch = nullptr; 1.806 + break; 1.807 + } 1.808 + 1.809 + // look for matches that belong in the same container 1.810 + if (oldmatch->GetContainer() == aInsertionPoint) { 1.811 + if (priority == findpriority) 1.812 + break; 1.813 + 1.814 + // If a match with a lower priority is active, the new 1.815 + // match can't replace it. 1.816 + if (oldmatch->IsActive()) 1.817 + hasEarlierActiveMatch = true; 1.818 + } 1.819 + 1.820 + prevmatch = oldmatch; 1.821 + oldmatch = oldmatch->mNext; 1.822 + } 1.823 + 1.824 + // At this point, oldmatch will either be null, or set to a match 1.825 + // with the same container and priority. If set, oldmatch will 1.826 + // need to be replaced by newmatch. 1.827 + 1.828 + if (oldmatch) 1.829 + newmatch->mNext = oldmatch->mNext; 1.830 + else if (prevmatch) 1.831 + newmatch->mNext = prevmatch->mNext; 1.832 + else 1.833 + newmatch->mNext = firstmatch; 1.834 + 1.835 + // hasEarlierActiveMatch will be set to true if a match with a 1.836 + // lower priority was found. The new match won't replace it in 1.837 + // this case. If hasEarlierActiveMatch is false, then the new match 1.838 + // may be become active if it matches one of the rules, and will 1.839 + // generate output. It's also possible however, that a match with 1.840 + // the same priority already exists, which means that the new match 1.841 + // will replace the old one. In this case, oldmatch will be set to 1.842 + // the old match. The content for the old match must be removed and 1.843 + // content for the new match generated in its place. 1.844 + if (! hasEarlierActiveMatch) { 1.845 + // If the old match was the active match, set replacedmatch to 1.846 + // indicate that it needs its content removed. 1.847 + if (oldmatch) { 1.848 + if (oldmatch->IsActive()) 1.849 + replacedmatch = oldmatch; 1.850 + replacedmatchtodelete = oldmatch; 1.851 + } 1.852 + 1.853 + // check if the new result matches the rules 1.854 + rv = DetermineMatchedRule(aInsertionPoint, newmatch->mResult, 1.855 + aQuerySet, &matchedrule, &ruleindex); 1.856 + if (NS_FAILED(rv)) { 1.857 + nsTemplateMatch::Destroy(newmatch, false); 1.858 + return rv; 1.859 + } 1.860 + 1.861 + if (matchedrule) { 1.862 + rv = newmatch->RuleMatched(aQuerySet, 1.863 + matchedrule, ruleindex, 1.864 + newmatch->mResult); 1.865 + if (NS_FAILED(rv)) { 1.866 + nsTemplateMatch::Destroy(newmatch, false); 1.867 + return rv; 1.868 + } 1.869 + 1.870 + // acceptedmatch may have been set in the block handling 1.871 + // aOldResult earlier. If so, we would only get here when 1.872 + // that match has a higher priority than this new match. 1.873 + // As only one match can have content generated for it, it 1.874 + // is OK to set acceptedmatch here to the new match, 1.875 + // ignoring the other one. 1.876 + acceptedmatch = newmatch; 1.877 + 1.878 + // Clear the matched state of the later results for the 1.879 + // same container. 1.880 + nsTemplateMatch* clearmatch = newmatch->mNext; 1.881 + while (clearmatch) { 1.882 + if (clearmatch->GetContainer() == aInsertionPoint && 1.883 + clearmatch->IsActive()) { 1.884 + clearmatch->SetInactive(); 1.885 + // Replacedmatch should be null here. If not, it 1.886 + // means that two matches were active which isn't 1.887 + // a valid state 1.888 + NS_ASSERTION(!replacedmatch, 1.889 + "replaced match already set"); 1.890 + replacedmatch = clearmatch; 1.891 + break; 1.892 + } 1.893 + clearmatch = clearmatch->mNext; 1.894 + } 1.895 + } 1.896 + else if (oldmatch && oldmatch->IsActive()) { 1.897 + // The result didn't match the rules, so look for a later 1.898 + // one. However, only do this if the old match was the 1.899 + // active match. 1.900 + newmatch = newmatch->mNext; 1.901 + while (newmatch) { 1.902 + if (newmatch->GetContainer() == aInsertionPoint) { 1.903 + rv = DetermineMatchedRule(aInsertionPoint, newmatch->mResult, 1.904 + aQuerySet, &matchedrule, &ruleindex); 1.905 + if (NS_FAILED(rv)) { 1.906 + nsTemplateMatch::Destroy(newmatch, false); 1.907 + return rv; 1.908 + } 1.909 + 1.910 + if (matchedrule) { 1.911 + rv = newmatch->RuleMatched(aQuerySet, 1.912 + matchedrule, ruleindex, 1.913 + newmatch->mResult); 1.914 + if (NS_FAILED(rv)) { 1.915 + nsTemplateMatch::Destroy(newmatch, false); 1.916 + return rv; 1.917 + } 1.918 + 1.919 + acceptedmatch = newmatch; 1.920 + break; 1.921 + } 1.922 + } 1.923 + 1.924 + newmatch = newmatch->mNext; 1.925 + } 1.926 + } 1.927 + 1.928 + // put the match in the map if there isn't a previous match 1.929 + if (! prevmatch) { 1.930 + mMatchMap.Put(aNewId, newmatch); 1.931 + } 1.932 + } 1.933 + 1.934 + // hook up the match last in case an error occurs 1.935 + if (prevmatch) 1.936 + prevmatch->mNext = newmatch; 1.937 + } 1.938 + else { 1.939 + // The id is not used in the hashtable yet so create a new match 1.940 + // and add it to the hashtable. 1.941 + rv = DetermineMatchedRule(aInsertionPoint, aNewResult, 1.942 + aQuerySet, &matchedrule, &ruleindex); 1.943 + if (NS_FAILED(rv)) { 1.944 + nsTemplateMatch::Destroy(newmatch, false); 1.945 + return rv; 1.946 + } 1.947 + 1.948 + if (matchedrule) { 1.949 + rv = newmatch->RuleMatched(aQuerySet, matchedrule, 1.950 + ruleindex, aNewResult); 1.951 + if (NS_FAILED(rv)) { 1.952 + nsTemplateMatch::Destroy(newmatch, false); 1.953 + return rv; 1.954 + } 1.955 + 1.956 + acceptedmatch = newmatch; 1.957 + } 1.958 + 1.959 + mMatchMap.Put(aNewId, newmatch); 1.960 + } 1.961 + } 1.962 + 1.963 + // The ReplaceMatch method is builder specific and removes the generated 1.964 + // content for a match. 1.965 + 1.966 + // Remove the content for a match that was active and needs to be replaced. 1.967 + if (replacedmatch) { 1.968 + rv = ReplaceMatch(replacedmatch->mResult, nullptr, nullptr, 1.969 + aInsertionPoint); 1.970 + 1.971 + if (mFlags & eLoggingEnabled) 1.972 + OutputMatchToLog(aNewId, replacedmatch, false); 1.973 + } 1.974 + 1.975 + // remove a match that needs to be deleted. 1.976 + if (replacedmatchtodelete) 1.977 + nsTemplateMatch::Destroy(replacedmatchtodelete, true); 1.978 + 1.979 + // If the old match was active, the content for it needs to be removed. 1.980 + // If the old match was not active, it shouldn't have had any content, 1.981 + // so just pass null to ReplaceMatch. If acceptedmatch was set, then 1.982 + // content needs to be generated for a new match. 1.983 + if (oldMatchWasActive || acceptedmatch) 1.984 + rv = ReplaceMatch(oldMatchWasActive ? aOldResult : nullptr, 1.985 + acceptedmatch, matchedrule, aInsertionPoint); 1.986 + 1.987 + // delete the old match that was replaced 1.988 + if (removedmatch) 1.989 + nsTemplateMatch::Destroy(removedmatch, true); 1.990 + 1.991 + if (mFlags & eLoggingEnabled && newmatch) 1.992 + OutputMatchToLog(aNewId, newmatch, true); 1.993 + 1.994 + return rv; 1.995 +} 1.996 + 1.997 +NS_IMETHODIMP 1.998 +nsXULTemplateBuilder::ResultBindingChanged(nsIXULTemplateResult* aResult) 1.999 +{ 1.1000 + // A binding update is used when only the values of the bindings have 1.1001 + // changed, so the same rule still applies. Just synchronize the content. 1.1002 + // The new result will have the new values. 1.1003 + NS_ENSURE_ARG_POINTER(aResult); 1.1004 + 1.1005 + if (!mRoot || !mQueriesCompiled) 1.1006 + return NS_OK; 1.1007 + 1.1008 + return SynchronizeResult(aResult); 1.1009 +} 1.1010 + 1.1011 +NS_IMETHODIMP 1.1012 +nsXULTemplateBuilder::GetRootResult(nsIXULTemplateResult** aResult) 1.1013 +{ 1.1014 + *aResult = mRootResult; 1.1015 + NS_IF_ADDREF(*aResult); 1.1016 + return NS_OK; 1.1017 +} 1.1018 + 1.1019 +NS_IMETHODIMP 1.1020 +nsXULTemplateBuilder::GetResultForId(const nsAString& aId, 1.1021 + nsIXULTemplateResult** aResult) 1.1022 +{ 1.1023 + if (aId.IsEmpty()) 1.1024 + return NS_ERROR_INVALID_ARG; 1.1025 + 1.1026 + nsCOMPtr<nsIRDFResource> resource; 1.1027 + gRDFService->GetUnicodeResource(aId, getter_AddRefs(resource)); 1.1028 + 1.1029 + *aResult = nullptr; 1.1030 + 1.1031 + nsTemplateMatch* match; 1.1032 + if (mMatchMap.Get(resource, &match)) { 1.1033 + // find the active match 1.1034 + while (match) { 1.1035 + if (match->IsActive()) { 1.1036 + *aResult = match->mResult; 1.1037 + NS_IF_ADDREF(*aResult); 1.1038 + break; 1.1039 + } 1.1040 + match = match->mNext; 1.1041 + } 1.1042 + } 1.1043 + 1.1044 + return NS_OK; 1.1045 +} 1.1046 + 1.1047 +NS_IMETHODIMP 1.1048 +nsXULTemplateBuilder::GetResultForContent(nsIDOMElement* aContent, 1.1049 + nsIXULTemplateResult** aResult) 1.1050 +{ 1.1051 + *aResult = nullptr; 1.1052 + return NS_OK; 1.1053 +} 1.1054 + 1.1055 +NS_IMETHODIMP 1.1056 +nsXULTemplateBuilder::AddListener(nsIXULBuilderListener* aListener) 1.1057 +{ 1.1058 + NS_ENSURE_ARG(aListener); 1.1059 + 1.1060 + if (!mListeners.AppendObject(aListener)) 1.1061 + return NS_ERROR_OUT_OF_MEMORY; 1.1062 + 1.1063 + return NS_OK; 1.1064 +} 1.1065 + 1.1066 +NS_IMETHODIMP 1.1067 +nsXULTemplateBuilder::RemoveListener(nsIXULBuilderListener* aListener) 1.1068 +{ 1.1069 + NS_ENSURE_ARG(aListener); 1.1070 + 1.1071 + mListeners.RemoveObject(aListener); 1.1072 + 1.1073 + return NS_OK; 1.1074 +} 1.1075 + 1.1076 +NS_IMETHODIMP 1.1077 +nsXULTemplateBuilder::Observe(nsISupports* aSubject, 1.1078 + const char* aTopic, 1.1079 + const char16_t* aData) 1.1080 +{ 1.1081 + // Uuuuber hack to clean up circular references that the cycle collector 1.1082 + // doesn't know about. See bug 394514. 1.1083 + if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) { 1.1084 + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject); 1.1085 + if (window) { 1.1086 + nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); 1.1087 + if (doc && doc == mObservedDocument) 1.1088 + NodeWillBeDestroyed(doc); 1.1089 + } 1.1090 + } 1.1091 + return NS_OK; 1.1092 +} 1.1093 +//---------------------------------------------------------------------- 1.1094 +// 1.1095 +// nsIDocumentOberver interface 1.1096 +// 1.1097 + 1.1098 +void 1.1099 +nsXULTemplateBuilder::AttributeChanged(nsIDocument* aDocument, 1.1100 + Element* aElement, 1.1101 + int32_t aNameSpaceID, 1.1102 + nsIAtom* aAttribute, 1.1103 + int32_t aModType) 1.1104 +{ 1.1105 + if (aElement == mRoot && aNameSpaceID == kNameSpaceID_None) { 1.1106 + // Check for a change to the 'ref' attribute on an atom, in which 1.1107 + // case we may need to nuke and rebuild the entire content model 1.1108 + // beneath the element. 1.1109 + if (aAttribute == nsGkAtoms::ref) 1.1110 + nsContentUtils::AddScriptRunner( 1.1111 + NS_NewRunnableMethod(this, &nsXULTemplateBuilder::RunnableRebuild)); 1.1112 + 1.1113 + // Check for a change to the 'datasources' attribute. If so, setup 1.1114 + // mDB by parsing the new value and rebuild. 1.1115 + else if (aAttribute == nsGkAtoms::datasources) { 1.1116 + nsContentUtils::AddScriptRunner( 1.1117 + NS_NewRunnableMethod(this, &nsXULTemplateBuilder::RunnableLoadAndRebuild)); 1.1118 + } 1.1119 + } 1.1120 +} 1.1121 + 1.1122 +void 1.1123 +nsXULTemplateBuilder::ContentRemoved(nsIDocument* aDocument, 1.1124 + nsIContent* aContainer, 1.1125 + nsIContent* aChild, 1.1126 + int32_t aIndexInContainer, 1.1127 + nsIContent* aPreviousSibling) 1.1128 +{ 1.1129 + if (mRoot && nsContentUtils::ContentIsDescendantOf(mRoot, aChild)) { 1.1130 + nsRefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this); 1.1131 + 1.1132 + if (mQueryProcessor) 1.1133 + mQueryProcessor->Done(); 1.1134 + 1.1135 + // Pass false to Uninit since content is going away anyway 1.1136 + nsContentUtils::AddScriptRunner( 1.1137 + NS_NewRunnableMethod(this, &nsXULTemplateBuilder::UninitFalse)); 1.1138 + 1.1139 + MOZ_ASSERT(aDocument == mObservedDocument); 1.1140 + StopObserving(); 1.1141 + 1.1142 + nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument); 1.1143 + if (xuldoc) 1.1144 + xuldoc->SetTemplateBuilderFor(mRoot, nullptr); 1.1145 + 1.1146 + // clear the template state when removing content so that template 1.1147 + // content will be regenerated again if the content is reinserted 1.1148 + nsXULElement *xulcontent = nsXULElement::FromContent(mRoot); 1.1149 + if (xulcontent) 1.1150 + xulcontent->ClearTemplateGenerated(); 1.1151 + 1.1152 + CleanUp(true); 1.1153 + 1.1154 + mDB = nullptr; 1.1155 + mCompDB = nullptr; 1.1156 + mDataSource = nullptr; 1.1157 + } 1.1158 +} 1.1159 + 1.1160 +void 1.1161 +nsXULTemplateBuilder::NodeWillBeDestroyed(const nsINode* aNode) 1.1162 +{ 1.1163 + // The call to RemoveObserver could release the last reference to 1.1164 + // |this|, so hold another reference. 1.1165 + nsRefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this); 1.1166 + 1.1167 + // Break circular references 1.1168 + if (mQueryProcessor) 1.1169 + mQueryProcessor->Done(); 1.1170 + 1.1171 + mDataSource = nullptr; 1.1172 + mDB = nullptr; 1.1173 + mCompDB = nullptr; 1.1174 + 1.1175 + nsContentUtils::AddScriptRunner( 1.1176 + NS_NewRunnableMethod(this, &nsXULTemplateBuilder::UninitTrue)); 1.1177 +} 1.1178 + 1.1179 + 1.1180 + 1.1181 + 1.1182 +//---------------------------------------------------------------------- 1.1183 +// 1.1184 +// Implementation methods 1.1185 +// 1.1186 + 1.1187 +nsresult 1.1188 +nsXULTemplateBuilder::LoadDataSources(nsIDocument* aDocument, 1.1189 + bool* aShouldDelayBuilding) 1.1190 +{ 1.1191 + NS_PRECONDITION(mRoot != nullptr, "not initialized"); 1.1192 + 1.1193 + nsresult rv; 1.1194 + bool isRDFQuery = false; 1.1195 + 1.1196 + // we'll set these again later, after we create a new composite ds 1.1197 + mDB = nullptr; 1.1198 + mCompDB = nullptr; 1.1199 + mDataSource = nullptr; 1.1200 + 1.1201 + *aShouldDelayBuilding = false; 1.1202 + 1.1203 + nsAutoString datasources; 1.1204 + mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::datasources, datasources); 1.1205 + 1.1206 + nsAutoString querytype; 1.1207 + mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::querytype, querytype); 1.1208 + 1.1209 + // create the query processor. The querytype attribute on the root element 1.1210 + // may be used to create one of a specific type. 1.1211 + 1.1212 + // XXX should non-chrome be restricted to specific names? 1.1213 + if (querytype.IsEmpty()) 1.1214 + querytype.AssignLiteral("rdf"); 1.1215 + 1.1216 + if (querytype.EqualsLiteral("rdf")) { 1.1217 + isRDFQuery = true; 1.1218 + mQueryProcessor = new nsXULTemplateQueryProcessorRDF(); 1.1219 + NS_ENSURE_TRUE(mQueryProcessor, NS_ERROR_OUT_OF_MEMORY); 1.1220 + } 1.1221 + else if (querytype.EqualsLiteral("xml")) { 1.1222 + mQueryProcessor = new nsXULTemplateQueryProcessorXML(); 1.1223 + NS_ENSURE_TRUE(mQueryProcessor, NS_ERROR_OUT_OF_MEMORY); 1.1224 + } 1.1225 + else if (querytype.EqualsLiteral("storage")) { 1.1226 + mQueryProcessor = new nsXULTemplateQueryProcessorStorage(); 1.1227 + NS_ENSURE_TRUE(mQueryProcessor, NS_ERROR_OUT_OF_MEMORY); 1.1228 + } 1.1229 + else { 1.1230 + nsAutoCString cid(NS_QUERY_PROCESSOR_CONTRACTID_PREFIX); 1.1231 + AppendUTF16toUTF8(querytype, cid); 1.1232 + mQueryProcessor = do_CreateInstance(cid.get(), &rv); 1.1233 + 1.1234 + if (!mQueryProcessor) { 1.1235 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_INVALID_QUERYPROCESSOR); 1.1236 + return rv; 1.1237 + } 1.1238 + } 1.1239 + 1.1240 + rv = LoadDataSourceUrls(aDocument, datasources, 1.1241 + isRDFQuery, aShouldDelayBuilding); 1.1242 + NS_ENSURE_SUCCESS(rv, rv); 1.1243 + 1.1244 + // Now set the database on the element, so that script writers can 1.1245 + // access it. 1.1246 + nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument); 1.1247 + if (xuldoc) 1.1248 + xuldoc->SetTemplateBuilderFor(mRoot, this); 1.1249 + 1.1250 + if (!mRoot->IsXUL()) { 1.1251 + // Hmm. This must be an HTML element. Try to set it as a 1.1252 + // JS property "by hand". 1.1253 + InitHTMLTemplateRoot(); 1.1254 + } 1.1255 + 1.1256 + return NS_OK; 1.1257 +} 1.1258 + 1.1259 +nsresult 1.1260 +nsXULTemplateBuilder::LoadDataSourceUrls(nsIDocument* aDocument, 1.1261 + const nsAString& aDataSources, 1.1262 + bool aIsRDFQuery, 1.1263 + bool* aShouldDelayBuilding) 1.1264 +{ 1.1265 + // Grab the doc's principal... 1.1266 + nsIPrincipal *docPrincipal = aDocument->NodePrincipal(); 1.1267 + 1.1268 + NS_ASSERTION(docPrincipal == mRoot->NodePrincipal(), 1.1269 + "Principal mismatch? Which one to use?"); 1.1270 + 1.1271 + bool isTrusted = false; 1.1272 + nsresult rv = IsSystemPrincipal(docPrincipal, &isTrusted); 1.1273 + NS_ENSURE_SUCCESS(rv, rv); 1.1274 + 1.1275 + // Parse datasources: they are assumed to be a whitespace 1.1276 + // separated list of URIs; e.g., 1.1277 + // 1.1278 + // rdf:bookmarks rdf:history http://foo.bar.com/blah.cgi?baz=9 1.1279 + // 1.1280 + nsIURI *docurl = aDocument->GetDocumentURI(); 1.1281 + 1.1282 + nsCOMPtr<nsIMutableArray> uriList = do_CreateInstance(NS_ARRAY_CONTRACTID); 1.1283 + if (!uriList) 1.1284 + return NS_ERROR_FAILURE; 1.1285 + 1.1286 + nsAutoString datasources(aDataSources); 1.1287 + uint32_t first = 0; 1.1288 + while (1) { 1.1289 + while (first < datasources.Length() && nsCRT::IsAsciiSpace(datasources.CharAt(first))) 1.1290 + ++first; 1.1291 + 1.1292 + if (first >= datasources.Length()) 1.1293 + break; 1.1294 + 1.1295 + uint32_t last = first; 1.1296 + while (last < datasources.Length() && !nsCRT::IsAsciiSpace(datasources.CharAt(last))) 1.1297 + ++last; 1.1298 + 1.1299 + nsAutoString uriStr; 1.1300 + datasources.Mid(uriStr, first, last - first); 1.1301 + first = last + 1; 1.1302 + 1.1303 + // A special 'dummy' datasource 1.1304 + if (uriStr.EqualsLiteral("rdf:null")) 1.1305 + continue; 1.1306 + 1.1307 + if (uriStr.CharAt(0) == '#') { 1.1308 + // ok, the datasource is certainly a node of the current document 1.1309 + nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(aDocument); 1.1310 + nsCOMPtr<nsIDOMElement> dsnode; 1.1311 + 1.1312 + domdoc->GetElementById(Substring(uriStr, 1), 1.1313 + getter_AddRefs(dsnode)); 1.1314 + 1.1315 + if (dsnode) 1.1316 + uriList->AppendElement(dsnode, false); 1.1317 + continue; 1.1318 + } 1.1319 + 1.1320 + // N.B. that `failure' (e.g., because it's an unknown 1.1321 + // protocol) leaves uriStr unaltered. 1.1322 + NS_MakeAbsoluteURI(uriStr, uriStr, docurl); 1.1323 + 1.1324 + nsCOMPtr<nsIURI> uri; 1.1325 + rv = NS_NewURI(getter_AddRefs(uri), uriStr); 1.1326 + if (NS_FAILED(rv) || !uri) 1.1327 + continue; // Necko will barf if our URI is weird 1.1328 + 1.1329 + // don't add the uri to the list if the document is not allowed to 1.1330 + // load it 1.1331 + if (!isTrusted && NS_FAILED(docPrincipal->CheckMayLoad(uri, true, false))) 1.1332 + continue; 1.1333 + 1.1334 + uriList->AppendElement(uri, false); 1.1335 + } 1.1336 + 1.1337 + nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(mRoot); 1.1338 + rv = mQueryProcessor->GetDatasource(uriList, 1.1339 + rootNode, 1.1340 + isTrusted, 1.1341 + this, 1.1342 + aShouldDelayBuilding, 1.1343 + getter_AddRefs(mDataSource)); 1.1344 + NS_ENSURE_SUCCESS(rv, rv); 1.1345 + 1.1346 + if (aIsRDFQuery && mDataSource) { 1.1347 + // check if we were given an inference engine type 1.1348 + nsCOMPtr<nsIRDFInferDataSource> inferDB = do_QueryInterface(mDataSource); 1.1349 + if (inferDB) { 1.1350 + nsCOMPtr<nsIRDFDataSource> ds; 1.1351 + inferDB->GetBaseDataSource(getter_AddRefs(ds)); 1.1352 + if (ds) 1.1353 + mCompDB = do_QueryInterface(ds); 1.1354 + } 1.1355 + 1.1356 + if (!mCompDB) 1.1357 + mCompDB = do_QueryInterface(mDataSource); 1.1358 + 1.1359 + mDB = do_QueryInterface(mDataSource); 1.1360 + } 1.1361 + 1.1362 + if (!mDB && isTrusted) { 1.1363 + gRDFService->GetDataSource("rdf:local-store", getter_AddRefs(mDB)); 1.1364 + } 1.1365 + 1.1366 + return NS_OK; 1.1367 +} 1.1368 + 1.1369 +nsresult 1.1370 +nsXULTemplateBuilder::InitHTMLTemplateRoot() 1.1371 +{ 1.1372 + // Use XPConnect and the JS APIs to whack mDB and this as the 1.1373 + // 'database' and 'builder' properties onto aElement. 1.1374 + nsresult rv; 1.1375 + 1.1376 + nsCOMPtr<nsIDocument> doc = mRoot->GetDocument(); 1.1377 + NS_ASSERTION(doc, "no document"); 1.1378 + if (! doc) 1.1379 + return NS_ERROR_UNEXPECTED; 1.1380 + 1.1381 + nsCOMPtr<nsIScriptGlobalObject> global = 1.1382 + do_QueryInterface(doc->GetWindow()); 1.1383 + if (! global) 1.1384 + return NS_ERROR_UNEXPECTED; 1.1385 + 1.1386 + nsCOMPtr<nsIGlobalObject> innerWin = 1.1387 + do_QueryInterface(doc->GetInnerWindow()); 1.1388 + 1.1389 + // We are going to run script via JS_SetProperty, so we need a script entry 1.1390 + // point, but as this is XUL related it does not appear in the HTML spec. 1.1391 + AutoEntryScript entryScript(innerWin, true); 1.1392 + JSContext* jscontext = entryScript.cx(); 1.1393 + 1.1394 + JS::Rooted<JS::Value> v(jscontext); 1.1395 + rv = nsContentUtils::WrapNative(jscontext, mRoot, mRoot, &v); 1.1396 + NS_ENSURE_SUCCESS(rv, rv); 1.1397 + 1.1398 + JS::Rooted<JSObject*> jselement(jscontext, JSVAL_TO_OBJECT(v)); 1.1399 + 1.1400 + if (mDB) { 1.1401 + // database 1.1402 + JS::Rooted<JS::Value> jsdatabase(jscontext); 1.1403 + rv = nsContentUtils::WrapNative(jscontext, mDB, 1.1404 + &NS_GET_IID(nsIRDFCompositeDataSource), 1.1405 + &jsdatabase); 1.1406 + NS_ENSURE_SUCCESS(rv, rv); 1.1407 + 1.1408 + bool ok = JS_SetProperty(jscontext, jselement, "database", jsdatabase); 1.1409 + NS_ASSERTION(ok, "unable to set database property"); 1.1410 + if (! ok) 1.1411 + return NS_ERROR_FAILURE; 1.1412 + } 1.1413 + 1.1414 + { 1.1415 + // builder 1.1416 + JS::Rooted<JS::Value> jsbuilder(jscontext); 1.1417 + rv = nsContentUtils::WrapNative(jscontext, 1.1418 + static_cast<nsIXULTemplateBuilder*>(this), 1.1419 + &NS_GET_IID(nsIXULTemplateBuilder), 1.1420 + &jsbuilder); 1.1421 + NS_ENSURE_SUCCESS(rv, rv); 1.1422 + 1.1423 + bool ok = JS_SetProperty(jscontext, jselement, "builder", jsbuilder); 1.1424 + if (! ok) 1.1425 + return NS_ERROR_FAILURE; 1.1426 + } 1.1427 + 1.1428 + return NS_OK; 1.1429 +} 1.1430 + 1.1431 +nsresult 1.1432 +nsXULTemplateBuilder::DetermineMatchedRule(nsIContent *aContainer, 1.1433 + nsIXULTemplateResult* aResult, 1.1434 + nsTemplateQuerySet* aQuerySet, 1.1435 + nsTemplateRule** aMatchedRule, 1.1436 + int16_t *aRuleIndex) 1.1437 +{ 1.1438 + // iterate through the rules and look for one that the result matches 1.1439 + int16_t count = aQuerySet->RuleCount(); 1.1440 + for (int16_t r = 0; r < count; r++) { 1.1441 + nsTemplateRule* rule = aQuerySet->GetRuleAt(r); 1.1442 + // If a tag was specified, it must match the tag of the container 1.1443 + // where content is being inserted. 1.1444 + nsIAtom* tag = rule->GetTag(); 1.1445 + if ((!aContainer || !tag || tag == aContainer->Tag()) && 1.1446 + rule->CheckMatch(aResult)) { 1.1447 + *aMatchedRule = rule; 1.1448 + *aRuleIndex = r; 1.1449 + return NS_OK; 1.1450 + } 1.1451 + } 1.1452 + 1.1453 + *aRuleIndex = -1; 1.1454 + *aMatchedRule = nullptr; 1.1455 + return NS_OK; 1.1456 +} 1.1457 + 1.1458 +void 1.1459 +nsXULTemplateBuilder::ParseAttribute(const nsAString& aAttributeValue, 1.1460 + void (*aVariableCallback)(nsXULTemplateBuilder*, const nsAString&, void*), 1.1461 + void (*aTextCallback)(nsXULTemplateBuilder*, const nsAString&, void*), 1.1462 + void* aClosure) 1.1463 +{ 1.1464 + nsAString::const_iterator done_parsing; 1.1465 + aAttributeValue.EndReading(done_parsing); 1.1466 + 1.1467 + nsAString::const_iterator iter; 1.1468 + aAttributeValue.BeginReading(iter); 1.1469 + 1.1470 + nsAString::const_iterator mark(iter), backup(iter); 1.1471 + 1.1472 + for (; iter != done_parsing; backup = ++iter) { 1.1473 + // A variable is either prefixed with '?' (in the extended 1.1474 + // syntax) or "rdf:" (in the simple syntax). 1.1475 + bool isvar; 1.1476 + if (*iter == char16_t('?') && (++iter != done_parsing)) { 1.1477 + isvar = true; 1.1478 + } 1.1479 + else if ((*iter == char16_t('r') && (++iter != done_parsing)) && 1.1480 + (*iter == char16_t('d') && (++iter != done_parsing)) && 1.1481 + (*iter == char16_t('f') && (++iter != done_parsing)) && 1.1482 + (*iter == char16_t(':') && (++iter != done_parsing))) { 1.1483 + isvar = true; 1.1484 + } 1.1485 + else { 1.1486 + isvar = false; 1.1487 + } 1.1488 + 1.1489 + if (! isvar) { 1.1490 + // It's not a variable, or we ran off the end of the 1.1491 + // string after the initial variable prefix. Since we may 1.1492 + // have slurped down some characters before realizing that 1.1493 + // fact, back up to the point where we started. 1.1494 + iter = backup; 1.1495 + continue; 1.1496 + } 1.1497 + else if (backup != mark && aTextCallback) { 1.1498 + // Okay, we've found a variable, and there's some vanilla 1.1499 + // text that's been buffered up. Flush it. 1.1500 + (*aTextCallback)(this, Substring(mark, backup), aClosure); 1.1501 + } 1.1502 + 1.1503 + if (*iter == char16_t('?')) { 1.1504 + // Well, it was not really a variable, but "??". We use one 1.1505 + // question mark (the second one, actually) literally. 1.1506 + mark = iter; 1.1507 + continue; 1.1508 + } 1.1509 + 1.1510 + // Construct a substring that is the symbol we need to look up 1.1511 + // in the rule's symbol table. The symbol is terminated by a 1.1512 + // space character, a caret, or the end of the string, 1.1513 + // whichever comes first. 1.1514 + nsAString::const_iterator first(backup); 1.1515 + 1.1516 + char16_t c = 0; 1.1517 + while (iter != done_parsing) { 1.1518 + c = *iter; 1.1519 + if ((c == char16_t(' ')) || (c == char16_t('^'))) 1.1520 + break; 1.1521 + 1.1522 + ++iter; 1.1523 + } 1.1524 + 1.1525 + nsAString::const_iterator last(iter); 1.1526 + 1.1527 + // Back up so we don't consume the terminating character 1.1528 + // *unless* the terminating character was a caret: the caret 1.1529 + // means "concatenate with no space in between". 1.1530 + if (c != char16_t('^')) 1.1531 + --iter; 1.1532 + 1.1533 + (*aVariableCallback)(this, Substring(first, last), aClosure); 1.1534 + mark = iter; 1.1535 + ++mark; 1.1536 + } 1.1537 + 1.1538 + if (backup != mark && aTextCallback) { 1.1539 + // If there's any text left over, then fire the text callback 1.1540 + (*aTextCallback)(this, Substring(mark, backup), aClosure); 1.1541 + } 1.1542 +} 1.1543 + 1.1544 + 1.1545 +struct MOZ_STACK_CLASS SubstituteTextClosure { 1.1546 + SubstituteTextClosure(nsIXULTemplateResult* aResult, nsAString& aString) 1.1547 + : result(aResult), str(aString) {} 1.1548 + 1.1549 + // some datasources are lazily initialized or modified while values are 1.1550 + // being retrieved, causing results to be removed. Due to this, hold a 1.1551 + // strong reference to the result. 1.1552 + nsCOMPtr<nsIXULTemplateResult> result; 1.1553 + nsAString& str; 1.1554 +}; 1.1555 + 1.1556 +nsresult 1.1557 +nsXULTemplateBuilder::SubstituteText(nsIXULTemplateResult* aResult, 1.1558 + const nsAString& aAttributeValue, 1.1559 + nsAString& aString) 1.1560 +{ 1.1561 + // See if it's the special value "..." 1.1562 + if (aAttributeValue.EqualsLiteral("...")) { 1.1563 + aResult->GetId(aString); 1.1564 + return NS_OK; 1.1565 + } 1.1566 + 1.1567 + // Reasonable guess at how big it should be 1.1568 + aString.SetCapacity(aAttributeValue.Length()); 1.1569 + 1.1570 + SubstituteTextClosure closure(aResult, aString); 1.1571 + ParseAttribute(aAttributeValue, 1.1572 + SubstituteTextReplaceVariable, 1.1573 + SubstituteTextAppendText, 1.1574 + &closure); 1.1575 + 1.1576 + return NS_OK; 1.1577 +} 1.1578 + 1.1579 + 1.1580 +void 1.1581 +nsXULTemplateBuilder::SubstituteTextAppendText(nsXULTemplateBuilder* aThis, 1.1582 + const nsAString& aText, 1.1583 + void* aClosure) 1.1584 +{ 1.1585 + // Append aString to the closure's result 1.1586 + SubstituteTextClosure* c = static_cast<SubstituteTextClosure*>(aClosure); 1.1587 + c->str.Append(aText); 1.1588 +} 1.1589 + 1.1590 +void 1.1591 +nsXULTemplateBuilder::SubstituteTextReplaceVariable(nsXULTemplateBuilder* aThis, 1.1592 + const nsAString& aVariable, 1.1593 + void* aClosure) 1.1594 +{ 1.1595 + // Substitute the value for the variable and append to the 1.1596 + // closure's result. 1.1597 + SubstituteTextClosure* c = static_cast<SubstituteTextClosure*>(aClosure); 1.1598 + 1.1599 + nsAutoString replacementText; 1.1600 + 1.1601 + // The symbol "rdf:*" is special, and means "this guy's URI" 1.1602 + if (aVariable.EqualsLiteral("rdf:*")){ 1.1603 + c->result->GetId(replacementText); 1.1604 + } 1.1605 + else { 1.1606 + // Got a variable; get the value it's assigned to 1.1607 + nsCOMPtr<nsIAtom> var = do_GetAtom(aVariable); 1.1608 + c->result->GetBindingFor(var, replacementText); 1.1609 + } 1.1610 + 1.1611 + c->str += replacementText; 1.1612 +} 1.1613 + 1.1614 +bool 1.1615 +nsXULTemplateBuilder::IsTemplateElement(nsIContent* aContent) 1.1616 +{ 1.1617 + return aContent->NodeInfo()->Equals(nsGkAtoms::_template, 1.1618 + kNameSpaceID_XUL); 1.1619 +} 1.1620 + 1.1621 +nsresult 1.1622 +nsXULTemplateBuilder::GetTemplateRoot(nsIContent** aResult) 1.1623 +{ 1.1624 + NS_PRECONDITION(mRoot != nullptr, "not initialized"); 1.1625 + if (! mRoot) 1.1626 + return NS_ERROR_NOT_INITIALIZED; 1.1627 + 1.1628 + // First, check and see if the root has a template attribute. This 1.1629 + // allows a template to be specified "out of line"; e.g., 1.1630 + // 1.1631 + // <window> 1.1632 + // <foo template="MyTemplate">...</foo> 1.1633 + // <template id="MyTemplate">...</template> 1.1634 + // </window> 1.1635 + // 1.1636 + nsAutoString templateID; 1.1637 + mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::_template, templateID); 1.1638 + 1.1639 + if (! templateID.IsEmpty()) { 1.1640 + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mRoot->GetDocument()); 1.1641 + if (! domDoc) 1.1642 + return NS_OK; 1.1643 + 1.1644 + nsCOMPtr<nsIDOMElement> domElement; 1.1645 + domDoc->GetElementById(templateID, getter_AddRefs(domElement)); 1.1646 + 1.1647 + if (domElement) { 1.1648 + nsCOMPtr<nsIContent> content = do_QueryInterface(domElement); 1.1649 + NS_ENSURE_STATE(content && 1.1650 + !nsContentUtils::ContentIsDescendantOf(mRoot, 1.1651 + content)); 1.1652 + content.forget(aResult); 1.1653 + return NS_OK; 1.1654 + } 1.1655 + } 1.1656 + 1.1657 + // If root node has no template attribute, then look for a child 1.1658 + // node which is a template tag. 1.1659 + for (nsIContent* child = mRoot->GetFirstChild(); 1.1660 + child; 1.1661 + child = child->GetNextSibling()) { 1.1662 + 1.1663 + if (IsTemplateElement(child)) { 1.1664 + NS_ADDREF(*aResult = child); 1.1665 + return NS_OK; 1.1666 + } 1.1667 + } 1.1668 + 1.1669 + // Look through the anonymous children as well. Although FlattenedChildIterator 1.1670 + // will find a template element that has been placed in an insertion point, many 1.1671 + // bindings do not have a specific insertion point for the template element, which 1.1672 + // would cause it to not be part of the flattened content tree. The check above to 1.1673 + // check the explicit children as well handles this case. 1.1674 + FlattenedChildIterator iter(mRoot); 1.1675 + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { 1.1676 + if (IsTemplateElement(child)) { 1.1677 + NS_ADDREF(*aResult = child); 1.1678 + return NS_OK; 1.1679 + } 1.1680 + } 1.1681 + 1.1682 + *aResult = nullptr; 1.1683 + return NS_OK; 1.1684 +} 1.1685 + 1.1686 +nsresult 1.1687 +nsXULTemplateBuilder::CompileQueries() 1.1688 +{ 1.1689 + nsCOMPtr<nsIContent> tmpl; 1.1690 + GetTemplateRoot(getter_AddRefs(tmpl)); 1.1691 + if (! tmpl) 1.1692 + return NS_OK; 1.1693 + 1.1694 + if (! mRoot) 1.1695 + return NS_ERROR_NOT_INITIALIZED; 1.1696 + 1.1697 + // Determine if there are any special settings we need to observe 1.1698 + mFlags = 0; 1.1699 + 1.1700 + nsAutoString flags; 1.1701 + mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::flags, flags); 1.1702 + 1.1703 + // if the dont-test-empty flag is set, containers should not be checked to 1.1704 + // see if they are empty. If dont-recurse is set, then don't process the 1.1705 + // template recursively and only show one level of results. The logging 1.1706 + // flag logs errors and results to the console, which is useful when 1.1707 + // debugging templates. 1.1708 + nsWhitespaceTokenizer tokenizer(flags); 1.1709 + while (tokenizer.hasMoreTokens()) { 1.1710 + const nsDependentSubstring& token(tokenizer.nextToken()); 1.1711 + if (token.EqualsLiteral("dont-test-empty")) 1.1712 + mFlags |= eDontTestEmpty; 1.1713 + else if (token.EqualsLiteral("dont-recurse")) 1.1714 + mFlags |= eDontRecurse; 1.1715 + else if (token.EqualsLiteral("logging")) 1.1716 + mFlags |= eLoggingEnabled; 1.1717 + } 1.1718 + 1.1719 +#ifdef PR_LOGGING 1.1720 + // always enable logging if the debug setting is used 1.1721 + if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) 1.1722 + mFlags |= eLoggingEnabled; 1.1723 +#endif 1.1724 + 1.1725 + nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot); 1.1726 + nsresult rv = 1.1727 + mQueryProcessor->InitializeForBuilding(mDataSource, this, rootnode); 1.1728 + if (NS_FAILED(rv)) 1.1729 + return rv; 1.1730 + 1.1731 + // Set the "container" and "member" variables, if the user has specified 1.1732 + // them. The container variable may be specified with the container 1.1733 + // attribute on the <template> and the member variable may be specified 1.1734 + // using the member attribute or the value of the uri attribute inside the 1.1735 + // first action body in the template. If not specified, the container 1.1736 + // variable defaults to '?uri' and the member variable defaults to '?' or 1.1737 + // 'rdf:*' for simple queries. 1.1738 + 1.1739 + // For RDF queries, the container variable may also be set via the 1.1740 + // <content> tag. 1.1741 + 1.1742 + nsAutoString containervar; 1.1743 + tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::container, containervar); 1.1744 + 1.1745 + if (containervar.IsEmpty()) 1.1746 + mRefVariable = do_GetAtom("?uri"); 1.1747 + else 1.1748 + mRefVariable = do_GetAtom(containervar); 1.1749 + 1.1750 + nsAutoString membervar; 1.1751 + tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::member, membervar); 1.1752 + 1.1753 + if (membervar.IsEmpty()) 1.1754 + mMemberVariable = nullptr; 1.1755 + else 1.1756 + mMemberVariable = do_GetAtom(membervar); 1.1757 + 1.1758 + nsTemplateQuerySet* queryset = new nsTemplateQuerySet(0); 1.1759 + if (!queryset) 1.1760 + return NS_ERROR_OUT_OF_MEMORY; 1.1761 + 1.1762 + if (!mQuerySets.AppendElement(queryset)) { 1.1763 + delete queryset; 1.1764 + return NS_ERROR_OUT_OF_MEMORY; 1.1765 + } 1.1766 + 1.1767 + bool canUseTemplate = false; 1.1768 + int32_t priority = 0; 1.1769 + rv = CompileTemplate(tmpl, queryset, false, &priority, &canUseTemplate); 1.1770 + 1.1771 + if (NS_FAILED(rv) || !canUseTemplate) { 1.1772 + for (int32_t q = mQuerySets.Length() - 1; q >= 0; q--) { 1.1773 + nsTemplateQuerySet* qs = mQuerySets[q]; 1.1774 + delete qs; 1.1775 + } 1.1776 + mQuerySets.Clear(); 1.1777 + } 1.1778 + 1.1779 + mQueriesCompiled = true; 1.1780 + 1.1781 + return NS_OK; 1.1782 +} 1.1783 + 1.1784 +nsresult 1.1785 +nsXULTemplateBuilder::CompileTemplate(nsIContent* aTemplate, 1.1786 + nsTemplateQuerySet* aQuerySet, 1.1787 + bool aIsQuerySet, 1.1788 + int32_t* aPriority, 1.1789 + bool* aCanUseTemplate) 1.1790 +{ 1.1791 + NS_ASSERTION(aQuerySet, "No queryset supplied"); 1.1792 + 1.1793 + nsresult rv = NS_OK; 1.1794 + 1.1795 + bool isQuerySetMode = false; 1.1796 + bool hasQuerySet = false, hasRule = false, hasQuery = false; 1.1797 + 1.1798 + for (nsIContent* rulenode = aTemplate->GetFirstChild(); 1.1799 + rulenode; 1.1800 + rulenode = rulenode->GetNextSibling()) { 1.1801 + 1.1802 + nsINodeInfo *ni = rulenode->NodeInfo(); 1.1803 + 1.1804 + // don't allow more queries than can be supported 1.1805 + if (*aPriority == INT16_MAX) 1.1806 + return NS_ERROR_FAILURE; 1.1807 + 1.1808 + // XXXndeakin queryset isn't a good name for this tag since it only 1.1809 + // ever contains one query 1.1810 + if (!aIsQuerySet && ni->Equals(nsGkAtoms::queryset, kNameSpaceID_XUL)) { 1.1811 + if (hasRule || hasQuery) { 1.1812 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_INVALID_QUERYSET); 1.1813 + continue; 1.1814 + } 1.1815 + 1.1816 + isQuerySetMode = true; 1.1817 + 1.1818 + // only create a queryset for those after the first since the 1.1819 + // first one is always created by CompileQueries 1.1820 + if (hasQuerySet) { 1.1821 + aQuerySet = new nsTemplateQuerySet(++*aPriority); 1.1822 + if (!aQuerySet) 1.1823 + return NS_ERROR_OUT_OF_MEMORY; 1.1824 + 1.1825 + // once the queryset is appended to the mQuerySets list, it 1.1826 + // will be removed by CompileQueries if an error occurs 1.1827 + if (!mQuerySets.AppendElement(aQuerySet)) { 1.1828 + delete aQuerySet; 1.1829 + return NS_ERROR_OUT_OF_MEMORY; 1.1830 + } 1.1831 + } 1.1832 + 1.1833 + hasQuerySet = true; 1.1834 + 1.1835 + rv = CompileTemplate(rulenode, aQuerySet, true, aPriority, aCanUseTemplate); 1.1836 + if (NS_FAILED(rv)) 1.1837 + return rv; 1.1838 + } 1.1839 + 1.1840 + // once a queryset is used, everything must be a queryset 1.1841 + if (isQuerySetMode) 1.1842 + continue; 1.1843 + 1.1844 + if (ni->Equals(nsGkAtoms::rule, kNameSpaceID_XUL)) { 1.1845 + nsCOMPtr<nsIContent> action; 1.1846 + nsXULContentUtils::FindChildByTag(rulenode, 1.1847 + kNameSpaceID_XUL, 1.1848 + nsGkAtoms::action, 1.1849 + getter_AddRefs(action)); 1.1850 + 1.1851 + if (action){ 1.1852 + nsCOMPtr<nsIAtom> memberVariable = mMemberVariable; 1.1853 + if (!memberVariable) { 1.1854 + memberVariable = DetermineMemberVariable(action); 1.1855 + if (!memberVariable) { 1.1856 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_NO_MEMBERVAR); 1.1857 + continue; 1.1858 + } 1.1859 + } 1.1860 + 1.1861 + if (hasQuery) { 1.1862 + nsCOMPtr<nsIAtom> tag; 1.1863 + DetermineRDFQueryRef(aQuerySet->mQueryNode, 1.1864 + getter_AddRefs(tag)); 1.1865 + if (tag) 1.1866 + aQuerySet->SetTag(tag); 1.1867 + 1.1868 + if (! aQuerySet->mCompiledQuery) { 1.1869 + nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode)); 1.1870 + 1.1871 + rv = mQueryProcessor->CompileQuery(this, query, 1.1872 + mRefVariable, memberVariable, 1.1873 + getter_AddRefs(aQuerySet->mCompiledQuery)); 1.1874 + if (NS_FAILED(rv)) 1.1875 + return rv; 1.1876 + } 1.1877 + 1.1878 + if (aQuerySet->mCompiledQuery) { 1.1879 + rv = CompileExtendedQuery(rulenode, action, memberVariable, 1.1880 + aQuerySet); 1.1881 + if (NS_FAILED(rv)) 1.1882 + return rv; 1.1883 + 1.1884 + *aCanUseTemplate = true; 1.1885 + } 1.1886 + } 1.1887 + else { 1.1888 + // backwards-compatible RDF template syntax where there is 1.1889 + // an <action> node but no <query> node. In this case, 1.1890 + // use the conditions as if it was the query. 1.1891 + 1.1892 + nsCOMPtr<nsIContent> conditions; 1.1893 + nsXULContentUtils::FindChildByTag(rulenode, 1.1894 + kNameSpaceID_XUL, 1.1895 + nsGkAtoms::conditions, 1.1896 + getter_AddRefs(conditions)); 1.1897 + 1.1898 + if (conditions) { 1.1899 + // create a new queryset if one hasn't been created already 1.1900 + if (hasQuerySet) { 1.1901 + aQuerySet = new nsTemplateQuerySet(++*aPriority); 1.1902 + if (! aQuerySet) 1.1903 + return NS_ERROR_OUT_OF_MEMORY; 1.1904 + 1.1905 + if (!mQuerySets.AppendElement(aQuerySet)) { 1.1906 + delete aQuerySet; 1.1907 + return NS_ERROR_OUT_OF_MEMORY; 1.1908 + } 1.1909 + } 1.1910 + 1.1911 + nsCOMPtr<nsIAtom> tag; 1.1912 + DetermineRDFQueryRef(conditions, getter_AddRefs(tag)); 1.1913 + if (tag) 1.1914 + aQuerySet->SetTag(tag); 1.1915 + 1.1916 + hasQuerySet = true; 1.1917 + 1.1918 + nsCOMPtr<nsIDOMNode> conditionsnode(do_QueryInterface(conditions)); 1.1919 + 1.1920 + aQuerySet->mQueryNode = conditions; 1.1921 + rv = mQueryProcessor->CompileQuery(this, conditionsnode, 1.1922 + mRefVariable, 1.1923 + memberVariable, 1.1924 + getter_AddRefs(aQuerySet->mCompiledQuery)); 1.1925 + if (NS_FAILED(rv)) 1.1926 + return rv; 1.1927 + 1.1928 + if (aQuerySet->mCompiledQuery) { 1.1929 + rv = CompileExtendedQuery(rulenode, action, memberVariable, 1.1930 + aQuerySet); 1.1931 + if (NS_FAILED(rv)) 1.1932 + return rv; 1.1933 + 1.1934 + *aCanUseTemplate = true; 1.1935 + } 1.1936 + } 1.1937 + } 1.1938 + } 1.1939 + else { 1.1940 + if (hasQuery) 1.1941 + continue; 1.1942 + 1.1943 + // a new queryset must always be created in this case 1.1944 + if (hasQuerySet) { 1.1945 + aQuerySet = new nsTemplateQuerySet(++*aPriority); 1.1946 + if (! aQuerySet) 1.1947 + return NS_ERROR_OUT_OF_MEMORY; 1.1948 + 1.1949 + if (!mQuerySets.AppendElement(aQuerySet)) { 1.1950 + delete aQuerySet; 1.1951 + return NS_ERROR_OUT_OF_MEMORY; 1.1952 + } 1.1953 + } 1.1954 + 1.1955 + hasQuerySet = true; 1.1956 + 1.1957 + rv = CompileSimpleQuery(rulenode, aQuerySet, aCanUseTemplate); 1.1958 + if (NS_FAILED(rv)) 1.1959 + return rv; 1.1960 + } 1.1961 + 1.1962 + hasRule = true; 1.1963 + } 1.1964 + else if (ni->Equals(nsGkAtoms::query, kNameSpaceID_XUL)) { 1.1965 + if (hasQuery) 1.1966 + continue; 1.1967 + 1.1968 + aQuerySet->mQueryNode = rulenode; 1.1969 + hasQuery = true; 1.1970 + } 1.1971 + else if (ni->Equals(nsGkAtoms::action, kNameSpaceID_XUL)) { 1.1972 + // the query must appear before the action 1.1973 + if (! hasQuery) 1.1974 + continue; 1.1975 + 1.1976 + nsCOMPtr<nsIAtom> tag; 1.1977 + DetermineRDFQueryRef(aQuerySet->mQueryNode, getter_AddRefs(tag)); 1.1978 + if (tag) 1.1979 + aQuerySet->SetTag(tag); 1.1980 + 1.1981 + nsCOMPtr<nsIAtom> memberVariable = mMemberVariable; 1.1982 + if (!memberVariable) { 1.1983 + memberVariable = DetermineMemberVariable(rulenode); 1.1984 + if (!memberVariable) { 1.1985 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_NO_MEMBERVAR); 1.1986 + continue; 1.1987 + } 1.1988 + } 1.1989 + 1.1990 + nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode)); 1.1991 + 1.1992 + rv = mQueryProcessor->CompileQuery(this, query, 1.1993 + mRefVariable, memberVariable, 1.1994 + getter_AddRefs(aQuerySet->mCompiledQuery)); 1.1995 + 1.1996 + if (aQuerySet->mCompiledQuery) { 1.1997 + nsTemplateRule* rule = aQuerySet->NewRule(aTemplate, rulenode, aQuerySet); 1.1998 + if (! rule) 1.1999 + return NS_ERROR_OUT_OF_MEMORY; 1.2000 + 1.2001 + rule->SetVars(mRefVariable, memberVariable); 1.2002 + 1.2003 + *aCanUseTemplate = true; 1.2004 + 1.2005 + return NS_OK; 1.2006 + } 1.2007 + } 1.2008 + } 1.2009 + 1.2010 + if (! hasRule && ! hasQuery && ! hasQuerySet) { 1.2011 + // if no rules are specified in the template, then the contents of the 1.2012 + // <template> tag are the one-and-only template. 1.2013 + rv = CompileSimpleQuery(aTemplate, aQuerySet, aCanUseTemplate); 1.2014 + } 1.2015 + 1.2016 + return rv; 1.2017 +} 1.2018 + 1.2019 +nsresult 1.2020 +nsXULTemplateBuilder::CompileExtendedQuery(nsIContent* aRuleElement, 1.2021 + nsIContent* aActionElement, 1.2022 + nsIAtom* aMemberVariable, 1.2023 + nsTemplateQuerySet* aQuerySet) 1.2024 +{ 1.2025 + // Compile an "extended" <template> rule. An extended rule may have 1.2026 + // a <conditions> child, an <action> child, and a <bindings> child. 1.2027 + nsresult rv; 1.2028 + 1.2029 + nsTemplateRule* rule = aQuerySet->NewRule(aRuleElement, aActionElement, aQuerySet); 1.2030 + if (! rule) 1.2031 + return NS_ERROR_OUT_OF_MEMORY; 1.2032 + 1.2033 + nsCOMPtr<nsIContent> conditions; 1.2034 + nsXULContentUtils::FindChildByTag(aRuleElement, 1.2035 + kNameSpaceID_XUL, 1.2036 + nsGkAtoms::conditions, 1.2037 + getter_AddRefs(conditions)); 1.2038 + 1.2039 + // allow the conditions to be placed directly inside the rule 1.2040 + if (!conditions) 1.2041 + conditions = aRuleElement; 1.2042 + 1.2043 + rv = CompileConditions(rule, conditions); 1.2044 + // If the rule compilation failed, then we have to bail. 1.2045 + if (NS_FAILED(rv)) { 1.2046 + aQuerySet->RemoveRule(rule); 1.2047 + return rv; 1.2048 + } 1.2049 + 1.2050 + rule->SetVars(mRefVariable, aMemberVariable); 1.2051 + 1.2052 + // If we've got bindings, add 'em. 1.2053 + nsCOMPtr<nsIContent> bindings; 1.2054 + nsXULContentUtils::FindChildByTag(aRuleElement, 1.2055 + kNameSpaceID_XUL, 1.2056 + nsGkAtoms::bindings, 1.2057 + getter_AddRefs(bindings)); 1.2058 + 1.2059 + // allow bindings to be placed directly inside rule 1.2060 + if (!bindings) 1.2061 + bindings = aRuleElement; 1.2062 + 1.2063 + rv = CompileBindings(rule, bindings); 1.2064 + NS_ENSURE_SUCCESS(rv, rv); 1.2065 + 1.2066 + return NS_OK; 1.2067 +} 1.2068 + 1.2069 +already_AddRefed<nsIAtom> 1.2070 +nsXULTemplateBuilder::DetermineMemberVariable(nsIContent* aElement) 1.2071 +{ 1.2072 + // recursively iterate over the children looking for an element 1.2073 + // with uri="?..." 1.2074 + for (nsIContent* child = aElement->GetFirstChild(); 1.2075 + child; 1.2076 + child = child->GetNextSibling()) { 1.2077 + nsAutoString uri; 1.2078 + child->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri); 1.2079 + if (!uri.IsEmpty() && uri[0] == char16_t('?')) { 1.2080 + return NS_NewAtom(uri); 1.2081 + } 1.2082 + 1.2083 + nsCOMPtr<nsIAtom> result = DetermineMemberVariable(child); 1.2084 + if (result) { 1.2085 + return result.forget(); 1.2086 + } 1.2087 + } 1.2088 + 1.2089 + return nullptr; 1.2090 +} 1.2091 + 1.2092 +void 1.2093 +nsXULTemplateBuilder::DetermineRDFQueryRef(nsIContent* aQueryElement, nsIAtom** aTag) 1.2094 +{ 1.2095 + // check for a tag 1.2096 + nsCOMPtr<nsIContent> content; 1.2097 + nsXULContentUtils::FindChildByTag(aQueryElement, 1.2098 + kNameSpaceID_XUL, 1.2099 + nsGkAtoms::content, 1.2100 + getter_AddRefs(content)); 1.2101 + 1.2102 + if (! content) { 1.2103 + // look for older treeitem syntax as well 1.2104 + nsXULContentUtils::FindChildByTag(aQueryElement, 1.2105 + kNameSpaceID_XUL, 1.2106 + nsGkAtoms::treeitem, 1.2107 + getter_AddRefs(content)); 1.2108 + } 1.2109 + 1.2110 + if (content) { 1.2111 + nsAutoString uri; 1.2112 + content->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri); 1.2113 + 1.2114 + if (!uri.IsEmpty()) 1.2115 + mRefVariable = do_GetAtom(uri); 1.2116 + 1.2117 + nsAutoString tag; 1.2118 + content->GetAttr(kNameSpaceID_None, nsGkAtoms::tag, tag); 1.2119 + 1.2120 + if (!tag.IsEmpty()) 1.2121 + *aTag = NS_NewAtom(tag).take(); 1.2122 + } 1.2123 +} 1.2124 + 1.2125 +nsresult 1.2126 +nsXULTemplateBuilder::CompileSimpleQuery(nsIContent* aRuleElement, 1.2127 + nsTemplateQuerySet* aQuerySet, 1.2128 + bool* aCanUseTemplate) 1.2129 +{ 1.2130 + // compile a simple query, which is a query with no <query> or 1.2131 + // <conditions>. This means that a default query is used. 1.2132 + nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aRuleElement)); 1.2133 + 1.2134 + nsCOMPtr<nsIAtom> memberVariable; 1.2135 + if (mMemberVariable) 1.2136 + memberVariable = mMemberVariable; 1.2137 + else 1.2138 + memberVariable = do_GetAtom("rdf:*"); 1.2139 + 1.2140 + // since there is no <query> node for a simple query, the query node will 1.2141 + // be either the <rule> node if multiple rules are used, or the <template> node. 1.2142 + aQuerySet->mQueryNode = aRuleElement; 1.2143 + nsresult rv = mQueryProcessor->CompileQuery(this, query, 1.2144 + mRefVariable, memberVariable, 1.2145 + getter_AddRefs(aQuerySet->mCompiledQuery)); 1.2146 + if (NS_FAILED(rv)) 1.2147 + return rv; 1.2148 + 1.2149 + if (! aQuerySet->mCompiledQuery) { 1.2150 + *aCanUseTemplate = false; 1.2151 + return NS_OK; 1.2152 + } 1.2153 + 1.2154 + nsTemplateRule* rule = aQuerySet->NewRule(aRuleElement, aRuleElement, aQuerySet); 1.2155 + if (! rule) 1.2156 + return NS_ERROR_OUT_OF_MEMORY; 1.2157 + 1.2158 + rule->SetVars(mRefVariable, memberVariable); 1.2159 + 1.2160 + nsAutoString tag; 1.2161 + aRuleElement->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag); 1.2162 + 1.2163 + if (!tag.IsEmpty()) { 1.2164 + nsCOMPtr<nsIAtom> tagatom = do_GetAtom(tag); 1.2165 + aQuerySet->SetTag(tagatom); 1.2166 + } 1.2167 + 1.2168 + *aCanUseTemplate = true; 1.2169 + 1.2170 + return AddSimpleRuleBindings(rule, aRuleElement); 1.2171 +} 1.2172 + 1.2173 +nsresult 1.2174 +nsXULTemplateBuilder::CompileConditions(nsTemplateRule* aRule, 1.2175 + nsIContent* aCondition) 1.2176 +{ 1.2177 + nsAutoString tag; 1.2178 + aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag); 1.2179 + 1.2180 + if (!tag.IsEmpty()) { 1.2181 + nsCOMPtr<nsIAtom> tagatom = do_GetAtom(tag); 1.2182 + aRule->SetTag(tagatom); 1.2183 + } 1.2184 + 1.2185 + nsTemplateCondition* currentCondition = nullptr; 1.2186 + 1.2187 + for (nsIContent* node = aCondition->GetFirstChild(); 1.2188 + node; 1.2189 + node = node->GetNextSibling()) { 1.2190 + 1.2191 + if (node->NodeInfo()->Equals(nsGkAtoms::where, kNameSpaceID_XUL)) { 1.2192 + nsresult rv = CompileWhereCondition(aRule, node, ¤tCondition); 1.2193 + if (NS_FAILED(rv)) 1.2194 + return rv; 1.2195 + } 1.2196 + } 1.2197 + 1.2198 + return NS_OK; 1.2199 +} 1.2200 + 1.2201 +nsresult 1.2202 +nsXULTemplateBuilder::CompileWhereCondition(nsTemplateRule* aRule, 1.2203 + nsIContent* aCondition, 1.2204 + nsTemplateCondition** aCurrentCondition) 1.2205 +{ 1.2206 + // Compile a <where> condition, which must be of the form: 1.2207 + // 1.2208 + // <where subject="?var1|string" rel="relation" value="?var2|string" /> 1.2209 + // 1.2210 + // The value of rel may be: 1.2211 + // equal - subject must be equal to object 1.2212 + // notequal - subject must not be equal to object 1.2213 + // less - subject must be less than object 1.2214 + // greater - subject must be greater than object 1.2215 + // startswith - subject must start with object 1.2216 + // endswith - subject must end with object 1.2217 + // contains - subject must contain object 1.2218 + // Comparisons are done as strings unless the subject is an integer. 1.2219 + 1.2220 + // subject 1.2221 + nsAutoString subject; 1.2222 + aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject); 1.2223 + if (subject.IsEmpty()) { 1.2224 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_SUBJECT); 1.2225 + return NS_OK; 1.2226 + } 1.2227 + 1.2228 + nsCOMPtr<nsIAtom> svar; 1.2229 + if (subject[0] == char16_t('?')) 1.2230 + svar = do_GetAtom(subject); 1.2231 + 1.2232 + nsAutoString relstring; 1.2233 + aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, relstring); 1.2234 + if (relstring.IsEmpty()) { 1.2235 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_RELATION); 1.2236 + return NS_OK; 1.2237 + } 1.2238 + 1.2239 + // object 1.2240 + nsAutoString value; 1.2241 + aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value); 1.2242 + if (value.IsEmpty()) { 1.2243 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_VALUE); 1.2244 + return NS_OK; 1.2245 + } 1.2246 + 1.2247 + // multiple 1.2248 + bool shouldMultiple = 1.2249 + aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::multiple, 1.2250 + nsGkAtoms::_true, eCaseMatters); 1.2251 + 1.2252 + nsCOMPtr<nsIAtom> vvar; 1.2253 + if (!shouldMultiple && (value[0] == char16_t('?'))) { 1.2254 + vvar = do_GetAtom(value); 1.2255 + } 1.2256 + 1.2257 + // ignorecase 1.2258 + bool shouldIgnoreCase = 1.2259 + aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorecase, 1.2260 + nsGkAtoms::_true, eCaseMatters); 1.2261 + 1.2262 + // negate 1.2263 + bool shouldNegate = 1.2264 + aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::negate, 1.2265 + nsGkAtoms::_true, eCaseMatters); 1.2266 + 1.2267 + nsTemplateCondition* condition; 1.2268 + 1.2269 + if (svar && vvar) { 1.2270 + condition = new nsTemplateCondition(svar, relstring, vvar, 1.2271 + shouldIgnoreCase, shouldNegate); 1.2272 + } 1.2273 + else if (svar && !value.IsEmpty()) { 1.2274 + condition = new nsTemplateCondition(svar, relstring, value, 1.2275 + shouldIgnoreCase, shouldNegate, shouldMultiple); 1.2276 + } 1.2277 + else if (vvar) { 1.2278 + condition = new nsTemplateCondition(subject, relstring, vvar, 1.2279 + shouldIgnoreCase, shouldNegate); 1.2280 + } 1.2281 + else { 1.2282 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_VAR); 1.2283 + return NS_OK; 1.2284 + } 1.2285 + 1.2286 + if (! condition) 1.2287 + return NS_ERROR_OUT_OF_MEMORY; 1.2288 + 1.2289 + if (*aCurrentCondition) { 1.2290 + (*aCurrentCondition)->SetNext(condition); 1.2291 + } 1.2292 + else { 1.2293 + aRule->SetCondition(condition); 1.2294 + } 1.2295 + 1.2296 + *aCurrentCondition = condition; 1.2297 + 1.2298 + return NS_OK; 1.2299 +} 1.2300 + 1.2301 +nsresult 1.2302 +nsXULTemplateBuilder::CompileBindings(nsTemplateRule* aRule, nsIContent* aBindings) 1.2303 +{ 1.2304 + // Add an extended rule's bindings. 1.2305 + nsresult rv; 1.2306 + 1.2307 + for (nsIContent* binding = aBindings->GetFirstChild(); 1.2308 + binding; 1.2309 + binding = binding->GetNextSibling()) { 1.2310 + 1.2311 + if (binding->NodeInfo()->Equals(nsGkAtoms::binding, 1.2312 + kNameSpaceID_XUL)) { 1.2313 + rv = CompileBinding(aRule, binding); 1.2314 + if (NS_FAILED(rv)) 1.2315 + return rv; 1.2316 + } 1.2317 + } 1.2318 + 1.2319 + aRule->AddBindingsToQueryProcessor(mQueryProcessor); 1.2320 + 1.2321 + return NS_OK; 1.2322 +} 1.2323 + 1.2324 + 1.2325 +nsresult 1.2326 +nsXULTemplateBuilder::CompileBinding(nsTemplateRule* aRule, 1.2327 + nsIContent* aBinding) 1.2328 +{ 1.2329 + // Compile a <binding> "condition", which must be of the form: 1.2330 + // 1.2331 + // <binding subject="?var1" 1.2332 + // predicate="resource" 1.2333 + // object="?var2" /> 1.2334 + // 1.2335 + // XXXwaterson Some day it would be cool to allow the 'predicate' 1.2336 + // to be bound to a variable. 1.2337 + 1.2338 + // subject 1.2339 + nsAutoString subject; 1.2340 + aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject); 1.2341 + if (subject.IsEmpty()) { 1.2342 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_SUBJECT); 1.2343 + return NS_OK; 1.2344 + } 1.2345 + 1.2346 + nsCOMPtr<nsIAtom> svar; 1.2347 + if (subject[0] == char16_t('?')) { 1.2348 + svar = do_GetAtom(subject); 1.2349 + } 1.2350 + else { 1.2351 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_SUBJECT); 1.2352 + return NS_OK; 1.2353 + } 1.2354 + 1.2355 + // predicate 1.2356 + nsAutoString predicate; 1.2357 + aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::predicate, predicate); 1.2358 + if (predicate.IsEmpty()) { 1.2359 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_PREDICATE); 1.2360 + return NS_OK; 1.2361 + } 1.2362 + 1.2363 + // object 1.2364 + nsAutoString object; 1.2365 + aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::object, object); 1.2366 + 1.2367 + if (object.IsEmpty()) { 1.2368 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT); 1.2369 + return NS_OK; 1.2370 + } 1.2371 + 1.2372 + nsCOMPtr<nsIAtom> ovar; 1.2373 + if (object[0] == char16_t('?')) { 1.2374 + ovar = do_GetAtom(object); 1.2375 + } 1.2376 + else { 1.2377 + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT); 1.2378 + return NS_OK; 1.2379 + } 1.2380 + 1.2381 + return aRule->AddBinding(svar, predicate, ovar); 1.2382 +} 1.2383 + 1.2384 +nsresult 1.2385 +nsXULTemplateBuilder::AddSimpleRuleBindings(nsTemplateRule* aRule, 1.2386 + nsIContent* aElement) 1.2387 +{ 1.2388 + // Crawl the content tree of a "simple" rule, adding a variable 1.2389 + // assignment for any attribute whose value is "rdf:". 1.2390 + 1.2391 + nsAutoTArray<nsIContent*, 8> elements; 1.2392 + 1.2393 + if (elements.AppendElement(aElement) == nullptr) 1.2394 + return NS_ERROR_OUT_OF_MEMORY; 1.2395 + 1.2396 + while (elements.Length()) { 1.2397 + // Pop the next element off the stack 1.2398 + uint32_t i = elements.Length() - 1; 1.2399 + nsIContent* element = elements[i]; 1.2400 + elements.RemoveElementAt(i); 1.2401 + 1.2402 + // Iterate through its attributes, looking for substitutions 1.2403 + // that we need to add as bindings. 1.2404 + uint32_t count = element->GetAttrCount(); 1.2405 + 1.2406 + for (i = 0; i < count; ++i) { 1.2407 + const nsAttrName* name = element->GetAttrNameAt(i); 1.2408 + 1.2409 + if (!name->Equals(nsGkAtoms::id, kNameSpaceID_None) && 1.2410 + !name->Equals(nsGkAtoms::uri, kNameSpaceID_None)) { 1.2411 + nsAutoString value; 1.2412 + element->GetAttr(name->NamespaceID(), name->LocalName(), value); 1.2413 + 1.2414 + // Scan the attribute for variables, adding a binding for 1.2415 + // each one. 1.2416 + ParseAttribute(value, AddBindingsFor, nullptr, aRule); 1.2417 + } 1.2418 + } 1.2419 + 1.2420 + // Push kids onto the stack, and search them next. 1.2421 + for (nsIContent* child = element->GetLastChild(); 1.2422 + child; 1.2423 + child = child->GetPreviousSibling()) { 1.2424 + 1.2425 + if (!elements.AppendElement(child)) 1.2426 + return NS_ERROR_OUT_OF_MEMORY; 1.2427 + } 1.2428 + } 1.2429 + 1.2430 + aRule->AddBindingsToQueryProcessor(mQueryProcessor); 1.2431 + 1.2432 + return NS_OK; 1.2433 +} 1.2434 + 1.2435 +void 1.2436 +nsXULTemplateBuilder::AddBindingsFor(nsXULTemplateBuilder* aThis, 1.2437 + const nsAString& aVariable, 1.2438 + void* aClosure) 1.2439 +{ 1.2440 + // We should *only* be recieving "rdf:"-style variables. Make 1.2441 + // sure... 1.2442 + if (!StringBeginsWith(aVariable, NS_LITERAL_STRING("rdf:"))) 1.2443 + return; 1.2444 + 1.2445 + nsTemplateRule* rule = static_cast<nsTemplateRule*>(aClosure); 1.2446 + 1.2447 + nsCOMPtr<nsIAtom> var = do_GetAtom(aVariable); 1.2448 + 1.2449 + // Strip it down to the raw RDF property by clobbering the "rdf:" 1.2450 + // prefix 1.2451 + nsAutoString property; 1.2452 + property.Assign(Substring(aVariable, uint32_t(4), aVariable.Length() - 4)); 1.2453 + 1.2454 + if (! rule->HasBinding(rule->GetMemberVariable(), property, var)) 1.2455 + // In the simple syntax, the binding is always from the 1.2456 + // member variable, through the property, to the target. 1.2457 + rule->AddBinding(rule->GetMemberVariable(), property, var); 1.2458 +} 1.2459 + 1.2460 + 1.2461 +nsresult 1.2462 +nsXULTemplateBuilder::IsSystemPrincipal(nsIPrincipal *principal, bool *result) 1.2463 +{ 1.2464 + if (!gSystemPrincipal) 1.2465 + return NS_ERROR_UNEXPECTED; 1.2466 + 1.2467 + *result = (principal == gSystemPrincipal); 1.2468 + return NS_OK; 1.2469 +} 1.2470 + 1.2471 +bool 1.2472 +nsXULTemplateBuilder::IsActivated(nsIRDFResource *aResource) 1.2473 +{ 1.2474 + for (ActivationEntry *entry = mTop; 1.2475 + entry != nullptr; 1.2476 + entry = entry->mPrevious) { 1.2477 + if (entry->mResource == aResource) 1.2478 + return true; 1.2479 + } 1.2480 + return false; 1.2481 +} 1.2482 + 1.2483 +nsresult 1.2484 +nsXULTemplateBuilder::GetResultResource(nsIXULTemplateResult* aResult, 1.2485 + nsIRDFResource** aResource) 1.2486 +{ 1.2487 + // get the resource for a result by checking its resource property. If it 1.2488 + // is not set, check the id. This allows non-chrome implementations to 1.2489 + // avoid having to use RDF. 1.2490 + nsresult rv = aResult->GetResource(aResource); 1.2491 + if (NS_FAILED(rv)) 1.2492 + return rv; 1.2493 + 1.2494 + if (! *aResource) { 1.2495 + nsAutoString id; 1.2496 + rv = aResult->GetId(id); 1.2497 + if (NS_FAILED(rv)) 1.2498 + return rv; 1.2499 + 1.2500 + return gRDFService->GetUnicodeResource(id, aResource); 1.2501 + } 1.2502 + 1.2503 + return rv; 1.2504 +} 1.2505 + 1.2506 + 1.2507 +void 1.2508 +nsXULTemplateBuilder::OutputMatchToLog(nsIRDFResource* aId, 1.2509 + nsTemplateMatch* aMatch, 1.2510 + bool aIsNew) 1.2511 +{ 1.2512 + int32_t priority = aMatch->QuerySetPriority() + 1; 1.2513 + int32_t activePriority = -1; 1.2514 + 1.2515 + nsAutoString msg; 1.2516 + 1.2517 + nsAutoString templateid; 1.2518 + mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::id, templateid); 1.2519 + msg.AppendLiteral("In template"); 1.2520 + if (!templateid.IsEmpty()) { 1.2521 + msg.AppendLiteral(" with id "); 1.2522 + msg.Append(templateid); 1.2523 + } 1.2524 + 1.2525 + nsAutoString refstring; 1.2526 + aMatch->mResult->GetBindingFor(mRefVariable, refstring); 1.2527 + if (!refstring.IsEmpty()) { 1.2528 + msg.AppendLiteral(" using ref "); 1.2529 + msg.Append(refstring); 1.2530 + } 1.2531 + 1.2532 + msg.AppendLiteral("\n "); 1.2533 + 1.2534 + nsTemplateMatch* match = nullptr; 1.2535 + if (mMatchMap.Get(aId, &match)){ 1.2536 + while (match) { 1.2537 + if (match == aMatch) 1.2538 + break; 1.2539 + if (match->IsActive() && 1.2540 + match->GetContainer() == aMatch->GetContainer()) { 1.2541 + activePriority = match->QuerySetPriority() + 1; 1.2542 + break; 1.2543 + } 1.2544 + match = match->mNext; 1.2545 + } 1.2546 + } 1.2547 + 1.2548 + if (aMatch->IsActive()) { 1.2549 + if (aIsNew) { 1.2550 + msg.AppendLiteral("New active result for query "); 1.2551 + msg.AppendInt(priority); 1.2552 + msg.AppendLiteral(" matching rule "); 1.2553 + msg.AppendInt(aMatch->RuleIndex() + 1); 1.2554 + } 1.2555 + else { 1.2556 + msg.AppendLiteral("Removed active result for query "); 1.2557 + msg.AppendInt(priority); 1.2558 + if (activePriority > 0) { 1.2559 + msg.AppendLiteral(" (new active query is "); 1.2560 + msg.AppendInt(activePriority); 1.2561 + msg.Append(')'); 1.2562 + } 1.2563 + else { 1.2564 + msg.AppendLiteral(" (no new active query)"); 1.2565 + } 1.2566 + } 1.2567 + } 1.2568 + else { 1.2569 + if (aIsNew) { 1.2570 + msg.AppendLiteral("New inactive result for query "); 1.2571 + msg.AppendInt(priority); 1.2572 + if (activePriority > 0) { 1.2573 + msg.AppendLiteral(" (overridden by query "); 1.2574 + msg.AppendInt(activePriority); 1.2575 + msg.Append(')'); 1.2576 + } 1.2577 + else { 1.2578 + msg.AppendLiteral(" (didn't match a rule)"); 1.2579 + } 1.2580 + } 1.2581 + else { 1.2582 + msg.AppendLiteral("Removed inactive result for query "); 1.2583 + msg.AppendInt(priority); 1.2584 + if (activePriority > 0) { 1.2585 + msg.AppendLiteral(" (active query is "); 1.2586 + msg.AppendInt(activePriority); 1.2587 + msg.Append(')'); 1.2588 + } 1.2589 + else { 1.2590 + msg.AppendLiteral(" (no active query)"); 1.2591 + } 1.2592 + } 1.2593 + } 1.2594 + 1.2595 + nsAutoString idstring; 1.2596 + nsXULContentUtils::GetTextForNode(aId, idstring); 1.2597 + msg.AppendLiteral(": "); 1.2598 + msg.Append(idstring); 1.2599 + 1.2600 + nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); 1.2601 + if (cs) 1.2602 + cs->LogStringMessage(msg.get()); 1.2603 +}