1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsNameSpaceManager.h"
8 #include "nsGkAtoms.h"
9 #include "nsTreeUtils.h"
10 #include "nsTreeContentView.h"
11 #include "ChildIterator.h"
12 #include "nsError.h"
13 #include "nsXULSortService.h"
14 #include "nsTreeBodyFrame.h"
15 #include "nsTreeColumns.h"
16 #include "mozilla/ErrorResult.h"
17 #include "mozilla/dom/Element.h"
18 #include "mozilla/dom/TreeContentViewBinding.h"
19 #include "mozilla/dom/XULTreeElement.h"
20 #include "nsServiceManagerUtils.h"
21 #include "mozilla/dom/Document.h"
22 
23 using namespace mozilla;
24 using namespace mozilla::dom;
25 
26 // A content model view implementation for the tree.
27 
28 #define ROW_FLAG_CONTAINER 0x01
29 #define ROW_FLAG_OPEN 0x02
30 #define ROW_FLAG_EMPTY 0x04
31 #define ROW_FLAG_SEPARATOR 0x08
32 
33 class Row {
34  public:
Row(Element * aContent,int32_t aParentIndex)35   Row(Element* aContent, int32_t aParentIndex)
36       : mContent(aContent),
37         mParentIndex(aParentIndex),
38         mSubtreeSize(0),
39         mFlags(0) {}
40 
41   ~Row() = default;
42 
SetContainer(bool aContainer)43   void SetContainer(bool aContainer) {
44     aContainer ? mFlags |= ROW_FLAG_CONTAINER : mFlags &= ~ROW_FLAG_CONTAINER;
45   }
IsContainer()46   bool IsContainer() { return mFlags & ROW_FLAG_CONTAINER; }
47 
SetOpen(bool aOpen)48   void SetOpen(bool aOpen) {
49     aOpen ? mFlags |= ROW_FLAG_OPEN : mFlags &= ~ROW_FLAG_OPEN;
50   }
IsOpen()51   bool IsOpen() { return !!(mFlags & ROW_FLAG_OPEN); }
52 
SetEmpty(bool aEmpty)53   void SetEmpty(bool aEmpty) {
54     aEmpty ? mFlags |= ROW_FLAG_EMPTY : mFlags &= ~ROW_FLAG_EMPTY;
55   }
IsEmpty()56   bool IsEmpty() { return !!(mFlags & ROW_FLAG_EMPTY); }
57 
SetSeparator(bool aSeparator)58   void SetSeparator(bool aSeparator) {
59     aSeparator ? mFlags |= ROW_FLAG_SEPARATOR : mFlags &= ~ROW_FLAG_SEPARATOR;
60   }
IsSeparator()61   bool IsSeparator() { return !!(mFlags & ROW_FLAG_SEPARATOR); }
62 
63   // Weak reference to a content item.
64   Element* mContent;
65 
66   // The parent index of the item, set to -1 for the top level items.
67   int32_t mParentIndex;
68 
69   // Subtree size for this item.
70   int32_t mSubtreeSize;
71 
72  private:
73   // State flags
74   int8_t mFlags;
75 };
76 
77 // We don't reference count the reference to the document
78 // If the document goes away first, we'll be informed and we
79 // can drop our reference.
80 // If we go away first, we'll get rid of ourselves from the
81 // document's observer list.
82 
nsTreeContentView(void)83 nsTreeContentView::nsTreeContentView(void)
84     : mTree(nullptr), mSelection(nullptr), mDocument(nullptr) {}
85 
~nsTreeContentView(void)86 nsTreeContentView::~nsTreeContentView(void) {
87   // Remove ourselves from mDocument's observers.
88   if (mDocument) mDocument->RemoveObserver(this);
89 }
90 
NS_NewTreeContentView(nsITreeView ** aResult)91 nsresult NS_NewTreeContentView(nsITreeView** aResult) {
92   *aResult = new nsTreeContentView;
93   if (!*aResult) return NS_ERROR_OUT_OF_MEMORY;
94   NS_ADDREF(*aResult);
95   return NS_OK;
96 }
97 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsTreeContentView,mTree,mSelection,mBody)98 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsTreeContentView, mTree, mSelection,
99                                       mBody)
100 
101 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeContentView)
102 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeContentView)
103 
104 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeContentView)
105   NS_INTERFACE_MAP_ENTRY(nsITreeView)
106   NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
107   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
108   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITreeView)
109   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
110 NS_INTERFACE_MAP_END
111 
112 JSObject* nsTreeContentView::WrapObject(JSContext* aCx,
113                                         JS::Handle<JSObject*> aGivenProto) {
114   return TreeContentView_Binding::Wrap(aCx, this, aGivenProto);
115 }
116 
GetParentObject()117 nsISupports* nsTreeContentView::GetParentObject() { return mTree; }
118 
119 NS_IMETHODIMP
GetRowCount(int32_t * aRowCount)120 nsTreeContentView::GetRowCount(int32_t* aRowCount) {
121   *aRowCount = mRows.Length();
122 
123   return NS_OK;
124 }
125 
126 NS_IMETHODIMP
GetSelection(nsITreeSelection ** aSelection)127 nsTreeContentView::GetSelection(nsITreeSelection** aSelection) {
128   NS_IF_ADDREF(*aSelection = GetSelection());
129 
130   return NS_OK;
131 }
132 
CanTrustTreeSelection(nsISupports * aValue)133 bool nsTreeContentView::CanTrustTreeSelection(nsISupports* aValue) {
134   // Untrusted content is only allowed to specify known-good views
135   if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) return true;
136   nsCOMPtr<nsINativeTreeSelection> nativeTreeSel = do_QueryInterface(aValue);
137   return nativeTreeSel && NS_SUCCEEDED(nativeTreeSel->EnsureNative());
138 }
139 
140 NS_IMETHODIMP
SetSelection(nsITreeSelection * aSelection)141 nsTreeContentView::SetSelection(nsITreeSelection* aSelection) {
142   ErrorResult rv;
143   SetSelection(aSelection, rv);
144   return rv.StealNSResult();
145 }
146 
SetSelection(nsITreeSelection * aSelection,ErrorResult & aError)147 void nsTreeContentView::SetSelection(nsITreeSelection* aSelection,
148                                      ErrorResult& aError) {
149   if (aSelection && !CanTrustTreeSelection(aSelection)) {
150     aError.ThrowSecurityError("Not allowed to set tree selection");
151     return;
152   }
153 
154   mSelection = aSelection;
155 }
156 
GetRowProperties(int32_t aRow,nsAString & aProperties,ErrorResult & aError)157 void nsTreeContentView::GetRowProperties(int32_t aRow, nsAString& aProperties,
158                                          ErrorResult& aError) {
159   aProperties.Truncate();
160   if (!IsValidRowIndex(aRow)) {
161     aError.Throw(NS_ERROR_INVALID_ARG);
162     return;
163   }
164 
165   Row* row = mRows[aRow].get();
166   nsIContent* realRow;
167   if (row->IsSeparator())
168     realRow = row->mContent;
169   else
170     realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
171 
172   if (realRow && realRow->IsElement()) {
173     realRow->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::properties,
174                                   aProperties);
175   }
176 }
177 
178 NS_IMETHODIMP
GetRowProperties(int32_t aIndex,nsAString & aProps)179 nsTreeContentView::GetRowProperties(int32_t aIndex, nsAString& aProps) {
180   ErrorResult rv;
181   GetRowProperties(aIndex, aProps, rv);
182   return rv.StealNSResult();
183 }
184 
GetCellProperties(int32_t aRow,nsTreeColumn & aColumn,nsAString & aProperties,ErrorResult & aError)185 void nsTreeContentView::GetCellProperties(int32_t aRow, nsTreeColumn& aColumn,
186                                           nsAString& aProperties,
187                                           ErrorResult& aError) {
188   if (!IsValidRowIndex(aRow)) {
189     aError.Throw(NS_ERROR_INVALID_ARG);
190     return;
191   }
192 
193   Row* row = mRows[aRow].get();
194   nsIContent* realRow =
195       nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
196   if (realRow) {
197     Element* cell = GetCell(realRow, aColumn);
198     if (cell) {
199       cell->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProperties);
200     }
201   }
202 }
203 
204 NS_IMETHODIMP
GetCellProperties(int32_t aRow,nsTreeColumn * aCol,nsAString & aProps)205 nsTreeContentView::GetCellProperties(int32_t aRow, nsTreeColumn* aCol,
206                                      nsAString& aProps) {
207   NS_ENSURE_ARG(aCol);
208 
209   ErrorResult rv;
210   GetCellProperties(aRow, *aCol, aProps, rv);
211   return rv.StealNSResult();
212 }
213 
GetColumnProperties(nsTreeColumn & aColumn,nsAString & aProperties)214 void nsTreeContentView::GetColumnProperties(nsTreeColumn& aColumn,
215                                             nsAString& aProperties) {
216   RefPtr<Element> element = aColumn.Element();
217 
218   if (element) {
219     element->GetAttr(nsGkAtoms::properties, aProperties);
220   }
221 }
222 
223 NS_IMETHODIMP
GetColumnProperties(nsTreeColumn * aCol,nsAString & aProps)224 nsTreeContentView::GetColumnProperties(nsTreeColumn* aCol, nsAString& aProps) {
225   NS_ENSURE_ARG(aCol);
226 
227   GetColumnProperties(*aCol, aProps);
228   return NS_OK;
229 }
230 
IsContainer(int32_t aRow,ErrorResult & aError)231 bool nsTreeContentView::IsContainer(int32_t aRow, ErrorResult& aError) {
232   if (!IsValidRowIndex(aRow)) {
233     aError.Throw(NS_ERROR_INVALID_ARG);
234     return false;
235   }
236 
237   return mRows[aRow]->IsContainer();
238 }
239 
240 NS_IMETHODIMP
IsContainer(int32_t aIndex,bool * _retval)241 nsTreeContentView::IsContainer(int32_t aIndex, bool* _retval) {
242   ErrorResult rv;
243   *_retval = IsContainer(aIndex, rv);
244   return rv.StealNSResult();
245 }
246 
IsContainerOpen(int32_t aRow,ErrorResult & aError)247 bool nsTreeContentView::IsContainerOpen(int32_t aRow, ErrorResult& aError) {
248   if (!IsValidRowIndex(aRow)) {
249     aError.Throw(NS_ERROR_INVALID_ARG);
250     return false;
251   }
252 
253   return mRows[aRow]->IsOpen();
254 }
255 
256 NS_IMETHODIMP
IsContainerOpen(int32_t aIndex,bool * _retval)257 nsTreeContentView::IsContainerOpen(int32_t aIndex, bool* _retval) {
258   ErrorResult rv;
259   *_retval = IsContainerOpen(aIndex, rv);
260   return rv.StealNSResult();
261 }
262 
IsContainerEmpty(int32_t aRow,ErrorResult & aError)263 bool nsTreeContentView::IsContainerEmpty(int32_t aRow, ErrorResult& aError) {
264   if (!IsValidRowIndex(aRow)) {
265     aError.Throw(NS_ERROR_INVALID_ARG);
266     return false;
267   }
268 
269   return mRows[aRow]->IsEmpty();
270 }
271 
272 NS_IMETHODIMP
IsContainerEmpty(int32_t aIndex,bool * _retval)273 nsTreeContentView::IsContainerEmpty(int32_t aIndex, bool* _retval) {
274   ErrorResult rv;
275   *_retval = IsContainerEmpty(aIndex, rv);
276   return rv.StealNSResult();
277 }
278 
IsSeparator(int32_t aRow,ErrorResult & aError)279 bool nsTreeContentView::IsSeparator(int32_t aRow, ErrorResult& aError) {
280   if (!IsValidRowIndex(aRow)) {
281     aError.Throw(NS_ERROR_INVALID_ARG);
282     return false;
283   }
284 
285   return mRows[aRow]->IsSeparator();
286 }
287 
288 NS_IMETHODIMP
IsSeparator(int32_t aIndex,bool * _retval)289 nsTreeContentView::IsSeparator(int32_t aIndex, bool* _retval) {
290   ErrorResult rv;
291   *_retval = IsSeparator(aIndex, rv);
292   return rv.StealNSResult();
293 }
294 
295 NS_IMETHODIMP
IsSorted(bool * _retval)296 nsTreeContentView::IsSorted(bool* _retval) {
297   *_retval = IsSorted();
298 
299   return NS_OK;
300 }
301 
CanDrop(int32_t aRow,int32_t aOrientation,ErrorResult & aError)302 bool nsTreeContentView::CanDrop(int32_t aRow, int32_t aOrientation,
303                                 ErrorResult& aError) {
304   if (!IsValidRowIndex(aRow)) {
305     aError.Throw(NS_ERROR_INVALID_ARG);
306   }
307   return false;
308 }
309 
CanDrop(int32_t aRow,int32_t aOrientation,DataTransfer * aDataTransfer,ErrorResult & aError)310 bool nsTreeContentView::CanDrop(int32_t aRow, int32_t aOrientation,
311                                 DataTransfer* aDataTransfer,
312                                 ErrorResult& aError) {
313   return CanDrop(aRow, aOrientation, aError);
314 }
315 
316 NS_IMETHODIMP
CanDrop(int32_t aIndex,int32_t aOrientation,DataTransfer * aDataTransfer,bool * _retval)317 nsTreeContentView::CanDrop(int32_t aIndex, int32_t aOrientation,
318                            DataTransfer* aDataTransfer, bool* _retval) {
319   ErrorResult rv;
320   *_retval = CanDrop(aIndex, aOrientation, rv);
321   return rv.StealNSResult();
322 }
323 
Drop(int32_t aRow,int32_t aOrientation,ErrorResult & aError)324 void nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation,
325                              ErrorResult& aError) {
326   if (!IsValidRowIndex(aRow)) {
327     aError.Throw(NS_ERROR_INVALID_ARG);
328   }
329 }
330 
Drop(int32_t aRow,int32_t aOrientation,DataTransfer * aDataTransfer,ErrorResult & aError)331 void nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation,
332                              DataTransfer* aDataTransfer, ErrorResult& aError) {
333   Drop(aRow, aOrientation, aError);
334 }
335 
336 NS_IMETHODIMP
Drop(int32_t aRow,int32_t aOrientation,DataTransfer * aDataTransfer)337 nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation,
338                         DataTransfer* aDataTransfer) {
339   ErrorResult rv;
340   Drop(aRow, aOrientation, rv);
341   return rv.StealNSResult();
342 }
343 
GetParentIndex(int32_t aRow,ErrorResult & aError)344 int32_t nsTreeContentView::GetParentIndex(int32_t aRow, ErrorResult& aError) {
345   if (!IsValidRowIndex(aRow)) {
346     aError.Throw(NS_ERROR_INVALID_ARG);
347     return 0;
348   }
349 
350   return mRows[aRow]->mParentIndex;
351 }
352 
353 NS_IMETHODIMP
GetParentIndex(int32_t aRowIndex,int32_t * _retval)354 nsTreeContentView::GetParentIndex(int32_t aRowIndex, int32_t* _retval) {
355   ErrorResult rv;
356   *_retval = GetParentIndex(aRowIndex, rv);
357   return rv.StealNSResult();
358 }
359 
HasNextSibling(int32_t aRow,int32_t aAfterIndex,ErrorResult & aError)360 bool nsTreeContentView::HasNextSibling(int32_t aRow, int32_t aAfterIndex,
361                                        ErrorResult& aError) {
362   if (!IsValidRowIndex(aRow)) {
363     aError.Throw(NS_ERROR_INVALID_ARG);
364     return false;
365   }
366 
367   // We have a next sibling if the row is not the last in the subtree.
368   int32_t parentIndex = mRows[aRow]->mParentIndex;
369   if (parentIndex < 0) {
370     return uint32_t(aRow) < mRows.Length() - 1;
371   }
372 
373   // Compute the last index in this subtree.
374   int32_t lastIndex = parentIndex + (mRows[parentIndex])->mSubtreeSize;
375   Row* row = mRows[lastIndex].get();
376   while (row->mParentIndex != parentIndex) {
377     lastIndex = row->mParentIndex;
378     row = mRows[lastIndex].get();
379   }
380 
381   return aRow < lastIndex;
382 }
383 
384 NS_IMETHODIMP
HasNextSibling(int32_t aRowIndex,int32_t aAfterIndex,bool * _retval)385 nsTreeContentView::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex,
386                                   bool* _retval) {
387   ErrorResult rv;
388   *_retval = HasNextSibling(aRowIndex, aAfterIndex, rv);
389   return rv.StealNSResult();
390 }
391 
GetLevel(int32_t aRow,ErrorResult & aError)392 int32_t nsTreeContentView::GetLevel(int32_t aRow, ErrorResult& aError) {
393   if (!IsValidRowIndex(aRow)) {
394     aError.Throw(NS_ERROR_INVALID_ARG);
395     return 0;
396   }
397 
398   int32_t level = 0;
399   Row* row = mRows[aRow].get();
400   while (row->mParentIndex >= 0) {
401     level++;
402     row = mRows[row->mParentIndex].get();
403   }
404   return level;
405 }
406 
407 NS_IMETHODIMP
GetLevel(int32_t aIndex,int32_t * _retval)408 nsTreeContentView::GetLevel(int32_t aIndex, int32_t* _retval) {
409   ErrorResult rv;
410   *_retval = GetLevel(aIndex, rv);
411   return rv.StealNSResult();
412 }
413 
GetImageSrc(int32_t aRow,nsTreeColumn & aColumn,nsAString & aSrc,ErrorResult & aError)414 void nsTreeContentView::GetImageSrc(int32_t aRow, nsTreeColumn& aColumn,
415                                     nsAString& aSrc, ErrorResult& aError) {
416   if (!IsValidRowIndex(aRow)) {
417     aError.Throw(NS_ERROR_INVALID_ARG);
418     return;
419   }
420 
421   Row* row = mRows[aRow].get();
422 
423   nsIContent* realRow =
424       nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
425   if (realRow) {
426     Element* cell = GetCell(realRow, aColumn);
427     if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::src, aSrc);
428   }
429 }
430 
431 NS_IMETHODIMP
GetImageSrc(int32_t aRow,nsTreeColumn * aCol,nsAString & _retval)432 nsTreeContentView::GetImageSrc(int32_t aRow, nsTreeColumn* aCol,
433                                nsAString& _retval) {
434   NS_ENSURE_ARG(aCol);
435 
436   ErrorResult rv;
437   GetImageSrc(aRow, *aCol, _retval, rv);
438   return rv.StealNSResult();
439 }
440 
GetCellValue(int32_t aRow,nsTreeColumn & aColumn,nsAString & aValue,ErrorResult & aError)441 void nsTreeContentView::GetCellValue(int32_t aRow, nsTreeColumn& aColumn,
442                                      nsAString& aValue, ErrorResult& aError) {
443   if (!IsValidRowIndex(aRow)) {
444     aError.Throw(NS_ERROR_INVALID_ARG);
445     return;
446   }
447 
448   Row* row = mRows[aRow].get();
449 
450   nsIContent* realRow =
451       nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
452   if (realRow) {
453     Element* cell = GetCell(realRow, aColumn);
454     if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue);
455   }
456 }
457 
458 NS_IMETHODIMP
GetCellValue(int32_t aRow,nsTreeColumn * aCol,nsAString & _retval)459 nsTreeContentView::GetCellValue(int32_t aRow, nsTreeColumn* aCol,
460                                 nsAString& _retval) {
461   NS_ENSURE_ARG(aCol);
462 
463   ErrorResult rv;
464   GetCellValue(aRow, *aCol, _retval, rv);
465   return rv.StealNSResult();
466 }
467 
GetCellText(int32_t aRow,nsTreeColumn & aColumn,nsAString & aText,ErrorResult & aError)468 void nsTreeContentView::GetCellText(int32_t aRow, nsTreeColumn& aColumn,
469                                     nsAString& aText, ErrorResult& aError) {
470   if (!IsValidRowIndex(aRow)) {
471     aError.Throw(NS_ERROR_INVALID_ARG);
472     return;
473   }
474 
475   Row* row = mRows[aRow].get();
476 
477   // Check for a "label" attribute - this is valid on an <treeitem>
478   // with a single implied column.
479   if (row->mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aText) &&
480       !aText.IsEmpty()) {
481     return;
482   }
483 
484   if (row->mContent->IsXULElement(nsGkAtoms::treeitem)) {
485     nsIContent* realRow =
486         nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
487     if (realRow) {
488       Element* cell = GetCell(realRow, aColumn);
489       if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aText);
490     }
491   }
492 }
493 
494 NS_IMETHODIMP
GetCellText(int32_t aRow,nsTreeColumn * aCol,nsAString & _retval)495 nsTreeContentView::GetCellText(int32_t aRow, nsTreeColumn* aCol,
496                                nsAString& _retval) {
497   NS_ENSURE_ARG(aCol);
498 
499   ErrorResult rv;
500   GetCellText(aRow, *aCol, _retval, rv);
501   return rv.StealNSResult();
502 }
503 
SetTree(XULTreeElement * aTree,ErrorResult & aError)504 void nsTreeContentView::SetTree(XULTreeElement* aTree, ErrorResult& aError) {
505   aError = SetTree(aTree);
506 }
507 
508 NS_IMETHODIMP
SetTree(XULTreeElement * aTree)509 nsTreeContentView::SetTree(XULTreeElement* aTree) {
510   ClearRows();
511 
512   mTree = aTree;
513 
514   if (aTree) {
515     // Add ourselves to document's observers.
516     Document* document = mTree->GetComposedDoc();
517     if (document) {
518       document->AddObserver(this);
519       mDocument = document;
520     }
521 
522     RefPtr<dom::Element> bodyElement = mTree->GetTreeBody();
523     if (bodyElement) {
524       mBody = std::move(bodyElement);
525       int32_t index = 0;
526       Serialize(mBody, -1, &index, mRows);
527     }
528   }
529 
530   return NS_OK;
531 }
532 
ToggleOpenState(int32_t aRow,ErrorResult & aError)533 void nsTreeContentView::ToggleOpenState(int32_t aRow, ErrorResult& aError) {
534   if (!IsValidRowIndex(aRow)) {
535     aError.Throw(NS_ERROR_INVALID_ARG);
536     return;
537   }
538 
539   // We don't serialize content right here, since content might be generated
540   // lazily.
541   Row* row = mRows[aRow].get();
542 
543   if (row->IsOpen())
544     row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, u"false"_ns,
545                            true);
546   else
547     row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, u"true"_ns,
548                            true);
549 }
550 
551 NS_IMETHODIMP
ToggleOpenState(int32_t aIndex)552 nsTreeContentView::ToggleOpenState(int32_t aIndex) {
553   ErrorResult rv;
554   ToggleOpenState(aIndex, rv);
555   return rv.StealNSResult();
556 }
557 
CycleHeader(nsTreeColumn & aColumn,ErrorResult & aError)558 void nsTreeContentView::CycleHeader(nsTreeColumn& aColumn,
559                                     ErrorResult& aError) {
560   if (!mTree) return;
561 
562   RefPtr<Element> column = aColumn.Element();
563   nsAutoString sort;
564   column->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
565   if (!sort.IsEmpty()) {
566     nsAutoString sortdirection;
567     static Element::AttrValuesArray strings[] = {
568         nsGkAtoms::ascending, nsGkAtoms::descending, nullptr};
569     switch (column->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sortDirection,
570                                     strings, eCaseMatters)) {
571       case 0:
572         sortdirection.AssignLiteral("descending");
573         break;
574       case 1:
575         sortdirection.AssignLiteral("natural");
576         break;
577       default:
578         sortdirection.AssignLiteral("ascending");
579         break;
580     }
581 
582     nsAutoString hints;
583     column->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints);
584     sortdirection.Append(' ');
585     sortdirection += hints;
586 
587     XULWidgetSort(mTree, sort, sortdirection);
588   }
589 }
590 
591 NS_IMETHODIMP
CycleHeader(nsTreeColumn * aCol)592 nsTreeContentView::CycleHeader(nsTreeColumn* aCol) {
593   NS_ENSURE_ARG(aCol);
594 
595   ErrorResult rv;
596   CycleHeader(*aCol, rv);
597   return rv.StealNSResult();
598 }
599 
600 NS_IMETHODIMP
SelectionChangedXPCOM()601 nsTreeContentView::SelectionChangedXPCOM() { return NS_OK; }
602 
603 NS_IMETHODIMP
CycleCell(int32_t aRow,nsTreeColumn * aCol)604 nsTreeContentView::CycleCell(int32_t aRow, nsTreeColumn* aCol) { return NS_OK; }
605 
IsEditable(int32_t aRow,nsTreeColumn & aColumn,ErrorResult & aError)606 bool nsTreeContentView::IsEditable(int32_t aRow, nsTreeColumn& aColumn,
607                                    ErrorResult& aError) {
608   if (!IsValidRowIndex(aRow)) {
609     aError.Throw(NS_ERROR_INVALID_ARG);
610     return false;
611   }
612 
613   Row* row = mRows[aRow].get();
614 
615   nsIContent* realRow =
616       nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
617   if (realRow) {
618     Element* cell = GetCell(realRow, aColumn);
619     if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
620                                   nsGkAtoms::_false, eCaseMatters)) {
621       return false;
622     }
623   }
624 
625   return true;
626 }
627 
628 NS_IMETHODIMP
IsEditable(int32_t aRow,nsTreeColumn * aCol,bool * _retval)629 nsTreeContentView::IsEditable(int32_t aRow, nsTreeColumn* aCol, bool* _retval) {
630   NS_ENSURE_ARG(aCol);
631 
632   ErrorResult rv;
633   *_retval = IsEditable(aRow, *aCol, rv);
634   return rv.StealNSResult();
635 }
636 
SetCellValue(int32_t aRow,nsTreeColumn & aColumn,const nsAString & aValue,ErrorResult & aError)637 void nsTreeContentView::SetCellValue(int32_t aRow, nsTreeColumn& aColumn,
638                                      const nsAString& aValue,
639                                      ErrorResult& aError) {
640   if (!IsValidRowIndex(aRow)) {
641     aError.Throw(NS_ERROR_INVALID_ARG);
642     return;
643   }
644 
645   Row* row = mRows[aRow].get();
646 
647   nsIContent* realRow =
648       nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
649   if (realRow) {
650     Element* cell = GetCell(realRow, aColumn);
651     if (cell) cell->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, true);
652   }
653 }
654 
655 NS_IMETHODIMP
SetCellValue(int32_t aRow,nsTreeColumn * aCol,const nsAString & aValue)656 nsTreeContentView::SetCellValue(int32_t aRow, nsTreeColumn* aCol,
657                                 const nsAString& aValue) {
658   NS_ENSURE_ARG(aCol);
659 
660   ErrorResult rv;
661   SetCellValue(aRow, *aCol, aValue, rv);
662   return rv.StealNSResult();
663 }
664 
SetCellText(int32_t aRow,nsTreeColumn & aColumn,const nsAString & aValue,ErrorResult & aError)665 void nsTreeContentView::SetCellText(int32_t aRow, nsTreeColumn& aColumn,
666                                     const nsAString& aValue,
667                                     ErrorResult& aError) {
668   if (!IsValidRowIndex(aRow)) {
669     aError.Throw(NS_ERROR_INVALID_ARG);
670     return;
671   }
672 
673   Row* row = mRows[aRow].get();
674 
675   nsIContent* realRow =
676       nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
677   if (realRow) {
678     Element* cell = GetCell(realRow, aColumn);
679     if (cell) cell->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aValue, true);
680   }
681 }
682 
683 NS_IMETHODIMP
SetCellText(int32_t aRow,nsTreeColumn * aCol,const nsAString & aValue)684 nsTreeContentView::SetCellText(int32_t aRow, nsTreeColumn* aCol,
685                                const nsAString& aValue) {
686   NS_ENSURE_ARG(aCol);
687 
688   ErrorResult rv;
689   SetCellText(aRow, *aCol, aValue, rv);
690   return rv.StealNSResult();
691 }
692 
GetItemAtIndex(int32_t aIndex,ErrorResult & aError)693 Element* nsTreeContentView::GetItemAtIndex(int32_t aIndex,
694                                            ErrorResult& aError) {
695   if (!IsValidRowIndex(aIndex)) {
696     aError.Throw(NS_ERROR_INVALID_ARG);
697     return nullptr;
698   }
699 
700   return mRows[aIndex]->mContent;
701 }
702 
GetIndexOfItem(Element * aItem)703 int32_t nsTreeContentView::GetIndexOfItem(Element* aItem) {
704   return FindContent(aItem);
705 }
706 
AttributeChanged(dom::Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)707 void nsTreeContentView::AttributeChanged(dom::Element* aElement,
708                                          int32_t aNameSpaceID,
709                                          nsAtom* aAttribute, int32_t aModType,
710                                          const nsAttrValue* aOldValue) {
711   // Lots of codepaths under here that do all sorts of stuff, so be safe.
712   nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
713 
714   // Make sure this notification concerns us.
715   // First check the tag to see if it's one that we care about.
716   if (aElement == mTree || aElement == mBody) {
717     mTree->ClearStyleAndImageCaches();
718     mTree->Invalidate();
719   }
720 
721   // We don't consider non-XUL nodes.
722   nsIContent* parent = nullptr;
723   if (!aElement->IsXULElement() ||
724       ((parent = aElement->GetParent()) && !parent->IsXULElement())) {
725     return;
726   }
727   if (!aElement->IsAnyOfXULElements(nsGkAtoms::treecol, nsGkAtoms::treeitem,
728                                     nsGkAtoms::treeseparator,
729                                     nsGkAtoms::treerow, nsGkAtoms::treecell)) {
730     return;
731   }
732 
733   // If we have a legal tag, go up to the tree/select and make sure
734   // that it's ours.
735 
736   for (nsIContent* element = aElement; element != mBody;
737        element = element->GetParent()) {
738     if (!element) return;                                // this is not for us
739     if (element->IsXULElement(nsGkAtoms::tree)) return;  // this is not for us
740   }
741 
742   // Handle changes of the hidden attribute.
743   if (aAttribute == nsGkAtoms::hidden &&
744       aElement->IsAnyOfXULElements(nsGkAtoms::treeitem,
745                                    nsGkAtoms::treeseparator)) {
746     bool hidden = aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
747                                         nsGkAtoms::_true, eCaseMatters);
748 
749     int32_t index = FindContent(aElement);
750     if (hidden && index >= 0) {
751       // Hide this row along with its children.
752       int32_t count = RemoveRow(index);
753       if (mTree) mTree->RowCountChanged(index, -count);
754     } else if (!hidden && index < 0) {
755       // Show this row along with its children.
756       nsCOMPtr<nsIContent> parent = aElement->GetParent();
757       if (parent) {
758         InsertRowFor(parent, aElement);
759       }
760     }
761 
762     return;
763   }
764 
765   if (aElement->IsXULElement(nsGkAtoms::treecol)) {
766     if (aAttribute == nsGkAtoms::properties) {
767       if (mTree) {
768         RefPtr<nsTreeColumns> cols = mTree->GetColumns();
769         if (cols) {
770           RefPtr<nsTreeColumn> col = cols->GetColumnFor(aElement);
771           mTree->InvalidateColumn(col);
772         }
773       }
774     }
775   } else if (aElement->IsXULElement(nsGkAtoms::treeitem)) {
776     int32_t index = FindContent(aElement);
777     if (index >= 0) {
778       Row* row = mRows[index].get();
779       if (aAttribute == nsGkAtoms::container) {
780         bool isContainer =
781             aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container,
782                                   nsGkAtoms::_true, eCaseMatters);
783         row->SetContainer(isContainer);
784         if (mTree) mTree->InvalidateRow(index);
785       } else if (aAttribute == nsGkAtoms::open) {
786         bool isOpen = aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
787                                             nsGkAtoms::_true, eCaseMatters);
788         bool wasOpen = row->IsOpen();
789         if (!isOpen && wasOpen)
790           CloseContainer(index);
791         else if (isOpen && !wasOpen)
792           OpenContainer(index);
793       } else if (aAttribute == nsGkAtoms::empty) {
794         bool isEmpty =
795             aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty,
796                                   nsGkAtoms::_true, eCaseMatters);
797         row->SetEmpty(isEmpty);
798         if (mTree) mTree->InvalidateRow(index);
799       }
800     }
801   } else if (aElement->IsXULElement(nsGkAtoms::treeseparator)) {
802     int32_t index = FindContent(aElement);
803     if (index >= 0) {
804       if (aAttribute == nsGkAtoms::properties && mTree) {
805         mTree->InvalidateRow(index);
806       }
807     }
808   } else if (aElement->IsXULElement(nsGkAtoms::treerow)) {
809     if (aAttribute == nsGkAtoms::properties) {
810       nsCOMPtr<nsIContent> parent = aElement->GetParent();
811       if (parent) {
812         int32_t index = FindContent(parent);
813         if (index >= 0 && mTree) {
814           mTree->InvalidateRow(index);
815         }
816       }
817     }
818   } else if (aElement->IsXULElement(nsGkAtoms::treecell)) {
819     if (aAttribute == nsGkAtoms::properties || aAttribute == nsGkAtoms::mode ||
820         aAttribute == nsGkAtoms::src || aAttribute == nsGkAtoms::value ||
821         aAttribute == nsGkAtoms::label) {
822       nsIContent* parent = aElement->GetParent();
823       if (parent) {
824         nsCOMPtr<nsIContent> grandParent = parent->GetParent();
825         if (grandParent && grandParent->IsXULElement()) {
826           int32_t index = FindContent(grandParent);
827           if (index >= 0 && mTree) {
828             // XXX Should we make an effort to invalidate only cell ?
829             mTree->InvalidateRow(index);
830           }
831         }
832       }
833     }
834   }
835 }
836 
ContentAppended(nsIContent * aFirstNewContent)837 void nsTreeContentView::ContentAppended(nsIContent* aFirstNewContent) {
838   for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
839     // Our contentinserted doesn't use the index
840     ContentInserted(cur);
841   }
842 }
843 
ContentInserted(nsIContent * aChild)844 void nsTreeContentView::ContentInserted(nsIContent* aChild) {
845   NS_ASSERTION(aChild, "null ptr");
846   nsIContent* container = aChild->GetParent();
847 
848   // Make sure this notification concerns us.
849   // First check the tag to see if it's one that we care about.
850 
851   // Don't allow non-XUL nodes.
852   if (!aChild->IsXULElement() || !container->IsXULElement()) return;
853 
854   if (!aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, nsGkAtoms::treeseparator,
855                                   nsGkAtoms::treechildren, nsGkAtoms::treerow,
856                                   nsGkAtoms::treecell)) {
857     return;
858   }
859 
860   // If we have a legal tag, go up to the tree/select and make sure
861   // that it's ours.
862 
863   for (nsIContent* element = container; element != mBody;
864        element = element->GetParent()) {
865     if (!element) return;                                // this is not for us
866     if (element->IsXULElement(nsGkAtoms::tree)) return;  // this is not for us
867   }
868 
869   // Lots of codepaths under here that do all sorts of stuff, so be safe.
870   nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
871 
872   if (aChild->IsXULElement(nsGkAtoms::treechildren)) {
873     int32_t index = FindContent(container);
874     if (index >= 0) {
875       Row* row = mRows[index].get();
876       row->SetEmpty(false);
877       if (mTree) mTree->InvalidateRow(index);
878       if (row->IsContainer() && row->IsOpen()) {
879         int32_t count = EnsureSubtree(index);
880         if (mTree) mTree->RowCountChanged(index + 1, count);
881       }
882     }
883   } else if (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem,
884                                         nsGkAtoms::treeseparator)) {
885     InsertRowFor(container, aChild);
886   } else if (aChild->IsXULElement(nsGkAtoms::treerow)) {
887     int32_t index = FindContent(container);
888     if (index >= 0 && mTree) mTree->InvalidateRow(index);
889   } else if (aChild->IsXULElement(nsGkAtoms::treecell)) {
890     nsCOMPtr<nsIContent> parent = container->GetParent();
891     if (parent) {
892       int32_t index = FindContent(parent);
893       if (index >= 0 && mTree) mTree->InvalidateRow(index);
894     }
895   }
896 }
897 
ContentRemoved(nsIContent * aChild,nsIContent * aPreviousSibling)898 void nsTreeContentView::ContentRemoved(nsIContent* aChild,
899                                        nsIContent* aPreviousSibling) {
900   NS_ASSERTION(aChild, "null ptr");
901 
902   nsIContent* container = aChild->GetParent();
903   // Make sure this notification concerns us.
904   // First check the tag to see if it's one that we care about.
905 
906   // We don't consider non-XUL nodes.
907   if (!aChild->IsXULElement() || !container->IsXULElement()) return;
908 
909   if (!aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, nsGkAtoms::treeseparator,
910                                   nsGkAtoms::treechildren, nsGkAtoms::treerow,
911                                   nsGkAtoms::treecell)) {
912     return;
913   }
914 
915   // If we have a legal tag, go up to the tree/select and make sure
916   // that it's ours.
917 
918   for (nsIContent* element = container; element != mBody;
919        element = element->GetParent()) {
920     if (!element) return;                                // this is not for us
921     if (element->IsXULElement(nsGkAtoms::tree)) return;  // this is not for us
922   }
923 
924   // Lots of codepaths under here that do all sorts of stuff, so be safe.
925   nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
926 
927   if (aChild->IsXULElement(nsGkAtoms::treechildren)) {
928     int32_t index = FindContent(container);
929     if (index >= 0) {
930       Row* row = mRows[index].get();
931       row->SetEmpty(true);
932       int32_t count = RemoveSubtree(index);
933       // Invalidate also the row to update twisty.
934       if (mTree) {
935         mTree->InvalidateRow(index);
936         mTree->RowCountChanged(index + 1, -count);
937       }
938     }
939   } else if (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem,
940                                         nsGkAtoms::treeseparator)) {
941     int32_t index = FindContent(aChild);
942     if (index >= 0) {
943       int32_t count = RemoveRow(index);
944       if (mTree) mTree->RowCountChanged(index, -count);
945     }
946   } else if (aChild->IsXULElement(nsGkAtoms::treerow)) {
947     int32_t index = FindContent(container);
948     if (index >= 0 && mTree) mTree->InvalidateRow(index);
949   } else if (aChild->IsXULElement(nsGkAtoms::treecell)) {
950     nsCOMPtr<nsIContent> parent = container->GetParent();
951     if (parent) {
952       int32_t index = FindContent(parent);
953       if (index >= 0 && mTree) mTree->InvalidateRow(index);
954     }
955   }
956 }
957 
NodeWillBeDestroyed(const nsINode * aNode)958 void nsTreeContentView::NodeWillBeDestroyed(const nsINode* aNode) {
959   // XXXbz do we need this strong ref?  Do we drop refs to self in ClearRows?
960   nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
961   ClearRows();
962 }
963 
964 // Recursively serialize content, starting with aContent.
Serialize(nsIContent * aContent,int32_t aParentIndex,int32_t * aIndex,nsTArray<UniquePtr<Row>> & aRows)965 void nsTreeContentView::Serialize(nsIContent* aContent, int32_t aParentIndex,
966                                   int32_t* aIndex,
967                                   nsTArray<UniquePtr<Row>>& aRows) {
968   // Don't allow non-XUL nodes.
969   if (!aContent->IsXULElement()) return;
970 
971   dom::FlattenedChildIterator iter(aContent);
972   for (nsIContent* content = iter.GetNextChild(); content;
973        content = iter.GetNextChild()) {
974     int32_t count = aRows.Length();
975 
976     if (content->IsXULElement(nsGkAtoms::treeitem)) {
977       SerializeItem(content->AsElement(), aParentIndex, aIndex, aRows);
978     } else if (content->IsXULElement(nsGkAtoms::treeseparator)) {
979       SerializeSeparator(content->AsElement(), aParentIndex, aIndex, aRows);
980     }
981 
982     *aIndex += aRows.Length() - count;
983   }
984 }
985 
SerializeItem(Element * aContent,int32_t aParentIndex,int32_t * aIndex,nsTArray<UniquePtr<Row>> & aRows)986 void nsTreeContentView::SerializeItem(Element* aContent, int32_t aParentIndex,
987                                       int32_t* aIndex,
988                                       nsTArray<UniquePtr<Row>>& aRows) {
989   if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
990                             nsGkAtoms::_true, eCaseMatters))
991     return;
992 
993   aRows.AppendElement(MakeUnique<Row>(aContent, aParentIndex));
994   Row* row = aRows.LastElement().get();
995 
996   if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container,
997                             nsGkAtoms::_true, eCaseMatters)) {
998     row->SetContainer(true);
999     if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
1000                               nsGkAtoms::_true, eCaseMatters)) {
1001       row->SetOpen(true);
1002       nsIContent* child =
1003           nsTreeUtils::GetImmediateChild(aContent, nsGkAtoms::treechildren);
1004       if (child && child->IsXULElement()) {
1005         // Now, recursively serialize our child.
1006         int32_t count = aRows.Length();
1007         int32_t index = 0;
1008         Serialize(child, aParentIndex + *aIndex + 1, &index, aRows);
1009         row->mSubtreeSize += aRows.Length() - count;
1010       } else
1011         row->SetEmpty(true);
1012     } else if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty,
1013                                      nsGkAtoms::_true, eCaseMatters)) {
1014       row->SetEmpty(true);
1015     }
1016   }
1017 }
1018 
SerializeSeparator(Element * aContent,int32_t aParentIndex,int32_t * aIndex,nsTArray<UniquePtr<Row>> & aRows)1019 void nsTreeContentView::SerializeSeparator(Element* aContent,
1020                                            int32_t aParentIndex,
1021                                            int32_t* aIndex,
1022                                            nsTArray<UniquePtr<Row>>& aRows) {
1023   if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
1024                             nsGkAtoms::_true, eCaseMatters))
1025     return;
1026 
1027   auto row = MakeUnique<Row>(aContent, aParentIndex);
1028   row->SetSeparator(true);
1029   aRows.AppendElement(std::move(row));
1030 }
1031 
GetIndexInSubtree(nsIContent * aContainer,nsIContent * aContent,int32_t * aIndex)1032 void nsTreeContentView::GetIndexInSubtree(nsIContent* aContainer,
1033                                           nsIContent* aContent,
1034                                           int32_t* aIndex) {
1035   if (!aContainer->IsXULElement()) return;
1036 
1037   for (nsIContent* content = aContainer->GetFirstChild(); content;
1038        content = content->GetNextSibling()) {
1039     if (content == aContent) break;
1040 
1041     if (content->IsXULElement(nsGkAtoms::treeitem)) {
1042       if (!content->AsElement()->AttrValueIs(kNameSpaceID_None,
1043                                              nsGkAtoms::hidden,
1044                                              nsGkAtoms::_true, eCaseMatters)) {
1045         (*aIndex)++;
1046         if (content->AsElement()->AttrValueIs(kNameSpaceID_None,
1047                                               nsGkAtoms::container,
1048                                               nsGkAtoms::_true, eCaseMatters) &&
1049             content->AsElement()->AttrValueIs(kNameSpaceID_None,
1050                                               nsGkAtoms::open, nsGkAtoms::_true,
1051                                               eCaseMatters)) {
1052           nsIContent* child =
1053               nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treechildren);
1054           if (child && child->IsXULElement())
1055             GetIndexInSubtree(child, aContent, aIndex);
1056         }
1057       }
1058     } else if (content->IsXULElement(nsGkAtoms::treeseparator)) {
1059       if (!content->AsElement()->AttrValueIs(kNameSpaceID_None,
1060                                              nsGkAtoms::hidden,
1061                                              nsGkAtoms::_true, eCaseMatters))
1062         (*aIndex)++;
1063     }
1064   }
1065 }
1066 
EnsureSubtree(int32_t aIndex)1067 int32_t nsTreeContentView::EnsureSubtree(int32_t aIndex) {
1068   Row* row = mRows[aIndex].get();
1069 
1070   nsIContent* child;
1071   child =
1072       nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treechildren);
1073   if (!child || !child->IsXULElement()) {
1074     return 0;
1075   }
1076 
1077   AutoTArray<UniquePtr<Row>, 8> rows;
1078   int32_t index = 0;
1079   Serialize(child, aIndex, &index, rows);
1080   // Insert |rows| into |mRows| at position |aIndex|, by first creating empty
1081   // UniquePtr entries and then Move'ing |rows|'s entries into them. (Note
1082   // that we can't simply use InsertElementsAt with an array argument, since
1083   // the destination can't steal ownership from its const source argument.)
1084   UniquePtr<Row>* newRows = mRows.InsertElementsAt(aIndex + 1, rows.Length());
1085   for (nsTArray<Row>::index_type i = 0; i < rows.Length(); i++) {
1086     newRows[i] = std::move(rows[i]);
1087   }
1088   int32_t count = rows.Length();
1089 
1090   row->mSubtreeSize += count;
1091   UpdateSubtreeSizes(row->mParentIndex, count);
1092 
1093   // Update parent indexes, but skip newly added rows.
1094   // They already have correct values.
1095   UpdateParentIndexes(aIndex, count + 1, count);
1096 
1097   return count;
1098 }
1099 
RemoveSubtree(int32_t aIndex)1100 int32_t nsTreeContentView::RemoveSubtree(int32_t aIndex) {
1101   Row* row = mRows[aIndex].get();
1102   int32_t count = row->mSubtreeSize;
1103 
1104   mRows.RemoveElementsAt(aIndex + 1, count);
1105 
1106   row->mSubtreeSize -= count;
1107   UpdateSubtreeSizes(row->mParentIndex, -count);
1108 
1109   UpdateParentIndexes(aIndex, 0, -count);
1110 
1111   return count;
1112 }
1113 
InsertRowFor(nsIContent * aParent,nsIContent * aChild)1114 void nsTreeContentView::InsertRowFor(nsIContent* aParent, nsIContent* aChild) {
1115   int32_t grandParentIndex = -1;
1116   bool insertRow = false;
1117 
1118   nsCOMPtr<nsIContent> grandParent = aParent->GetParent();
1119 
1120   if (grandParent->IsXULElement(nsGkAtoms::tree)) {
1121     // Allow insertion to the outermost container.
1122     insertRow = true;
1123   } else {
1124     // Test insertion to an inner container.
1125 
1126     // First try to find this parent in our array of rows, if we find one
1127     // we can be sure that all other parents are open too.
1128     grandParentIndex = FindContent(grandParent);
1129     if (grandParentIndex >= 0) {
1130       // Got it, now test if it is open.
1131       if (mRows[grandParentIndex]->IsOpen()) insertRow = true;
1132     }
1133   }
1134 
1135   if (insertRow) {
1136     int32_t index = 0;
1137     GetIndexInSubtree(aParent, aChild, &index);
1138 
1139     int32_t count = InsertRow(grandParentIndex, index, aChild);
1140     if (mTree) mTree->RowCountChanged(grandParentIndex + index + 1, count);
1141   }
1142 }
1143 
InsertRow(int32_t aParentIndex,int32_t aIndex,nsIContent * aContent)1144 int32_t nsTreeContentView::InsertRow(int32_t aParentIndex, int32_t aIndex,
1145                                      nsIContent* aContent) {
1146   AutoTArray<UniquePtr<Row>, 8> rows;
1147   if (aContent->IsXULElement(nsGkAtoms::treeitem)) {
1148     SerializeItem(aContent->AsElement(), aParentIndex, &aIndex, rows);
1149   } else if (aContent->IsXULElement(nsGkAtoms::treeseparator)) {
1150     SerializeSeparator(aContent->AsElement(), aParentIndex, &aIndex, rows);
1151   }
1152 
1153   // We can't use InsertElementsAt since the destination can't steal
1154   // ownership from its const source argument.
1155   int32_t count = rows.Length();
1156   for (nsTArray<Row>::index_type i = 0; i < size_t(count); i++) {
1157     mRows.InsertElementAt(aParentIndex + aIndex + i + 1, std::move(rows[i]));
1158   }
1159 
1160   UpdateSubtreeSizes(aParentIndex, count);
1161 
1162   // Update parent indexes, but skip added rows.
1163   // They already have correct values.
1164   UpdateParentIndexes(aParentIndex + aIndex, count + 1, count);
1165 
1166   return count;
1167 }
1168 
RemoveRow(int32_t aIndex)1169 int32_t nsTreeContentView::RemoveRow(int32_t aIndex) {
1170   Row* row = mRows[aIndex].get();
1171   int32_t count = row->mSubtreeSize + 1;
1172   int32_t parentIndex = row->mParentIndex;
1173 
1174   mRows.RemoveElementsAt(aIndex, count);
1175 
1176   UpdateSubtreeSizes(parentIndex, -count);
1177 
1178   UpdateParentIndexes(aIndex, 0, -count);
1179 
1180   return count;
1181 }
1182 
ClearRows()1183 void nsTreeContentView::ClearRows() {
1184   mRows.Clear();
1185   mBody = nullptr;
1186   // Remove ourselves from mDocument's observers.
1187   if (mDocument) {
1188     mDocument->RemoveObserver(this);
1189     mDocument = nullptr;
1190   }
1191 }
1192 
OpenContainer(int32_t aIndex)1193 void nsTreeContentView::OpenContainer(int32_t aIndex) {
1194   Row* row = mRows[aIndex].get();
1195   row->SetOpen(true);
1196 
1197   int32_t count = EnsureSubtree(aIndex);
1198   if (mTree) {
1199     mTree->InvalidateRow(aIndex);
1200     mTree->RowCountChanged(aIndex + 1, count);
1201   }
1202 }
1203 
CloseContainer(int32_t aIndex)1204 void nsTreeContentView::CloseContainer(int32_t aIndex) {
1205   Row* row = mRows[aIndex].get();
1206   row->SetOpen(false);
1207 
1208   int32_t count = RemoveSubtree(aIndex);
1209   if (mTree) {
1210     mTree->InvalidateRow(aIndex);
1211     mTree->RowCountChanged(aIndex + 1, -count);
1212   }
1213 }
1214 
FindContent(nsIContent * aContent)1215 int32_t nsTreeContentView::FindContent(nsIContent* aContent) {
1216   for (uint32_t i = 0; i < mRows.Length(); i++) {
1217     if (mRows[i]->mContent == aContent) {
1218       return i;
1219     }
1220   }
1221 
1222   return -1;
1223 }
1224 
UpdateSubtreeSizes(int32_t aParentIndex,int32_t count)1225 void nsTreeContentView::UpdateSubtreeSizes(int32_t aParentIndex,
1226                                            int32_t count) {
1227   while (aParentIndex >= 0) {
1228     Row* row = mRows[aParentIndex].get();
1229     row->mSubtreeSize += count;
1230     aParentIndex = row->mParentIndex;
1231   }
1232 }
1233 
UpdateParentIndexes(int32_t aIndex,int32_t aSkip,int32_t aCount)1234 void nsTreeContentView::UpdateParentIndexes(int32_t aIndex, int32_t aSkip,
1235                                             int32_t aCount) {
1236   int32_t count = mRows.Length();
1237   for (int32_t i = aIndex + aSkip; i < count; i++) {
1238     Row* row = mRows[i].get();
1239     if (row->mParentIndex > aIndex) {
1240       row->mParentIndex += aCount;
1241     }
1242   }
1243 }
1244 
GetCell(nsIContent * aContainer,nsTreeColumn & aCol)1245 Element* nsTreeContentView::GetCell(nsIContent* aContainer,
1246                                     nsTreeColumn& aCol) {
1247   int32_t colIndex(aCol.GetIndex());
1248 
1249   // Traverse through cells, try to find the cell by index in a row.
1250   Element* result = nullptr;
1251   int32_t j = 0;
1252   dom::FlattenedChildIterator iter(aContainer);
1253   for (nsIContent* cell = iter.GetNextChild(); cell;
1254        cell = iter.GetNextChild()) {
1255     if (cell->IsXULElement(nsGkAtoms::treecell)) {
1256       if (j == colIndex) {
1257         result = cell->AsElement();
1258         break;
1259       }
1260       j++;
1261     }
1262   }
1263 
1264   return result;
1265 }
1266 
IsValidRowIndex(int32_t aRowIndex)1267 bool nsTreeContentView::IsValidRowIndex(int32_t aRowIndex) {
1268   return aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length());
1269 }
1270