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