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/. */
7 #include "nsNameSpaceManager.h"
9 #include "nsTreeUtils.h"
10 #include "nsTreeContentView.h"
11 #include "ChildIterator.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"
23 using namespace mozilla
;
24 using namespace mozilla::dom
;
26 // A content model view implementation for the tree.
28 #define ROW_FLAG_CONTAINER 0x01
29 #define ROW_FLAG_OPEN 0x02
30 #define ROW_FLAG_EMPTY 0x04
31 #define ROW_FLAG_SEPARATOR 0x08
35 Row(Element
* aContent
, int32_t aParentIndex
)
37 mParentIndex(aParentIndex
),
43 void SetContainer(bool aContainer
) {
44 aContainer
? mFlags
|= ROW_FLAG_CONTAINER
: mFlags
&= ~ROW_FLAG_CONTAINER
;
46 bool IsContainer() { return mFlags
& ROW_FLAG_CONTAINER
; }
48 void SetOpen(bool aOpen
) {
49 aOpen
? mFlags
|= ROW_FLAG_OPEN
: mFlags
&= ~ROW_FLAG_OPEN
;
51 bool IsOpen() { return !!(mFlags
& ROW_FLAG_OPEN
); }
53 void SetEmpty(bool aEmpty
) {
54 aEmpty
? mFlags
|= ROW_FLAG_EMPTY
: mFlags
&= ~ROW_FLAG_EMPTY
;
56 bool IsEmpty() { return !!(mFlags
& ROW_FLAG_EMPTY
); }
58 void SetSeparator(bool aSeparator
) {
59 aSeparator
? mFlags
|= ROW_FLAG_SEPARATOR
: mFlags
&= ~ROW_FLAG_SEPARATOR
;
61 bool IsSeparator() { return !!(mFlags
& ROW_FLAG_SEPARATOR
); }
63 // Weak reference to a content item.
66 // The parent index of the item, set to -1 for the top level items.
69 // Subtree size for this item.
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.
83 nsTreeContentView::nsTreeContentView(void)
84 : mTree(nullptr), mSelection(nullptr), mDocument(nullptr) {}
86 nsTreeContentView::~nsTreeContentView(void) {
87 // Remove ourselves from mDocument's observers.
88 if (mDocument
) mDocument
->RemoveObserver(this);
91 nsresult
NS_NewTreeContentView(nsITreeView
** aResult
) {
92 *aResult
= new nsTreeContentView
;
93 if (!*aResult
) return NS_ERROR_OUT_OF_MEMORY
;
98 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsTreeContentView
, mTree
, mSelection
,
101 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeContentView
)
102 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeContentView
)
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
112 JSObject
* nsTreeContentView::WrapObject(JSContext
* aCx
,
113 JS::Handle
<JSObject
*> aGivenProto
) {
114 return TreeContentView_Binding::Wrap(aCx
, this, aGivenProto
);
117 nsISupports
* nsTreeContentView::GetParentObject() { return mTree
; }
120 nsTreeContentView::GetRowCount(int32_t* aRowCount
) {
121 *aRowCount
= mRows
.Length();
127 nsTreeContentView::GetSelection(nsITreeSelection
** aSelection
) {
128 NS_IF_ADDREF(*aSelection
= GetSelection());
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());
141 nsTreeContentView::SetSelection(nsITreeSelection
* aSelection
) {
143 SetSelection(aSelection
, rv
);
144 return rv
.StealNSResult();
147 void nsTreeContentView::SetSelection(nsITreeSelection
* aSelection
,
148 ErrorResult
& aError
) {
149 if (aSelection
&& !CanTrustTreeSelection(aSelection
)) {
150 aError
.ThrowSecurityError("Not allowed to set tree selection");
154 mSelection
= aSelection
;
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
);
165 Row
* row
= mRows
[aRow
].get();
167 if (row
->IsSeparator())
168 realRow
= row
->mContent
;
170 realRow
= nsTreeUtils::GetImmediateChild(row
->mContent
, nsGkAtoms::treerow
);
172 if (realRow
&& realRow
->IsElement()) {
173 realRow
->AsElement()->GetAttr(nsGkAtoms::properties
, aProperties
);
178 nsTreeContentView::GetRowProperties(int32_t aIndex
, nsAString
& aProps
) {
180 GetRowProperties(aIndex
, aProps
, rv
);
181 return rv
.StealNSResult();
184 void nsTreeContentView::GetCellProperties(int32_t aRow
, nsTreeColumn
& aColumn
,
185 nsAString
& aProperties
,
186 ErrorResult
& aError
) {
187 if (!IsValidRowIndex(aRow
)) {
188 aError
.Throw(NS_ERROR_INVALID_ARG
);
192 Row
* row
= mRows
[aRow
].get();
193 nsIContent
* realRow
=
194 nsTreeUtils::GetImmediateChild(row
->mContent
, nsGkAtoms::treerow
);
196 Element
* cell
= GetCell(realRow
, aColumn
);
198 cell
->GetAttr(nsGkAtoms::properties
, aProperties
);
204 nsTreeContentView::GetCellProperties(int32_t aRow
, nsTreeColumn
* aCol
,
209 GetCellProperties(aRow
, *aCol
, aProps
, rv
);
210 return rv
.StealNSResult();
213 void nsTreeContentView::GetColumnProperties(nsTreeColumn
& aColumn
,
214 nsAString
& aProperties
) {
215 RefPtr
<Element
> element
= aColumn
.Element();
218 element
->GetAttr(nsGkAtoms::properties
, aProperties
);
223 nsTreeContentView::GetColumnProperties(nsTreeColumn
* aCol
, nsAString
& aProps
) {
226 GetColumnProperties(*aCol
, aProps
);
230 bool nsTreeContentView::IsContainer(int32_t aRow
, ErrorResult
& aError
) {
231 if (!IsValidRowIndex(aRow
)) {
232 aError
.Throw(NS_ERROR_INVALID_ARG
);
236 return mRows
[aRow
]->IsContainer();
240 nsTreeContentView::IsContainer(int32_t aIndex
, bool* _retval
) {
242 *_retval
= IsContainer(aIndex
, rv
);
243 return rv
.StealNSResult();
246 bool nsTreeContentView::IsContainerOpen(int32_t aRow
, ErrorResult
& aError
) {
247 if (!IsValidRowIndex(aRow
)) {
248 aError
.Throw(NS_ERROR_INVALID_ARG
);
252 return mRows
[aRow
]->IsOpen();
256 nsTreeContentView::IsContainerOpen(int32_t aIndex
, bool* _retval
) {
258 *_retval
= IsContainerOpen(aIndex
, rv
);
259 return rv
.StealNSResult();
262 bool nsTreeContentView::IsContainerEmpty(int32_t aRow
, ErrorResult
& aError
) {
263 if (!IsValidRowIndex(aRow
)) {
264 aError
.Throw(NS_ERROR_INVALID_ARG
);
268 return mRows
[aRow
]->IsEmpty();
272 nsTreeContentView::IsContainerEmpty(int32_t aIndex
, bool* _retval
) {
274 *_retval
= IsContainerEmpty(aIndex
, rv
);
275 return rv
.StealNSResult();
278 bool nsTreeContentView::IsSeparator(int32_t aRow
, ErrorResult
& aError
) {
279 if (!IsValidRowIndex(aRow
)) {
280 aError
.Throw(NS_ERROR_INVALID_ARG
);
284 return mRows
[aRow
]->IsSeparator();
288 nsTreeContentView::IsSeparator(int32_t aIndex
, bool* _retval
) {
290 *_retval
= IsSeparator(aIndex
, rv
);
291 return rv
.StealNSResult();
295 nsTreeContentView::IsSorted(bool* _retval
) {
296 *_retval
= IsSorted();
301 bool nsTreeContentView::CanDrop(int32_t aRow
, int32_t aOrientation
,
302 ErrorResult
& aError
) {
303 if (!IsValidRowIndex(aRow
)) {
304 aError
.Throw(NS_ERROR_INVALID_ARG
);
309 bool nsTreeContentView::CanDrop(int32_t aRow
, int32_t aOrientation
,
310 DataTransfer
* aDataTransfer
,
311 ErrorResult
& aError
) {
312 return CanDrop(aRow
, aOrientation
, aError
);
316 nsTreeContentView::CanDrop(int32_t aIndex
, int32_t aOrientation
,
317 DataTransfer
* aDataTransfer
, bool* _retval
) {
319 *_retval
= CanDrop(aIndex
, aOrientation
, rv
);
320 return rv
.StealNSResult();
323 void nsTreeContentView::Drop(int32_t aRow
, int32_t aOrientation
,
324 ErrorResult
& aError
) {
325 if (!IsValidRowIndex(aRow
)) {
326 aError
.Throw(NS_ERROR_INVALID_ARG
);
330 void nsTreeContentView::Drop(int32_t aRow
, int32_t aOrientation
,
331 DataTransfer
* aDataTransfer
, ErrorResult
& aError
) {
332 Drop(aRow
, aOrientation
, aError
);
336 nsTreeContentView::Drop(int32_t aRow
, int32_t aOrientation
,
337 DataTransfer
* aDataTransfer
) {
339 Drop(aRow
, aOrientation
, rv
);
340 return rv
.StealNSResult();
343 int32_t nsTreeContentView::GetParentIndex(int32_t aRow
, ErrorResult
& aError
) {
344 if (!IsValidRowIndex(aRow
)) {
345 aError
.Throw(NS_ERROR_INVALID_ARG
);
349 return mRows
[aRow
]->mParentIndex
;
353 nsTreeContentView::GetParentIndex(int32_t aRowIndex
, int32_t* _retval
) {
355 *_retval
= GetParentIndex(aRowIndex
, rv
);
356 return rv
.StealNSResult();
359 bool nsTreeContentView::HasNextSibling(int32_t aRow
, int32_t aAfterIndex
,
360 ErrorResult
& aError
) {
361 if (!IsValidRowIndex(aRow
)) {
362 aError
.Throw(NS_ERROR_INVALID_ARG
);
366 // We have a next sibling if the row is not the last in the subtree.
367 int32_t parentIndex
= mRows
[aRow
]->mParentIndex
;
368 if (parentIndex
< 0) {
369 return uint32_t(aRow
) < mRows
.Length() - 1;
372 // Compute the last index in this subtree.
373 int32_t lastIndex
= parentIndex
+ (mRows
[parentIndex
])->mSubtreeSize
;
374 Row
* row
= mRows
[lastIndex
].get();
375 while (row
->mParentIndex
!= parentIndex
) {
376 lastIndex
= row
->mParentIndex
;
377 row
= mRows
[lastIndex
].get();
380 return aRow
< lastIndex
;
384 nsTreeContentView::HasNextSibling(int32_t aRowIndex
, int32_t aAfterIndex
,
387 *_retval
= HasNextSibling(aRowIndex
, aAfterIndex
, rv
);
388 return rv
.StealNSResult();
391 int32_t nsTreeContentView::GetLevel(int32_t aRow
, ErrorResult
& aError
) {
392 if (!IsValidRowIndex(aRow
)) {
393 aError
.Throw(NS_ERROR_INVALID_ARG
);
398 Row
* row
= mRows
[aRow
].get();
399 while (row
->mParentIndex
>= 0) {
401 row
= mRows
[row
->mParentIndex
].get();
407 nsTreeContentView::GetLevel(int32_t aIndex
, int32_t* _retval
) {
409 *_retval
= GetLevel(aIndex
, rv
);
410 return rv
.StealNSResult();
413 void nsTreeContentView::GetImageSrc(int32_t aRow
, nsTreeColumn
& aColumn
,
414 nsAString
& aSrc
, ErrorResult
& aError
) {
415 if (!IsValidRowIndex(aRow
)) {
416 aError
.Throw(NS_ERROR_INVALID_ARG
);
420 Row
* row
= mRows
[aRow
].get();
422 nsIContent
* realRow
=
423 nsTreeUtils::GetImmediateChild(row
->mContent
, nsGkAtoms::treerow
);
425 Element
* cell
= GetCell(realRow
, aColumn
);
426 if (cell
) cell
->GetAttr(nsGkAtoms::src
, aSrc
);
431 nsTreeContentView::GetImageSrc(int32_t aRow
, nsTreeColumn
* aCol
,
432 nsAString
& _retval
) {
436 GetImageSrc(aRow
, *aCol
, _retval
, rv
);
437 return rv
.StealNSResult();
440 void nsTreeContentView::GetCellValue(int32_t aRow
, nsTreeColumn
& aColumn
,
441 nsAString
& aValue
, ErrorResult
& aError
) {
442 if (!IsValidRowIndex(aRow
)) {
443 aError
.Throw(NS_ERROR_INVALID_ARG
);
447 Row
* row
= mRows
[aRow
].get();
449 nsIContent
* realRow
=
450 nsTreeUtils::GetImmediateChild(row
->mContent
, nsGkAtoms::treerow
);
452 Element
* cell
= GetCell(realRow
, aColumn
);
453 if (cell
) cell
->GetAttr(nsGkAtoms::value
, aValue
);
458 nsTreeContentView::GetCellValue(int32_t aRow
, nsTreeColumn
* aCol
,
459 nsAString
& _retval
) {
463 GetCellValue(aRow
, *aCol
, _retval
, rv
);
464 return rv
.StealNSResult();
467 void nsTreeContentView::GetCellText(int32_t aRow
, nsTreeColumn
& aColumn
,
468 nsAString
& aText
, ErrorResult
& aError
) {
469 if (!IsValidRowIndex(aRow
)) {
470 aError
.Throw(NS_ERROR_INVALID_ARG
);
474 Row
* row
= mRows
[aRow
].get();
476 // Check for a "label" attribute - this is valid on an <treeitem>
477 // with a single implied column.
478 if (row
->mContent
->GetAttr(nsGkAtoms::label
, aText
) && !aText
.IsEmpty()) {
482 if (row
->mContent
->IsXULElement(nsGkAtoms::treeitem
)) {
483 nsIContent
* realRow
=
484 nsTreeUtils::GetImmediateChild(row
->mContent
, nsGkAtoms::treerow
);
486 Element
* cell
= GetCell(realRow
, aColumn
);
487 if (cell
) cell
->GetAttr(nsGkAtoms::label
, aText
);
493 nsTreeContentView::GetCellText(int32_t aRow
, nsTreeColumn
* aCol
,
494 nsAString
& _retval
) {
498 GetCellText(aRow
, *aCol
, _retval
, rv
);
499 return rv
.StealNSResult();
502 void nsTreeContentView::SetTree(XULTreeElement
* aTree
, ErrorResult
& aError
) {
503 aError
= SetTree(aTree
);
507 nsTreeContentView::SetTree(XULTreeElement
* aTree
) {
513 // Add ourselves to document's observers.
514 Document
* document
= mTree
->GetComposedDoc();
516 document
->AddObserver(this);
517 mDocument
= document
;
520 RefPtr
<dom::Element
> bodyElement
= mTree
->GetTreeBody();
522 mBody
= std::move(bodyElement
);
524 Serialize(mBody
, -1, &index
, mRows
);
531 void nsTreeContentView::ToggleOpenState(int32_t aRow
, ErrorResult
& aError
) {
532 if (!IsValidRowIndex(aRow
)) {
533 aError
.Throw(NS_ERROR_INVALID_ARG
);
537 // We don't serialize content right here, since content might be generated
539 Row
* row
= mRows
[aRow
].get();
542 row
->mContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::open
, u
"false"_ns
,
545 row
->mContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::open
, u
"true"_ns
,
550 nsTreeContentView::ToggleOpenState(int32_t aIndex
) {
552 ToggleOpenState(aIndex
, rv
);
553 return rv
.StealNSResult();
556 void nsTreeContentView::CycleHeader(nsTreeColumn
& aColumn
,
557 ErrorResult
& aError
) {
560 RefPtr
<Element
> column
= aColumn
.Element();
562 column
->GetAttr(nsGkAtoms::sort
, sort
);
563 if (!sort
.IsEmpty()) {
564 nsAutoString sortdirection
;
565 static Element::AttrValuesArray strings
[] = {
566 nsGkAtoms::ascending
, nsGkAtoms::descending
, nullptr};
567 switch (column
->FindAttrValueIn(kNameSpaceID_None
, nsGkAtoms::sortDirection
,
568 strings
, eCaseMatters
)) {
570 sortdirection
.AssignLiteral("descending");
573 sortdirection
.AssignLiteral("natural");
576 sortdirection
.AssignLiteral("ascending");
581 column
->GetAttr(nsGkAtoms::sorthints
, hints
);
582 sortdirection
.Append(' ');
583 sortdirection
+= hints
;
585 XULWidgetSort(mTree
, sort
, sortdirection
);
590 nsTreeContentView::CycleHeader(nsTreeColumn
* aCol
) {
594 CycleHeader(*aCol
, rv
);
595 return rv
.StealNSResult();
599 nsTreeContentView::SelectionChangedXPCOM() { return NS_OK
; }
602 nsTreeContentView::CycleCell(int32_t aRow
, nsTreeColumn
* aCol
) { return NS_OK
; }
604 bool nsTreeContentView::IsEditable(int32_t aRow
, nsTreeColumn
& aColumn
,
605 ErrorResult
& aError
) {
606 if (!IsValidRowIndex(aRow
)) {
607 aError
.Throw(NS_ERROR_INVALID_ARG
);
611 Row
* row
= mRows
[aRow
].get();
613 nsIContent
* realRow
=
614 nsTreeUtils::GetImmediateChild(row
->mContent
, nsGkAtoms::treerow
);
616 Element
* cell
= GetCell(realRow
, aColumn
);
617 if (cell
&& cell
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::editable
,
618 nsGkAtoms::_false
, eCaseMatters
)) {
627 nsTreeContentView::IsEditable(int32_t aRow
, nsTreeColumn
* aCol
, bool* _retval
) {
631 *_retval
= IsEditable(aRow
, *aCol
, rv
);
632 return rv
.StealNSResult();
635 void nsTreeContentView::SetCellValue(int32_t aRow
, nsTreeColumn
& aColumn
,
636 const nsAString
& aValue
,
637 ErrorResult
& aError
) {
638 if (!IsValidRowIndex(aRow
)) {
639 aError
.Throw(NS_ERROR_INVALID_ARG
);
643 Row
* row
= mRows
[aRow
].get();
645 nsIContent
* realRow
=
646 nsTreeUtils::GetImmediateChild(row
->mContent
, nsGkAtoms::treerow
);
648 Element
* cell
= GetCell(realRow
, aColumn
);
649 if (cell
) cell
->SetAttr(kNameSpaceID_None
, nsGkAtoms::value
, aValue
, true);
654 nsTreeContentView::SetCellValue(int32_t aRow
, nsTreeColumn
* aCol
,
655 const nsAString
& aValue
) {
659 SetCellValue(aRow
, *aCol
, aValue
, rv
);
660 return rv
.StealNSResult();
663 void nsTreeContentView::SetCellText(int32_t aRow
, nsTreeColumn
& aColumn
,
664 const nsAString
& aValue
,
665 ErrorResult
& aError
) {
666 if (!IsValidRowIndex(aRow
)) {
667 aError
.Throw(NS_ERROR_INVALID_ARG
);
671 Row
* row
= mRows
[aRow
].get();
673 nsIContent
* realRow
=
674 nsTreeUtils::GetImmediateChild(row
->mContent
, nsGkAtoms::treerow
);
676 Element
* cell
= GetCell(realRow
, aColumn
);
677 if (cell
) cell
->SetAttr(kNameSpaceID_None
, nsGkAtoms::label
, aValue
, true);
682 nsTreeContentView::SetCellText(int32_t aRow
, nsTreeColumn
* aCol
,
683 const nsAString
& aValue
) {
687 SetCellText(aRow
, *aCol
, aValue
, rv
);
688 return rv
.StealNSResult();
691 Element
* nsTreeContentView::GetItemAtIndex(int32_t aIndex
,
692 ErrorResult
& aError
) {
693 if (!IsValidRowIndex(aIndex
)) {
694 aError
.Throw(NS_ERROR_INVALID_ARG
);
698 return mRows
[aIndex
]->mContent
;
701 int32_t nsTreeContentView::GetIndexOfItem(Element
* aItem
) {
702 return FindContent(aItem
);
705 void nsTreeContentView::AttributeChanged(dom::Element
* aElement
,
706 int32_t aNameSpaceID
,
707 nsAtom
* aAttribute
, int32_t aModType
,
708 const nsAttrValue
* aOldValue
) {
709 // Lots of codepaths under here that do all sorts of stuff, so be safe.
710 nsCOMPtr
<nsIMutationObserver
> kungFuDeathGrip(this);
712 // Make sure this notification concerns us.
713 // First check the tag to see if it's one that we care about.
714 if (aElement
== mTree
|| aElement
== mBody
) {
715 mTree
->ClearStyleAndImageCaches();
719 // We don't consider non-XUL nodes.
720 nsIContent
* parent
= nullptr;
721 if (!aElement
->IsXULElement() ||
722 ((parent
= aElement
->GetParent()) && !parent
->IsXULElement())) {
725 if (!aElement
->IsAnyOfXULElements(nsGkAtoms::treecol
, nsGkAtoms::treeitem
,
726 nsGkAtoms::treeseparator
,
727 nsGkAtoms::treerow
, nsGkAtoms::treecell
)) {
731 // If we have a legal tag, go up to the tree/select and make sure
734 for (nsIContent
* element
= aElement
; element
!= mBody
;
735 element
= element
->GetParent()) {
736 if (!element
) return; // this is not for us
737 if (element
->IsXULElement(nsGkAtoms::tree
)) return; // this is not for us
740 // Handle changes of the hidden attribute.
741 if (aAttribute
== nsGkAtoms::hidden
&&
742 aElement
->IsAnyOfXULElements(nsGkAtoms::treeitem
,
743 nsGkAtoms::treeseparator
)) {
744 bool hidden
= aElement
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::hidden
,
745 nsGkAtoms::_true
, eCaseMatters
);
747 int32_t index
= FindContent(aElement
);
748 if (hidden
&& index
>= 0) {
749 // Hide this row along with its children.
750 int32_t count
= RemoveRow(index
);
751 if (mTree
) mTree
->RowCountChanged(index
, -count
);
752 } else if (!hidden
&& index
< 0) {
753 // Show this row along with its children.
754 nsCOMPtr
<nsIContent
> parent
= aElement
->GetParent();
756 InsertRowFor(parent
, aElement
);
763 if (aElement
->IsXULElement(nsGkAtoms::treecol
)) {
764 if (aAttribute
== nsGkAtoms::properties
) {
766 RefPtr
<nsTreeColumns
> cols
= mTree
->GetColumns();
768 RefPtr
<nsTreeColumn
> col
= cols
->GetColumnFor(aElement
);
769 mTree
->InvalidateColumn(col
);
773 } else if (aElement
->IsXULElement(nsGkAtoms::treeitem
)) {
774 int32_t index
= FindContent(aElement
);
776 Row
* row
= mRows
[index
].get();
777 if (aAttribute
== nsGkAtoms::container
) {
779 aElement
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::container
,
780 nsGkAtoms::_true
, eCaseMatters
);
781 row
->SetContainer(isContainer
);
782 if (mTree
) mTree
->InvalidateRow(index
);
783 } else if (aAttribute
== nsGkAtoms::open
) {
784 bool isOpen
= aElement
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::open
,
785 nsGkAtoms::_true
, eCaseMatters
);
786 bool wasOpen
= row
->IsOpen();
787 if (!isOpen
&& wasOpen
)
788 CloseContainer(index
);
789 else if (isOpen
&& !wasOpen
)
790 OpenContainer(index
);
791 } else if (aAttribute
== nsGkAtoms::empty
) {
793 aElement
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::empty
,
794 nsGkAtoms::_true
, eCaseMatters
);
795 row
->SetEmpty(isEmpty
);
796 if (mTree
) mTree
->InvalidateRow(index
);
799 } else if (aElement
->IsXULElement(nsGkAtoms::treeseparator
)) {
800 int32_t index
= FindContent(aElement
);
802 if (aAttribute
== nsGkAtoms::properties
&& mTree
) {
803 mTree
->InvalidateRow(index
);
806 } else if (aElement
->IsXULElement(nsGkAtoms::treerow
)) {
807 if (aAttribute
== nsGkAtoms::properties
) {
808 nsCOMPtr
<nsIContent
> parent
= aElement
->GetParent();
810 int32_t index
= FindContent(parent
);
811 if (index
>= 0 && mTree
) {
812 mTree
->InvalidateRow(index
);
816 } else if (aElement
->IsXULElement(nsGkAtoms::treecell
)) {
817 if (aAttribute
== nsGkAtoms::properties
|| aAttribute
== nsGkAtoms::mode
||
818 aAttribute
== nsGkAtoms::src
|| aAttribute
== nsGkAtoms::value
||
819 aAttribute
== nsGkAtoms::label
) {
820 nsIContent
* parent
= aElement
->GetParent();
822 nsCOMPtr
<nsIContent
> grandParent
= parent
->GetParent();
823 if (grandParent
&& grandParent
->IsXULElement()) {
824 int32_t index
= FindContent(grandParent
);
825 if (index
>= 0 && mTree
) {
826 // XXX Should we make an effort to invalidate only cell ?
827 mTree
->InvalidateRow(index
);
835 void nsTreeContentView::ContentAppended(nsIContent
* aFirstNewContent
) {
836 for (nsIContent
* cur
= aFirstNewContent
; cur
; cur
= cur
->GetNextSibling()) {
837 // Our contentinserted doesn't use the index
838 ContentInserted(cur
);
842 void nsTreeContentView::ContentInserted(nsIContent
* aChild
) {
843 NS_ASSERTION(aChild
, "null ptr");
844 nsIContent
* container
= aChild
->GetParent();
846 // Make sure this notification concerns us.
847 // First check the tag to see if it's one that we care about.
849 // Don't allow non-XUL nodes.
850 if (!aChild
->IsXULElement() || !container
->IsXULElement()) return;
852 if (!aChild
->IsAnyOfXULElements(nsGkAtoms::treeitem
, nsGkAtoms::treeseparator
,
853 nsGkAtoms::treechildren
, nsGkAtoms::treerow
,
854 nsGkAtoms::treecell
)) {
858 // If we have a legal tag, go up to the tree/select and make sure
861 for (nsIContent
* element
= container
; element
!= mBody
;
862 element
= element
->GetParent()) {
863 if (!element
) return; // this is not for us
864 if (element
->IsXULElement(nsGkAtoms::tree
)) return; // this is not for us
867 // Lots of codepaths under here that do all sorts of stuff, so be safe.
868 nsCOMPtr
<nsIMutationObserver
> kungFuDeathGrip(this);
870 if (aChild
->IsXULElement(nsGkAtoms::treechildren
)) {
871 int32_t index
= FindContent(container
);
873 Row
* row
= mRows
[index
].get();
874 row
->SetEmpty(false);
875 if (mTree
) mTree
->InvalidateRow(index
);
876 if (row
->IsContainer() && row
->IsOpen()) {
877 int32_t count
= EnsureSubtree(index
);
878 if (mTree
) mTree
->RowCountChanged(index
+ 1, count
);
881 } else if (aChild
->IsAnyOfXULElements(nsGkAtoms::treeitem
,
882 nsGkAtoms::treeseparator
)) {
883 InsertRowFor(container
, aChild
);
884 } else if (aChild
->IsXULElement(nsGkAtoms::treerow
)) {
885 int32_t index
= FindContent(container
);
886 if (index
>= 0 && mTree
) mTree
->InvalidateRow(index
);
887 } else if (aChild
->IsXULElement(nsGkAtoms::treecell
)) {
888 nsCOMPtr
<nsIContent
> parent
= container
->GetParent();
890 int32_t index
= FindContent(parent
);
891 if (index
>= 0 && mTree
) mTree
->InvalidateRow(index
);
896 void nsTreeContentView::ContentRemoved(nsIContent
* aChild
,
897 nsIContent
* aPreviousSibling
) {
898 NS_ASSERTION(aChild
, "null ptr");
900 nsIContent
* container
= aChild
->GetParent();
901 // Make sure this notification concerns us.
902 // First check the tag to see if it's one that we care about.
904 // We don't consider non-XUL nodes.
905 if (!aChild
->IsXULElement() || !container
->IsXULElement()) return;
907 if (!aChild
->IsAnyOfXULElements(nsGkAtoms::treeitem
, nsGkAtoms::treeseparator
,
908 nsGkAtoms::treechildren
, nsGkAtoms::treerow
,
909 nsGkAtoms::treecell
)) {
913 // If we have a legal tag, go up to the tree/select and make sure
916 for (nsIContent
* element
= container
; element
!= mBody
;
917 element
= element
->GetParent()) {
918 if (!element
) return; // this is not for us
919 if (element
->IsXULElement(nsGkAtoms::tree
)) return; // this is not for us
922 // Lots of codepaths under here that do all sorts of stuff, so be safe.
923 nsCOMPtr
<nsIMutationObserver
> kungFuDeathGrip(this);
925 if (aChild
->IsXULElement(nsGkAtoms::treechildren
)) {
926 int32_t index
= FindContent(container
);
928 Row
* row
= mRows
[index
].get();
930 int32_t count
= RemoveSubtree(index
);
931 // Invalidate also the row to update twisty.
933 mTree
->InvalidateRow(index
);
934 mTree
->RowCountChanged(index
+ 1, -count
);
937 } else if (aChild
->IsAnyOfXULElements(nsGkAtoms::treeitem
,
938 nsGkAtoms::treeseparator
)) {
939 int32_t index
= FindContent(aChild
);
941 int32_t count
= RemoveRow(index
);
942 if (mTree
) mTree
->RowCountChanged(index
, -count
);
944 } else if (aChild
->IsXULElement(nsGkAtoms::treerow
)) {
945 int32_t index
= FindContent(container
);
946 if (index
>= 0 && mTree
) mTree
->InvalidateRow(index
);
947 } else if (aChild
->IsXULElement(nsGkAtoms::treecell
)) {
948 nsCOMPtr
<nsIContent
> parent
= container
->GetParent();
950 int32_t index
= FindContent(parent
);
951 if (index
>= 0 && mTree
) mTree
->InvalidateRow(index
);
956 void nsTreeContentView::NodeWillBeDestroyed(nsINode
* aNode
) {
957 // XXXbz do we need this strong ref? Do we drop refs to self in ClearRows?
958 nsCOMPtr
<nsIMutationObserver
> kungFuDeathGrip(this);
962 // Recursively serialize content, starting with aContent.
963 void nsTreeContentView::Serialize(nsIContent
* aContent
, int32_t aParentIndex
,
965 nsTArray
<UniquePtr
<Row
>>& aRows
) {
966 // Don't allow non-XUL nodes.
967 if (!aContent
->IsXULElement()) return;
969 dom::FlattenedChildIterator
iter(aContent
);
970 for (nsIContent
* content
= iter
.GetNextChild(); content
;
971 content
= iter
.GetNextChild()) {
972 int32_t count
= aRows
.Length();
974 if (content
->IsXULElement(nsGkAtoms::treeitem
)) {
975 SerializeItem(content
->AsElement(), aParentIndex
, aIndex
, aRows
);
976 } else if (content
->IsXULElement(nsGkAtoms::treeseparator
)) {
977 SerializeSeparator(content
->AsElement(), aParentIndex
, aIndex
, aRows
);
980 *aIndex
+= aRows
.Length() - count
;
984 void nsTreeContentView::SerializeItem(Element
* aContent
, int32_t aParentIndex
,
986 nsTArray
<UniquePtr
<Row
>>& aRows
) {
987 if (aContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::hidden
,
988 nsGkAtoms::_true
, eCaseMatters
))
991 aRows
.AppendElement(MakeUnique
<Row
>(aContent
, aParentIndex
));
992 Row
* row
= aRows
.LastElement().get();
994 if (aContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::container
,
995 nsGkAtoms::_true
, eCaseMatters
)) {
996 row
->SetContainer(true);
997 if (aContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::open
,
998 nsGkAtoms::_true
, eCaseMatters
)) {
1001 nsTreeUtils::GetImmediateChild(aContent
, nsGkAtoms::treechildren
);
1002 if (child
&& child
->IsXULElement()) {
1003 // Now, recursively serialize our child.
1004 int32_t count
= aRows
.Length();
1006 Serialize(child
, aParentIndex
+ *aIndex
+ 1, &index
, aRows
);
1007 row
->mSubtreeSize
+= aRows
.Length() - count
;
1009 row
->SetEmpty(true);
1010 } else if (aContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::empty
,
1011 nsGkAtoms::_true
, eCaseMatters
)) {
1012 row
->SetEmpty(true);
1017 void nsTreeContentView::SerializeSeparator(Element
* aContent
,
1018 int32_t aParentIndex
,
1020 nsTArray
<UniquePtr
<Row
>>& aRows
) {
1021 if (aContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::hidden
,
1022 nsGkAtoms::_true
, eCaseMatters
))
1025 auto row
= MakeUnique
<Row
>(aContent
, aParentIndex
);
1026 row
->SetSeparator(true);
1027 aRows
.AppendElement(std::move(row
));
1030 void nsTreeContentView::GetIndexInSubtree(nsIContent
* aContainer
,
1031 nsIContent
* aContent
,
1033 if (!aContainer
->IsXULElement()) return;
1035 for (nsIContent
* content
= aContainer
->GetFirstChild(); content
;
1036 content
= content
->GetNextSibling()) {
1037 if (content
== aContent
) break;
1039 if (content
->IsXULElement(nsGkAtoms::treeitem
)) {
1040 if (!content
->AsElement()->AttrValueIs(kNameSpaceID_None
,
1042 nsGkAtoms::_true
, eCaseMatters
)) {
1044 if (content
->AsElement()->AttrValueIs(kNameSpaceID_None
,
1045 nsGkAtoms::container
,
1046 nsGkAtoms::_true
, eCaseMatters
) &&
1047 content
->AsElement()->AttrValueIs(kNameSpaceID_None
,
1048 nsGkAtoms::open
, nsGkAtoms::_true
,
1051 nsTreeUtils::GetImmediateChild(content
, nsGkAtoms::treechildren
);
1052 if (child
&& child
->IsXULElement())
1053 GetIndexInSubtree(child
, aContent
, aIndex
);
1056 } else if (content
->IsXULElement(nsGkAtoms::treeseparator
)) {
1057 if (!content
->AsElement()->AttrValueIs(kNameSpaceID_None
,
1059 nsGkAtoms::_true
, eCaseMatters
))
1065 int32_t nsTreeContentView::EnsureSubtree(int32_t aIndex
) {
1066 Row
* row
= mRows
[aIndex
].get();
1070 nsTreeUtils::GetImmediateChild(row
->mContent
, nsGkAtoms::treechildren
);
1071 if (!child
|| !child
->IsXULElement()) {
1075 AutoTArray
<UniquePtr
<Row
>, 8> rows
;
1077 Serialize(child
, aIndex
, &index
, rows
);
1078 // Insert |rows| into |mRows| at position |aIndex|, by first creating empty
1079 // UniquePtr entries and then Move'ing |rows|'s entries into them. (Note
1080 // that we can't simply use InsertElementsAt with an array argument, since
1081 // the destination can't steal ownership from its const source argument.)
1082 UniquePtr
<Row
>* newRows
= mRows
.InsertElementsAt(aIndex
+ 1, rows
.Length());
1083 for (nsTArray
<Row
>::index_type i
= 0; i
< rows
.Length(); i
++) {
1084 newRows
[i
] = std::move(rows
[i
]);
1086 int32_t count
= rows
.Length();
1088 row
->mSubtreeSize
+= count
;
1089 UpdateSubtreeSizes(row
->mParentIndex
, count
);
1091 // Update parent indexes, but skip newly added rows.
1092 // They already have correct values.
1093 UpdateParentIndexes(aIndex
, count
+ 1, count
);
1098 int32_t nsTreeContentView::RemoveSubtree(int32_t aIndex
) {
1099 Row
* row
= mRows
[aIndex
].get();
1100 int32_t count
= row
->mSubtreeSize
;
1102 mRows
.RemoveElementsAt(aIndex
+ 1, count
);
1104 row
->mSubtreeSize
-= count
;
1105 UpdateSubtreeSizes(row
->mParentIndex
, -count
);
1107 UpdateParentIndexes(aIndex
, 0, -count
);
1112 void nsTreeContentView::InsertRowFor(nsIContent
* aParent
, nsIContent
* aChild
) {
1113 int32_t grandParentIndex
= -1;
1114 bool insertRow
= false;
1116 nsCOMPtr
<nsIContent
> grandParent
= aParent
->GetParent();
1118 if (grandParent
->IsXULElement(nsGkAtoms::tree
)) {
1119 // Allow insertion to the outermost container.
1122 // Test insertion to an inner container.
1124 // First try to find this parent in our array of rows, if we find one
1125 // we can be sure that all other parents are open too.
1126 grandParentIndex
= FindContent(grandParent
);
1127 if (grandParentIndex
>= 0) {
1128 // Got it, now test if it is open.
1129 if (mRows
[grandParentIndex
]->IsOpen()) insertRow
= true;
1135 GetIndexInSubtree(aParent
, aChild
, &index
);
1137 int32_t count
= InsertRow(grandParentIndex
, index
, aChild
);
1138 if (mTree
) mTree
->RowCountChanged(grandParentIndex
+ index
+ 1, count
);
1142 int32_t nsTreeContentView::InsertRow(int32_t aParentIndex
, int32_t aIndex
,
1143 nsIContent
* aContent
) {
1144 AutoTArray
<UniquePtr
<Row
>, 8> rows
;
1145 if (aContent
->IsXULElement(nsGkAtoms::treeitem
)) {
1146 SerializeItem(aContent
->AsElement(), aParentIndex
, &aIndex
, rows
);
1147 } else if (aContent
->IsXULElement(nsGkAtoms::treeseparator
)) {
1148 SerializeSeparator(aContent
->AsElement(), aParentIndex
, &aIndex
, rows
);
1151 // We can't use InsertElementsAt since the destination can't steal
1152 // ownership from its const source argument.
1153 int32_t count
= rows
.Length();
1154 for (nsTArray
<Row
>::index_type i
= 0; i
< size_t(count
); i
++) {
1155 mRows
.InsertElementAt(aParentIndex
+ aIndex
+ i
+ 1, std::move(rows
[i
]));
1158 UpdateSubtreeSizes(aParentIndex
, count
);
1160 // Update parent indexes, but skip added rows.
1161 // They already have correct values.
1162 UpdateParentIndexes(aParentIndex
+ aIndex
, count
+ 1, count
);
1167 int32_t nsTreeContentView::RemoveRow(int32_t aIndex
) {
1168 Row
* row
= mRows
[aIndex
].get();
1169 int32_t count
= row
->mSubtreeSize
+ 1;
1170 int32_t parentIndex
= row
->mParentIndex
;
1172 mRows
.RemoveElementsAt(aIndex
, count
);
1174 UpdateSubtreeSizes(parentIndex
, -count
);
1176 UpdateParentIndexes(aIndex
, 0, -count
);
1181 void nsTreeContentView::ClearRows() {
1184 // Remove ourselves from mDocument's observers.
1186 mDocument
->RemoveObserver(this);
1187 mDocument
= nullptr;
1191 void nsTreeContentView::OpenContainer(int32_t aIndex
) {
1192 Row
* row
= mRows
[aIndex
].get();
1195 int32_t count
= EnsureSubtree(aIndex
);
1197 mTree
->InvalidateRow(aIndex
);
1198 mTree
->RowCountChanged(aIndex
+ 1, count
);
1202 void nsTreeContentView::CloseContainer(int32_t aIndex
) {
1203 Row
* row
= mRows
[aIndex
].get();
1204 row
->SetOpen(false);
1206 int32_t count
= RemoveSubtree(aIndex
);
1208 mTree
->InvalidateRow(aIndex
);
1209 mTree
->RowCountChanged(aIndex
+ 1, -count
);
1213 int32_t nsTreeContentView::FindContent(nsIContent
* aContent
) {
1214 for (uint32_t i
= 0; i
< mRows
.Length(); i
++) {
1215 if (mRows
[i
]->mContent
== aContent
) {
1223 void nsTreeContentView::UpdateSubtreeSizes(int32_t aParentIndex
,
1225 while (aParentIndex
>= 0) {
1226 Row
* row
= mRows
[aParentIndex
].get();
1227 row
->mSubtreeSize
+= count
;
1228 aParentIndex
= row
->mParentIndex
;
1232 void nsTreeContentView::UpdateParentIndexes(int32_t aIndex
, int32_t aSkip
,
1234 int32_t count
= mRows
.Length();
1235 for (int32_t i
= aIndex
+ aSkip
; i
< count
; i
++) {
1236 Row
* row
= mRows
[i
].get();
1237 if (row
->mParentIndex
> aIndex
) {
1238 row
->mParentIndex
+= aCount
;
1243 Element
* nsTreeContentView::GetCell(nsIContent
* aContainer
,
1244 nsTreeColumn
& aCol
) {
1245 int32_t colIndex(aCol
.GetIndex());
1247 // Traverse through cells, try to find the cell by index in a row.
1248 Element
* result
= nullptr;
1250 dom::FlattenedChildIterator
iter(aContainer
);
1251 for (nsIContent
* cell
= iter
.GetNextChild(); cell
;
1252 cell
= iter
.GetNextChild()) {
1253 if (cell
->IsXULElement(nsGkAtoms::treecell
)) {
1254 if (j
== colIndex
) {
1255 result
= cell
->AsElement();
1265 bool nsTreeContentView::IsValidRowIndex(int32_t aRowIndex
) {
1266 return aRowIndex
>= 0 && aRowIndex
< int32_t(mRows
.Length());