1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #ifndef nsXULTemplateBuilder_h__
7 #define nsXULTemplateBuilder_h__
8 
9 #include "nsStubDocumentObserver.h"
10 #include "nsIScriptSecurityManager.h"
11 #include "nsIObserver.h"
12 #include "nsIRDFCompositeDataSource.h"
13 #include "nsIRDFContainer.h"
14 #include "nsIRDFContainerUtils.h"
15 #include "nsIRDFDataSource.h"
16 #include "nsIRDFObserver.h"
17 #include "nsIRDFService.h"
18 #include "nsIXULTemplateBuilder.h"
19 
20 #include "nsCOMArray.h"
21 #include "nsTArray.h"
22 #include "nsDataHashtable.h"
23 #include "nsTemplateRule.h"
24 #include "nsTemplateMatch.h"
25 #include "nsIXULTemplateQueryProcessor.h"
26 #include "nsCycleCollectionParticipant.h"
27 
28 #include "mozilla/Logging.h"
29 extern mozilla::LazyLogModule gXULTemplateLog;
30 
31 class nsIContent;
32 class nsIObserverService;
33 class nsIRDFCompositeDataSource;
34 
35 /**
36  * An object that translates an RDF graph into a presentation using a
37  * set of rules.
38  */
39 class nsXULTemplateBuilder : public nsIXULTemplateBuilder,
40                              public nsIObserver,
41                              public nsStubDocumentObserver
42 {
43     void CleanUp(bool aIsFinal);
44     void DestroyMatchMap();
45 
46 public:
47     nsXULTemplateBuilder();
48 
49     nsresult InitGlobals();
50 
51     /**
52      * Clear the template builder structures. The aIsFinal flag is set to true
53      * when the template is going away.
54      */
55     virtual void Uninit(bool aIsFinal);
56 
57     // nsISupports interface
58     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
59     NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULTemplateBuilder,
60                                              nsIXULTemplateBuilder)
61 
62     // nsIXULTemplateBuilder interface
63     NS_DECL_NSIXULTEMPLATEBUILDER
64 
65     // nsIObserver Interface
66     NS_DECL_NSIOBSERVER
67 
68     // nsIMutationObserver
69     NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
70     NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
71     NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
72 
73     /**
74      * Remove an old result and/or add a new result. This method will retrieve
75      * the set of containers where the result could be inserted and either add
76      * the new result to those containers, or remove the result from those
77      * containers. UpdateResultInContainer is called for each container.
78      *
79      * @param aOldResult result to remove
80      * @param aNewResult result to add
81      * @param aQueryNode query node for new result
82      */
83     nsresult
84     UpdateResult(nsIXULTemplateResult* aOldResult,
85                  nsIXULTemplateResult* aNewResult,
86                  nsIDOMNode* aQueryNode);
87 
88     /**
89      * Remove an old result and/or add a new result from a specific container.
90      *
91      * @param aOldResult result to remove
92      * @param aNewResult result to add
93      * @param aQueryNode queryset for the new result
94      * @param aOldId id of old result
95      * @param aNewId id of new result
96      * @param aInsertionPoint container to remove or add result inside
97      */
98     nsresult
99     UpdateResultInContainer(nsIXULTemplateResult* aOldResult,
100                             nsIXULTemplateResult* aNewResult,
101                             nsTemplateQuerySet* aQuerySet,
102                             nsIRDFResource* aOldId,
103                             nsIRDFResource* aNewId,
104                             nsIContent* aInsertionPoint);
105 
106     nsresult
107     ComputeContainmentProperties();
108 
109     static bool
110     IsTemplateElement(nsIContent* aContent);
111 
112     virtual nsresult
113     RebuildAll() = 0; // must be implemented by subclasses
114 
RunnableRebuild()115     void RunnableRebuild() { Rebuild(); }
RunnableLoadAndRebuild()116     void RunnableLoadAndRebuild() {
117       Uninit(false);  // Reset results
118 
119       nsCOMPtr<nsIDocument> doc = mRoot ? mRoot->GetComposedDoc() : nullptr;
120       if (doc) {
121         bool shouldDelay;
122         LoadDataSources(doc, &shouldDelay);
123         if (!shouldDelay) {
124           Rebuild();
125         }
126       }
127     }
128 
129     // mRoot should not be cleared until after Uninit is finished so that
130     // generated content can be removed during uninitialization.
UninitFalse()131     void UninitFalse() { Uninit(false); mRoot = nullptr; }
UninitTrue()132     void UninitTrue() { Uninit(true); mRoot = nullptr; }
133 
134     /**
135      * Find the <template> tag that applies for this builder
136      */
137     nsresult
138     GetTemplateRoot(nsIContent** aResult);
139 
140     /**
141      * Compile the template's queries
142      */
143     nsresult
144     CompileQueries();
145 
146     /**
147      * Compile the template given a <template> in aTemplate. This function
148      * is called recursively to handle queries inside a queryset. For the
149      * outer pass, aIsQuerySet will be false, while the inner pass this will
150      * be true.
151      *
152      * aCanUseTemplate will be set to true if the template's queries could be
153      * compiled, and false otherwise. If false, the entire template is
154      * invalid.
155      *
156      * @param aTemplate <template> to compile
157      * @param aQuerySet first queryset
158      * @param aIsQuerySet true if
159      * @param aPriority the queryset index, incremented when a new one is added
160      * @param aCanUseTemplate true if template is valid
161      */
162     nsresult
163     CompileTemplate(nsIContent* aTemplate,
164                     nsTemplateQuerySet* aQuerySet,
165                     bool aIsQuerySet,
166                     int32_t* aPriority,
167                     bool* aCanUseTemplate);
168 
169     /**
170      * Compile a query using the extended syntax. For backwards compatible RDF
171      * syntax where there is no <query>, the <conditions> becomes the query.
172      *
173      * @param aRuleElement <rule> element
174      * @param aActionElement <action> element
175      * @param aMemberVariable member variable for the query
176      * @param aQuerySet the queryset
177      */
178     nsresult
179     CompileExtendedQuery(nsIContent* aRuleElement,
180                          nsIContent* aActionElement,
181                          nsIAtom* aMemberVariable,
182                          nsTemplateQuerySet* aQuerySet);
183 
184     /**
185      * Determine the ref variable and tag from inside a RDF query.
186      */
187     void DetermineRDFQueryRef(nsIContent* aQueryElement, nsIAtom** tag);
188 
189     /**
190      * Determine the member variable from inside an action body. It will be
191      * the value of the uri attribute on a node.
192      */
193     already_AddRefed<nsIAtom> DetermineMemberVariable(nsIContent* aElement);
194 
195     /**
196      * Compile a simple query. A simple query is one that doesn't have a
197      * <query> and should use a default query which would normally just return
198      * a list of children of the reference point.
199      *
200      * @param aRuleElement the <rule>
201      * @param aQuerySet the query set
202      * @param aCanUseTemplate true if the query is valid
203      */
204     nsresult
205     CompileSimpleQuery(nsIContent* aRuleElement,
206                        nsTemplateQuerySet* aQuerySet,
207                        bool* aCanUseTemplate);
208 
209     /**
210      * Compile the <conditions> tag in a rule
211      *
212      * @param aRule template rule
213      * @param aConditions <conditions> element
214      */
215     nsresult
216     CompileConditions(nsTemplateRule* aRule, nsIContent* aConditions);
217 
218     /**
219      * Compile a <where> tag in a condition. The caller should set
220      * *aCurrentCondition to null for the first condition. This value will be
221      * updated to point to the new condition before returning. The conditions
222      * will be added to the rule aRule by this method.
223      *
224      * @param aRule template rule
225      * @param aCondition <where> element
226      * @param aCurrentCondition compiled condition
227      */
228     nsresult
229     CompileWhereCondition(nsTemplateRule* aRule,
230                           nsIContent* aCondition,
231                           nsTemplateCondition** aCurrentCondition);
232 
233     /**
234      * Compile the <bindings> for an extended template syntax rule.
235      */
236     nsresult
237     CompileBindings(nsTemplateRule* aRule, nsIContent* aBindings);
238 
239     /**
240      * Compile a single binding for an extended template syntax rule.
241      */
242     nsresult
243     CompileBinding(nsTemplateRule* aRule, nsIContent* aBinding);
244 
245     /**
246      * Add automatic bindings for simple rules
247      */
248     nsresult
249     AddSimpleRuleBindings(nsTemplateRule* aRule, nsIContent* aElement);
250 
251     static void
252     AddBindingsFor(nsXULTemplateBuilder* aSelf,
253                    const nsAString& aVariable,
254                    void* aClosure);
255 
256     /**
257      * Load the datasources for the template. shouldDelayBuilding is an out
258      * parameter which will be set to true to indicate that content building
259      * should not be performed yet as the datasource has not yet loaded. If
260      * false, the datasource has already loaded so building can proceed
261      * immediately. In the former case, the datasource or query processor
262      * should either rebuild the template or update results when the
263      * datasource is loaded as needed.
264      */
265     nsresult
266     LoadDataSources(nsIDocument* aDoc, bool* shouldDelayBuilding);
267 
268     /**
269      * Called by LoadDataSources to load a datasource given a uri list
270      * in aDataSource. The list is a set of uris separated by spaces.
271      * If aIsRDFQuery is true, then this is for an RDF datasource which
272      * causes the method to check for additional flags specific to the
273      * RDF processor.
274      */
275     nsresult
276     LoadDataSourceUrls(nsIDocument* aDocument,
277                        const nsAString& aDataSources,
278                        bool aIsRDFQuery,
279                        bool* aShouldDelayBuilding);
280 
281     nsresult
282     InitHTMLTemplateRoot();
283 
284     /**
285      * Determine which rule matches a given result. aContainer is used for
286      * tag matching and is optional for non-content generating builders.
287      * The returned matched rule is always one of the rules owned by the
288      * query set aQuerySet.
289      *
290      * @param aContainer parent where generated content will be inserted
291      * @param aResult result to match
292      * @param aQuerySet query set to examine the rules of
293      * @param aMatchedRule [out] rule that has matched, or null if any.
294      * @param aRuleIndex [out] index of the rule
295      */
296     nsresult
297     DetermineMatchedRule(nsIContent* aContainer,
298                          nsIXULTemplateResult* aResult,
299                          nsTemplateQuerySet* aQuerySet,
300                          nsTemplateRule** aMatchedRule,
301                          int16_t *aRuleIndex);
302 
303     // XXX sigh, the string template foo doesn't mix with
304     // operator->*() on egcs-1.1.2, so we'll need to explicitly pass
305     // "this" and use good ol' fashioned static callbacks.
306     void
307     ParseAttribute(const nsAString& aAttributeValue,
308                    void (*aVariableCallback)(nsXULTemplateBuilder* aThis, const nsAString&, void*),
309                    void (*aTextCallback)(nsXULTemplateBuilder* aThis, const nsAString&, void*),
310                    void* aClosure);
311 
312     nsresult
313     SubstituteText(nsIXULTemplateResult* aMatch,
314                    const nsAString& aAttributeValue,
315                    nsAString& aResult);
316 
317     static void
318     SubstituteTextAppendText(nsXULTemplateBuilder* aThis, const nsAString& aText, void* aClosure);
319 
320     static void
321     SubstituteTextReplaceVariable(nsXULTemplateBuilder* aThis, const nsAString& aVariable, void* aClosure);
322 
323     nsresult
324     IsSystemPrincipal(nsIPrincipal *principal, bool *result);
325 
326     /**
327      * Convenience method which gets a resource for a result. If a result
328      * doesn't have a resource set, it will create one from the result's id.
329      */
330     nsresult GetResultResource(nsIXULTemplateResult* aResult,
331                                nsIRDFResource** aResource);
332 
333 protected:
334     virtual ~nsXULTemplateBuilder();
335 
336     nsCOMPtr<nsISupports> mDataSource;
337     nsCOMPtr<nsIRDFDataSource> mDB;
338     nsCOMPtr<nsIRDFCompositeDataSource> mCompDB;
339 
340     /**
341      * Circular reference, broken when the document is destroyed.
342      */
343     nsCOMPtr<nsIContent> mRoot;
344 
345     /**
346      * The root result, translated from the root element's ref
347      */
348     nsCOMPtr<nsIXULTemplateResult> mRootResult;
349 
350     nsCOMArray<nsIXULBuilderListener> mListeners;
351 
352     /**
353      * The query processor which generates results
354      */
355     nsCOMPtr<nsIXULTemplateQueryProcessor> mQueryProcessor;
356 
357     /**
358      * The list of querysets
359      */
360     nsTArray<nsTemplateQuerySet *> mQuerySets;
361 
362     /**
363      * Set to true if the rules have already been compiled
364      */
365     bool          mQueriesCompiled;
366 
367     /**
368      * The default reference and member variables.
369      */
370     nsCOMPtr<nsIAtom> mRefVariable;
371     nsCOMPtr<nsIAtom> mMemberVariable;
372 
373     /**
374      * The match map contains nsTemplateMatch objects, one for each unique
375      * match found, keyed by the resource for that match. A particular match
376      * will contain a linked list of all of the matches for that unique result
377      * id. Only one is active at a time. When a match is retracted, look in
378      * the match map, remove it, and apply the next valid match in sequence to
379      * make active.
380      */
381     nsDataHashtable<nsISupportsHashKey, nsTemplateMatch*> mMatchMap;
382 
383     // pseudo-constants
384     static nsrefcnt gRefCnt;
385     static nsIRDFService*            gRDFService;
386     static nsIRDFContainerUtils*     gRDFContainerUtils;
387     static nsIScriptSecurityManager* gScriptSecurityManager;
388     static nsIPrincipal*             gSystemPrincipal;
389     static nsIObserverService*       gObserverService;
390 
391     enum {
392         eDontTestEmpty = (1 << 0),
393         eDontRecurse = (1 << 1),
394         eLoggingEnabled = (1 << 2)
395     };
396 
397     int32_t mFlags;
398 
399     /**
400      * Stack-based helper class to maintain a list of ``activated''
401      * resources; i.e., resources for which we are currently building
402      * content.
403      */
404     class ActivationEntry {
405     public:
406         nsIRDFResource   *mResource;
407         ActivationEntry  *mPrevious;
408         ActivationEntry **mLink;
409 
ActivationEntry(nsIRDFResource * aResource,ActivationEntry ** aLink)410         ActivationEntry(nsIRDFResource *aResource, ActivationEntry **aLink)
411             : mResource(aResource),
412               mPrevious(*aLink),
413               mLink(aLink) { *mLink = this; }
414 
~ActivationEntry()415         ~ActivationEntry() { *mLink = mPrevious; }
416     };
417 
418     /**
419      * The top of the stack of resources that we're currently building
420      * content for.
421      */
422     ActivationEntry *mTop;
423 
424     /**
425      * Determine if a resource is currently on the activation stack.
426      */
427     bool
428     IsActivated(nsIRDFResource *aResource);
429 
430     /**
431      * Returns true if content may be generated for a result, or false if it
432      * cannot, for example, if it would be created inside a closed container.
433      * Those results will be generated when the container is opened.
434      * If false is returned, no content should be generated. Possible
435      * insertion locations may optionally be set for new content, depending on
436      * the builder being used. Note that *aLocations or some items within
437      * aLocations may be null.
438      */
439     virtual bool
440     GetInsertionLocations(nsIXULTemplateResult* aResult,
441                           nsCOMArray<nsIContent>** aLocations) = 0;
442 
443     /**
444      * Must be implemented by subclasses. Handle removing the generated
445      * output for aOldMatch and adding new output for aNewMatch. Either
446      * aOldMatch or aNewMatch may be null. aContext is the location returned
447      * from the call to MayGenerateResult.
448      */
449     virtual nsresult
450     ReplaceMatch(nsIXULTemplateResult* aOldResult,
451                  nsTemplateMatch* aNewMatch,
452                  nsTemplateRule* aNewMatchRule,
453                  void *aContext) = 0;
454 
455     /**
456      * Must be implemented by subclasses. Handle change in bound
457      * variable values for aResult. aModifiedVars contains the set
458      * of variables that have changed.
459      * @param aResult the ersult for which variable bindings has changed.
460      * @param aModifiedVars the set of variables for which the bindings
461      * have changed.
462      */
463     virtual nsresult
464     SynchronizeResult(nsIXULTemplateResult* aResult) = 0;
465 
466     /**
467      * Output a new match or removed match to the console.
468      *
469      * @param aId id of the result
470      * @param aMatch new or removed match
471      * @param aIsNew true for new matched, false for removed matches
472      */
473     void
474     OutputMatchToLog(nsIRDFResource* aId,
475                      nsTemplateMatch* aMatch,
476                      bool aIsNew);
477 
Traverse(nsCycleCollectionTraversalCallback & cb)478     virtual void Traverse(nsCycleCollectionTraversalCallback &cb) const
479     {
480     }
481 
482     /**
483      * Start observing events from the observer service and the given
484      * document.
485      *
486      * @param aDocument the document to observe
487      */
488     void StartObserving(nsIDocument* aDocument);
489 
490     /**
491      * Stop observing events from the observer service and any associated
492      * document.
493      */
494     void StopObserving();
495 
496     /**
497      * Document that we're observing. Weak ref!
498      */
499     nsIDocument* mObservedDocument;
500 };
501 
502 #endif // nsXULTemplateBuilder_h__
503