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