content/xul/templates/src/nsXULContentBuilder.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "mozilla/ArrayUtils.h"
michael@0 7
michael@0 8 #include "nsContentCID.h"
michael@0 9 #include "nsIDocument.h"
michael@0 10 #include "nsIDOMNodeList.h"
michael@0 11 #include "nsIDOMXULDocument.h"
michael@0 12 #include "nsINodeInfo.h"
michael@0 13 #include "nsIServiceManager.h"
michael@0 14 #include "nsIXULDocument.h"
michael@0 15
michael@0 16 #include "nsContentSupportMap.h"
michael@0 17 #include "nsRDFConMemberTestNode.h"
michael@0 18 #include "nsRDFPropertyTestNode.h"
michael@0 19 #include "nsXULSortService.h"
michael@0 20 #include "nsTemplateRule.h"
michael@0 21 #include "nsTemplateMap.h"
michael@0 22 #include "nsTArray.h"
michael@0 23 #include "nsXPIDLString.h"
michael@0 24 #include "nsGkAtoms.h"
michael@0 25 #include "nsXULContentUtils.h"
michael@0 26 #include "nsXULElement.h"
michael@0 27 #include "nsXULTemplateBuilder.h"
michael@0 28 #include "nsNodeInfoManager.h"
michael@0 29 #include "nsContentCreatorFunctions.h"
michael@0 30 #include "nsContentUtils.h"
michael@0 31 #include "nsAttrName.h"
michael@0 32 #include "nsNodeUtils.h"
michael@0 33 #include "mozAutoDocUpdate.h"
michael@0 34 #include "nsTextNode.h"
michael@0 35 #include "mozilla/dom/Element.h"
michael@0 36
michael@0 37 #include "pldhash.h"
michael@0 38 #include "rdf.h"
michael@0 39
michael@0 40 using namespace mozilla;
michael@0 41 using namespace mozilla::dom;
michael@0 42
michael@0 43 //----------------------------------------------------------------------
michael@0 44 //
michael@0 45 // Return values for EnsureElementHasGenericChild()
michael@0 46 //
michael@0 47 #define NS_ELEMENT_GOT_CREATED NS_RDF_NO_VALUE
michael@0 48 #define NS_ELEMENT_WAS_THERE NS_OK
michael@0 49
michael@0 50 //----------------------------------------------------------------------
michael@0 51 //
michael@0 52 // nsXULContentBuilder
michael@0 53 //
michael@0 54
michael@0 55 /**
michael@0 56 * The content builder generates DOM nodes from a template. The actual content
michael@0 57 * generation is done entirely inside BuildContentFromTemplate.
michael@0 58 *
michael@0 59 * Content generation is centered around the generation node (the node with
michael@0 60 * uri="?member" on it). Nodes above the generation node are unique and
michael@0 61 * generated only once. BuildContentFromTemplate will be passed the unique
michael@0 62 * flag as an argument for content at this point and will recurse until it
michael@0 63 * finds the generation node.
michael@0 64 *
michael@0 65 * Once the generation node has been found, the results for that content node
michael@0 66 * are added to the content map, stored in mContentSupportMap.
michael@0 67 *
michael@0 68 * If recursion is allowed, generation continues, where the generation node
michael@0 69 * becomes the container to insert into.
michael@0 70 */
michael@0 71 class nsXULContentBuilder : public nsXULTemplateBuilder
michael@0 72 {
michael@0 73 public:
michael@0 74 // nsIXULTemplateBuilder interface
michael@0 75 NS_IMETHOD CreateContents(nsIContent* aElement, bool aForceCreation);
michael@0 76
michael@0 77 NS_IMETHOD HasGeneratedContent(nsIRDFResource* aResource,
michael@0 78 nsIAtom* aTag,
michael@0 79 bool* aGenerated);
michael@0 80
michael@0 81 NS_IMETHOD GetResultForContent(nsIDOMElement* aContent,
michael@0 82 nsIXULTemplateResult** aResult);
michael@0 83
michael@0 84 // nsIMutationObserver interface
michael@0 85 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
michael@0 86 NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
michael@0 87
michael@0 88 protected:
michael@0 89 friend nsresult
michael@0 90 NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult);
michael@0 91
michael@0 92 nsXULContentBuilder();
michael@0 93
michael@0 94 void Traverse(nsCycleCollectionTraversalCallback &cb) const
michael@0 95 {
michael@0 96 mSortState.Traverse(cb);
michael@0 97 }
michael@0 98
michael@0 99 virtual void Uninit(bool aIsFinal);
michael@0 100
michael@0 101 // Implementation methods
michael@0 102 nsresult
michael@0 103 OpenContainer(nsIContent* aElement);
michael@0 104
michael@0 105 nsresult
michael@0 106 CloseContainer(nsIContent* aElement);
michael@0 107
michael@0 108 /**
michael@0 109 * Build content from a template for a given result. This will be called
michael@0 110 * recursively or on demand and will be called for every node in the
michael@0 111 * generated content tree.
michael@0 112 */
michael@0 113 nsresult
michael@0 114 BuildContentFromTemplate(nsIContent *aTemplateNode,
michael@0 115 nsIContent *aResourceNode,
michael@0 116 nsIContent *aRealNode,
michael@0 117 bool aIsUnique,
michael@0 118 bool aIsSelfReference,
michael@0 119 nsIXULTemplateResult* aChild,
michael@0 120 bool aNotify,
michael@0 121 nsTemplateMatch* aMatch,
michael@0 122 nsIContent** aContainer,
michael@0 123 int32_t* aNewIndexInContainer);
michael@0 124
michael@0 125 /**
michael@0 126 * Copy the attributes from the template node to the node generated
michael@0 127 * from it, performing any substitutions.
michael@0 128 *
michael@0 129 * @param aTemplateNode node within template
michael@0 130 * @param aRealNode generated node to set attibutes upon
michael@0 131 * @param aResult result to look up variable->value bindings in
michael@0 132 * @param aNotify true to notify of DOM changes
michael@0 133 */
michael@0 134 nsresult
michael@0 135 CopyAttributesToElement(nsIContent* aTemplateNode,
michael@0 136 nsIContent* aRealNode,
michael@0 137 nsIXULTemplateResult* aResult,
michael@0 138 bool aNotify);
michael@0 139
michael@0 140 /**
michael@0 141 * Add any necessary persistent attributes (persist="...") from the
michael@0 142 * local store to a generated node.
michael@0 143 *
michael@0 144 * @param aTemplateNode node within template
michael@0 145 * @param aRealNode generated node to set persisted attibutes upon
michael@0 146 * @param aResult result to look up variable->value bindings in
michael@0 147 */
michael@0 148 nsresult
michael@0 149 AddPersistentAttributes(Element* aTemplateNode,
michael@0 150 nsIXULTemplateResult* aResult,
michael@0 151 nsIContent* aRealNode);
michael@0 152
michael@0 153 /**
michael@0 154 * Recalculate any attributes that have variable references. This will
michael@0 155 * be called when a binding has been changed to update the attributes.
michael@0 156 * The attributes are copied from the node aTemplateNode in the template
michael@0 157 * to the generated node aRealNode, using the values from the result
michael@0 158 * aResult. This method will operate recursively.
michael@0 159 *
michael@0 160 * @param aTemplateNode node within template
michael@0 161 * @param aRealNode generated node to set attibutes upon
michael@0 162 * @param aResult result to look up variable->value bindings in
michael@0 163 */
michael@0 164 nsresult
michael@0 165 SynchronizeUsingTemplate(nsIContent *aTemplateNode,
michael@0 166 nsIContent* aRealNode,
michael@0 167 nsIXULTemplateResult* aResult);
michael@0 168
michael@0 169 /**
michael@0 170 * Remove the generated node aContent from the DOM and the hashtables
michael@0 171 * used by the content builder.
michael@0 172 */
michael@0 173 nsresult
michael@0 174 RemoveMember(nsIContent* aContent);
michael@0 175
michael@0 176 /**
michael@0 177 * Create the appropriate generated content for aElement, by calling
michael@0 178 * CreateContainerContents.
michael@0 179 *
michael@0 180 * @param aElement element to generate content inside
michael@0 181 * @param aForceCreation true to force creation for closed items such as menus
michael@0 182 */
michael@0 183 nsresult
michael@0 184 CreateTemplateAndContainerContents(nsIContent* aElement,
michael@0 185 bool aForceCreation);
michael@0 186
michael@0 187 /**
michael@0 188 * Generate the results for a template, by calling
michael@0 189 * CreateContainerContentsForQuerySet for each queryset.
michael@0 190 *
michael@0 191 * @param aElement element to generate content inside
michael@0 192 * @param aResult reference point for query
michael@0 193 * @param aForceCreation true to force creation for closed items such as menus
michael@0 194 * @param aNotify true to notify of DOM changes as each element is inserted
michael@0 195 * @param aNotifyAtEnd notify at the end of all DOM changes
michael@0 196 */
michael@0 197 nsresult
michael@0 198 CreateContainerContents(nsIContent* aElement,
michael@0 199 nsIXULTemplateResult* aResult,
michael@0 200 bool aForceCreation,
michael@0 201 bool aNotify,
michael@0 202 bool aNotifyAtEnd);
michael@0 203
michael@0 204 /**
michael@0 205 * Generate the results for a query.
michael@0 206 *
michael@0 207 * @param aElement element to generate content inside
michael@0 208 * @param aResult reference point for query
michael@0 209 * @param aNotify true to notify of DOM changes
michael@0 210 * @param aContainer container content was added inside
michael@0 211 * @param aNewIndexInContainer index with container in which content was added
michael@0 212 */
michael@0 213 nsresult
michael@0 214 CreateContainerContentsForQuerySet(nsIContent* aElement,
michael@0 215 nsIXULTemplateResult* aResult,
michael@0 216 bool aNotify,
michael@0 217 nsTemplateQuerySet* aQuerySet,
michael@0 218 nsIContent** aContainer,
michael@0 219 int32_t* aNewIndexInContainer);
michael@0 220
michael@0 221 /**
michael@0 222 * Check if an element with a particular tag exists with a container.
michael@0 223 * If it is not present, append a new element with that tag into the
michael@0 224 * container.
michael@0 225 *
michael@0 226 * @param aParent parent container
michael@0 227 * @param aNameSpaceID namespace of tag to locate or create
michael@0 228 * @param aTag tag to locate or create
michael@0 229 * @param aNotify true to notify of DOM changes
michael@0 230 * @param aResult set to the found or created node.
michael@0 231 */
michael@0 232 nsresult
michael@0 233 EnsureElementHasGenericChild(nsIContent* aParent,
michael@0 234 int32_t aNameSpaceID,
michael@0 235 nsIAtom* aTag,
michael@0 236 bool aNotify,
michael@0 237 nsIContent** aResult);
michael@0 238
michael@0 239 bool
michael@0 240 IsOpen(nsIContent* aElement);
michael@0 241
michael@0 242 nsresult
michael@0 243 RemoveGeneratedContent(nsIContent* aElement);
michael@0 244
michael@0 245 nsresult
michael@0 246 GetElementsForResult(nsIXULTemplateResult* aResult,
michael@0 247 nsCOMArray<nsIContent>& aElements);
michael@0 248
michael@0 249 nsresult
michael@0 250 CreateElement(int32_t aNameSpaceID,
michael@0 251 nsIAtom* aTag,
michael@0 252 Element** aResult);
michael@0 253
michael@0 254 /**
michael@0 255 * Set the container and empty attributes on a node. If
michael@0 256 * aIgnoreNonContainers is true, then the element is not changed
michael@0 257 * for non-containers. Otherwise, the container attribute will be set to
michael@0 258 * false.
michael@0 259 *
michael@0 260 * @param aElement element to set attributes on
michael@0 261 * @param aResult result to use to determine state of attributes
michael@0 262 * @param aIgnoreNonContainers true to not change for non-containers
michael@0 263 * @param aNotify true to notify of DOM changes
michael@0 264 */
michael@0 265 nsresult
michael@0 266 SetContainerAttrs(nsIContent *aElement,
michael@0 267 nsIXULTemplateResult* aResult,
michael@0 268 bool aIgnoreNonContainers,
michael@0 269 bool aNotify);
michael@0 270
michael@0 271 virtual nsresult
michael@0 272 RebuildAll();
michael@0 273
michael@0 274 // GetInsertionLocations, ReplaceMatch and SynchronizeResult are inherited
michael@0 275 // from nsXULTemplateBuilder
michael@0 276
michael@0 277 /**
michael@0 278 * Return true if the result can be inserted into the template as
michael@0 279 * generated content. For the content builder, aLocations will be set
michael@0 280 * to the list of containers where the content should be inserted.
michael@0 281 */
michael@0 282 virtual bool
michael@0 283 GetInsertionLocations(nsIXULTemplateResult* aOldResult,
michael@0 284 nsCOMArray<nsIContent>** aLocations);
michael@0 285
michael@0 286 /**
michael@0 287 * Remove the content associated with aOldResult which no longer matches,
michael@0 288 * and/or generate content for a new match.
michael@0 289 */
michael@0 290 virtual nsresult
michael@0 291 ReplaceMatch(nsIXULTemplateResult* aOldResult,
michael@0 292 nsTemplateMatch* aNewMatch,
michael@0 293 nsTemplateRule* aNewMatchRule,
michael@0 294 void *aContext);
michael@0 295
michael@0 296 /**
michael@0 297 * Synchronize a result bindings with the generated content for that
michael@0 298 * result. This will be called as a result of the template builder's
michael@0 299 * ResultBindingChanged method.
michael@0 300 */
michael@0 301 virtual nsresult
michael@0 302 SynchronizeResult(nsIXULTemplateResult* aResult);
michael@0 303
michael@0 304 /**
michael@0 305 * Compare a result to a content node. If the generated content for the
michael@0 306 * result should come before aContent, set aSortOrder to -1. If it should
michael@0 307 * come after, set sortOrder to 1. If both are equal, set to 0.
michael@0 308 */
michael@0 309 nsresult
michael@0 310 CompareResultToNode(nsIXULTemplateResult* aResult, nsIContent* aContent,
michael@0 311 int32_t* aSortOrder);
michael@0 312
michael@0 313 /**
michael@0 314 * Insert a generated node into the container where it should go according
michael@0 315 * to the current sort. aNode is the generated content node and aResult is
michael@0 316 * the result for the generated node.
michael@0 317 */
michael@0 318 nsresult
michael@0 319 InsertSortedNode(nsIContent* aContainer,
michael@0 320 nsIContent* aNode,
michael@0 321 nsIXULTemplateResult* aResult,
michael@0 322 bool aNotify);
michael@0 323
michael@0 324 /**
michael@0 325 * Maintains a mapping between elements in the DOM and the matches
michael@0 326 * that they support.
michael@0 327 */
michael@0 328 nsContentSupportMap mContentSupportMap;
michael@0 329
michael@0 330 /**
michael@0 331 * Maintains a mapping from an element in the DOM to the template
michael@0 332 * element that it was created from.
michael@0 333 */
michael@0 334 nsTemplateMap mTemplateMap;
michael@0 335
michael@0 336 /**
michael@0 337 * Information about the currently active sort
michael@0 338 */
michael@0 339 nsSortState mSortState;
michael@0 340 };
michael@0 341
michael@0 342 nsresult
michael@0 343 NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult)
michael@0 344 {
michael@0 345 NS_PRECONDITION(aOuter == nullptr, "no aggregation");
michael@0 346 if (aOuter)
michael@0 347 return NS_ERROR_NO_AGGREGATION;
michael@0 348
michael@0 349 nsresult rv;
michael@0 350 nsXULContentBuilder* result = new nsXULContentBuilder();
michael@0 351 if (!result)
michael@0 352 return NS_ERROR_OUT_OF_MEMORY;
michael@0 353
michael@0 354 NS_ADDREF(result); // stabilize
michael@0 355
michael@0 356 rv = result->InitGlobals();
michael@0 357
michael@0 358 if (NS_SUCCEEDED(rv))
michael@0 359 rv = result->QueryInterface(aIID, aResult);
michael@0 360
michael@0 361 NS_RELEASE(result);
michael@0 362 return rv;
michael@0 363 }
michael@0 364
michael@0 365 nsXULContentBuilder::nsXULContentBuilder()
michael@0 366 {
michael@0 367 mSortState.initialized = false;
michael@0 368 }
michael@0 369
michael@0 370 void
michael@0 371 nsXULContentBuilder::Uninit(bool aIsFinal)
michael@0 372 {
michael@0 373 if (! aIsFinal && mRoot) {
michael@0 374 nsresult rv = RemoveGeneratedContent(mRoot);
michael@0 375 if (NS_FAILED(rv))
michael@0 376 return;
michael@0 377 }
michael@0 378
michael@0 379 // Nuke the content support map completely.
michael@0 380 mContentSupportMap.Clear();
michael@0 381 mTemplateMap.Clear();
michael@0 382
michael@0 383 mSortState.initialized = false;
michael@0 384
michael@0 385 nsXULTemplateBuilder::Uninit(aIsFinal);
michael@0 386 }
michael@0 387
michael@0 388 nsresult
michael@0 389 nsXULContentBuilder::BuildContentFromTemplate(nsIContent *aTemplateNode,
michael@0 390 nsIContent *aResourceNode,
michael@0 391 nsIContent *aRealNode,
michael@0 392 bool aIsUnique,
michael@0 393 bool aIsSelfReference,
michael@0 394 nsIXULTemplateResult* aChild,
michael@0 395 bool aNotify,
michael@0 396 nsTemplateMatch* aMatch,
michael@0 397 nsIContent** aContainer,
michael@0 398 int32_t* aNewIndexInContainer)
michael@0 399 {
michael@0 400 // This is the mother lode. Here is where we grovel through an
michael@0 401 // element in the template, copying children from the template
michael@0 402 // into the "real" content tree, performing substitution as we go
michael@0 403 // by looking stuff up using the results.
michael@0 404 //
michael@0 405 // |aTemplateNode| is the element in the "template tree", whose
michael@0 406 // children we will duplicate and move into the "real" content
michael@0 407 // tree.
michael@0 408 //
michael@0 409 // |aResourceNode| is the element in the "real" content tree that
michael@0 410 // has the "id" attribute set to an result's id. This is
michael@0 411 // not directly used here, but rather passed down to the XUL
michael@0 412 // sort service to perform container-level sort.
michael@0 413 //
michael@0 414 // |aRealNode| is the element in the "real" content tree to which
michael@0 415 // the new elements will be copied.
michael@0 416 //
michael@0 417 // |aIsUnique| is set to "true" so long as content has been
michael@0 418 // "unique" (or "above" the resource element) so far in the
michael@0 419 // template.
michael@0 420 //
michael@0 421 // |aIsSelfReference| should be set to "true" for cases where
michael@0 422 // the reference and member variables are the same, indicating
michael@0 423 // that the generated node is the same as the reference point,
michael@0 424 // so generation should not recurse, or else an infinite loop
michael@0 425 // would occur.
michael@0 426 //
michael@0 427 // |aChild| is the result for which we are building content.
michael@0 428 //
michael@0 429 // |aNotify| is set to "true" if content should be constructed
michael@0 430 // "noisily"; that is, whether the document observers should be
michael@0 431 // notified when new content is added to the content model.
michael@0 432 //
michael@0 433 // |aContainer| is an out parameter that will be set to the first
michael@0 434 // container element in the "real" content tree to which content
michael@0 435 // was appended.
michael@0 436 //
michael@0 437 // |aNewIndexInContainer| is an out parameter that will be set to
michael@0 438 // the index in aContainer at which new content is first
michael@0 439 // constructed.
michael@0 440 //
michael@0 441 // If |aNotify| is "false", then |aContainer| and
michael@0 442 // |aNewIndexInContainer| are used to determine where in the
michael@0 443 // content model new content is constructed. This allows a single
michael@0 444 // notification to be propagated to document observers.
michael@0 445 //
michael@0 446
michael@0 447 nsresult rv;
michael@0 448
michael@0 449 #ifdef PR_LOGGING
michael@0 450 if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) {
michael@0 451 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
michael@0 452 ("nsXULContentBuilder::BuildContentFromTemplate (is unique: %d)",
michael@0 453 aIsUnique));
michael@0 454
michael@0 455 nsAutoString id;
michael@0 456 aChild->GetId(id);
michael@0 457
michael@0 458 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
michael@0 459 ("Tags: [Template: %s Resource: %s Real: %s] for id %s",
michael@0 460 nsAtomCString(aTemplateNode->Tag()).get(),
michael@0 461 nsAtomCString(aResourceNode->Tag()).get(),
michael@0 462 nsAtomCString(aRealNode->Tag()).get(), NS_ConvertUTF16toUTF8(id).get()));
michael@0 463 }
michael@0 464 #endif
michael@0 465
michael@0 466 // Iterate through all of the template children, constructing
michael@0 467 // "real" content model nodes for each "template" child.
michael@0 468 for (nsIContent* tmplKid = aTemplateNode->GetFirstChild();
michael@0 469 tmplKid;
michael@0 470 tmplKid = tmplKid->GetNextSibling()) {
michael@0 471
michael@0 472 int32_t nameSpaceID = tmplKid->GetNameSpaceID();
michael@0 473
michael@0 474 // Check whether this element is the generation element. The generation
michael@0 475 // element is the element that is cookie-cutter copied once for each
michael@0 476 // different result specified by |aChild|.
michael@0 477 //
michael@0 478 // Nodes that appear -above- the generation element
michael@0 479 // (that is, are ancestors of the generation element in the
michael@0 480 // content model) are unique across all values of |aChild|,
michael@0 481 // and are created only once.
michael@0 482 //
michael@0 483 // Nodes that appear -below- the generation element (that is,
michael@0 484 // are descendants of the generation element in the content
michael@0 485 // model), are cookie-cutter copied for each distinct value of
michael@0 486 // |aChild|.
michael@0 487 //
michael@0 488 // For example, in a <tree> template:
michael@0 489 //
michael@0 490 // <tree>
michael@0 491 // <template>
michael@0 492 // <treechildren> [1]
michael@0 493 // <treeitem uri="rdf:*"> [2]
michael@0 494 // <treerow> [3]
michael@0 495 // <treecell value="rdf:urn:foo" /> [4]
michael@0 496 // <treecell value="rdf:urn:bar" /> [5]
michael@0 497 // </treerow>
michael@0 498 // </treeitem>
michael@0 499 // </treechildren>
michael@0 500 // </template>
michael@0 501 // </tree>
michael@0 502 //
michael@0 503 // The <treeitem> element [2] is the generation element. This
michael@0 504 // element, and all of its descendants ([3], [4], and [5])
michael@0 505 // will be duplicated for each different |aChild|.
michael@0 506 // It's ancestor <treechildren> [1] is unique, and
michael@0 507 // will only be created -once-, no matter how many <treeitem>s
michael@0 508 // are created below it.
michael@0 509 //
michael@0 510 // isUnique will be true for nodes above the generation element,
michael@0 511 // isGenerationElement will be true for the generation element,
michael@0 512 // and both will be false for descendants
michael@0 513 bool isGenerationElement = false;
michael@0 514 bool isUnique = aIsUnique;
michael@0 515
michael@0 516 // We identify the resource element by presence of a
michael@0 517 // "uri='rdf:*'" attribute. (We also support the older
michael@0 518 // "uri='...'" syntax.)
michael@0 519 if (tmplKid->HasAttr(kNameSpaceID_None, nsGkAtoms::uri) && aMatch->IsActive()) {
michael@0 520 isGenerationElement = true;
michael@0 521 isUnique = false;
michael@0 522 }
michael@0 523
michael@0 524 MOZ_ASSERT_IF(isGenerationElement, tmplKid->IsElement());
michael@0 525
michael@0 526 nsIAtom *tag = tmplKid->Tag();
michael@0 527
michael@0 528 #ifdef PR_LOGGING
michael@0 529 if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) {
michael@0 530 PR_LOG(gXULTemplateLog, PR_LOG_DEBUG,
michael@0 531 ("xultemplate[%p] building %s %s %s",
michael@0 532 this, nsAtomCString(tag).get(),
michael@0 533 (isGenerationElement ? "[resource]" : ""),
michael@0 534 (isUnique ? "[unique]" : "")));
michael@0 535 }
michael@0 536 #endif
michael@0 537
michael@0 538 // Set to true if the child we're trying to create now
michael@0 539 // already existed in the content model.
michael@0 540 bool realKidAlreadyExisted = false;
michael@0 541
michael@0 542 nsCOMPtr<nsIContent> realKid;
michael@0 543 if (isUnique) {
michael@0 544 // The content is "unique"; that is, we haven't descended
michael@0 545 // far enough into the template to hit the generation
michael@0 546 // element yet. |EnsureElementHasGenericChild()| will
michael@0 547 // conditionally create the element iff it isn't there
michael@0 548 // already.
michael@0 549 rv = EnsureElementHasGenericChild(aRealNode, nameSpaceID, tag, aNotify, getter_AddRefs(realKid));
michael@0 550 if (NS_FAILED(rv))
michael@0 551 return rv;
michael@0 552
michael@0 553 if (rv == NS_ELEMENT_WAS_THERE) {
michael@0 554 realKidAlreadyExisted = true;
michael@0 555 }
michael@0 556 else {
michael@0 557 // Potentially remember the index of this element as the first
michael@0 558 // element that we've generated. Note that we remember
michael@0 559 // this -before- we recurse!
michael@0 560 if (aContainer && !*aContainer) {
michael@0 561 *aContainer = aRealNode;
michael@0 562 NS_ADDREF(*aContainer);
michael@0 563
michael@0 564 uint32_t indx = aRealNode->GetChildCount();
michael@0 565
michael@0 566 // Since EnsureElementHasGenericChild() added us, make
michael@0 567 // sure to subtract one for our real index.
michael@0 568 *aNewIndexInContainer = indx - 1;
michael@0 569 }
michael@0 570 }
michael@0 571
michael@0 572 // Recurse until we get to the resource element. Since
michael@0 573 // -we're- unique, assume that our child will be
michael@0 574 // unique. The check for the "resource" element at the top
michael@0 575 // of the function will trip this to |false| as soon as we
michael@0 576 // encounter it.
michael@0 577 rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, true,
michael@0 578 aIsSelfReference, aChild, aNotify, aMatch,
michael@0 579 aContainer, aNewIndexInContainer);
michael@0 580
michael@0 581 if (NS_FAILED(rv))
michael@0 582 return rv;
michael@0 583 }
michael@0 584 else if (isGenerationElement) {
michael@0 585 // It's the "resource" element. Create a new element using
michael@0 586 // the namespace ID and tag from the template element.
michael@0 587 nsCOMPtr<Element> element;
michael@0 588 rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
michael@0 589 if (NS_FAILED(rv))
michael@0 590 return rv;
michael@0 591 realKid = element.forget();
michael@0 592
michael@0 593 // Add the resource element to the content support map so
michael@0 594 // we can remove the match based on the content node later.
michael@0 595 mContentSupportMap.Put(realKid, aMatch);
michael@0 596
michael@0 597 // Assign the element an 'id' attribute using result's id
michael@0 598 nsAutoString id;
michael@0 599 rv = aChild->GetId(id);
michael@0 600 if (NS_FAILED(rv))
michael@0 601 return rv;
michael@0 602
michael@0 603 rv = realKid->SetAttr(kNameSpaceID_None, nsGkAtoms::id, id, false);
michael@0 604 if (NS_FAILED(rv))
michael@0 605 return rv;
michael@0 606
michael@0 607 // Set up the element's 'container' and 'empty' attributes.
michael@0 608 SetContainerAttrs(realKid, aChild, true, false);
michael@0 609 }
michael@0 610 else if (tag == nsGkAtoms::textnode &&
michael@0 611 nameSpaceID == kNameSpaceID_XUL) {
michael@0 612 // <xul:text value="..."> is replaced by text of the
michael@0 613 // actual value of the 'rdf:resource' attribute for the
michael@0 614 // given node.
michael@0 615 // SynchronizeUsingTemplate contains code used to update textnodes,
michael@0 616 // so make sure to modify both when changing this
michael@0 617 char16_t attrbuf[128];
michael@0 618 nsFixedString attrValue(attrbuf, ArrayLength(attrbuf), 0);
michael@0 619 tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
michael@0 620 if (!attrValue.IsEmpty()) {
michael@0 621 nsAutoString value;
michael@0 622 rv = SubstituteText(aChild, attrValue, value);
michael@0 623 if (NS_FAILED(rv)) return rv;
michael@0 624
michael@0 625 nsRefPtr<nsTextNode> content =
michael@0 626 new nsTextNode(mRoot->NodeInfo()->NodeInfoManager());
michael@0 627
michael@0 628 content->SetText(value, false);
michael@0 629
michael@0 630 rv = aRealNode->AppendChildTo(content, aNotify);
michael@0 631 if (NS_FAILED(rv)) return rv;
michael@0 632
michael@0 633 // XXX Don't bother remembering text nodes as the
michael@0 634 // first element we've generated?
michael@0 635 }
michael@0 636 }
michael@0 637 else if (tmplKid->IsNodeOfType(nsINode::eTEXT)) {
michael@0 638 nsCOMPtr<nsIDOMNode> tmplTextNode = do_QueryInterface(tmplKid);
michael@0 639 if (!tmplTextNode) {
michael@0 640 NS_ERROR("textnode not implementing nsIDOMNode??");
michael@0 641 return NS_ERROR_FAILURE;
michael@0 642 }
michael@0 643 nsCOMPtr<nsIDOMNode> clonedNode;
michael@0 644 tmplTextNode->CloneNode(false, 1, getter_AddRefs(clonedNode));
michael@0 645 nsCOMPtr<nsIContent> clonedContent = do_QueryInterface(clonedNode);
michael@0 646 if (!clonedContent) {
michael@0 647 NS_ERROR("failed to clone textnode");
michael@0 648 return NS_ERROR_FAILURE;
michael@0 649 }
michael@0 650 rv = aRealNode->AppendChildTo(clonedContent, aNotify);
michael@0 651 if (NS_FAILED(rv)) return rv;
michael@0 652 }
michael@0 653 else {
michael@0 654 // It's just a generic element. Create it!
michael@0 655 nsCOMPtr<Element> element;
michael@0 656 rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
michael@0 657 if (NS_FAILED(rv)) return rv;
michael@0 658 realKid = element.forget();
michael@0 659 }
michael@0 660
michael@0 661 if (realKid && !realKidAlreadyExisted) {
michael@0 662 // Potentially remember the index of this element as the
michael@0 663 // first element that we've generated.
michael@0 664 if (aContainer && !*aContainer) {
michael@0 665 *aContainer = aRealNode;
michael@0 666 NS_ADDREF(*aContainer);
michael@0 667
michael@0 668 uint32_t indx = aRealNode->GetChildCount();
michael@0 669
michael@0 670 // Since we haven't inserted any content yet, our new
michael@0 671 // index in the container will be the current count of
michael@0 672 // elements in the container.
michael@0 673 *aNewIndexInContainer = indx;
michael@0 674 }
michael@0 675
michael@0 676 // Remember the template kid from which we created the
michael@0 677 // real kid. This allows us to sync back up with the
michael@0 678 // template to incrementally build content.
michael@0 679 mTemplateMap.Put(realKid, tmplKid);
michael@0 680
michael@0 681 rv = CopyAttributesToElement(tmplKid, realKid, aChild, false);
michael@0 682 if (NS_FAILED(rv)) return rv;
michael@0 683
michael@0 684 // Add any persistent attributes
michael@0 685 if (isGenerationElement) {
michael@0 686 rv = AddPersistentAttributes(tmplKid->AsElement(), aChild,
michael@0 687 realKid);
michael@0 688 if (NS_FAILED(rv)) return rv;
michael@0 689 }
michael@0 690
michael@0 691 // the unique content recurses up above. Also, don't recurse if
michael@0 692 // this is a self reference (a reference to the same resource)
michael@0 693 // or we'll end up regenerating the same content.
michael@0 694 if (!aIsSelfReference && !isUnique) {
michael@0 695 // this call creates the content inside the generation node,
michael@0 696 // for example the label below:
michael@0 697 // <vbox uri="?">
michael@0 698 // <label value="?title"/>
michael@0 699 // </vbox>
michael@0 700 rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, false,
michael@0 701 false, aChild, false, aMatch,
michael@0 702 nullptr /* don't care */,
michael@0 703 nullptr /* don't care */);
michael@0 704 if (NS_FAILED(rv)) return rv;
michael@0 705
michael@0 706 if (isGenerationElement) {
michael@0 707 // build the next level of children
michael@0 708 rv = CreateContainerContents(realKid, aChild, false,
michael@0 709 false, false);
michael@0 710 if (NS_FAILED(rv)) return rv;
michael@0 711 }
michael@0 712 }
michael@0 713
michael@0 714 // We'll _already_ have added the unique elements; but if
michael@0 715 // it's -not- unique, then use the XUL sort service now to
michael@0 716 // append the element to the content model.
michael@0 717 if (! isUnique) {
michael@0 718 rv = NS_ERROR_UNEXPECTED;
michael@0 719
michael@0 720 if (isGenerationElement)
michael@0 721 rv = InsertSortedNode(aRealNode, realKid, aChild, aNotify);
michael@0 722
michael@0 723 if (NS_FAILED(rv)) {
michael@0 724 rv = aRealNode->AppendChildTo(realKid, aNotify);
michael@0 725 NS_ASSERTION(NS_SUCCEEDED(rv), "unable to insert element");
michael@0 726 }
michael@0 727 }
michael@0 728 }
michael@0 729 }
michael@0 730
michael@0 731 return NS_OK;
michael@0 732 }
michael@0 733
michael@0 734 nsresult
michael@0 735 nsXULContentBuilder::CopyAttributesToElement(nsIContent* aTemplateNode,
michael@0 736 nsIContent* aRealNode,
michael@0 737 nsIXULTemplateResult* aResult,
michael@0 738 bool aNotify)
michael@0 739 {
michael@0 740 nsresult rv;
michael@0 741
michael@0 742 // Copy all attributes from the template to the new element
michael@0 743 uint32_t numAttribs = aTemplateNode->GetAttrCount();
michael@0 744
michael@0 745 for (uint32_t attr = 0; attr < numAttribs; attr++) {
michael@0 746 const nsAttrName* name = aTemplateNode->GetAttrNameAt(attr);
michael@0 747 int32_t attribNameSpaceID = name->NamespaceID();
michael@0 748 // Hold a strong reference here so that the atom doesn't go away
michael@0 749 // during UnsetAttr.
michael@0 750 nsCOMPtr<nsIAtom> attribName = name->LocalName();
michael@0 751
michael@0 752 // XXXndeakin ignore namespaces until bug 321182 is fixed
michael@0 753 if (attribName != nsGkAtoms::id && attribName != nsGkAtoms::uri) {
michael@0 754 // Create a buffer here, because there's a chance that an
michael@0 755 // attribute in the template is going to be an RDF URI, which is
michael@0 756 // usually longish.
michael@0 757 char16_t attrbuf[128];
michael@0 758 nsFixedString attribValue(attrbuf, ArrayLength(attrbuf), 0);
michael@0 759 aTemplateNode->GetAttr(attribNameSpaceID, attribName, attribValue);
michael@0 760 if (!attribValue.IsEmpty()) {
michael@0 761 nsAutoString value;
michael@0 762 rv = SubstituteText(aResult, attribValue, value);
michael@0 763 if (NS_FAILED(rv))
michael@0 764 return rv;
michael@0 765
michael@0 766 // if the string is empty after substitutions, remove the
michael@0 767 // attribute
michael@0 768 if (!value.IsEmpty()) {
michael@0 769 rv = aRealNode->SetAttr(attribNameSpaceID,
michael@0 770 attribName,
michael@0 771 name->GetPrefix(),
michael@0 772 value,
michael@0 773 aNotify);
michael@0 774 }
michael@0 775 else {
michael@0 776 rv = aRealNode->UnsetAttr(attribNameSpaceID,
michael@0 777 attribName,
michael@0 778 aNotify);
michael@0 779 }
michael@0 780
michael@0 781 if (NS_FAILED(rv))
michael@0 782 return rv;
michael@0 783 }
michael@0 784 }
michael@0 785 }
michael@0 786
michael@0 787 return NS_OK;
michael@0 788 }
michael@0 789
michael@0 790 nsresult
michael@0 791 nsXULContentBuilder::AddPersistentAttributes(Element* aTemplateNode,
michael@0 792 nsIXULTemplateResult* aResult,
michael@0 793 nsIContent* aRealNode)
michael@0 794 {
michael@0 795 if (!mRoot)
michael@0 796 return NS_OK;
michael@0 797
michael@0 798 nsCOMPtr<nsIRDFResource> resource;
michael@0 799 nsresult rv = GetResultResource(aResult, getter_AddRefs(resource));
michael@0 800 NS_ENSURE_SUCCESS(rv, rv);
michael@0 801
michael@0 802 nsAutoString attribute, persist;
michael@0 803 aTemplateNode->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
michael@0 804
michael@0 805 while (!persist.IsEmpty()) {
michael@0 806 attribute.Truncate();
michael@0 807
michael@0 808 int32_t offset = persist.FindCharInSet(" ,");
michael@0 809 if (offset > 0) {
michael@0 810 persist.Left(attribute, offset);
michael@0 811 persist.Cut(0, offset + 1);
michael@0 812 }
michael@0 813 else {
michael@0 814 attribute = persist;
michael@0 815 persist.Truncate();
michael@0 816 }
michael@0 817
michael@0 818 attribute.Trim(" ");
michael@0 819
michael@0 820 if (attribute.IsEmpty())
michael@0 821 break;
michael@0 822
michael@0 823 nsCOMPtr<nsIAtom> tag;
michael@0 824 int32_t nameSpaceID;
michael@0 825
michael@0 826 nsCOMPtr<nsINodeInfo> ni =
michael@0 827 aTemplateNode->GetExistingAttrNameFromQName(attribute);
michael@0 828 if (ni) {
michael@0 829 tag = ni->NameAtom();
michael@0 830 nameSpaceID = ni->NamespaceID();
michael@0 831 }
michael@0 832 else {
michael@0 833 tag = do_GetAtom(attribute);
michael@0 834 NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
michael@0 835
michael@0 836 nameSpaceID = kNameSpaceID_None;
michael@0 837 }
michael@0 838
michael@0 839 nsCOMPtr<nsIRDFResource> property;
michael@0 840 rv = nsXULContentUtils::GetResource(nameSpaceID, tag, getter_AddRefs(property));
michael@0 841 NS_ENSURE_SUCCESS(rv, rv);
michael@0 842
michael@0 843 nsCOMPtr<nsIRDFNode> target;
michael@0 844 rv = mDB->GetTarget(resource, property, true, getter_AddRefs(target));
michael@0 845 NS_ENSURE_SUCCESS(rv, rv);
michael@0 846
michael@0 847 if (! target)
michael@0 848 continue;
michael@0 849
michael@0 850 nsCOMPtr<nsIRDFLiteral> value = do_QueryInterface(target);
michael@0 851 NS_ASSERTION(value != nullptr, "unable to stomach that sort of node");
michael@0 852 if (! value)
michael@0 853 continue;
michael@0 854
michael@0 855 const char16_t* valueStr;
michael@0 856 rv = value->GetValueConst(&valueStr);
michael@0 857 NS_ENSURE_SUCCESS(rv, rv);
michael@0 858
michael@0 859 rv = aRealNode->SetAttr(nameSpaceID, tag, nsDependentString(valueStr),
michael@0 860 false);
michael@0 861 NS_ENSURE_SUCCESS(rv, rv);
michael@0 862 }
michael@0 863
michael@0 864 return NS_OK;
michael@0 865 }
michael@0 866
michael@0 867 nsresult
michael@0 868 nsXULContentBuilder::SynchronizeUsingTemplate(nsIContent* aTemplateNode,
michael@0 869 nsIContent* aRealElement,
michael@0 870 nsIXULTemplateResult* aResult)
michael@0 871 {
michael@0 872 // check all attributes on the template node; if they reference a resource,
michael@0 873 // update the equivalent attribute on the content node
michael@0 874 nsresult rv;
michael@0 875 rv = CopyAttributesToElement(aTemplateNode, aRealElement, aResult, true);
michael@0 876 if (NS_FAILED(rv))
michael@0 877 return rv;
michael@0 878
michael@0 879 uint32_t count = aTemplateNode->GetChildCount();
michael@0 880
michael@0 881 for (uint32_t loop = 0; loop < count; ++loop) {
michael@0 882 nsIContent *tmplKid = aTemplateNode->GetChildAt(loop);
michael@0 883
michael@0 884 if (! tmplKid)
michael@0 885 break;
michael@0 886
michael@0 887 nsIContent *realKid = aRealElement->GetChildAt(loop);
michael@0 888 if (! realKid)
michael@0 889 break;
michael@0 890
michael@0 891 // check for text nodes and update them accordingly.
michael@0 892 // This code is similar to that in BuildContentFromTemplate
michael@0 893 if (tmplKid->NodeInfo()->Equals(nsGkAtoms::textnode,
michael@0 894 kNameSpaceID_XUL)) {
michael@0 895 char16_t attrbuf[128];
michael@0 896 nsFixedString attrValue(attrbuf, ArrayLength(attrbuf), 0);
michael@0 897 tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
michael@0 898 if (!attrValue.IsEmpty()) {
michael@0 899 nsAutoString value;
michael@0 900 rv = SubstituteText(aResult, attrValue, value);
michael@0 901 if (NS_FAILED(rv)) return rv;
michael@0 902 realKid->SetText(value, true);
michael@0 903 }
michael@0 904 }
michael@0 905
michael@0 906 rv = SynchronizeUsingTemplate(tmplKid, realKid, aResult);
michael@0 907 if (NS_FAILED(rv)) return rv;
michael@0 908 }
michael@0 909
michael@0 910 return NS_OK;
michael@0 911 }
michael@0 912
michael@0 913 nsresult
michael@0 914 nsXULContentBuilder::RemoveMember(nsIContent* aContent)
michael@0 915 {
michael@0 916 nsCOMPtr<nsIContent> parent = aContent->GetParent();
michael@0 917 if (parent) {
michael@0 918 int32_t pos = parent->IndexOf(aContent);
michael@0 919
michael@0 920 NS_ASSERTION(pos >= 0, "parent doesn't think this child has an index");
michael@0 921 if (pos < 0) return NS_OK;
michael@0 922
michael@0 923 // Note: RemoveChildAt sets |child|'s document to null so that
michael@0 924 // it'll get knocked out of the XUL doc's resource-to-element
michael@0 925 // map.
michael@0 926 parent->RemoveChildAt(pos, true);
michael@0 927 }
michael@0 928
michael@0 929 // Remove from the content support map.
michael@0 930 mContentSupportMap.Remove(aContent);
michael@0 931
michael@0 932 // Remove from the template map
michael@0 933 mTemplateMap.Remove(aContent);
michael@0 934
michael@0 935 return NS_OK;
michael@0 936 }
michael@0 937
michael@0 938 nsresult
michael@0 939 nsXULContentBuilder::CreateTemplateAndContainerContents(nsIContent* aElement,
michael@0 940 bool aForceCreation)
michael@0 941 {
michael@0 942 // Generate both 1) the template content for the current element,
michael@0 943 // and 2) recursive subcontent (if the current element refers to a
michael@0 944 // container result).
michael@0 945
michael@0 946 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
michael@0 947 ("nsXULContentBuilder::CreateTemplateAndContainerContents start - flags: %d",
michael@0 948 mFlags));
michael@0 949
michael@0 950 if (! mQueryProcessor)
michael@0 951 return NS_OK;
michael@0 952
michael@0 953 // for the root element, get the ref attribute and generate content
michael@0 954 if (aElement == mRoot) {
michael@0 955 if (! mRootResult) {
michael@0 956 nsAutoString ref;
michael@0 957 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, ref);
michael@0 958
michael@0 959 if (! ref.IsEmpty()) {
michael@0 960 nsresult rv = mQueryProcessor->TranslateRef(mDataSource, ref,
michael@0 961 getter_AddRefs(mRootResult));
michael@0 962 if (NS_FAILED(rv))
michael@0 963 return rv;
michael@0 964 }
michael@0 965 }
michael@0 966
michael@0 967 if (mRootResult) {
michael@0 968 CreateContainerContents(aElement, mRootResult, aForceCreation,
michael@0 969 false, true);
michael@0 970 }
michael@0 971 }
michael@0 972 else if (!(mFlags & eDontRecurse)) {
michael@0 973 // The content map will contain the generation elements (the ones that
michael@0 974 // are given ids) and only those elements, so get the reference point
michael@0 975 // from the corresponding match.
michael@0 976 nsTemplateMatch *match = nullptr;
michael@0 977 if (mContentSupportMap.Get(aElement, &match))
michael@0 978 CreateContainerContents(aElement, match->mResult, aForceCreation,
michael@0 979 false, true);
michael@0 980 }
michael@0 981
michael@0 982 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
michael@0 983 ("nsXULContentBuilder::CreateTemplateAndContainerContents end"));
michael@0 984
michael@0 985 return NS_OK;
michael@0 986 }
michael@0 987
michael@0 988 nsresult
michael@0 989 nsXULContentBuilder::CreateContainerContents(nsIContent* aElement,
michael@0 990 nsIXULTemplateResult* aResult,
michael@0 991 bool aForceCreation,
michael@0 992 bool aNotify,
michael@0 993 bool aNotifyAtEnd)
michael@0 994 {
michael@0 995 if (!aForceCreation && !IsOpen(aElement))
michael@0 996 return NS_OK;
michael@0 997
michael@0 998 // don't generate children if recursion or child processing isn't allowed
michael@0 999 if (aResult != mRootResult) {
michael@0 1000 if (mFlags & eDontRecurse)
michael@0 1001 return NS_OK;
michael@0 1002
michael@0 1003 bool mayProcessChildren;
michael@0 1004 nsresult rv = aResult->GetMayProcessChildren(&mayProcessChildren);
michael@0 1005 if (NS_FAILED(rv) || !mayProcessChildren)
michael@0 1006 return rv;
michael@0 1007 }
michael@0 1008
michael@0 1009 nsCOMPtr<nsIRDFResource> refResource;
michael@0 1010 GetResultResource(aResult, getter_AddRefs(refResource));
michael@0 1011 if (! refResource)
michael@0 1012 return NS_ERROR_FAILURE;
michael@0 1013
michael@0 1014 // Avoid re-entrant builds for the same resource.
michael@0 1015 if (IsActivated(refResource))
michael@0 1016 return NS_OK;
michael@0 1017
michael@0 1018 ActivationEntry entry(refResource, &mTop);
michael@0 1019
michael@0 1020 // Compile the rules now, if they haven't been already.
michael@0 1021 if (! mQueriesCompiled) {
michael@0 1022 nsresult rv = CompileQueries();
michael@0 1023 if (NS_FAILED(rv))
michael@0 1024 return rv;
michael@0 1025 }
michael@0 1026
michael@0 1027 if (mQuerySets.Length() == 0)
michael@0 1028 return NS_OK;
michael@0 1029
michael@0 1030 // See if the element's templates contents have been generated:
michael@0 1031 // this prevents a re-entrant call from triggering another
michael@0 1032 // generation.
michael@0 1033 nsXULElement *xulcontent = nsXULElement::FromContent(aElement);
michael@0 1034 if (xulcontent) {
michael@0 1035 if (xulcontent->GetTemplateGenerated())
michael@0 1036 return NS_OK;
michael@0 1037
michael@0 1038 // Now mark the element's contents as being generated so that
michael@0 1039 // any re-entrant calls don't trigger an infinite recursion.
michael@0 1040 xulcontent->SetTemplateGenerated();
michael@0 1041 }
michael@0 1042
michael@0 1043 int32_t newIndexInContainer = -1;
michael@0 1044 nsIContent* container = nullptr;
michael@0 1045
michael@0 1046 int32_t querySetCount = mQuerySets.Length();
michael@0 1047
michael@0 1048 for (int32_t r = 0; r < querySetCount; r++) {
michael@0 1049 nsTemplateQuerySet* queryset = mQuerySets[r];
michael@0 1050
michael@0 1051 nsIAtom* tag = queryset->GetTag();
michael@0 1052 if (tag && tag != aElement->Tag())
michael@0 1053 continue;
michael@0 1054
michael@0 1055 CreateContainerContentsForQuerySet(aElement, aResult, aNotify, queryset,
michael@0 1056 &container, &newIndexInContainer);
michael@0 1057 }
michael@0 1058
michael@0 1059 if (aNotifyAtEnd && container) {
michael@0 1060 MOZ_AUTO_DOC_UPDATE(container->GetCurrentDoc(), UPDATE_CONTENT_MODEL,
michael@0 1061 true);
michael@0 1062 nsNodeUtils::ContentAppended(container,
michael@0 1063 container->GetChildAt(newIndexInContainer),
michael@0 1064 newIndexInContainer);
michael@0 1065 }
michael@0 1066
michael@0 1067 NS_IF_RELEASE(container);
michael@0 1068
michael@0 1069 return NS_OK;
michael@0 1070 }
michael@0 1071
michael@0 1072 nsresult
michael@0 1073 nsXULContentBuilder::CreateContainerContentsForQuerySet(nsIContent* aElement,
michael@0 1074 nsIXULTemplateResult* aResult,
michael@0 1075 bool aNotify,
michael@0 1076 nsTemplateQuerySet* aQuerySet,
michael@0 1077 nsIContent** aContainer,
michael@0 1078 int32_t* aNewIndexInContainer)
michael@0 1079 {
michael@0 1080 #ifdef PR_LOGGING
michael@0 1081 if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) {
michael@0 1082 nsAutoString id;
michael@0 1083 aResult->GetId(id);
michael@0 1084 PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
michael@0 1085 ("nsXULContentBuilder::CreateContainerContentsForQuerySet start for ref %s\n",
michael@0 1086 NS_ConvertUTF16toUTF8(id).get()));
michael@0 1087 }
michael@0 1088 #endif
michael@0 1089
michael@0 1090 if (! mQueryProcessor)
michael@0 1091 return NS_OK;
michael@0 1092
michael@0 1093 nsCOMPtr<nsISimpleEnumerator> results;
michael@0 1094 nsresult rv = mQueryProcessor->GenerateResults(mDataSource, aResult,
michael@0 1095 aQuerySet->mCompiledQuery,
michael@0 1096 getter_AddRefs(results));
michael@0 1097 if (NS_FAILED(rv) || !results)
michael@0 1098 return rv;
michael@0 1099
michael@0 1100 bool hasMoreResults;
michael@0 1101 rv = results->HasMoreElements(&hasMoreResults);
michael@0 1102
michael@0 1103 for (; NS_SUCCEEDED(rv) && hasMoreResults;
michael@0 1104 rv = results->HasMoreElements(&hasMoreResults)) {
michael@0 1105 nsCOMPtr<nsISupports> nr;
michael@0 1106 rv = results->GetNext(getter_AddRefs(nr));
michael@0 1107 if (NS_FAILED(rv))
michael@0 1108 return rv;
michael@0 1109
michael@0 1110 nsCOMPtr<nsIXULTemplateResult> nextresult = do_QueryInterface(nr);
michael@0 1111 if (!nextresult)
michael@0 1112 return NS_ERROR_UNEXPECTED;
michael@0 1113
michael@0 1114 nsCOMPtr<nsIRDFResource> resultid;
michael@0 1115 rv = GetResultResource(nextresult, getter_AddRefs(resultid));
michael@0 1116 if (NS_FAILED(rv))
michael@0 1117 return rv;
michael@0 1118
michael@0 1119 if (!resultid)
michael@0 1120 continue;
michael@0 1121
michael@0 1122 nsTemplateMatch *newmatch =
michael@0 1123 nsTemplateMatch::Create(aQuerySet->Priority(),
michael@0 1124 nextresult, aElement);
michael@0 1125 if (!newmatch)
michael@0 1126 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1127
michael@0 1128 // check if there is already an existing match. If so, a previous
michael@0 1129 // query already generated content so the match is just added to the
michael@0 1130 // end of the set of matches.
michael@0 1131
michael@0 1132 bool generateContent = true;
michael@0 1133
michael@0 1134 nsTemplateMatch* prevmatch = nullptr;
michael@0 1135 nsTemplateMatch* existingmatch = nullptr;
michael@0 1136 nsTemplateMatch* removematch = nullptr;
michael@0 1137 if (mMatchMap.Get(resultid, &existingmatch)){
michael@0 1138 // check if there is an existing match that matched a rule
michael@0 1139 while (existingmatch) {
michael@0 1140 // break out once we've reached a query in the list with a
michael@0 1141 // higher priority, as the new match list is sorted by
michael@0 1142 // priority, and the new match should be inserted here
michael@0 1143 int32_t priority = existingmatch->QuerySetPriority();
michael@0 1144 if (priority > aQuerySet->Priority())
michael@0 1145 break;
michael@0 1146
michael@0 1147 // skip over non-matching containers
michael@0 1148 if (existingmatch->GetContainer() == aElement) {
michael@0 1149 // if the same priority is already found, replace it. This can happen
michael@0 1150 // when a container is removed and readded
michael@0 1151 if (priority == aQuerySet->Priority()) {
michael@0 1152 removematch = existingmatch;
michael@0 1153 break;
michael@0 1154 }
michael@0 1155
michael@0 1156 if (existingmatch->IsActive())
michael@0 1157 generateContent = false;
michael@0 1158 }
michael@0 1159
michael@0 1160 prevmatch = existingmatch;
michael@0 1161 existingmatch = existingmatch->mNext;
michael@0 1162 }
michael@0 1163 }
michael@0 1164
michael@0 1165 if (removematch) {
michael@0 1166 // remove the generated content for the existing match
michael@0 1167 rv = ReplaceMatch(removematch->mResult, nullptr, nullptr, aElement);
michael@0 1168 if (NS_FAILED(rv))
michael@0 1169 return rv;
michael@0 1170
michael@0 1171 if (mFlags & eLoggingEnabled)
michael@0 1172 OutputMatchToLog(resultid, removematch, false);
michael@0 1173 }
michael@0 1174
michael@0 1175 if (generateContent) {
michael@0 1176 // find the rule that matches. If none match, the content does not
michael@0 1177 // need to be generated
michael@0 1178
michael@0 1179 int16_t ruleindex;
michael@0 1180 nsTemplateRule* matchedrule = nullptr;
michael@0 1181 rv = DetermineMatchedRule(aElement, nextresult, aQuerySet,
michael@0 1182 &matchedrule, &ruleindex);
michael@0 1183 if (NS_FAILED(rv)) {
michael@0 1184 nsTemplateMatch::Destroy(newmatch, false);
michael@0 1185 return rv;
michael@0 1186 }
michael@0 1187
michael@0 1188 if (matchedrule) {
michael@0 1189 rv = newmatch->RuleMatched(aQuerySet, matchedrule,
michael@0 1190 ruleindex, nextresult);
michael@0 1191 if (NS_FAILED(rv)) {
michael@0 1192 nsTemplateMatch::Destroy(newmatch, false);
michael@0 1193 return rv;
michael@0 1194 }
michael@0 1195
michael@0 1196 // Grab the template node
michael@0 1197 nsCOMPtr<nsIContent> action = matchedrule->GetAction();
michael@0 1198 BuildContentFromTemplate(action, aElement, aElement, true,
michael@0 1199 mRefVariable == matchedrule->GetMemberVariable(),
michael@0 1200 nextresult, aNotify, newmatch,
michael@0 1201 aContainer, aNewIndexInContainer);
michael@0 1202 }
michael@0 1203 }
michael@0 1204
michael@0 1205 if (mFlags & eLoggingEnabled)
michael@0 1206 OutputMatchToLog(resultid, newmatch, true);
michael@0 1207
michael@0 1208 if (prevmatch) {
michael@0 1209 prevmatch->mNext = newmatch;
michael@0 1210 }
michael@0 1211 else {
michael@0 1212 mMatchMap.Put(resultid, newmatch);
michael@0 1213 }
michael@0 1214
michael@0 1215 if (removematch) {
michael@0 1216 newmatch->mNext = removematch->mNext;
michael@0 1217 nsTemplateMatch::Destroy(removematch, true);
michael@0 1218 }
michael@0 1219 else {
michael@0 1220 newmatch->mNext = existingmatch;
michael@0 1221 }
michael@0 1222 }
michael@0 1223
michael@0 1224 return rv;
michael@0 1225 }
michael@0 1226
michael@0 1227 nsresult
michael@0 1228 nsXULContentBuilder::EnsureElementHasGenericChild(nsIContent* parent,
michael@0 1229 int32_t nameSpaceID,
michael@0 1230 nsIAtom* tag,
michael@0 1231 bool aNotify,
michael@0 1232 nsIContent** result)
michael@0 1233 {
michael@0 1234 nsresult rv;
michael@0 1235
michael@0 1236 rv = nsXULContentUtils::FindChildByTag(parent, nameSpaceID, tag, result);
michael@0 1237 if (NS_FAILED(rv))
michael@0 1238 return rv;
michael@0 1239
michael@0 1240 if (rv == NS_RDF_NO_VALUE) {
michael@0 1241 // we need to construct a new child element.
michael@0 1242 nsCOMPtr<Element> element;
michael@0 1243
michael@0 1244 rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
michael@0 1245 if (NS_FAILED(rv))
michael@0 1246 return rv;
michael@0 1247
michael@0 1248 // XXX Note that the notification ensures we won't batch insertions! This could be bad! - Dave
michael@0 1249 rv = parent->AppendChildTo(element, aNotify);
michael@0 1250 if (NS_FAILED(rv))
michael@0 1251 return rv;
michael@0 1252
michael@0 1253 *result = element;
michael@0 1254 NS_ADDREF(*result);
michael@0 1255 return NS_ELEMENT_GOT_CREATED;
michael@0 1256 }
michael@0 1257 else {
michael@0 1258 return NS_ELEMENT_WAS_THERE;
michael@0 1259 }
michael@0 1260 }
michael@0 1261
michael@0 1262 bool
michael@0 1263 nsXULContentBuilder::IsOpen(nsIContent* aElement)
michael@0 1264 {
michael@0 1265 // Determine if this is a <treeitem> or <menu> element
michael@0 1266 if (!aElement->IsXUL())
michael@0 1267 return true;
michael@0 1268
michael@0 1269 // XXXhyatt Use the XBL service to obtain a base tag.
michael@0 1270 nsIAtom *tag = aElement->Tag();
michael@0 1271 if (tag == nsGkAtoms::menu ||
michael@0 1272 tag == nsGkAtoms::menubutton ||
michael@0 1273 tag == nsGkAtoms::toolbarbutton ||
michael@0 1274 tag == nsGkAtoms::button ||
michael@0 1275 tag == nsGkAtoms::treeitem)
michael@0 1276 return aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
michael@0 1277 nsGkAtoms::_true, eCaseMatters);
michael@0 1278 return true;
michael@0 1279 }
michael@0 1280
michael@0 1281 nsresult
michael@0 1282 nsXULContentBuilder::RemoveGeneratedContent(nsIContent* aElement)
michael@0 1283 {
michael@0 1284 // Keep a queue of "ungenerated" elements that we have to probe
michael@0 1285 // for generated content.
michael@0 1286 nsAutoTArray<nsIContent*, 8> ungenerated;
michael@0 1287 if (ungenerated.AppendElement(aElement) == nullptr)
michael@0 1288 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1289
michael@0 1290 uint32_t count;
michael@0 1291 while (0 != (count = ungenerated.Length())) {
michael@0 1292 // Pull the next "ungenerated" element off the queue.
michael@0 1293 uint32_t last = count - 1;
michael@0 1294 nsCOMPtr<nsIContent> element = ungenerated[last];
michael@0 1295 ungenerated.RemoveElementAt(last);
michael@0 1296
michael@0 1297 uint32_t i = element->GetChildCount();
michael@0 1298
michael@0 1299 while (i-- > 0) {
michael@0 1300 nsCOMPtr<nsIContent> child = element->GetChildAt(i);
michael@0 1301
michael@0 1302 // Optimize for the <template> element, because we *know*
michael@0 1303 // it won't have any generated content: there's no reason
michael@0 1304 // to even check this subtree.
michael@0 1305 // XXX should this check |child| rather than |element|? Otherwise
michael@0 1306 // it should be moved outside the inner loop. Bug 297290.
michael@0 1307 if (element->NodeInfo()->Equals(nsGkAtoms::_template,
michael@0 1308 kNameSpaceID_XUL) ||
michael@0 1309 !element->IsElement())
michael@0 1310 continue;
michael@0 1311
michael@0 1312 // If the element is in the template map, then we
michael@0 1313 // assume it's been generated and nuke it.
michael@0 1314 nsCOMPtr<nsIContent> tmpl;
michael@0 1315 mTemplateMap.GetTemplateFor(child, getter_AddRefs(tmpl));
michael@0 1316
michael@0 1317 if (! tmpl) {
michael@0 1318 // No 'template' attribute, so this must not have been
michael@0 1319 // generated. We'll need to examine its kids.
michael@0 1320 if (ungenerated.AppendElement(child) == nullptr)
michael@0 1321 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1322 continue;
michael@0 1323 }
michael@0 1324
michael@0 1325 // If we get here, it's "generated". Bye bye!
michael@0 1326 element->RemoveChildAt(i, true);
michael@0 1327
michael@0 1328 // Remove this and any children from the content support map.
michael@0 1329 mContentSupportMap.Remove(child);
michael@0 1330
michael@0 1331 // Remove from the template map
michael@0 1332 mTemplateMap.Remove(child);
michael@0 1333 }
michael@0 1334 }
michael@0 1335
michael@0 1336 return NS_OK;
michael@0 1337 }
michael@0 1338
michael@0 1339 nsresult
michael@0 1340 nsXULContentBuilder::GetElementsForResult(nsIXULTemplateResult* aResult,
michael@0 1341 nsCOMArray<nsIContent>& aElements)
michael@0 1342 {
michael@0 1343 // if the root has been removed from the document, just return
michael@0 1344 // since there won't be any generated content any more
michael@0 1345 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetDocument());
michael@0 1346 if (! xuldoc)
michael@0 1347 return NS_OK;
michael@0 1348
michael@0 1349 nsAutoString id;
michael@0 1350 aResult->GetId(id);
michael@0 1351
michael@0 1352 xuldoc->GetElementsForID(id, aElements);
michael@0 1353
michael@0 1354 return NS_OK;
michael@0 1355 }
michael@0 1356
michael@0 1357 nsresult
michael@0 1358 nsXULContentBuilder::CreateElement(int32_t aNameSpaceID,
michael@0 1359 nsIAtom* aTag,
michael@0 1360 Element** aResult)
michael@0 1361 {
michael@0 1362 nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
michael@0 1363 NS_ASSERTION(doc != nullptr, "not initialized");
michael@0 1364 if (! doc)
michael@0 1365 return NS_ERROR_NOT_INITIALIZED;
michael@0 1366
michael@0 1367 nsCOMPtr<nsINodeInfo> nodeInfo =
michael@0 1368 doc->NodeInfoManager()->GetNodeInfo(aTag, nullptr, aNameSpaceID,
michael@0 1369 nsIDOMNode::ELEMENT_NODE);
michael@0 1370
michael@0 1371 return NS_NewElement(aResult, nodeInfo.forget(), NOT_FROM_PARSER);
michael@0 1372 }
michael@0 1373
michael@0 1374 nsresult
michael@0 1375 nsXULContentBuilder::SetContainerAttrs(nsIContent *aElement,
michael@0 1376 nsIXULTemplateResult* aResult,
michael@0 1377 bool aIgnoreNonContainers,
michael@0 1378 bool aNotify)
michael@0 1379 {
michael@0 1380 NS_PRECONDITION(aResult != nullptr, "null ptr");
michael@0 1381 if (! aResult)
michael@0 1382 return NS_ERROR_NULL_POINTER;
michael@0 1383
michael@0 1384 bool iscontainer;
michael@0 1385 aResult->GetIsContainer(&iscontainer);
michael@0 1386
michael@0 1387 if (aIgnoreNonContainers && !iscontainer)
michael@0 1388 return NS_OK;
michael@0 1389
michael@0 1390 NS_NAMED_LITERAL_STRING(true_, "true");
michael@0 1391 NS_NAMED_LITERAL_STRING(false_, "false");
michael@0 1392
michael@0 1393 const nsAString& newcontainer =
michael@0 1394 iscontainer ? true_ : false_;
michael@0 1395
michael@0 1396 aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::container,
michael@0 1397 newcontainer, aNotify);
michael@0 1398
michael@0 1399 if (iscontainer && !(mFlags & eDontTestEmpty)) {
michael@0 1400 bool isempty;
michael@0 1401 aResult->GetIsEmpty(&isempty);
michael@0 1402
michael@0 1403 const nsAString& newempty =
michael@0 1404 (iscontainer && isempty) ? true_ : false_;
michael@0 1405
michael@0 1406 aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::empty,
michael@0 1407 newempty, aNotify);
michael@0 1408 }
michael@0 1409
michael@0 1410 return NS_OK;
michael@0 1411 }
michael@0 1412
michael@0 1413
michael@0 1414 //----------------------------------------------------------------------
michael@0 1415 //
michael@0 1416 // nsIXULTemplateBuilder methods
michael@0 1417 //
michael@0 1418
michael@0 1419 NS_IMETHODIMP
michael@0 1420 nsXULContentBuilder::CreateContents(nsIContent* aElement, bool aForceCreation)
michael@0 1421 {
michael@0 1422 NS_PRECONDITION(aElement != nullptr, "null ptr");
michael@0 1423 if (! aElement)
michael@0 1424 return NS_ERROR_NULL_POINTER;
michael@0 1425
michael@0 1426 // don't build contents for closed elements. aForceCreation will be true
michael@0 1427 // when a menu is about to be opened, so the content should be built anyway.
michael@0 1428 if (!aForceCreation && !IsOpen(aElement))
michael@0 1429 return NS_OK;
michael@0 1430
michael@0 1431 return CreateTemplateAndContainerContents(aElement, aForceCreation);
michael@0 1432 }
michael@0 1433
michael@0 1434 NS_IMETHODIMP
michael@0 1435 nsXULContentBuilder::HasGeneratedContent(nsIRDFResource* aResource,
michael@0 1436 nsIAtom* aTag,
michael@0 1437 bool* aGenerated)
michael@0 1438 {
michael@0 1439 *aGenerated = false;
michael@0 1440 NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
michael@0 1441 NS_ENSURE_STATE(mRootResult);
michael@0 1442
michael@0 1443 nsCOMPtr<nsIRDFResource> rootresource;
michael@0 1444 nsresult rv = mRootResult->GetResource(getter_AddRefs(rootresource));
michael@0 1445 if (NS_FAILED(rv))
michael@0 1446 return rv;
michael@0 1447
michael@0 1448 // the root resource is always acceptable
michael@0 1449 if (aResource == rootresource) {
michael@0 1450 if (!aTag || mRoot->Tag() == aTag)
michael@0 1451 *aGenerated = true;
michael@0 1452 }
michael@0 1453 else {
michael@0 1454 const char* uri;
michael@0 1455 aResource->GetValueConst(&uri);
michael@0 1456
michael@0 1457 NS_ConvertUTF8toUTF16 refID(uri);
michael@0 1458
michael@0 1459 // just return if the node is no longer in a document
michael@0 1460 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetDocument());
michael@0 1461 if (! xuldoc)
michael@0 1462 return NS_OK;
michael@0 1463
michael@0 1464 nsCOMArray<nsIContent> elements;
michael@0 1465 xuldoc->GetElementsForID(refID, elements);
michael@0 1466
michael@0 1467 uint32_t cnt = elements.Count();
michael@0 1468
michael@0 1469 for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
michael@0 1470 nsCOMPtr<nsIContent> content = elements.SafeObjectAt(i);
michael@0 1471
michael@0 1472 do {
michael@0 1473 nsTemplateMatch* match;
michael@0 1474 if (content == mRoot || mContentSupportMap.Get(content, &match)) {
michael@0 1475 // If we've got a tag, check it to ensure we're consistent.
michael@0 1476 if (!aTag || content->Tag() == aTag) {
michael@0 1477 *aGenerated = true;
michael@0 1478 return NS_OK;
michael@0 1479 }
michael@0 1480 }
michael@0 1481
michael@0 1482 content = content->GetParent();
michael@0 1483 } while (content);
michael@0 1484 }
michael@0 1485 }
michael@0 1486
michael@0 1487 return NS_OK;
michael@0 1488 }
michael@0 1489
michael@0 1490 NS_IMETHODIMP
michael@0 1491 nsXULContentBuilder::GetResultForContent(nsIDOMElement* aElement,
michael@0 1492 nsIXULTemplateResult** aResult)
michael@0 1493 {
michael@0 1494 NS_ENSURE_ARG_POINTER(aElement);
michael@0 1495 NS_ENSURE_ARG_POINTER(aResult);
michael@0 1496
michael@0 1497 nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
michael@0 1498 if (content == mRoot) {
michael@0 1499 *aResult = mRootResult;
michael@0 1500 }
michael@0 1501 else {
michael@0 1502 nsTemplateMatch *match = nullptr;
michael@0 1503 if (mContentSupportMap.Get(content, &match))
michael@0 1504 *aResult = match->mResult;
michael@0 1505 else
michael@0 1506 *aResult = nullptr;
michael@0 1507 }
michael@0 1508
michael@0 1509 NS_IF_ADDREF(*aResult);
michael@0 1510 return NS_OK;
michael@0 1511 }
michael@0 1512
michael@0 1513 //----------------------------------------------------------------------
michael@0 1514 //
michael@0 1515 // nsIDocumentObserver methods
michael@0 1516 //
michael@0 1517
michael@0 1518 void
michael@0 1519 nsXULContentBuilder::AttributeChanged(nsIDocument* aDocument,
michael@0 1520 Element* aElement,
michael@0 1521 int32_t aNameSpaceID,
michael@0 1522 nsIAtom* aAttribute,
michael@0 1523 int32_t aModType)
michael@0 1524 {
michael@0 1525 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
michael@0 1526
michael@0 1527 // Handle "open" and "close" cases. We do this handling before
michael@0 1528 // we've notified the observer, so that content is already created
michael@0 1529 // for the frame system to walk.
michael@0 1530 if (aElement->GetNameSpaceID() == kNameSpaceID_XUL &&
michael@0 1531 aAttribute == nsGkAtoms::open) {
michael@0 1532 // We're on a XUL tag, and an ``open'' attribute changed.
michael@0 1533 if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
michael@0 1534 nsGkAtoms::_true, eCaseMatters))
michael@0 1535 OpenContainer(aElement);
michael@0 1536 else
michael@0 1537 CloseContainer(aElement);
michael@0 1538 }
michael@0 1539
michael@0 1540 if ((aNameSpaceID == kNameSpaceID_XUL) &&
michael@0 1541 ((aAttribute == nsGkAtoms::sort) ||
michael@0 1542 (aAttribute == nsGkAtoms::sortDirection) ||
michael@0 1543 (aAttribute == nsGkAtoms::sortResource) ||
michael@0 1544 (aAttribute == nsGkAtoms::sortResource2)))
michael@0 1545 mSortState.initialized = false;
michael@0 1546
michael@0 1547 // Pass along to the generic template builder.
michael@0 1548 nsXULTemplateBuilder::AttributeChanged(aDocument, aElement, aNameSpaceID,
michael@0 1549 aAttribute, aModType);
michael@0 1550 }
michael@0 1551
michael@0 1552 void
michael@0 1553 nsXULContentBuilder::NodeWillBeDestroyed(const nsINode* aNode)
michael@0 1554 {
michael@0 1555 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
michael@0 1556 // Break circular references
michael@0 1557 mContentSupportMap.Clear();
michael@0 1558
michael@0 1559 nsXULTemplateBuilder::NodeWillBeDestroyed(aNode);
michael@0 1560 }
michael@0 1561
michael@0 1562
michael@0 1563 //----------------------------------------------------------------------
michael@0 1564 //
michael@0 1565 // nsXULTemplateBuilder methods
michael@0 1566 //
michael@0 1567
michael@0 1568 bool
michael@0 1569 nsXULContentBuilder::GetInsertionLocations(nsIXULTemplateResult* aResult,
michael@0 1570 nsCOMArray<nsIContent>** aLocations)
michael@0 1571 {
michael@0 1572 *aLocations = nullptr;
michael@0 1573
michael@0 1574 nsAutoString ref;
michael@0 1575 nsresult rv = aResult->GetBindingFor(mRefVariable, ref);
michael@0 1576 if (NS_FAILED(rv))
michael@0 1577 return false;
michael@0 1578
michael@0 1579 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetDocument());
michael@0 1580 if (! xuldoc)
michael@0 1581 return false;
michael@0 1582
michael@0 1583 *aLocations = new nsCOMArray<nsIContent>;
michael@0 1584 NS_ENSURE_TRUE(*aLocations, false);
michael@0 1585
michael@0 1586 xuldoc->GetElementsForID(ref, **aLocations);
michael@0 1587 uint32_t count = (*aLocations)->Count();
michael@0 1588
michael@0 1589 bool found = false;
michael@0 1590
michael@0 1591 for (uint32_t t = 0; t < count; t++) {
michael@0 1592 nsCOMPtr<nsIContent> content = (*aLocations)->SafeObjectAt(t);
michael@0 1593
michael@0 1594 nsTemplateMatch* refmatch;
michael@0 1595 if (content == mRoot || mContentSupportMap.Get(content, &refmatch)) {
michael@0 1596 // See if we've built the container contents for "content"
michael@0 1597 // yet. If not, we don't need to build any content. This
michael@0 1598 // happens, for example, if we receive an assertion on a
michael@0 1599 // closed folder in a tree widget or on a menu that hasn't
michael@0 1600 // yet been opened.
michael@0 1601 nsXULElement *xulcontent = nsXULElement::FromContent(content);
michael@0 1602 if (!xulcontent || xulcontent->GetTemplateGenerated()) {
michael@0 1603 found = true;
michael@0 1604 continue;
michael@0 1605 }
michael@0 1606 }
michael@0 1607
michael@0 1608 // clear the item in the list since we don't want to insert there
michael@0 1609 (*aLocations)->ReplaceObjectAt(nullptr, t);
michael@0 1610 }
michael@0 1611
michael@0 1612 return found;
michael@0 1613 }
michael@0 1614
michael@0 1615 nsresult
michael@0 1616 nsXULContentBuilder::ReplaceMatch(nsIXULTemplateResult* aOldResult,
michael@0 1617 nsTemplateMatch* aNewMatch,
michael@0 1618 nsTemplateRule* aNewMatchRule,
michael@0 1619 void *aContext)
michael@0 1620
michael@0 1621 {
michael@0 1622 nsresult rv;
michael@0 1623 nsIContent* content = static_cast<nsIContent*>(aContext);
michael@0 1624
michael@0 1625 // update the container attributes for the match
michael@0 1626 if (content) {
michael@0 1627 nsAutoString ref;
michael@0 1628 if (aNewMatch)
michael@0 1629 rv = aNewMatch->mResult->GetBindingFor(mRefVariable, ref);
michael@0 1630 else
michael@0 1631 rv = aOldResult->GetBindingFor(mRefVariable, ref);
michael@0 1632 if (NS_FAILED(rv))
michael@0 1633 return rv;
michael@0 1634
michael@0 1635 if (!ref.IsEmpty()) {
michael@0 1636 nsCOMPtr<nsIXULTemplateResult> refResult;
michael@0 1637 rv = GetResultForId(ref, getter_AddRefs(refResult));
michael@0 1638 if (NS_FAILED(rv))
michael@0 1639 return rv;
michael@0 1640
michael@0 1641 if (refResult)
michael@0 1642 SetContainerAttrs(content, refResult, false, true);
michael@0 1643 }
michael@0 1644 }
michael@0 1645
michael@0 1646 if (aOldResult) {
michael@0 1647 nsCOMArray<nsIContent> elements;
michael@0 1648 rv = GetElementsForResult(aOldResult, elements);
michael@0 1649 if (NS_FAILED(rv))
michael@0 1650 return rv;
michael@0 1651
michael@0 1652 uint32_t count = elements.Count();
michael@0 1653
michael@0 1654 for (int32_t e = int32_t(count) - 1; e >= 0; --e) {
michael@0 1655 nsCOMPtr<nsIContent> child = elements.SafeObjectAt(e);
michael@0 1656
michael@0 1657 nsTemplateMatch* match;
michael@0 1658 if (mContentSupportMap.Get(child, &match)) {
michael@0 1659 if (content == match->GetContainer())
michael@0 1660 RemoveMember(child);
michael@0 1661 }
michael@0 1662 }
michael@0 1663 }
michael@0 1664
michael@0 1665 if (aNewMatch) {
michael@0 1666 nsCOMPtr<nsIContent> action = aNewMatchRule->GetAction();
michael@0 1667 return BuildContentFromTemplate(action, content, content, true,
michael@0 1668 mRefVariable == aNewMatchRule->GetMemberVariable(),
michael@0 1669 aNewMatch->mResult, true, aNewMatch,
michael@0 1670 nullptr, nullptr);
michael@0 1671 }
michael@0 1672
michael@0 1673 return NS_OK;
michael@0 1674 }
michael@0 1675
michael@0 1676
michael@0 1677 nsresult
michael@0 1678 nsXULContentBuilder::SynchronizeResult(nsIXULTemplateResult* aResult)
michael@0 1679 {
michael@0 1680 nsCOMArray<nsIContent> elements;
michael@0 1681 GetElementsForResult(aResult, elements);
michael@0 1682
michael@0 1683 uint32_t cnt = elements.Count();
michael@0 1684
michael@0 1685 for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
michael@0 1686 nsCOMPtr<nsIContent> element = elements.SafeObjectAt(i);
michael@0 1687
michael@0 1688 nsTemplateMatch* match;
michael@0 1689 if (! mContentSupportMap.Get(element, &match))
michael@0 1690 continue;
michael@0 1691
michael@0 1692 nsCOMPtr<nsIContent> templateNode;
michael@0 1693 mTemplateMap.GetTemplateFor(element, getter_AddRefs(templateNode));
michael@0 1694
michael@0 1695 NS_ASSERTION(templateNode, "couldn't find template node for element");
michael@0 1696 if (! templateNode)
michael@0 1697 continue;
michael@0 1698
michael@0 1699 // this node was created by a XUL template, so update it accordingly
michael@0 1700 SynchronizeUsingTemplate(templateNode, element, aResult);
michael@0 1701 }
michael@0 1702
michael@0 1703 return NS_OK;
michael@0 1704 }
michael@0 1705
michael@0 1706 //----------------------------------------------------------------------
michael@0 1707 //
michael@0 1708 // Implementation methods
michael@0 1709 //
michael@0 1710
michael@0 1711 nsresult
michael@0 1712 nsXULContentBuilder::OpenContainer(nsIContent* aElement)
michael@0 1713 {
michael@0 1714 if (aElement != mRoot) {
michael@0 1715 if (mFlags & eDontRecurse)
michael@0 1716 return NS_OK;
michael@0 1717
michael@0 1718 bool rightBuilder = false;
michael@0 1719
michael@0 1720 nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aElement->GetDocument());
michael@0 1721 if (! xuldoc)
michael@0 1722 return NS_OK;
michael@0 1723
michael@0 1724 // See if we're responsible for this element
michael@0 1725 nsIContent* content = aElement;
michael@0 1726 do {
michael@0 1727 nsCOMPtr<nsIXULTemplateBuilder> builder;
michael@0 1728 xuldoc->GetTemplateBuilderFor(content, getter_AddRefs(builder));
michael@0 1729 if (builder) {
michael@0 1730 if (builder == this)
michael@0 1731 rightBuilder = true;
michael@0 1732 break;
michael@0 1733 }
michael@0 1734
michael@0 1735 content = content->GetParent();
michael@0 1736 } while (content);
michael@0 1737
michael@0 1738 if (! rightBuilder)
michael@0 1739 return NS_OK;
michael@0 1740 }
michael@0 1741
michael@0 1742 CreateTemplateAndContainerContents(aElement, false);
michael@0 1743
michael@0 1744 return NS_OK;
michael@0 1745 }
michael@0 1746
michael@0 1747 nsresult
michael@0 1748 nsXULContentBuilder::CloseContainer(nsIContent* aElement)
michael@0 1749 {
michael@0 1750 return NS_OK;
michael@0 1751 }
michael@0 1752
michael@0 1753 nsresult
michael@0 1754 nsXULContentBuilder::RebuildAll()
michael@0 1755 {
michael@0 1756 NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
michael@0 1757
michael@0 1758 // Bail out early if we are being torn down.
michael@0 1759 nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
michael@0 1760 if (!doc)
michael@0 1761 return NS_OK;
michael@0 1762
michael@0 1763 if (mQueriesCompiled)
michael@0 1764 Uninit(false);
michael@0 1765
michael@0 1766 nsresult rv = CompileQueries();
michael@0 1767 if (NS_FAILED(rv))
michael@0 1768 return rv;
michael@0 1769
michael@0 1770 if (mQuerySets.Length() == 0)
michael@0 1771 return NS_OK;
michael@0 1772
michael@0 1773 nsXULElement *xulcontent = nsXULElement::FromContent(mRoot);
michael@0 1774 if (xulcontent)
michael@0 1775 xulcontent->ClearTemplateGenerated();
michael@0 1776
michael@0 1777 // Now, regenerate both the template- and container-generated
michael@0 1778 // contents for the current element...
michael@0 1779 CreateTemplateAndContainerContents(mRoot, false);
michael@0 1780
michael@0 1781 return NS_OK;
michael@0 1782 }
michael@0 1783
michael@0 1784 /**** Sorting Methods ****/
michael@0 1785
michael@0 1786 nsresult
michael@0 1787 nsXULContentBuilder::CompareResultToNode(nsIXULTemplateResult* aResult,
michael@0 1788 nsIContent* aContent,
michael@0 1789 int32_t* aSortOrder)
michael@0 1790 {
michael@0 1791 NS_ASSERTION(aSortOrder, "CompareResultToNode: null out param aSortOrder");
michael@0 1792
michael@0 1793 *aSortOrder = 0;
michael@0 1794
michael@0 1795 nsTemplateMatch *match = nullptr;
michael@0 1796 if (!mContentSupportMap.Get(aContent, &match)) {
michael@0 1797 *aSortOrder = mSortState.sortStaticsLast ? -1 : 1;
michael@0 1798 return NS_OK;
michael@0 1799 }
michael@0 1800
michael@0 1801 if (!mQueryProcessor)
michael@0 1802 return NS_OK;
michael@0 1803
michael@0 1804 if (mSortState.direction == nsSortState_natural) {
michael@0 1805 // sort in natural order
michael@0 1806 nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
michael@0 1807 nullptr, mSortState.sortHints,
michael@0 1808 aSortOrder);
michael@0 1809 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1810 }
michael@0 1811 else {
michael@0 1812 // iterate over each sort key and compare. If the nodes are equal,
michael@0 1813 // continue to compare using the next sort key. If not equal, stop.
michael@0 1814 int32_t length = mSortState.sortKeys.Count();
michael@0 1815 for (int32_t t = 0; t < length; t++) {
michael@0 1816 nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
michael@0 1817 mSortState.sortKeys[t],
michael@0 1818 mSortState.sortHints, aSortOrder);
michael@0 1819 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1820
michael@0 1821 if (*aSortOrder)
michael@0 1822 break;
michael@0 1823 }
michael@0 1824 }
michael@0 1825
michael@0 1826 // flip the sort order if performing a descending sorting
michael@0 1827 if (mSortState.direction == nsSortState_descending)
michael@0 1828 *aSortOrder = -*aSortOrder;
michael@0 1829
michael@0 1830 return NS_OK;
michael@0 1831 }
michael@0 1832
michael@0 1833 nsresult
michael@0 1834 nsXULContentBuilder::InsertSortedNode(nsIContent* aContainer,
michael@0 1835 nsIContent* aNode,
michael@0 1836 nsIXULTemplateResult* aResult,
michael@0 1837 bool aNotify)
michael@0 1838 {
michael@0 1839 nsresult rv;
michael@0 1840
michael@0 1841 if (!mSortState.initialized) {
michael@0 1842 nsAutoString sort, sortDirection, sortHints;
michael@0 1843 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
michael@0 1844 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, sortDirection);
michael@0 1845 mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, sortHints);
michael@0 1846 sortDirection.AppendLiteral(" ");
michael@0 1847 sortDirection += sortHints;
michael@0 1848 rv = XULSortServiceImpl::InitializeSortState(mRoot, aContainer,
michael@0 1849 sort, sortDirection, &mSortState);
michael@0 1850 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1851 }
michael@0 1852
michael@0 1853 // when doing a natural sort, items will typically be sorted according to
michael@0 1854 // the order they appear in the datasource. For RDF, cache whether the
michael@0 1855 // reference parent is an RDF Seq. That way, the items can be sorted in the
michael@0 1856 // order they are in the Seq.
michael@0 1857 mSortState.isContainerRDFSeq = false;
michael@0 1858 if (mSortState.direction == nsSortState_natural) {
michael@0 1859 nsCOMPtr<nsISupports> ref;
michael@0 1860 nsresult rv = aResult->GetBindingObjectFor(mRefVariable, getter_AddRefs(ref));
michael@0 1861 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1862
michael@0 1863 nsCOMPtr<nsIRDFResource> container = do_QueryInterface(ref);
michael@0 1864
michael@0 1865 if (container) {
michael@0 1866 rv = gRDFContainerUtils->IsSeq(mDB, container, &mSortState.isContainerRDFSeq);
michael@0 1867 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1868 }
michael@0 1869 }
michael@0 1870
michael@0 1871 bool childAdded = false;
michael@0 1872 uint32_t numChildren = aContainer->GetChildCount();
michael@0 1873
michael@0 1874 if (mSortState.direction != nsSortState_natural ||
michael@0 1875 (mSortState.direction == nsSortState_natural && mSortState.isContainerRDFSeq))
michael@0 1876 {
michael@0 1877 // because numChildren gets modified
michael@0 1878 int32_t realNumChildren = numChildren;
michael@0 1879 nsIContent *child = nullptr;
michael@0 1880
michael@0 1881 // rjc says: determine where static XUL ends and generated XUL/RDF begins
michael@0 1882 int32_t staticCount = 0;
michael@0 1883
michael@0 1884 nsAutoString staticValue;
michael@0 1885 aContainer->GetAttr(kNameSpaceID_None, nsGkAtoms::staticHint, staticValue);
michael@0 1886 if (!staticValue.IsEmpty())
michael@0 1887 {
michael@0 1888 // found "static" XUL element count hint
michael@0 1889 nsresult strErr = NS_OK;
michael@0 1890 staticCount = staticValue.ToInteger(&strErr);
michael@0 1891 if (NS_FAILED(strErr))
michael@0 1892 staticCount = 0;
michael@0 1893 } else {
michael@0 1894 // compute the "static" XUL element count
michael@0 1895 for (nsIContent* child = aContainer->GetFirstChild();
michael@0 1896 child;
michael@0 1897 child = child->GetNextSibling()) {
michael@0 1898
michael@0 1899 if (nsContentUtils::HasNonEmptyAttr(child, kNameSpaceID_None,
michael@0 1900 nsGkAtoms::_template))
michael@0 1901 break;
michael@0 1902 else
michael@0 1903 ++staticCount;
michael@0 1904 }
michael@0 1905
michael@0 1906 if (mSortState.sortStaticsLast) {
michael@0 1907 // indicate that static XUL comes after RDF-generated content by
michael@0 1908 // making negative
michael@0 1909 staticCount = -staticCount;
michael@0 1910 }
michael@0 1911
michael@0 1912 // save the "static" XUL element count hint
michael@0 1913 nsAutoString valueStr;
michael@0 1914 valueStr.AppendInt(staticCount);
michael@0 1915 aContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::staticHint, valueStr, false);
michael@0 1916 }
michael@0 1917
michael@0 1918 if (staticCount <= 0) {
michael@0 1919 numChildren += staticCount;
michael@0 1920 staticCount = 0;
michael@0 1921 } else if (staticCount > (int32_t)numChildren) {
michael@0 1922 staticCount = numChildren;
michael@0 1923 numChildren -= staticCount;
michael@0 1924 }
michael@0 1925
michael@0 1926 // figure out where to insert the node when a sort order is being imposed
michael@0 1927 if (numChildren > 0) {
michael@0 1928 nsIContent *temp;
michael@0 1929 int32_t direction;
michael@0 1930
michael@0 1931 // rjc says: The following is an implementation of a fairly optimal
michael@0 1932 // binary search insertion sort... with interpolation at either end-point.
michael@0 1933
michael@0 1934 if (mSortState.lastWasFirst) {
michael@0 1935 child = aContainer->GetChildAt(staticCount);
michael@0 1936 temp = child;
michael@0 1937 rv = CompareResultToNode(aResult, temp, &direction);
michael@0 1938 if (direction < 0) {
michael@0 1939 aContainer->InsertChildAt(aNode, staticCount, aNotify);
michael@0 1940 childAdded = true;
michael@0 1941 } else
michael@0 1942 mSortState.lastWasFirst = false;
michael@0 1943 } else if (mSortState.lastWasLast) {
michael@0 1944 child = aContainer->GetChildAt(realNumChildren - 1);
michael@0 1945 temp = child;
michael@0 1946 rv = CompareResultToNode(aResult, temp, &direction);
michael@0 1947 if (direction > 0) {
michael@0 1948 aContainer->InsertChildAt(aNode, realNumChildren, aNotify);
michael@0 1949 childAdded = true;
michael@0 1950 } else
michael@0 1951 mSortState.lastWasLast = false;
michael@0 1952 }
michael@0 1953
michael@0 1954 int32_t left = staticCount + 1, right = realNumChildren, x;
michael@0 1955 while (!childAdded && right >= left) {
michael@0 1956 x = (left + right) / 2;
michael@0 1957 child = aContainer->GetChildAt(x - 1);
michael@0 1958 temp = child;
michael@0 1959
michael@0 1960 rv = CompareResultToNode(aResult, temp, &direction);
michael@0 1961 if ((x == left && direction < 0) ||
michael@0 1962 (x == right && direction >= 0) ||
michael@0 1963 left == right)
michael@0 1964 {
michael@0 1965 int32_t thePos = (direction > 0 ? x : x - 1);
michael@0 1966 aContainer->InsertChildAt(aNode, thePos, aNotify);
michael@0 1967 childAdded = true;
michael@0 1968
michael@0 1969 mSortState.lastWasFirst = (thePos == staticCount);
michael@0 1970 mSortState.lastWasLast = (thePos >= realNumChildren);
michael@0 1971
michael@0 1972 break;
michael@0 1973 }
michael@0 1974 if (direction < 0)
michael@0 1975 right = x - 1;
michael@0 1976 else
michael@0 1977 left = x + 1;
michael@0 1978 }
michael@0 1979 }
michael@0 1980 }
michael@0 1981
michael@0 1982 // if the child hasn't been inserted yet, just add it at the end. Note
michael@0 1983 // that an append isn't done as there may be static content afterwards.
michael@0 1984 if (!childAdded)
michael@0 1985 aContainer->InsertChildAt(aNode, numChildren, aNotify);
michael@0 1986
michael@0 1987 return NS_OK;
michael@0 1988 }

mercurial