Mypal/dom/xul/templates/nsXULTemplateBuilder.h

503 lines
16 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/. */
#ifndef nsXULTemplateBuilder_h__
#define nsXULTemplateBuilder_h__
#include "nsStubDocumentObserver.h"
#include "nsIScriptSecurityManager.h"
#include "nsIObserver.h"
#include "nsIRDFCompositeDataSource.h"
#include "nsIRDFContainer.h"
#include "nsIRDFContainerUtils.h"
#include "nsIRDFDataSource.h"
#include "nsIRDFObserver.h"
#include "nsIRDFService.h"
#include "nsIXULTemplateBuilder.h"
#include "nsCOMArray.h"
#include "nsTArray.h"
#include "nsDataHashtable.h"
#include "nsTemplateRule.h"
#include "nsTemplateMatch.h"
#include "nsIXULTemplateQueryProcessor.h"
#include "nsCycleCollectionParticipant.h"
#include "mozilla/Logging.h"
extern mozilla::LazyLogModule gXULTemplateLog;
class nsIContent;
class nsIObserverService;
class nsIRDFCompositeDataSource;
/**
* An object that translates an RDF graph into a presentation using a
* set of rules.
*/
class nsXULTemplateBuilder : public nsIXULTemplateBuilder,
public nsIObserver,
public nsStubDocumentObserver
{
void CleanUp(bool aIsFinal);
void DestroyMatchMap();
public:
nsXULTemplateBuilder();
nsresult InitGlobals();
/**
* Clear the template builder structures. The aIsFinal flag is set to true
* when the template is going away.
*/
virtual void Uninit(bool aIsFinal);
// nsISupports interface
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULTemplateBuilder,
nsIXULTemplateBuilder)
// nsIXULTemplateBuilder interface
NS_DECL_NSIXULTEMPLATEBUILDER
// nsIObserver Interface
NS_DECL_NSIOBSERVER
// nsIMutationObserver
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
/**
* Remove an old result and/or add a new result. This method will retrieve
* the set of containers where the result could be inserted and either add
* the new result to those containers, or remove the result from those
* containers. UpdateResultInContainer is called for each container.
*
* @param aOldResult result to remove
* @param aNewResult result to add
* @param aQueryNode query node for new result
*/
nsresult
UpdateResult(nsIXULTemplateResult* aOldResult,
nsIXULTemplateResult* aNewResult,
nsIDOMNode* aQueryNode);
/**
* Remove an old result and/or add a new result from a specific container.
*
* @param aOldResult result to remove
* @param aNewResult result to add
* @param aQueryNode queryset for the new result
* @param aOldId id of old result
* @param aNewId id of new result
* @param aInsertionPoint container to remove or add result inside
*/
nsresult
UpdateResultInContainer(nsIXULTemplateResult* aOldResult,
nsIXULTemplateResult* aNewResult,
nsTemplateQuerySet* aQuerySet,
nsIRDFResource* aOldId,
nsIRDFResource* aNewId,
nsIContent* aInsertionPoint);
nsresult
ComputeContainmentProperties();
static bool
IsTemplateElement(nsIContent* aContent);
virtual nsresult
RebuildAll() = 0; // must be implemented by subclasses
void RunnableRebuild() { Rebuild(); }
void RunnableLoadAndRebuild() {
Uninit(false); // Reset results
nsCOMPtr<nsIDocument> doc = mRoot ? mRoot->GetComposedDoc() : nullptr;
if (doc) {
bool shouldDelay;
LoadDataSources(doc, &shouldDelay);
if (!shouldDelay) {
Rebuild();
}
}
}
// mRoot should not be cleared until after Uninit is finished so that
// generated content can be removed during uninitialization.
void UninitFalse() { Uninit(false); mRoot = nullptr; }
void UninitTrue() { Uninit(true); mRoot = nullptr; }
/**
* Find the <template> tag that applies for this builder
*/
nsresult
GetTemplateRoot(nsIContent** aResult);
/**
* Compile the template's queries
*/
nsresult
CompileQueries();
/**
* Compile the template given a <template> in aTemplate. This function
* is called recursively to handle queries inside a queryset. For the
* outer pass, aIsQuerySet will be false, while the inner pass this will
* be true.
*
* aCanUseTemplate will be set to true if the template's queries could be
* compiled, and false otherwise. If false, the entire template is
* invalid.
*
* @param aTemplate <template> to compile
* @param aQuerySet first queryset
* @param aIsQuerySet true if
* @param aPriority the queryset index, incremented when a new one is added
* @param aCanUseTemplate true if template is valid
*/
nsresult
CompileTemplate(nsIContent* aTemplate,
nsTemplateQuerySet* aQuerySet,
bool aIsQuerySet,
int32_t* aPriority,
bool* aCanUseTemplate);
/**
* Compile a query using the extended syntax. For backwards compatible RDF
* syntax where there is no <query>, the <conditions> becomes the query.
*
* @param aRuleElement <rule> element
* @param aActionElement <action> element
* @param aMemberVariable member variable for the query
* @param aQuerySet the queryset
*/
nsresult
CompileExtendedQuery(nsIContent* aRuleElement,
nsIContent* aActionElement,
nsIAtom* aMemberVariable,
nsTemplateQuerySet* aQuerySet);
/**
* Determine the ref variable and tag from inside a RDF query.
*/
void DetermineRDFQueryRef(nsIContent* aQueryElement, nsIAtom** tag);
/**
* Determine the member variable from inside an action body. It will be
* the value of the uri attribute on a node.
*/
already_AddRefed<nsIAtom> DetermineMemberVariable(nsIContent* aElement);
/**
* Compile a simple query. A simple query is one that doesn't have a
* <query> and should use a default query which would normally just return
* a list of children of the reference point.
*
* @param aRuleElement the <rule>
* @param aQuerySet the query set
* @param aCanUseTemplate true if the query is valid
*/
nsresult
CompileSimpleQuery(nsIContent* aRuleElement,
nsTemplateQuerySet* aQuerySet,
bool* aCanUseTemplate);
/**
* Compile the <conditions> tag in a rule
*
* @param aRule template rule
* @param aConditions <conditions> element
*/
nsresult
CompileConditions(nsTemplateRule* aRule, nsIContent* aConditions);
/**
* Compile a <where> tag in a condition. The caller should set
* *aCurrentCondition to null for the first condition. This value will be
* updated to point to the new condition before returning. The conditions
* will be added to the rule aRule by this method.
*
* @param aRule template rule
* @param aCondition <where> element
* @param aCurrentCondition compiled condition
*/
nsresult
CompileWhereCondition(nsTemplateRule* aRule,
nsIContent* aCondition,
nsTemplateCondition** aCurrentCondition);
/**
* Compile the <bindings> for an extended template syntax rule.
*/
nsresult
CompileBindings(nsTemplateRule* aRule, nsIContent* aBindings);
/**
* Compile a single binding for an extended template syntax rule.
*/
nsresult
CompileBinding(nsTemplateRule* aRule, nsIContent* aBinding);
/**
* Add automatic bindings for simple rules
*/
nsresult
AddSimpleRuleBindings(nsTemplateRule* aRule, nsIContent* aElement);
static void
AddBindingsFor(nsXULTemplateBuilder* aSelf,
const nsAString& aVariable,
void* aClosure);
/**
* Load the datasources for the template. shouldDelayBuilding is an out
* parameter which will be set to true to indicate that content building
* should not be performed yet as the datasource has not yet loaded. If
* false, the datasource has already loaded so building can proceed
* immediately. In the former case, the datasource or query processor
* should either rebuild the template or update results when the
* datasource is loaded as needed.
*/
nsresult
LoadDataSources(nsIDocument* aDoc, bool* shouldDelayBuilding);
/**
* Called by LoadDataSources to load a datasource given a uri list
* in aDataSource. The list is a set of uris separated by spaces.
* If aIsRDFQuery is true, then this is for an RDF datasource which
* causes the method to check for additional flags specific to the
* RDF processor.
*/
nsresult
LoadDataSourceUrls(nsIDocument* aDocument,
const nsAString& aDataSources,
bool aIsRDFQuery,
bool* aShouldDelayBuilding);
nsresult
InitHTMLTemplateRoot();
/**
* Determine which rule matches a given result. aContainer is used for
* tag matching and is optional for non-content generating builders.
* The returned matched rule is always one of the rules owned by the
* query set aQuerySet.
*
* @param aContainer parent where generated content will be inserted
* @param aResult result to match
* @param aQuerySet query set to examine the rules of
* @param aMatchedRule [out] rule that has matched, or null if any.
* @param aRuleIndex [out] index of the rule
*/
nsresult
DetermineMatchedRule(nsIContent* aContainer,
nsIXULTemplateResult* aResult,
nsTemplateQuerySet* aQuerySet,
nsTemplateRule** aMatchedRule,
int16_t *aRuleIndex);
// XXX sigh, the string template foo doesn't mix with
// operator->*() on egcs-1.1.2, so we'll need to explicitly pass
// "this" and use good ol' fashioned static callbacks.
void
ParseAttribute(const nsAString& aAttributeValue,
void (*aVariableCallback)(nsXULTemplateBuilder* aThis, const nsAString&, void*),
void (*aTextCallback)(nsXULTemplateBuilder* aThis, const nsAString&, void*),
void* aClosure);
nsresult
SubstituteText(nsIXULTemplateResult* aMatch,
const nsAString& aAttributeValue,
nsAString& aResult);
static void
SubstituteTextAppendText(nsXULTemplateBuilder* aThis, const nsAString& aText, void* aClosure);
static void
SubstituteTextReplaceVariable(nsXULTemplateBuilder* aThis, const nsAString& aVariable, void* aClosure);
nsresult
IsSystemPrincipal(nsIPrincipal *principal, bool *result);
/**
* Convenience method which gets a resource for a result. If a result
* doesn't have a resource set, it will create one from the result's id.
*/
nsresult GetResultResource(nsIXULTemplateResult* aResult,
nsIRDFResource** aResource);
protected:
virtual ~nsXULTemplateBuilder();
nsCOMPtr<nsISupports> mDataSource;
nsCOMPtr<nsIRDFDataSource> mDB;
nsCOMPtr<nsIRDFCompositeDataSource> mCompDB;
/**
* Circular reference, broken when the document is destroyed.
*/
nsCOMPtr<nsIContent> mRoot;
/**
* The root result, translated from the root element's ref
*/
nsCOMPtr<nsIXULTemplateResult> mRootResult;
nsCOMArray<nsIXULBuilderListener> mListeners;
/**
* The query processor which generates results
*/
nsCOMPtr<nsIXULTemplateQueryProcessor> mQueryProcessor;
/**
* The list of querysets
*/
nsTArray<nsTemplateQuerySet *> mQuerySets;
/**
* Set to true if the rules have already been compiled
*/
bool mQueriesCompiled;
/**
* The default reference and member variables.
*/
nsCOMPtr<nsIAtom> mRefVariable;
nsCOMPtr<nsIAtom> mMemberVariable;
/**
* The match map contains nsTemplateMatch objects, one for each unique
* match found, keyed by the resource for that match. A particular match
* will contain a linked list of all of the matches for that unique result
* id. Only one is active at a time. When a match is retracted, look in
* the match map, remove it, and apply the next valid match in sequence to
* make active.
*/
nsDataHashtable<nsISupportsHashKey, nsTemplateMatch*> mMatchMap;
// pseudo-constants
static nsrefcnt gRefCnt;
static nsIRDFService* gRDFService;
static nsIRDFContainerUtils* gRDFContainerUtils;
static nsIScriptSecurityManager* gScriptSecurityManager;
static nsIPrincipal* gSystemPrincipal;
static nsIObserverService* gObserverService;
enum {
eDontTestEmpty = (1 << 0),
eDontRecurse = (1 << 1),
eLoggingEnabled = (1 << 2)
};
int32_t mFlags;
/**
* Stack-based helper class to maintain a list of ``activated''
* resources; i.e., resources for which we are currently building
* content.
*/
class ActivationEntry {
public:
nsIRDFResource *mResource;
ActivationEntry *mPrevious;
ActivationEntry **mLink;
ActivationEntry(nsIRDFResource *aResource, ActivationEntry **aLink)
: mResource(aResource),
mPrevious(*aLink),
mLink(aLink) { *mLink = this; }
~ActivationEntry() { *mLink = mPrevious; }
};
/**
* The top of the stack of resources that we're currently building
* content for.
*/
ActivationEntry *mTop;
/**
* Determine if a resource is currently on the activation stack.
*/
bool
IsActivated(nsIRDFResource *aResource);
/**
* Returns true if content may be generated for a result, or false if it
* cannot, for example, if it would be created inside a closed container.
* Those results will be generated when the container is opened.
* If false is returned, no content should be generated. Possible
* insertion locations may optionally be set for new content, depending on
* the builder being used. Note that *aLocations or some items within
* aLocations may be null.
*/
virtual bool
GetInsertionLocations(nsIXULTemplateResult* aResult,
nsCOMArray<nsIContent>** aLocations) = 0;
/**
* Must be implemented by subclasses. Handle removing the generated
* output for aOldMatch and adding new output for aNewMatch. Either
* aOldMatch or aNewMatch may be null. aContext is the location returned
* from the call to MayGenerateResult.
*/
virtual nsresult
ReplaceMatch(nsIXULTemplateResult* aOldResult,
nsTemplateMatch* aNewMatch,
nsTemplateRule* aNewMatchRule,
void *aContext) = 0;
/**
* Must be implemented by subclasses. Handle change in bound
* variable values for aResult. aModifiedVars contains the set
* of variables that have changed.
* @param aResult the ersult for which variable bindings has changed.
* @param aModifiedVars the set of variables for which the bindings
* have changed.
*/
virtual nsresult
SynchronizeResult(nsIXULTemplateResult* aResult) = 0;
/**
* Output a new match or removed match to the console.
*
* @param aId id of the result
* @param aMatch new or removed match
* @param aIsNew true for new matched, false for removed matches
*/
void
OutputMatchToLog(nsIRDFResource* aId,
nsTemplateMatch* aMatch,
bool aIsNew);
virtual void Traverse(nsCycleCollectionTraversalCallback &cb) const
{
}
/**
* Start observing events from the observer service and the given
* document.
*
* @param aDocument the document to observe
*/
void StartObserving(nsIDocument* aDocument);
/**
* Stop observing events from the observer service and any associated
* document.
*/
void StopObserving();
/**
* Document that we're observing. Weak ref!
*/
nsIDocument* mObservedDocument;
};
#endif // nsXULTemplateBuilder_h__