зеркало из https://github.com/mozilla/gecko-dev.git
2573 строки
86 KiB
C++
2573 строки
86 KiB
C++
/* -*- 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:
|
|
|
|
NSPR_LOG_MODULES nsXULTemplateBuilder:5
|
|
|
|
*/
|
|
|
|
#include "nsCOMPtr.h"
|
|
#include "nsCRT.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIDOMElement.h"
|
|
#include "nsIDOMNode.h"
|
|
#include "nsIDOMDocument.h"
|
|
#include "nsIDOMXMLDocument.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"
|
|
|
|
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)) {
|
|
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject);
|
|
if (window) {
|
|
nsCOMPtr<nsIDocument> doc = 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(
|
|
NS_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(
|
|
NS_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(
|
|
NS_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(
|
|
NS_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 entryScript(innerWin, "nsXULTemplateBuilder creation", true);
|
|
JSContext* jscontext = entryScript.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 = do_GetAtom(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 = do_GetAtom("?uri");
|
|
else
|
|
mRefVariable = do_GetAtom(containervar);
|
|
|
|
nsAutoString membervar;
|
|
tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::member, membervar);
|
|
|
|
if (membervar.IsEmpty())
|
|
mMemberVariable = nullptr;
|
|
else
|
|
mMemberVariable = do_GetAtom(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_NewAtom(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 = do_GetAtom(uri);
|
|
|
|
nsAutoString tag;
|
|
content->GetAttr(kNameSpaceID_None, nsGkAtoms::tag, tag);
|
|
|
|
if (!tag.IsEmpty())
|
|
*aTag = NS_NewAtom(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 = do_GetAtom("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 = do_GetAtom(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 = do_GetAtom(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 = do_GetAtom(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 = do_GetAtom(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 = do_GetAtom(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 = do_GetAtom(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:".
|
|
|
|
nsAutoTArray<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 = do_GetAtom(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());
|
|
}
|