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