diff options
Diffstat (limited to 'dom/xul/templates/nsXULTemplateBuilder.cpp')
-rw-r--r-- | dom/xul/templates/nsXULTemplateBuilder.cpp | 2573 |
1 files changed, 2573 insertions, 0 deletions
diff --git a/dom/xul/templates/nsXULTemplateBuilder.cpp b/dom/xul/templates/nsXULTemplateBuilder.cpp new file mode 100644 index 000000000..49fb3335d --- /dev/null +++ b/dom/xul/templates/nsXULTemplateBuilder.cpp @@ -0,0 +1,2573 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + Builds content from a datasource using the XUL <template> tag. + + TO DO + + . Fix ContentTagTest's location in the network construction + + To turn on logging for this module, set: + + MOZ_LOG=nsXULTemplateBuilder:5 + + */ + +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "nsIContent.h" +#include "nsIDOMElement.h" +#include "nsIDOMNode.h" +#include "nsIDOMDocument.h" +#include "nsIDOMXULElement.h" +#include "nsIDocument.h" +#include "nsBindingManager.h" +#include "nsIDOMNodeList.h" +#include "nsIObserverService.h" +#include "nsIRDFCompositeDataSource.h" +#include "nsIRDFInferDataSource.h" +#include "nsIRDFContainerUtils.h" +#include "nsIXULDocument.h" +#include "nsIXULTemplateBuilder.h" +#include "nsIXULBuilderListener.h" +#include "nsIRDFRemoteDataSource.h" +#include "nsIRDFService.h" +#include "nsIScriptContext.h" +#include "nsIScriptGlobalObject.h" +#include "nsIServiceManager.h" +#include "nsISimpleEnumerator.h" +#include "nsIMutableArray.h" +#include "nsIURL.h" +#include "nsIXPConnect.h" +#include "nsContentCID.h" +#include "nsNameSpaceManager.h" +#include "nsRDFCID.h" +#include "nsXULContentUtils.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsXPIDLString.h" +#include "nsWhitespaceTokenizer.h" +#include "nsGkAtoms.h" +#include "nsXULElement.h" +#include "jsapi.h" +#include "mozilla/Logging.h" +#include "rdf.h" +#include "PLDHashTable.h" +#include "plhash.h" +#include "nsDOMClassInfoID.h" +#include "nsPIDOMWindow.h" +#include "nsIConsoleService.h" +#include "nsNetUtil.h" +#include "nsXULTemplateBuilder.h" +#include "nsXULTemplateQueryProcessorRDF.h" +#include "nsXULTemplateQueryProcessorXML.h" +#include "nsXULTemplateQueryProcessorStorage.h" +#include "nsContentUtils.h" +#include "ChildIterator.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsGlobalWindow.h" + +using namespace mozilla::dom; +using namespace mozilla; + +//---------------------------------------------------------------------- +// +// nsXULTemplateBuilder +// + +nsrefcnt nsXULTemplateBuilder::gRefCnt = 0; +nsIRDFService* nsXULTemplateBuilder::gRDFService; +nsIRDFContainerUtils* nsXULTemplateBuilder::gRDFContainerUtils; +nsIScriptSecurityManager* nsXULTemplateBuilder::gScriptSecurityManager; +nsIPrincipal* nsXULTemplateBuilder::gSystemPrincipal; +nsIObserverService* nsXULTemplateBuilder::gObserverService; + +LazyLogModule gXULTemplateLog("nsXULTemplateBuilder"); + +#define NS_QUERY_PROCESSOR_CONTRACTID_PREFIX "@mozilla.org/xul/xul-query-processor;1?name=" + +//---------------------------------------------------------------------- +// +// nsXULTemplateBuilder methods +// + +nsXULTemplateBuilder::nsXULTemplateBuilder(void) + : mQueriesCompiled(false), + mFlags(0), + mTop(nullptr), + mObservedDocument(nullptr) +{ + MOZ_COUNT_CTOR(nsXULTemplateBuilder); +} + +void +nsXULTemplateBuilder::DestroyMatchMap() +{ + for (auto iter = mMatchMap.Iter(); !iter.Done(); iter.Next()) { + nsTemplateMatch*& match = iter.Data(); + // delete all the matches in the list + while (match) { + nsTemplateMatch* next = match->mNext; + nsTemplateMatch::Destroy(match, true); + match = next; + } + + iter.Remove(); + } +} + +nsXULTemplateBuilder::~nsXULTemplateBuilder(void) +{ + Uninit(true); + + if (--gRefCnt == 0) { + NS_IF_RELEASE(gRDFService); + NS_IF_RELEASE(gRDFContainerUtils); + NS_IF_RELEASE(gSystemPrincipal); + NS_IF_RELEASE(gScriptSecurityManager); + NS_IF_RELEASE(gObserverService); + } + + MOZ_COUNT_DTOR(nsXULTemplateBuilder); +} + + +nsresult +nsXULTemplateBuilder::InitGlobals() +{ + nsresult rv; + + if (gRefCnt++ == 0) { + // Initialize the global shared reference to the service + // manager and get some shared resource objects. + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + rv = CallGetService(kRDFServiceCID, &gRDFService); + if (NS_FAILED(rv)) + return rv; + + NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); + rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils); + if (NS_FAILED(rv)) + return rv; + + rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, + &gScriptSecurityManager); + if (NS_FAILED(rv)) + return rv; + + rv = gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal); + if (NS_FAILED(rv)) + return rv; + + rv = CallGetService(NS_OBSERVERSERVICE_CONTRACTID, &gObserverService); + if (NS_FAILED(rv)) + return rv; + } + + return NS_OK; +} + +void +nsXULTemplateBuilder::StartObserving(nsIDocument* aDocument) +{ + aDocument->AddObserver(this); + mObservedDocument = aDocument; + gObserverService->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, false); +} + +void +nsXULTemplateBuilder::StopObserving() +{ + MOZ_ASSERT(mObservedDocument); + mObservedDocument->RemoveObserver(this); + mObservedDocument = nullptr; + gObserverService->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); +} + +void +nsXULTemplateBuilder::CleanUp(bool aIsFinal) +{ + for (int32_t q = mQuerySets.Length() - 1; q >= 0; q--) { + nsTemplateQuerySet* qs = mQuerySets[q]; + delete qs; + } + + mQuerySets.Clear(); + + DestroyMatchMap(); + + // Setting mQueryProcessor to null will close connections. This would be + // handled by the cycle collector, but we want to close them earlier. + if (aIsFinal) + mQueryProcessor = nullptr; +} + +void +nsXULTemplateBuilder::Uninit(bool aIsFinal) +{ + if (mObservedDocument && aIsFinal) { + StopObserving(); + } + + if (mQueryProcessor) + mQueryProcessor->Done(); + + CleanUp(aIsFinal); + + mRootResult = nullptr; + mRefVariable = nullptr; + mMemberVariable = nullptr; + + mQueriesCompiled = false; +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULTemplateBuilder) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULTemplateBuilder) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDataSource) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDB) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCompDB) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootResult) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueryProcessor) + tmp->DestroyMatchMap(); + for (uint32_t i = 0; i < tmp->mQuerySets.Length(); ++i) { + nsTemplateQuerySet* qs = tmp->mQuerySets[i]; + delete qs; + } + tmp->mQuerySets.Clear(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULTemplateBuilder) + if (tmp->mObservedDocument && !cb.WantAllTraces()) { + // The global observer service holds us alive. + return NS_SUCCESS_INTERRUPTED_TRAVERSE; + } + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDataSource) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDB) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCompDB) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootResult) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueryProcessor) + + for (auto iter = tmp->mMatchMap.Iter(); !iter.Done(); iter.Next()) { + cb.NoteXPCOMChild(iter.Key()); + nsTemplateMatch* match = iter.UserData(); + while (match) { + cb.NoteXPCOMChild(match->GetContainer()); + cb.NoteXPCOMChild(match->mResult); + match = match->mNext; + } + } + + { + uint32_t i, count = tmp->mQuerySets.Length(); + for (i = 0; i < count; ++i) { + nsTemplateQuerySet *set = tmp->mQuerySets[i]; + cb.NoteXPCOMChild(set->mQueryNode); + cb.NoteXPCOMChild(set->mCompiledQuery); + uint16_t j, rulesCount = set->RuleCount(); + for (j = 0; j < rulesCount; ++j) { + set->GetRuleAt(j)->Traverse(cb); + } + } + } + tmp->Traverse(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULTemplateBuilder) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULTemplateBuilder) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULTemplateBuilder) + NS_INTERFACE_MAP_ENTRY(nsIXULTemplateBuilder) + NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULTemplateBuilder) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULTemplateBuilder) +NS_INTERFACE_MAP_END + +//---------------------------------------------------------------------- +// +// nsIXULTemplateBuilder methods +// + +NS_IMETHODIMP +nsXULTemplateBuilder::GetRoot(nsIDOMElement** aResult) +{ + if (mRoot) { + return CallQueryInterface(mRoot, aResult); + } + *aResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::GetDatasource(nsISupports** aResult) +{ + if (mCompDB) + NS_ADDREF(*aResult = mCompDB); + else + NS_IF_ADDREF(*aResult = mDataSource); + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::SetDatasource(nsISupports* aResult) +{ + mDataSource = aResult; + mCompDB = do_QueryInterface(mDataSource); + + return Rebuild(); +} + +NS_IMETHODIMP +nsXULTemplateBuilder::GetDatabase(nsIRDFCompositeDataSource** aResult) +{ + NS_IF_ADDREF(*aResult = mCompDB); + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::GetQueryProcessor(nsIXULTemplateQueryProcessor** aResult) +{ + NS_IF_ADDREF(*aResult = mQueryProcessor.get()); + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::AddRuleFilter(nsIDOMNode* aRule, nsIXULTemplateRuleFilter* aFilter) +{ + if (!aRule || !aFilter) + return NS_ERROR_NULL_POINTER; + + // a custom rule filter may be added, one for each rule. If a new one is + // added, it replaces the old one. Look for the right rule and set its + // filter + + int32_t count = mQuerySets.Length(); + for (int32_t q = 0; q < count; q++) { + nsTemplateQuerySet* queryset = mQuerySets[q]; + + int16_t rulecount = queryset->RuleCount(); + for (int16_t r = 0; r < rulecount; r++) { + nsTemplateRule* rule = queryset->GetRuleAt(r); + + nsCOMPtr<nsIDOMNode> rulenode; + rule->GetRuleNode(getter_AddRefs(rulenode)); + if (aRule == rulenode) { + rule->SetRuleFilter(aFilter); + return NS_OK; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::Rebuild() +{ + int32_t i; + + for (i = mListeners.Count() - 1; i >= 0; --i) { + mListeners[i]->WillRebuild(this); + } + + nsresult rv = RebuildAll(); + + for (i = mListeners.Count() - 1; i >= 0; --i) { + mListeners[i]->DidRebuild(this); + } + + return rv; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::Refresh() +{ + nsresult rv; + + if (!mCompDB) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsISimpleEnumerator> dslist; + rv = mCompDB->GetDataSources(getter_AddRefs(dslist)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + nsCOMPtr<nsISupports> next; + nsCOMPtr<nsIRDFRemoteDataSource> rds; + + while(NS_SUCCEEDED(dslist->HasMoreElements(&hasMore)) && hasMore) { + dslist->GetNext(getter_AddRefs(next)); + if (next && (rds = do_QueryInterface(next))) { + rds->Refresh(false); + } + } + + // XXXbsmedberg: it would be kinda nice to install an async nsIRDFXMLSink + // observer and call rebuild() once the load is complete. See bug 254600. + + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::Init(nsIContent* aElement) +{ + NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); + mRoot = aElement; + + nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc(); + NS_ASSERTION(doc, "element has no document"); + if (! doc) + return NS_ERROR_UNEXPECTED; + + bool shouldDelay; + nsresult rv = LoadDataSources(doc, &shouldDelay); + + if (NS_SUCCEEDED(rv)) { + StartObserving(doc); + } + + return rv; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::CreateContents(nsIContent* aElement, bool aForceCreation) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::HasGeneratedContent(nsIRDFResource* aResource, + nsIAtom* aTag, + bool* aGenerated) +{ + *aGenerated = false; + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::AddResult(nsIXULTemplateResult* aResult, + nsIDOMNode* aQueryNode) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aQueryNode); + + return UpdateResult(nullptr, aResult, aQueryNode); +} + +NS_IMETHODIMP +nsXULTemplateBuilder::RemoveResult(nsIXULTemplateResult* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + return UpdateResult(aResult, nullptr, nullptr); +} + +NS_IMETHODIMP +nsXULTemplateBuilder::ReplaceResult(nsIXULTemplateResult* aOldResult, + nsIXULTemplateResult* aNewResult, + nsIDOMNode* aQueryNode) +{ + NS_ENSURE_ARG_POINTER(aOldResult); + NS_ENSURE_ARG_POINTER(aNewResult); + NS_ENSURE_ARG_POINTER(aQueryNode); + + // just remove the old result and then add a new result separately + + nsresult rv = UpdateResult(aOldResult, nullptr, nullptr); + if (NS_FAILED(rv)) + return rv; + + return UpdateResult(nullptr, aNewResult, aQueryNode); +} + +nsresult +nsXULTemplateBuilder::UpdateResult(nsIXULTemplateResult* aOldResult, + nsIXULTemplateResult* aNewResult, + nsIDOMNode* aQueryNode) +{ + MOZ_LOG(gXULTemplateLog, LogLevel::Info, + ("nsXULTemplateBuilder::UpdateResult %p %p %p", + aOldResult, aNewResult, aQueryNode)); + + if (!mRoot || !mQueriesCompiled) + return NS_OK; + + // get the containers where content may be inserted. If + // GetInsertionLocations returns false, no container has generated + // any content yet so new content should not be generated either. This + // will be false if the result applies to content that is in a closed menu + // or treeitem for example. + + nsAutoPtr<nsCOMArray<nsIContent> > insertionPoints; + bool mayReplace = GetInsertionLocations(aOldResult ? aOldResult : aNewResult, + getter_Transfers(insertionPoints)); + if (! mayReplace) + return NS_OK; + + nsresult rv = NS_OK; + + nsCOMPtr<nsIRDFResource> oldId, newId; + nsTemplateQuerySet* queryset = nullptr; + + if (aOldResult) { + rv = GetResultResource(aOldResult, getter_AddRefs(oldId)); + if (NS_FAILED(rv)) + return rv; + + // Ignore re-entrant builds for content that is currently in our + // activation stack. + if (IsActivated(oldId)) + return NS_OK; + } + + if (aNewResult) { + rv = GetResultResource(aNewResult, getter_AddRefs(newId)); + if (NS_FAILED(rv)) + return rv; + + // skip results that don't have ids + if (! newId) + return NS_OK; + + // Ignore re-entrant builds for content that is currently in our + // activation stack. + if (IsActivated(newId)) + return NS_OK; + + // look for the queryset associated with the supplied query node + nsCOMPtr<nsIContent> querycontent = do_QueryInterface(aQueryNode); + + int32_t count = mQuerySets.Length(); + for (int32_t q = 0; q < count; q++) { + nsTemplateQuerySet* qs = mQuerySets[q]; + if (qs->mQueryNode == querycontent) { + queryset = qs; + break; + } + } + + if (! queryset) + return NS_OK; + } + + if (insertionPoints) { + // iterate over each insertion point and add or remove the result from + // that container + uint32_t count = insertionPoints->Count(); + for (uint32_t t = 0; t < count; t++) { + nsCOMPtr<nsIContent> insertionPoint = insertionPoints->SafeObjectAt(t); + if (insertionPoint) { + rv = UpdateResultInContainer(aOldResult, aNewResult, queryset, + oldId, newId, insertionPoint); + if (NS_FAILED(rv)) + return rv; + } + } + } + else { + // The tree builder doesn't use insertion points, so no insertion + // points will be set. In this case, just update the one result. + rv = UpdateResultInContainer(aOldResult, aNewResult, queryset, + oldId, newId, nullptr); + } + + return NS_OK; +} + +nsresult +nsXULTemplateBuilder::UpdateResultInContainer(nsIXULTemplateResult* aOldResult, + nsIXULTemplateResult* aNewResult, + nsTemplateQuerySet* aQuerySet, + nsIRDFResource* aOldId, + nsIRDFResource* aNewId, + nsIContent* aInsertionPoint) +{ + // This method takes a result that no longer applies (aOldResult) and + // replaces it with a new result (aNewResult). Either may be null + // indicating to just remove a result or add a new one without replacing. + // + // Matches are stored in the hashtable mMatchMap, keyed by result id. If + // there is more than one query, or the same id is found in different + // containers, the values in the hashtable will be a linked list of all + // the matches for that id. The matches are sorted according to the + // queries they are associated with. Matches for earlier queries in the + // template take priority over matches from later queries. The priority + // for a match is determined from the match's QuerySetPriority method. + // The first query has a priority 0, and higher numbers are for later + // queries with successively higher priorities. Thus, a match takes + // precedence if it has a lower priority than another. If there is only + // one query or container, then the match doesn't have any linked items. + // + // Matches are nsTemplateMatch objects. They are wrappers around + // nsIXULTemplateResult result objects and are created with + // nsTemplateMatch::Create below. The aQuerySet argument specifies which + // query the match is associated with. + // + // When a result id exists in multiple containers, the match's mContainer + // field is set to the container it corresponds to. The aInsertionPoint + // argument specifies which container is being updated. Even though they + // are stored in the same linked list as other matches of the same id, the + // matches for different containers are treated separately. They are only + // stored in the same hashtable to avoid a more complex data structure, as + // the use of the same id in multiple containers isn't a common occurance. + // + // Only one match with a given id per container is active at a time. When + // a match is active, content is generated for it. When a match is + // inactive, content is not generated for it. A match becomes active if + // another match with the same id and container with a lower priority + // isn't already active, and the match has a rule or conditions clause + // which evaluates to true. The former is checked by comparing the value + // of the QuerySetPriority method of the match with earlier matches. The + // latter is checked with the DetermineMatchedRule method. + // + // Naturally, if a match with a lower priority is active, it overrides + // the new match, so the new match is hooked up into the match linked + // list as inactive, and no content is generated for it. If a match with a + // higher priority is active, and the new match's conditions evaluate + // to true, then this existing match with the higher priority needs to have + // its generated content removed and replaced with the new match's + // generated content. + // + // Similar situations apply when removing an existing match. If the match + // is active, the existing generated content will need to be removed, and + // a match of higher priority that is revealed may become active and need + // to have content generated. + // + // Content removal and generation is done by the ReplaceMatch method which + // is overridden for the content builder and tree builder to update the + // generated output for each type. + // + // The code below handles all of the various cases and ensures that the + // match lists are maintained properly. + + nsresult rv = NS_OK; + int16_t ruleindex; + nsTemplateRule* matchedrule = nullptr; + + // Indicates that the old match was active and must have its content + // removed + bool oldMatchWasActive = false; + + // acceptedmatch will be set to a new match that has to have new content + // generated for it. If a new match doesn't need to have content + // generated, (because for example, a match with a lower priority + // already applies), then acceptedmatch will be null, but the match will + // be still hooked up into the chain, since it may become active later + // as other results are updated. + nsTemplateMatch* acceptedmatch = nullptr; + + // When aOldResult is specified, removematch will be set to the + // corresponding match. This match needs to be deleted as it no longer + // applies. However, removedmatch will be null when aOldResult is null, or + // when no match was found corresponding to aOldResult. + nsTemplateMatch* removedmatch = nullptr; + + // These will be set when aNewResult is specified indicating to add a + // result, but will end up replacing an existing match. The former + // indicates a match being replaced that was active and had content + // generated for it, while the latter indicates a match that wasn't active + // and just needs to be deleted. Both may point to different matches. For + // example, if the new match becomes active, replacing an inactive match, + // the inactive match will need to be deleted. However, if another match + // with a higher priority is active, the new match will override it, so + // content will need to be generated for the new match and removed for + // this existing active match. + nsTemplateMatch* replacedmatch = nullptr; + nsTemplateMatch* replacedmatchtodelete = nullptr; + + if (aOldResult) { + nsTemplateMatch* firstmatch; + if (mMatchMap.Get(aOldId, &firstmatch)) { + nsTemplateMatch* oldmatch = firstmatch; + nsTemplateMatch* prevmatch = nullptr; + + // look for the right match if there was more than one + while (oldmatch && (oldmatch->mResult != aOldResult)) { + prevmatch = oldmatch; + oldmatch = oldmatch->mNext; + } + + if (oldmatch) { + nsTemplateMatch* findmatch = oldmatch->mNext; + + // Keep a reference so that linked list can be hooked up at + // the end in case an error occurs. + nsTemplateMatch* nextmatch = findmatch; + + if (oldmatch->IsActive()) { + // Indicate that the old match was active so its content + // will be removed later. + oldMatchWasActive = true; + + // The match being removed is the active match, so scan + // through the later matches to determine if one should + // now become the active match. + while (findmatch) { + // only other matches with the same container should + // now match, leave other containers alone + if (findmatch->GetContainer() == aInsertionPoint) { + nsTemplateQuerySet* qs = + mQuerySets[findmatch->QuerySetPriority()]; + + DetermineMatchedRule(aInsertionPoint, findmatch->mResult, + qs, &matchedrule, &ruleindex); + + if (matchedrule) { + rv = findmatch->RuleMatched(qs, + matchedrule, ruleindex, + findmatch->mResult); + if (NS_FAILED(rv)) + return rv; + + acceptedmatch = findmatch; + break; + } + } + + findmatch = findmatch->mNext; + } + } + + if (oldmatch == firstmatch) { + // the match to remove is at the beginning + if (oldmatch->mNext) { + mMatchMap.Put(aOldId, oldmatch->mNext); + } + else { + mMatchMap.Remove(aOldId); + } + } + + if (prevmatch) + prevmatch->mNext = nextmatch; + + removedmatch = oldmatch; + if (mFlags & eLoggingEnabled) + OutputMatchToLog(aOldId, removedmatch, false); + } + } + } + + nsTemplateMatch *newmatch = nullptr; + if (aNewResult) { + // only allow a result to be inserted into containers with a matching tag + nsIAtom* tag = aQuerySet->GetTag(); + if (aInsertionPoint && tag && + tag != aInsertionPoint->NodeInfo()->NameAtom()) + return NS_OK; + + int32_t findpriority = aQuerySet->Priority(); + + newmatch = nsTemplateMatch::Create(findpriority, + aNewResult, aInsertionPoint); + if (!newmatch) + return NS_ERROR_OUT_OF_MEMORY; + + nsTemplateMatch* firstmatch; + if (mMatchMap.Get(aNewId, &firstmatch)) { + bool hasEarlierActiveMatch = false; + + // Scan through the existing matches to find where the new one + // should be inserted. oldmatch will be set to the old match for + // the same query and prevmatch will be set to the match before it. + nsTemplateMatch* prevmatch = nullptr; + nsTemplateMatch* oldmatch = firstmatch; + while (oldmatch) { + // Break out once we've reached a query in the list with a + // lower priority. The new match will be inserted at this + // location so that the match list is sorted by priority. + int32_t priority = oldmatch->QuerySetPriority(); + if (priority > findpriority) { + oldmatch = nullptr; + break; + } + + // look for matches that belong in the same container + if (oldmatch->GetContainer() == aInsertionPoint) { + if (priority == findpriority) + break; + + // If a match with a lower priority is active, the new + // match can't replace it. + if (oldmatch->IsActive()) + hasEarlierActiveMatch = true; + } + + prevmatch = oldmatch; + oldmatch = oldmatch->mNext; + } + + // At this point, oldmatch will either be null, or set to a match + // with the same container and priority. If set, oldmatch will + // need to be replaced by newmatch. + + if (oldmatch) + newmatch->mNext = oldmatch->mNext; + else if (prevmatch) + newmatch->mNext = prevmatch->mNext; + else + newmatch->mNext = firstmatch; + + // hasEarlierActiveMatch will be set to true if a match with a + // lower priority was found. The new match won't replace it in + // this case. If hasEarlierActiveMatch is false, then the new match + // may be become active if it matches one of the rules, and will + // generate output. It's also possible however, that a match with + // the same priority already exists, which means that the new match + // will replace the old one. In this case, oldmatch will be set to + // the old match. The content for the old match must be removed and + // content for the new match generated in its place. + if (! hasEarlierActiveMatch) { + // If the old match was the active match, set replacedmatch to + // indicate that it needs its content removed. + if (oldmatch) { + if (oldmatch->IsActive()) + replacedmatch = oldmatch; + replacedmatchtodelete = oldmatch; + } + + // check if the new result matches the rules + rv = DetermineMatchedRule(aInsertionPoint, newmatch->mResult, + aQuerySet, &matchedrule, &ruleindex); + if (NS_FAILED(rv)) { + nsTemplateMatch::Destroy(newmatch, false); + return rv; + } + + if (matchedrule) { + rv = newmatch->RuleMatched(aQuerySet, + matchedrule, ruleindex, + newmatch->mResult); + if (NS_FAILED(rv)) { + nsTemplateMatch::Destroy(newmatch, false); + return rv; + } + + // acceptedmatch may have been set in the block handling + // aOldResult earlier. If so, we would only get here when + // that match has a higher priority than this new match. + // As only one match can have content generated for it, it + // is OK to set acceptedmatch here to the new match, + // ignoring the other one. + acceptedmatch = newmatch; + + // Clear the matched state of the later results for the + // same container. + nsTemplateMatch* clearmatch = newmatch->mNext; + while (clearmatch) { + if (clearmatch->GetContainer() == aInsertionPoint && + clearmatch->IsActive()) { + clearmatch->SetInactive(); + // Replacedmatch should be null here. If not, it + // means that two matches were active which isn't + // a valid state + NS_ASSERTION(!replacedmatch, + "replaced match already set"); + replacedmatch = clearmatch; + break; + } + clearmatch = clearmatch->mNext; + } + } + else if (oldmatch && oldmatch->IsActive()) { + // The result didn't match the rules, so look for a later + // one. However, only do this if the old match was the + // active match. + newmatch = newmatch->mNext; + while (newmatch) { + if (newmatch->GetContainer() == aInsertionPoint) { + rv = DetermineMatchedRule(aInsertionPoint, newmatch->mResult, + aQuerySet, &matchedrule, &ruleindex); + if (NS_FAILED(rv)) { + nsTemplateMatch::Destroy(newmatch, false); + return rv; + } + + if (matchedrule) { + rv = newmatch->RuleMatched(aQuerySet, + matchedrule, ruleindex, + newmatch->mResult); + if (NS_FAILED(rv)) { + nsTemplateMatch::Destroy(newmatch, false); + return rv; + } + + acceptedmatch = newmatch; + break; + } + } + + newmatch = newmatch->mNext; + } + } + + // put the match in the map if there isn't a previous match + if (! prevmatch) { + mMatchMap.Put(aNewId, newmatch); + } + } + + // hook up the match last in case an error occurs + if (prevmatch) + prevmatch->mNext = newmatch; + } + else { + // The id is not used in the hashtable yet so create a new match + // and add it to the hashtable. + rv = DetermineMatchedRule(aInsertionPoint, aNewResult, + aQuerySet, &matchedrule, &ruleindex); + if (NS_FAILED(rv)) { + nsTemplateMatch::Destroy(newmatch, false); + return rv; + } + + if (matchedrule) { + rv = newmatch->RuleMatched(aQuerySet, matchedrule, + ruleindex, aNewResult); + if (NS_FAILED(rv)) { + nsTemplateMatch::Destroy(newmatch, false); + return rv; + } + + acceptedmatch = newmatch; + } + + mMatchMap.Put(aNewId, newmatch); + } + } + + // The ReplaceMatch method is builder specific and removes the generated + // content for a match. + + // Remove the content for a match that was active and needs to be replaced. + if (replacedmatch) { + rv = ReplaceMatch(replacedmatch->mResult, nullptr, nullptr, + aInsertionPoint); + + if (mFlags & eLoggingEnabled) + OutputMatchToLog(aNewId, replacedmatch, false); + } + + // remove a match that needs to be deleted. + if (replacedmatchtodelete) + nsTemplateMatch::Destroy(replacedmatchtodelete, true); + + // If the old match was active, the content for it needs to be removed. + // If the old match was not active, it shouldn't have had any content, + // so just pass null to ReplaceMatch. If acceptedmatch was set, then + // content needs to be generated for a new match. + if (oldMatchWasActive || acceptedmatch) + rv = ReplaceMatch(oldMatchWasActive ? aOldResult : nullptr, + acceptedmatch, matchedrule, aInsertionPoint); + + // delete the old match that was replaced + if (removedmatch) + nsTemplateMatch::Destroy(removedmatch, true); + + if (mFlags & eLoggingEnabled && newmatch) + OutputMatchToLog(aNewId, newmatch, true); + + return rv; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::ResultBindingChanged(nsIXULTemplateResult* aResult) +{ + // A binding update is used when only the values of the bindings have + // changed, so the same rule still applies. Just synchronize the content. + // The new result will have the new values. + NS_ENSURE_ARG_POINTER(aResult); + + if (!mRoot || !mQueriesCompiled) + return NS_OK; + + return SynchronizeResult(aResult); +} + +NS_IMETHODIMP +nsXULTemplateBuilder::GetRootResult(nsIXULTemplateResult** aResult) +{ + *aResult = mRootResult; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::GetResultForId(const nsAString& aId, + nsIXULTemplateResult** aResult) +{ + if (aId.IsEmpty()) + return NS_ERROR_INVALID_ARG; + + nsCOMPtr<nsIRDFResource> resource; + gRDFService->GetUnicodeResource(aId, getter_AddRefs(resource)); + + *aResult = nullptr; + + nsTemplateMatch* match; + if (mMatchMap.Get(resource, &match)) { + // find the active match + while (match) { + if (match->IsActive()) { + *aResult = match->mResult; + NS_IF_ADDREF(*aResult); + break; + } + match = match->mNext; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::GetResultForContent(nsIDOMElement* aContent, + nsIXULTemplateResult** aResult) +{ + *aResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::AddListener(nsIXULBuilderListener* aListener) +{ + NS_ENSURE_ARG(aListener); + + if (!mListeners.AppendObject(aListener)) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::RemoveListener(nsIXULBuilderListener* aListener) +{ + NS_ENSURE_ARG(aListener); + + mListeners.RemoveObject(aListener); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULTemplateBuilder::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + // Uuuuber hack to clean up circular references that the cycle collector + // doesn't know about. See bug 394514. + if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) { + if (nsCOMPtr<mozIDOMWindow> window = do_QueryInterface(aSubject)) { + nsCOMPtr<nsIDocument> doc = + nsPIDOMWindowInner::From(window)->GetExtantDoc(); + if (doc && doc == mObservedDocument) + NodeWillBeDestroyed(doc); + } + } + return NS_OK; +} +//---------------------------------------------------------------------- +// +// nsIDocumentOberver interface +// + +void +nsXULTemplateBuilder::AttributeChanged(nsIDocument* aDocument, + Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) +{ + if (aElement == mRoot && aNameSpaceID == kNameSpaceID_None) { + // Check for a change to the 'ref' attribute on an atom, in which + // case we may need to nuke and rebuild the entire content model + // beneath the element. + if (aAttribute == nsGkAtoms::ref) + nsContentUtils::AddScriptRunner( + NewRunnableMethod(this, &nsXULTemplateBuilder::RunnableRebuild)); + + // Check for a change to the 'datasources' attribute. If so, setup + // mDB by parsing the new value and rebuild. + else if (aAttribute == nsGkAtoms::datasources) { + nsContentUtils::AddScriptRunner( + NewRunnableMethod(this, &nsXULTemplateBuilder::RunnableLoadAndRebuild)); + } + } +} + +void +nsXULTemplateBuilder::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + if (mRoot && nsContentUtils::ContentIsDescendantOf(mRoot, aChild)) { + RefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this); + + if (mQueryProcessor) + mQueryProcessor->Done(); + + // Pass false to Uninit since content is going away anyway + nsContentUtils::AddScriptRunner( + NewRunnableMethod(this, &nsXULTemplateBuilder::UninitFalse)); + + MOZ_ASSERT(aDocument == mObservedDocument); + StopObserving(); + + nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument); + if (xuldoc) + xuldoc->SetTemplateBuilderFor(mRoot, nullptr); + + // clear the template state when removing content so that template + // content will be regenerated again if the content is reinserted + nsXULElement *xulcontent = nsXULElement::FromContent(mRoot); + if (xulcontent) + xulcontent->ClearTemplateGenerated(); + + CleanUp(true); + + mDB = nullptr; + mCompDB = nullptr; + mDataSource = nullptr; + } +} + +void +nsXULTemplateBuilder::NodeWillBeDestroyed(const nsINode* aNode) +{ + // The call to RemoveObserver could release the last reference to + // |this|, so hold another reference. + RefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this); + + // Break circular references + if (mQueryProcessor) + mQueryProcessor->Done(); + + mDataSource = nullptr; + mDB = nullptr; + mCompDB = nullptr; + + nsContentUtils::AddScriptRunner( + NewRunnableMethod(this, &nsXULTemplateBuilder::UninitTrue)); +} + + + + +//---------------------------------------------------------------------- +// +// Implementation methods +// + +nsresult +nsXULTemplateBuilder::LoadDataSources(nsIDocument* aDocument, + bool* aShouldDelayBuilding) +{ + NS_PRECONDITION(mRoot != nullptr, "not initialized"); + + nsresult rv; + bool isRDFQuery = false; + + // we'll set these again later, after we create a new composite ds + mDB = nullptr; + mCompDB = nullptr; + mDataSource = nullptr; + + *aShouldDelayBuilding = false; + + nsAutoString datasources; + mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::datasources, datasources); + + nsAutoString querytype; + mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::querytype, querytype); + + // create the query processor. The querytype attribute on the root element + // may be used to create one of a specific type. + + // XXX should non-chrome be restricted to specific names? + if (querytype.IsEmpty()) + querytype.AssignLiteral("rdf"); + + if (querytype.EqualsLiteral("rdf")) { + isRDFQuery = true; + mQueryProcessor = new nsXULTemplateQueryProcessorRDF(); + } + else if (querytype.EqualsLiteral("xml")) { + mQueryProcessor = new nsXULTemplateQueryProcessorXML(); + } + else if (querytype.EqualsLiteral("storage")) { + mQueryProcessor = new nsXULTemplateQueryProcessorStorage(); + } + else { + nsAutoCString cid(NS_QUERY_PROCESSOR_CONTRACTID_PREFIX); + AppendUTF16toUTF8(querytype, cid); + mQueryProcessor = do_CreateInstance(cid.get(), &rv); + + if (!mQueryProcessor) { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_INVALID_QUERYPROCESSOR); + return rv; + } + } + + rv = LoadDataSourceUrls(aDocument, datasources, + isRDFQuery, aShouldDelayBuilding); + NS_ENSURE_SUCCESS(rv, rv); + + // Now set the database on the element, so that script writers can + // access it. + nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument); + if (xuldoc) + xuldoc->SetTemplateBuilderFor(mRoot, this); + + if (!mRoot->IsXULElement()) { + // Hmm. This must be an HTML element. Try to set it as a + // JS property "by hand". + InitHTMLTemplateRoot(); + } + + return NS_OK; +} + +nsresult +nsXULTemplateBuilder::LoadDataSourceUrls(nsIDocument* aDocument, + const nsAString& aDataSources, + bool aIsRDFQuery, + bool* aShouldDelayBuilding) +{ + // Grab the doc's principal... + nsIPrincipal *docPrincipal = aDocument->NodePrincipal(); + + NS_ASSERTION(docPrincipal == mRoot->NodePrincipal(), + "Principal mismatch? Which one to use?"); + + bool isTrusted = false; + nsresult rv = IsSystemPrincipal(docPrincipal, &isTrusted); + NS_ENSURE_SUCCESS(rv, rv); + + // Parse datasources: they are assumed to be a whitespace + // separated list of URIs; e.g., + // + // rdf:bookmarks rdf:history http://foo.bar.com/blah.cgi?baz=9 + // + nsIURI *docurl = aDocument->GetDocumentURI(); + + nsCOMPtr<nsIMutableArray> uriList = do_CreateInstance(NS_ARRAY_CONTRACTID); + if (!uriList) + return NS_ERROR_FAILURE; + + nsAutoString datasources(aDataSources); + uint32_t first = 0; + while (1) { + while (first < datasources.Length() && nsCRT::IsAsciiSpace(datasources.CharAt(first))) + ++first; + + if (first >= datasources.Length()) + break; + + uint32_t last = first; + while (last < datasources.Length() && !nsCRT::IsAsciiSpace(datasources.CharAt(last))) + ++last; + + nsAutoString uriStr; + datasources.Mid(uriStr, first, last - first); + first = last + 1; + + // A special 'dummy' datasource + if (uriStr.EqualsLiteral("rdf:null")) + continue; + + if (uriStr.CharAt(0) == '#') { + // ok, the datasource is certainly a node of the current document + nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(aDocument); + nsCOMPtr<nsIDOMElement> dsnode; + + domdoc->GetElementById(Substring(uriStr, 1), + getter_AddRefs(dsnode)); + + if (dsnode) + uriList->AppendElement(dsnode, false); + continue; + } + + // N.B. that `failure' (e.g., because it's an unknown + // protocol) leaves uriStr unaltered. + NS_MakeAbsoluteURI(uriStr, uriStr, docurl); + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), uriStr); + if (NS_FAILED(rv) || !uri) + continue; // Necko will barf if our URI is weird + + // don't add the uri to the list if the document is not allowed to + // load it + if (!isTrusted && NS_FAILED(docPrincipal->CheckMayLoad(uri, true, false))) + continue; + + uriList->AppendElement(uri, false); + } + + nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(mRoot); + rv = mQueryProcessor->GetDatasource(uriList, + rootNode, + isTrusted, + this, + aShouldDelayBuilding, + getter_AddRefs(mDataSource)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aIsRDFQuery && mDataSource) { + // check if we were given an inference engine type + nsCOMPtr<nsIRDFInferDataSource> inferDB = do_QueryInterface(mDataSource); + if (inferDB) { + nsCOMPtr<nsIRDFDataSource> ds; + inferDB->GetBaseDataSource(getter_AddRefs(ds)); + if (ds) + mCompDB = do_QueryInterface(ds); + } + + if (!mCompDB) + mCompDB = do_QueryInterface(mDataSource); + + mDB = do_QueryInterface(mDataSource); + } + + if (!mDB && isTrusted) { + gRDFService->GetDataSource("rdf:local-store", getter_AddRefs(mDB)); + } + + return NS_OK; +} + +nsresult +nsXULTemplateBuilder::InitHTMLTemplateRoot() +{ + // Use XPConnect and the JS APIs to whack mDB and this as the + // 'database' and 'builder' properties onto aElement. + nsresult rv; + + nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc(); + NS_ASSERTION(doc, "no document"); + if (! doc) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIScriptGlobalObject> global = + do_QueryInterface(doc->GetWindow()); + if (! global) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIGlobalObject> innerWin = + do_QueryInterface(doc->GetInnerWindow()); + + // We are going to run script via JS_SetProperty, so we need a script entry + // point, but as this is XUL related it does not appear in the HTML spec. + AutoEntryScript aes(innerWin, "nsXULTemplateBuilder creation", true); + JSContext* jscontext = aes.cx(); + + JS::Rooted<JS::Value> v(jscontext); + rv = nsContentUtils::WrapNative(jscontext, mRoot, mRoot, &v); + NS_ENSURE_SUCCESS(rv, rv); + + JS::Rooted<JSObject*> jselement(jscontext, v.toObjectOrNull()); + + if (mDB) { + // database + JS::Rooted<JS::Value> jsdatabase(jscontext); + rv = nsContentUtils::WrapNative(jscontext, mDB, + &NS_GET_IID(nsIRDFCompositeDataSource), + &jsdatabase); + NS_ENSURE_SUCCESS(rv, rv); + + bool ok = JS_SetProperty(jscontext, jselement, "database", jsdatabase); + NS_ASSERTION(ok, "unable to set database property"); + if (! ok) + return NS_ERROR_FAILURE; + } + + { + // builder + JS::Rooted<JS::Value> jsbuilder(jscontext); + rv = nsContentUtils::WrapNative(jscontext, + static_cast<nsIXULTemplateBuilder*>(this), + &NS_GET_IID(nsIXULTemplateBuilder), + &jsbuilder); + NS_ENSURE_SUCCESS(rv, rv); + + bool ok = JS_SetProperty(jscontext, jselement, "builder", jsbuilder); + if (! ok) + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +nsXULTemplateBuilder::DetermineMatchedRule(nsIContent *aContainer, + nsIXULTemplateResult* aResult, + nsTemplateQuerySet* aQuerySet, + nsTemplateRule** aMatchedRule, + int16_t *aRuleIndex) +{ + // iterate through the rules and look for one that the result matches + int16_t count = aQuerySet->RuleCount(); + for (int16_t r = 0; r < count; r++) { + nsTemplateRule* rule = aQuerySet->GetRuleAt(r); + // If a tag was specified, it must match the tag of the container + // where content is being inserted. + nsIAtom* tag = rule->GetTag(); + if ((!aContainer || !tag || + tag == aContainer->NodeInfo()->NameAtom()) && + rule->CheckMatch(aResult)) { + *aMatchedRule = rule; + *aRuleIndex = r; + return NS_OK; + } + } + + *aRuleIndex = -1; + *aMatchedRule = nullptr; + return NS_OK; +} + +void +nsXULTemplateBuilder::ParseAttribute(const nsAString& aAttributeValue, + void (*aVariableCallback)(nsXULTemplateBuilder*, const nsAString&, void*), + void (*aTextCallback)(nsXULTemplateBuilder*, const nsAString&, void*), + void* aClosure) +{ + nsAString::const_iterator done_parsing; + aAttributeValue.EndReading(done_parsing); + + nsAString::const_iterator iter; + aAttributeValue.BeginReading(iter); + + nsAString::const_iterator mark(iter), backup(iter); + + for (; iter != done_parsing; backup = ++iter) { + // A variable is either prefixed with '?' (in the extended + // syntax) or "rdf:" (in the simple syntax). + bool isvar; + if (*iter == char16_t('?') && (++iter != done_parsing)) { + isvar = true; + } + else if ((*iter == char16_t('r') && (++iter != done_parsing)) && + (*iter == char16_t('d') && (++iter != done_parsing)) && + (*iter == char16_t('f') && (++iter != done_parsing)) && + (*iter == char16_t(':') && (++iter != done_parsing))) { + isvar = true; + } + else { + isvar = false; + } + + if (! isvar) { + // It's not a variable, or we ran off the end of the + // string after the initial variable prefix. Since we may + // have slurped down some characters before realizing that + // fact, back up to the point where we started. + iter = backup; + continue; + } + else if (backup != mark && aTextCallback) { + // Okay, we've found a variable, and there's some vanilla + // text that's been buffered up. Flush it. + (*aTextCallback)(this, Substring(mark, backup), aClosure); + } + + if (*iter == char16_t('?')) { + // Well, it was not really a variable, but "??". We use one + // question mark (the second one, actually) literally. + mark = iter; + continue; + } + + // Construct a substring that is the symbol we need to look up + // in the rule's symbol table. The symbol is terminated by a + // space character, a caret, or the end of the string, + // whichever comes first. + nsAString::const_iterator first(backup); + + char16_t c = 0; + while (iter != done_parsing) { + c = *iter; + if ((c == char16_t(' ')) || (c == char16_t('^'))) + break; + + ++iter; + } + + nsAString::const_iterator last(iter); + + // Back up so we don't consume the terminating character + // *unless* the terminating character was a caret: the caret + // means "concatenate with no space in between". + if (c != char16_t('^')) + --iter; + + (*aVariableCallback)(this, Substring(first, last), aClosure); + mark = iter; + ++mark; + } + + if (backup != mark && aTextCallback) { + // If there's any text left over, then fire the text callback + (*aTextCallback)(this, Substring(mark, backup), aClosure); + } +} + + +struct MOZ_STACK_CLASS SubstituteTextClosure { + SubstituteTextClosure(nsIXULTemplateResult* aResult, nsAString& aString) + : result(aResult), str(aString) {} + + // some datasources are lazily initialized or modified while values are + // being retrieved, causing results to be removed. Due to this, hold a + // strong reference to the result. + nsCOMPtr<nsIXULTemplateResult> result; + nsAString& str; +}; + +nsresult +nsXULTemplateBuilder::SubstituteText(nsIXULTemplateResult* aResult, + const nsAString& aAttributeValue, + nsAString& aString) +{ + // See if it's the special value "..." + if (aAttributeValue.EqualsLiteral("...")) { + aResult->GetId(aString); + return NS_OK; + } + + // Reasonable guess at how big it should be + aString.SetCapacity(aAttributeValue.Length()); + + SubstituteTextClosure closure(aResult, aString); + ParseAttribute(aAttributeValue, + SubstituteTextReplaceVariable, + SubstituteTextAppendText, + &closure); + + return NS_OK; +} + + +void +nsXULTemplateBuilder::SubstituteTextAppendText(nsXULTemplateBuilder* aThis, + const nsAString& aText, + void* aClosure) +{ + // Append aString to the closure's result + SubstituteTextClosure* c = static_cast<SubstituteTextClosure*>(aClosure); + c->str.Append(aText); +} + +void +nsXULTemplateBuilder::SubstituteTextReplaceVariable(nsXULTemplateBuilder* aThis, + const nsAString& aVariable, + void* aClosure) +{ + // Substitute the value for the variable and append to the + // closure's result. + SubstituteTextClosure* c = static_cast<SubstituteTextClosure*>(aClosure); + + nsAutoString replacementText; + + // The symbol "rdf:*" is special, and means "this guy's URI" + if (aVariable.EqualsLiteral("rdf:*")){ + c->result->GetId(replacementText); + } + else { + // Got a variable; get the value it's assigned to + nsCOMPtr<nsIAtom> var = NS_Atomize(aVariable); + c->result->GetBindingFor(var, replacementText); + } + + c->str += replacementText; +} + +bool +nsXULTemplateBuilder::IsTemplateElement(nsIContent* aContent) +{ + return aContent->NodeInfo()->Equals(nsGkAtoms::_template, + kNameSpaceID_XUL); +} + +nsresult +nsXULTemplateBuilder::GetTemplateRoot(nsIContent** aResult) +{ + NS_PRECONDITION(mRoot != nullptr, "not initialized"); + if (! mRoot) + return NS_ERROR_NOT_INITIALIZED; + + // First, check and see if the root has a template attribute. This + // allows a template to be specified "out of line"; e.g., + // + // <window> + // <foo template="MyTemplate">...</foo> + // <template id="MyTemplate">...</template> + // </window> + // + nsAutoString templateID; + mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::_template, templateID); + + if (! templateID.IsEmpty()) { + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mRoot->GetComposedDoc()); + if (! domDoc) + return NS_OK; + + nsCOMPtr<nsIDOMElement> domElement; + domDoc->GetElementById(templateID, getter_AddRefs(domElement)); + + if (domElement) { + nsCOMPtr<nsIContent> content = do_QueryInterface(domElement); + NS_ENSURE_STATE(content && + !nsContentUtils::ContentIsDescendantOf(mRoot, + content)); + content.forget(aResult); + return NS_OK; + } + } + + // If root node has no template attribute, then look for a child + // node which is a template tag. + for (nsIContent* child = mRoot->GetFirstChild(); + child; + child = child->GetNextSibling()) { + + if (IsTemplateElement(child)) { + NS_ADDREF(*aResult = child); + return NS_OK; + } + } + + // Look through the anonymous children as well. Although FlattenedChildIterator + // will find a template element that has been placed in an insertion point, many + // bindings do not have a specific insertion point for the template element, which + // would cause it to not be part of the flattened content tree. The check above to + // check the explicit children as well handles this case. + FlattenedChildIterator iter(mRoot); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + if (IsTemplateElement(child)) { + NS_ADDREF(*aResult = child); + return NS_OK; + } + } + + *aResult = nullptr; + return NS_OK; +} + +nsresult +nsXULTemplateBuilder::CompileQueries() +{ + nsCOMPtr<nsIContent> tmpl; + GetTemplateRoot(getter_AddRefs(tmpl)); + if (! tmpl) + return NS_OK; + + if (! mRoot) + return NS_ERROR_NOT_INITIALIZED; + + // Determine if there are any special settings we need to observe + mFlags = 0; + + nsAutoString flags; + mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::flags, flags); + + // if the dont-test-empty flag is set, containers should not be checked to + // see if they are empty. If dont-recurse is set, then don't process the + // template recursively and only show one level of results. The logging + // flag logs errors and results to the console, which is useful when + // debugging templates. + nsWhitespaceTokenizer tokenizer(flags); + while (tokenizer.hasMoreTokens()) { + const nsDependentSubstring& token(tokenizer.nextToken()); + if (token.EqualsLiteral("dont-test-empty")) + mFlags |= eDontTestEmpty; + else if (token.EqualsLiteral("dont-recurse")) + mFlags |= eDontRecurse; + else if (token.EqualsLiteral("logging")) + mFlags |= eLoggingEnabled; + } + + // always enable logging if the debug setting is used + if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) + mFlags |= eLoggingEnabled; + + nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot); + nsresult rv = + mQueryProcessor->InitializeForBuilding(mDataSource, this, rootnode); + if (NS_FAILED(rv)) + return rv; + + // Set the "container" and "member" variables, if the user has specified + // them. The container variable may be specified with the container + // attribute on the <template> and the member variable may be specified + // using the member attribute or the value of the uri attribute inside the + // first action body in the template. If not specified, the container + // variable defaults to '?uri' and the member variable defaults to '?' or + // 'rdf:*' for simple queries. + + // For RDF queries, the container variable may also be set via the + // <content> tag. + + nsAutoString containervar; + tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::container, containervar); + + if (containervar.IsEmpty()) + mRefVariable = NS_Atomize("?uri"); + else + mRefVariable = NS_Atomize(containervar); + + nsAutoString membervar; + tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::member, membervar); + + if (membervar.IsEmpty()) + mMemberVariable = nullptr; + else + mMemberVariable = NS_Atomize(membervar); + + nsTemplateQuerySet* queryset = new nsTemplateQuerySet(0); + if (!mQuerySets.AppendElement(queryset)) { + delete queryset; + return NS_ERROR_OUT_OF_MEMORY; + } + + bool canUseTemplate = false; + int32_t priority = 0; + rv = CompileTemplate(tmpl, queryset, false, &priority, &canUseTemplate); + + if (NS_FAILED(rv) || !canUseTemplate) { + for (int32_t q = mQuerySets.Length() - 1; q >= 0; q--) { + nsTemplateQuerySet* qs = mQuerySets[q]; + delete qs; + } + mQuerySets.Clear(); + } + + mQueriesCompiled = true; + + return NS_OK; +} + +nsresult +nsXULTemplateBuilder::CompileTemplate(nsIContent* aTemplate, + nsTemplateQuerySet* aQuerySet, + bool aIsQuerySet, + int32_t* aPriority, + bool* aCanUseTemplate) +{ + NS_ASSERTION(aQuerySet, "No queryset supplied"); + + nsresult rv = NS_OK; + + bool isQuerySetMode = false; + bool hasQuerySet = false, hasRule = false, hasQuery = false; + + for (nsIContent* rulenode = aTemplate->GetFirstChild(); + rulenode; + rulenode = rulenode->GetNextSibling()) { + + mozilla::dom::NodeInfo *ni = rulenode->NodeInfo(); + + // don't allow more queries than can be supported + if (*aPriority == INT16_MAX) + return NS_ERROR_FAILURE; + + // XXXndeakin queryset isn't a good name for this tag since it only + // ever contains one query + if (!aIsQuerySet && ni->Equals(nsGkAtoms::queryset, kNameSpaceID_XUL)) { + if (hasRule || hasQuery) { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_INVALID_QUERYSET); + continue; + } + + isQuerySetMode = true; + + // only create a queryset for those after the first since the + // first one is always created by CompileQueries + if (hasQuerySet) { + aQuerySet = new nsTemplateQuerySet(++*aPriority); + + // once the queryset is appended to the mQuerySets list, it + // will be removed by CompileQueries if an error occurs + if (!mQuerySets.AppendElement(aQuerySet)) { + delete aQuerySet; + return NS_ERROR_OUT_OF_MEMORY; + } + } + + hasQuerySet = true; + + rv = CompileTemplate(rulenode, aQuerySet, true, aPriority, aCanUseTemplate); + if (NS_FAILED(rv)) + return rv; + } + + // once a queryset is used, everything must be a queryset + if (isQuerySetMode) + continue; + + if (ni->Equals(nsGkAtoms::rule, kNameSpaceID_XUL)) { + nsCOMPtr<nsIContent> action; + nsXULContentUtils::FindChildByTag(rulenode, + kNameSpaceID_XUL, + nsGkAtoms::action, + getter_AddRefs(action)); + + if (action){ + nsCOMPtr<nsIAtom> memberVariable = mMemberVariable; + if (!memberVariable) { + memberVariable = DetermineMemberVariable(action); + if (!memberVariable) { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_NO_MEMBERVAR); + continue; + } + } + + if (hasQuery) { + nsCOMPtr<nsIAtom> tag; + DetermineRDFQueryRef(aQuerySet->mQueryNode, + getter_AddRefs(tag)); + if (tag) + aQuerySet->SetTag(tag); + + if (! aQuerySet->mCompiledQuery) { + nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode)); + + rv = mQueryProcessor->CompileQuery(this, query, + mRefVariable, memberVariable, + getter_AddRefs(aQuerySet->mCompiledQuery)); + if (NS_FAILED(rv)) + return rv; + } + + if (aQuerySet->mCompiledQuery) { + rv = CompileExtendedQuery(rulenode, action, memberVariable, + aQuerySet); + if (NS_FAILED(rv)) + return rv; + + *aCanUseTemplate = true; + } + } + else { + // backwards-compatible RDF template syntax where there is + // an <action> node but no <query> node. In this case, + // use the conditions as if it was the query. + + nsCOMPtr<nsIContent> conditions; + nsXULContentUtils::FindChildByTag(rulenode, + kNameSpaceID_XUL, + nsGkAtoms::conditions, + getter_AddRefs(conditions)); + + if (conditions) { + // create a new queryset if one hasn't been created already + if (hasQuerySet) { + aQuerySet = new nsTemplateQuerySet(++*aPriority); + if (!mQuerySets.AppendElement(aQuerySet)) { + delete aQuerySet; + return NS_ERROR_OUT_OF_MEMORY; + } + } + + nsCOMPtr<nsIAtom> tag; + DetermineRDFQueryRef(conditions, getter_AddRefs(tag)); + if (tag) + aQuerySet->SetTag(tag); + + hasQuerySet = true; + + nsCOMPtr<nsIDOMNode> conditionsnode(do_QueryInterface(conditions)); + + aQuerySet->mQueryNode = conditions; + rv = mQueryProcessor->CompileQuery(this, conditionsnode, + mRefVariable, + memberVariable, + getter_AddRefs(aQuerySet->mCompiledQuery)); + if (NS_FAILED(rv)) + return rv; + + if (aQuerySet->mCompiledQuery) { + rv = CompileExtendedQuery(rulenode, action, memberVariable, + aQuerySet); + if (NS_FAILED(rv)) + return rv; + + *aCanUseTemplate = true; + } + } + } + } + else { + if (hasQuery) + continue; + + // a new queryset must always be created in this case + if (hasQuerySet) { + aQuerySet = new nsTemplateQuerySet(++*aPriority); + if (!mQuerySets.AppendElement(aQuerySet)) { + delete aQuerySet; + return NS_ERROR_OUT_OF_MEMORY; + } + } + + hasQuerySet = true; + + rv = CompileSimpleQuery(rulenode, aQuerySet, aCanUseTemplate); + if (NS_FAILED(rv)) + return rv; + } + + hasRule = true; + } + else if (ni->Equals(nsGkAtoms::query, kNameSpaceID_XUL)) { + if (hasQuery) + continue; + + aQuerySet->mQueryNode = rulenode; + hasQuery = true; + } + else if (ni->Equals(nsGkAtoms::action, kNameSpaceID_XUL)) { + // the query must appear before the action + if (! hasQuery) + continue; + + nsCOMPtr<nsIAtom> tag; + DetermineRDFQueryRef(aQuerySet->mQueryNode, getter_AddRefs(tag)); + if (tag) + aQuerySet->SetTag(tag); + + nsCOMPtr<nsIAtom> memberVariable = mMemberVariable; + if (!memberVariable) { + memberVariable = DetermineMemberVariable(rulenode); + if (!memberVariable) { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_NO_MEMBERVAR); + continue; + } + } + + nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode)); + + rv = mQueryProcessor->CompileQuery(this, query, + mRefVariable, memberVariable, + getter_AddRefs(aQuerySet->mCompiledQuery)); + + if (aQuerySet->mCompiledQuery) { + nsTemplateRule* rule = aQuerySet->NewRule(aTemplate, rulenode, aQuerySet); + if (! rule) + return NS_ERROR_OUT_OF_MEMORY; + + rule->SetVars(mRefVariable, memberVariable); + + *aCanUseTemplate = true; + + return NS_OK; + } + } + } + + if (! hasRule && ! hasQuery && ! hasQuerySet) { + // if no rules are specified in the template, then the contents of the + // <template> tag are the one-and-only template. + rv = CompileSimpleQuery(aTemplate, aQuerySet, aCanUseTemplate); + } + + return rv; +} + +nsresult +nsXULTemplateBuilder::CompileExtendedQuery(nsIContent* aRuleElement, + nsIContent* aActionElement, + nsIAtom* aMemberVariable, + nsTemplateQuerySet* aQuerySet) +{ + // Compile an "extended" <template> rule. An extended rule may have + // a <conditions> child, an <action> child, and a <bindings> child. + nsresult rv; + + nsTemplateRule* rule = aQuerySet->NewRule(aRuleElement, aActionElement, aQuerySet); + if (! rule) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsIContent> conditions; + nsXULContentUtils::FindChildByTag(aRuleElement, + kNameSpaceID_XUL, + nsGkAtoms::conditions, + getter_AddRefs(conditions)); + + // allow the conditions to be placed directly inside the rule + if (!conditions) + conditions = aRuleElement; + + rv = CompileConditions(rule, conditions); + // If the rule compilation failed, then we have to bail. + if (NS_FAILED(rv)) { + aQuerySet->RemoveRule(rule); + return rv; + } + + rule->SetVars(mRefVariable, aMemberVariable); + + // If we've got bindings, add 'em. + nsCOMPtr<nsIContent> bindings; + nsXULContentUtils::FindChildByTag(aRuleElement, + kNameSpaceID_XUL, + nsGkAtoms::bindings, + getter_AddRefs(bindings)); + + // allow bindings to be placed directly inside rule + if (!bindings) + bindings = aRuleElement; + + rv = CompileBindings(rule, bindings); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +already_AddRefed<nsIAtom> +nsXULTemplateBuilder::DetermineMemberVariable(nsIContent* aElement) +{ + // recursively iterate over the children looking for an element + // with uri="?..." + for (nsIContent* child = aElement->GetFirstChild(); + child; + child = child->GetNextSibling()) { + nsAutoString uri; + child->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri); + if (!uri.IsEmpty() && uri[0] == char16_t('?')) { + return NS_Atomize(uri); + } + + nsCOMPtr<nsIAtom> result = DetermineMemberVariable(child); + if (result) { + return result.forget(); + } + } + + return nullptr; +} + +void +nsXULTemplateBuilder::DetermineRDFQueryRef(nsIContent* aQueryElement, nsIAtom** aTag) +{ + // check for a tag + nsCOMPtr<nsIContent> content; + nsXULContentUtils::FindChildByTag(aQueryElement, + kNameSpaceID_XUL, + nsGkAtoms::content, + getter_AddRefs(content)); + + if (! content) { + // look for older treeitem syntax as well + nsXULContentUtils::FindChildByTag(aQueryElement, + kNameSpaceID_XUL, + nsGkAtoms::treeitem, + getter_AddRefs(content)); + } + + if (content) { + nsAutoString uri; + content->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri); + + if (!uri.IsEmpty()) + mRefVariable = NS_Atomize(uri); + + nsAutoString tag; + content->GetAttr(kNameSpaceID_None, nsGkAtoms::tag, tag); + + if (!tag.IsEmpty()) + *aTag = NS_Atomize(tag).take(); + } +} + +nsresult +nsXULTemplateBuilder::CompileSimpleQuery(nsIContent* aRuleElement, + nsTemplateQuerySet* aQuerySet, + bool* aCanUseTemplate) +{ + // compile a simple query, which is a query with no <query> or + // <conditions>. This means that a default query is used. + nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aRuleElement)); + + nsCOMPtr<nsIAtom> memberVariable; + if (mMemberVariable) + memberVariable = mMemberVariable; + else + memberVariable = NS_Atomize("rdf:*"); + + // since there is no <query> node for a simple query, the query node will + // be either the <rule> node if multiple rules are used, or the <template> node. + aQuerySet->mQueryNode = aRuleElement; + nsresult rv = mQueryProcessor->CompileQuery(this, query, + mRefVariable, memberVariable, + getter_AddRefs(aQuerySet->mCompiledQuery)); + if (NS_FAILED(rv)) + return rv; + + if (! aQuerySet->mCompiledQuery) { + *aCanUseTemplate = false; + return NS_OK; + } + + nsTemplateRule* rule = aQuerySet->NewRule(aRuleElement, aRuleElement, aQuerySet); + if (! rule) + return NS_ERROR_OUT_OF_MEMORY; + + rule->SetVars(mRefVariable, memberVariable); + + nsAutoString tag; + aRuleElement->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag); + + if (!tag.IsEmpty()) { + nsCOMPtr<nsIAtom> tagatom = NS_Atomize(tag); + aQuerySet->SetTag(tagatom); + } + + *aCanUseTemplate = true; + + return AddSimpleRuleBindings(rule, aRuleElement); +} + +nsresult +nsXULTemplateBuilder::CompileConditions(nsTemplateRule* aRule, + nsIContent* aCondition) +{ + nsAutoString tag; + aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag); + + if (!tag.IsEmpty()) { + nsCOMPtr<nsIAtom> tagatom = NS_Atomize(tag); + aRule->SetTag(tagatom); + } + + nsTemplateCondition* currentCondition = nullptr; + + for (nsIContent* node = aCondition->GetFirstChild(); + node; + node = node->GetNextSibling()) { + + if (node->NodeInfo()->Equals(nsGkAtoms::where, kNameSpaceID_XUL)) { + nsresult rv = CompileWhereCondition(aRule, node, ¤tCondition); + if (NS_FAILED(rv)) + return rv; + } + } + + return NS_OK; +} + +nsresult +nsXULTemplateBuilder::CompileWhereCondition(nsTemplateRule* aRule, + nsIContent* aCondition, + nsTemplateCondition** aCurrentCondition) +{ + // Compile a <where> condition, which must be of the form: + // + // <where subject="?var1|string" rel="relation" value="?var2|string" /> + // + // The value of rel may be: + // equal - subject must be equal to object + // notequal - subject must not be equal to object + // less - subject must be less than object + // greater - subject must be greater than object + // startswith - subject must start with object + // endswith - subject must end with object + // contains - subject must contain object + // Comparisons are done as strings unless the subject is an integer. + + // subject + nsAutoString subject; + aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject); + if (subject.IsEmpty()) { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_SUBJECT); + return NS_OK; + } + + nsCOMPtr<nsIAtom> svar; + if (subject[0] == char16_t('?')) + svar = NS_Atomize(subject); + + nsAutoString relstring; + aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, relstring); + if (relstring.IsEmpty()) { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_RELATION); + return NS_OK; + } + + // object + nsAutoString value; + aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value); + if (value.IsEmpty()) { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_VALUE); + return NS_OK; + } + + // multiple + bool shouldMultiple = + aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::multiple, + nsGkAtoms::_true, eCaseMatters); + + nsCOMPtr<nsIAtom> vvar; + if (!shouldMultiple && (value[0] == char16_t('?'))) { + vvar = NS_Atomize(value); + } + + // ignorecase + bool shouldIgnoreCase = + aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorecase, + nsGkAtoms::_true, eCaseMatters); + + // negate + bool shouldNegate = + aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::negate, + nsGkAtoms::_true, eCaseMatters); + + nsTemplateCondition* condition; + + if (svar && vvar) { + condition = new nsTemplateCondition(svar, relstring, vvar, + shouldIgnoreCase, shouldNegate); + } + else if (svar && !value.IsEmpty()) { + condition = new nsTemplateCondition(svar, relstring, value, + shouldIgnoreCase, shouldNegate, shouldMultiple); + } + else if (vvar) { + condition = new nsTemplateCondition(subject, relstring, vvar, + shouldIgnoreCase, shouldNegate); + } + else { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_VAR); + return NS_OK; + } + + if (*aCurrentCondition) { + (*aCurrentCondition)->SetNext(condition); + } + else { + aRule->SetCondition(condition); + } + + *aCurrentCondition = condition; + + return NS_OK; +} + +nsresult +nsXULTemplateBuilder::CompileBindings(nsTemplateRule* aRule, nsIContent* aBindings) +{ + // Add an extended rule's bindings. + nsresult rv; + + for (nsIContent* binding = aBindings->GetFirstChild(); + binding; + binding = binding->GetNextSibling()) { + + if (binding->NodeInfo()->Equals(nsGkAtoms::binding, + kNameSpaceID_XUL)) { + rv = CompileBinding(aRule, binding); + if (NS_FAILED(rv)) + return rv; + } + } + + aRule->AddBindingsToQueryProcessor(mQueryProcessor); + + return NS_OK; +} + + +nsresult +nsXULTemplateBuilder::CompileBinding(nsTemplateRule* aRule, + nsIContent* aBinding) +{ + // Compile a <binding> "condition", which must be of the form: + // + // <binding subject="?var1" + // predicate="resource" + // object="?var2" /> + // + // XXXwaterson Some day it would be cool to allow the 'predicate' + // to be bound to a variable. + + // subject + nsAutoString subject; + aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject); + if (subject.IsEmpty()) { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_SUBJECT); + return NS_OK; + } + + nsCOMPtr<nsIAtom> svar; + if (subject[0] == char16_t('?')) { + svar = NS_Atomize(subject); + } + else { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_SUBJECT); + return NS_OK; + } + + // predicate + nsAutoString predicate; + aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::predicate, predicate); + if (predicate.IsEmpty()) { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_PREDICATE); + return NS_OK; + } + + // object + nsAutoString object; + aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::object, object); + + if (object.IsEmpty()) { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT); + return NS_OK; + } + + nsCOMPtr<nsIAtom> ovar; + if (object[0] == char16_t('?')) { + ovar = NS_Atomize(object); + } + else { + nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT); + return NS_OK; + } + + return aRule->AddBinding(svar, predicate, ovar); +} + +nsresult +nsXULTemplateBuilder::AddSimpleRuleBindings(nsTemplateRule* aRule, + nsIContent* aElement) +{ + // Crawl the content tree of a "simple" rule, adding a variable + // assignment for any attribute whose value is "rdf:". + + AutoTArray<nsIContent*, 8> elements; + + if (elements.AppendElement(aElement) == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + while (elements.Length()) { + // Pop the next element off the stack + uint32_t i = elements.Length() - 1; + nsIContent* element = elements[i]; + elements.RemoveElementAt(i); + + // Iterate through its attributes, looking for substitutions + // that we need to add as bindings. + uint32_t count = element->GetAttrCount(); + + for (i = 0; i < count; ++i) { + const nsAttrName* name = element->GetAttrNameAt(i); + + if (!name->Equals(nsGkAtoms::id, kNameSpaceID_None) && + !name->Equals(nsGkAtoms::uri, kNameSpaceID_None)) { + nsAutoString value; + element->GetAttr(name->NamespaceID(), name->LocalName(), value); + + // Scan the attribute for variables, adding a binding for + // each one. + ParseAttribute(value, AddBindingsFor, nullptr, aRule); + } + } + + // Push kids onto the stack, and search them next. + for (nsIContent* child = element->GetLastChild(); + child; + child = child->GetPreviousSibling()) { + + if (!elements.AppendElement(child)) + return NS_ERROR_OUT_OF_MEMORY; + } + } + + aRule->AddBindingsToQueryProcessor(mQueryProcessor); + + return NS_OK; +} + +void +nsXULTemplateBuilder::AddBindingsFor(nsXULTemplateBuilder* aThis, + const nsAString& aVariable, + void* aClosure) +{ + // We should *only* be recieving "rdf:"-style variables. Make + // sure... + if (!StringBeginsWith(aVariable, NS_LITERAL_STRING("rdf:"))) + return; + + nsTemplateRule* rule = static_cast<nsTemplateRule*>(aClosure); + + nsCOMPtr<nsIAtom> var = NS_Atomize(aVariable); + + // Strip it down to the raw RDF property by clobbering the "rdf:" + // prefix + nsAutoString property; + property.Assign(Substring(aVariable, uint32_t(4), aVariable.Length() - 4)); + + if (! rule->HasBinding(rule->GetMemberVariable(), property, var)) + // In the simple syntax, the binding is always from the + // member variable, through the property, to the target. + rule->AddBinding(rule->GetMemberVariable(), property, var); +} + + +nsresult +nsXULTemplateBuilder::IsSystemPrincipal(nsIPrincipal *principal, bool *result) +{ + if (!gSystemPrincipal) + return NS_ERROR_UNEXPECTED; + + *result = (principal == gSystemPrincipal); + return NS_OK; +} + +bool +nsXULTemplateBuilder::IsActivated(nsIRDFResource *aResource) +{ + for (ActivationEntry *entry = mTop; + entry != nullptr; + entry = entry->mPrevious) { + if (entry->mResource == aResource) + return true; + } + return false; +} + +nsresult +nsXULTemplateBuilder::GetResultResource(nsIXULTemplateResult* aResult, + nsIRDFResource** aResource) +{ + // get the resource for a result by checking its resource property. If it + // is not set, check the id. This allows non-chrome implementations to + // avoid having to use RDF. + nsresult rv = aResult->GetResource(aResource); + if (NS_FAILED(rv)) + return rv; + + if (! *aResource) { + nsAutoString id; + rv = aResult->GetId(id); + if (NS_FAILED(rv)) + return rv; + + return gRDFService->GetUnicodeResource(id, aResource); + } + + return rv; +} + + +void +nsXULTemplateBuilder::OutputMatchToLog(nsIRDFResource* aId, + nsTemplateMatch* aMatch, + bool aIsNew) +{ + int32_t priority = aMatch->QuerySetPriority() + 1; + int32_t activePriority = -1; + + nsAutoString msg; + + nsAutoString templateid; + mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::id, templateid); + msg.AppendLiteral("In template"); + if (!templateid.IsEmpty()) { + msg.AppendLiteral(" with id "); + msg.Append(templateid); + } + + nsAutoString refstring; + aMatch->mResult->GetBindingFor(mRefVariable, refstring); + if (!refstring.IsEmpty()) { + msg.AppendLiteral(" using ref "); + msg.Append(refstring); + } + + msg.AppendLiteral("\n "); + + nsTemplateMatch* match = nullptr; + if (mMatchMap.Get(aId, &match)){ + while (match) { + if (match == aMatch) + break; + if (match->IsActive() && + match->GetContainer() == aMatch->GetContainer()) { + activePriority = match->QuerySetPriority() + 1; + break; + } + match = match->mNext; + } + } + + if (aMatch->IsActive()) { + if (aIsNew) { + msg.AppendLiteral("New active result for query "); + msg.AppendInt(priority); + msg.AppendLiteral(" matching rule "); + msg.AppendInt(aMatch->RuleIndex() + 1); + } + else { + msg.AppendLiteral("Removed active result for query "); + msg.AppendInt(priority); + if (activePriority > 0) { + msg.AppendLiteral(" (new active query is "); + msg.AppendInt(activePriority); + msg.Append(')'); + } + else { + msg.AppendLiteral(" (no new active query)"); + } + } + } + else { + if (aIsNew) { + msg.AppendLiteral("New inactive result for query "); + msg.AppendInt(priority); + if (activePriority > 0) { + msg.AppendLiteral(" (overridden by query "); + msg.AppendInt(activePriority); + msg.Append(')'); + } + else { + msg.AppendLiteral(" (didn't match a rule)"); + } + } + else { + msg.AppendLiteral("Removed inactive result for query "); + msg.AppendInt(priority); + if (activePriority > 0) { + msg.AppendLiteral(" (active query is "); + msg.AppendInt(activePriority); + msg.Append(')'); + } + else { + msg.AppendLiteral(" (no active query)"); + } + } + } + + nsAutoString idstring; + nsXULContentUtils::GetTextForNode(aId, idstring); + msg.AppendLiteral(": "); + msg.Append(idstring); + + nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (cs) + cs->LogStringMessage(msg.get()); +} |