summaryrefslogtreecommitdiffstats
path: root/dom/xul/templates/nsXULTemplateBuilder.h
blob: 7da8ffc98381d466643d175bc640032a3ef65b3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
/* -*- 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__