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