Bug 1867190 - Initialise the PHC allocate delay later r=glandium
[gecko.git] / layout / xul / tree / nsTreeContentView.cpp
blob75652918a73e08a1b7e9df29f8d01bccf2c0bb10
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"
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"
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
33 class Row {
34 public:
35 Row(Element* aContent, int32_t aParentIndex)
36 : mContent(aContent),
37 mParentIndex(aParentIndex),
38 mSubtreeSize(0),
39 mFlags(0) {}
41 ~Row() = default;
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.
64 Element* mContent;
66 // The parent index of the item, set to -1 for the top level items.
67 int32_t mParentIndex;
69 // Subtree size for this item.
70 int32_t mSubtreeSize;
72 private:
73 // State flags
74 int8_t mFlags;
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;
94 NS_ADDREF(*aResult);
95 return NS_OK;
98 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsTreeContentView, mTree, mSelection,
99 mBody)
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
110 NS_INTERFACE_MAP_END
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; }
119 NS_IMETHODIMP
120 nsTreeContentView::GetRowCount(int32_t* aRowCount) {
121 *aRowCount = mRows.Length();
123 return NS_OK;
126 NS_IMETHODIMP
127 nsTreeContentView::GetSelection(nsITreeSelection** aSelection) {
128 NS_IF_ADDREF(*aSelection = GetSelection());
130 return NS_OK;
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());
140 NS_IMETHODIMP
141 nsTreeContentView::SetSelection(nsITreeSelection* aSelection) {
142 ErrorResult rv;
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");
151 return;
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);
162 return;
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);
172 if (realRow && realRow->IsElement()) {
173 realRow->AsElement()->GetAttr(nsGkAtoms::properties, aProperties);
177 NS_IMETHODIMP
178 nsTreeContentView::GetRowProperties(int32_t aIndex, nsAString& aProps) {
179 ErrorResult rv;
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);
189 return;
192 Row* row = mRows[aRow].get();
193 nsIContent* realRow =
194 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
195 if (realRow) {
196 Element* cell = GetCell(realRow, aColumn);
197 if (cell) {
198 cell->GetAttr(nsGkAtoms::properties, aProperties);
203 NS_IMETHODIMP
204 nsTreeContentView::GetCellProperties(int32_t aRow, nsTreeColumn* aCol,
205 nsAString& aProps) {
206 NS_ENSURE_ARG(aCol);
208 ErrorResult rv;
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();
217 if (element) {
218 element->GetAttr(nsGkAtoms::properties, aProperties);
222 NS_IMETHODIMP
223 nsTreeContentView::GetColumnProperties(nsTreeColumn* aCol, nsAString& aProps) {
224 NS_ENSURE_ARG(aCol);
226 GetColumnProperties(*aCol, aProps);
227 return NS_OK;
230 bool nsTreeContentView::IsContainer(int32_t aRow, ErrorResult& aError) {
231 if (!IsValidRowIndex(aRow)) {
232 aError.Throw(NS_ERROR_INVALID_ARG);
233 return false;
236 return mRows[aRow]->IsContainer();
239 NS_IMETHODIMP
240 nsTreeContentView::IsContainer(int32_t aIndex, bool* _retval) {
241 ErrorResult rv;
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);
249 return false;
252 return mRows[aRow]->IsOpen();
255 NS_IMETHODIMP
256 nsTreeContentView::IsContainerOpen(int32_t aIndex, bool* _retval) {
257 ErrorResult rv;
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);
265 return false;
268 return mRows[aRow]->IsEmpty();
271 NS_IMETHODIMP
272 nsTreeContentView::IsContainerEmpty(int32_t aIndex, bool* _retval) {
273 ErrorResult rv;
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);
281 return false;
284 return mRows[aRow]->IsSeparator();
287 NS_IMETHODIMP
288 nsTreeContentView::IsSeparator(int32_t aIndex, bool* _retval) {
289 ErrorResult rv;
290 *_retval = IsSeparator(aIndex, rv);
291 return rv.StealNSResult();
294 NS_IMETHODIMP
295 nsTreeContentView::IsSorted(bool* _retval) {
296 *_retval = IsSorted();
298 return NS_OK;
301 bool nsTreeContentView::CanDrop(int32_t aRow, int32_t aOrientation,
302 ErrorResult& aError) {
303 if (!IsValidRowIndex(aRow)) {
304 aError.Throw(NS_ERROR_INVALID_ARG);
306 return false;
309 bool nsTreeContentView::CanDrop(int32_t aRow, int32_t aOrientation,
310 DataTransfer* aDataTransfer,
311 ErrorResult& aError) {
312 return CanDrop(aRow, aOrientation, aError);
315 NS_IMETHODIMP
316 nsTreeContentView::CanDrop(int32_t aIndex, int32_t aOrientation,
317 DataTransfer* aDataTransfer, bool* _retval) {
318 ErrorResult rv;
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);
335 NS_IMETHODIMP
336 nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation,
337 DataTransfer* aDataTransfer) {
338 ErrorResult rv;
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);
346 return 0;
349 return mRows[aRow]->mParentIndex;
352 NS_IMETHODIMP
353 nsTreeContentView::GetParentIndex(int32_t aRowIndex, int32_t* _retval) {
354 ErrorResult rv;
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);
363 return false;
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;
383 NS_IMETHODIMP
384 nsTreeContentView::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex,
385 bool* _retval) {
386 ErrorResult rv;
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);
394 return 0;
397 int32_t level = 0;
398 Row* row = mRows[aRow].get();
399 while (row->mParentIndex >= 0) {
400 level++;
401 row = mRows[row->mParentIndex].get();
403 return level;
406 NS_IMETHODIMP
407 nsTreeContentView::GetLevel(int32_t aIndex, int32_t* _retval) {
408 ErrorResult rv;
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);
417 return;
420 Row* row = mRows[aRow].get();
422 nsIContent* realRow =
423 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
424 if (realRow) {
425 Element* cell = GetCell(realRow, aColumn);
426 if (cell) cell->GetAttr(nsGkAtoms::src, aSrc);
430 NS_IMETHODIMP
431 nsTreeContentView::GetImageSrc(int32_t aRow, nsTreeColumn* aCol,
432 nsAString& _retval) {
433 NS_ENSURE_ARG(aCol);
435 ErrorResult rv;
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);
444 return;
447 Row* row = mRows[aRow].get();
449 nsIContent* realRow =
450 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
451 if (realRow) {
452 Element* cell = GetCell(realRow, aColumn);
453 if (cell) cell->GetAttr(nsGkAtoms::value, aValue);
457 NS_IMETHODIMP
458 nsTreeContentView::GetCellValue(int32_t aRow, nsTreeColumn* aCol,
459 nsAString& _retval) {
460 NS_ENSURE_ARG(aCol);
462 ErrorResult rv;
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);
471 return;
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()) {
479 return;
482 if (row->mContent->IsXULElement(nsGkAtoms::treeitem)) {
483 nsIContent* realRow =
484 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
485 if (realRow) {
486 Element* cell = GetCell(realRow, aColumn);
487 if (cell) cell->GetAttr(nsGkAtoms::label, aText);
492 NS_IMETHODIMP
493 nsTreeContentView::GetCellText(int32_t aRow, nsTreeColumn* aCol,
494 nsAString& _retval) {
495 NS_ENSURE_ARG(aCol);
497 ErrorResult rv;
498 GetCellText(aRow, *aCol, _retval, rv);
499 return rv.StealNSResult();
502 void nsTreeContentView::SetTree(XULTreeElement* aTree, ErrorResult& aError) {
503 aError = SetTree(aTree);
506 NS_IMETHODIMP
507 nsTreeContentView::SetTree(XULTreeElement* aTree) {
508 ClearRows();
510 mTree = aTree;
512 if (aTree) {
513 // Add ourselves to document's observers.
514 Document* document = mTree->GetComposedDoc();
515 if (document) {
516 document->AddObserver(this);
517 mDocument = document;
520 RefPtr<dom::Element> bodyElement = mTree->GetTreeBody();
521 if (bodyElement) {
522 mBody = std::move(bodyElement);
523 int32_t index = 0;
524 Serialize(mBody, -1, &index, mRows);
528 return NS_OK;
531 void nsTreeContentView::ToggleOpenState(int32_t aRow, ErrorResult& aError) {
532 if (!IsValidRowIndex(aRow)) {
533 aError.Throw(NS_ERROR_INVALID_ARG);
534 return;
537 // We don't serialize content right here, since content might be generated
538 // lazily.
539 Row* row = mRows[aRow].get();
541 if (row->IsOpen())
542 row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, u"false"_ns,
543 true);
544 else
545 row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, u"true"_ns,
546 true);
549 NS_IMETHODIMP
550 nsTreeContentView::ToggleOpenState(int32_t aIndex) {
551 ErrorResult rv;
552 ToggleOpenState(aIndex, rv);
553 return rv.StealNSResult();
556 void nsTreeContentView::CycleHeader(nsTreeColumn& aColumn,
557 ErrorResult& aError) {
558 if (!mTree) return;
560 RefPtr<Element> column = aColumn.Element();
561 nsAutoString sort;
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)) {
569 case 0:
570 sortdirection.AssignLiteral("descending");
571 break;
572 case 1:
573 sortdirection.AssignLiteral("natural");
574 break;
575 default:
576 sortdirection.AssignLiteral("ascending");
577 break;
580 nsAutoString hints;
581 column->GetAttr(nsGkAtoms::sorthints, hints);
582 sortdirection.Append(' ');
583 sortdirection += hints;
585 XULWidgetSort(mTree, sort, sortdirection);
589 NS_IMETHODIMP
590 nsTreeContentView::CycleHeader(nsTreeColumn* aCol) {
591 NS_ENSURE_ARG(aCol);
593 ErrorResult rv;
594 CycleHeader(*aCol, rv);
595 return rv.StealNSResult();
598 NS_IMETHODIMP
599 nsTreeContentView::SelectionChangedXPCOM() { return NS_OK; }
601 NS_IMETHODIMP
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);
608 return false;
611 Row* row = mRows[aRow].get();
613 nsIContent* realRow =
614 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
615 if (realRow) {
616 Element* cell = GetCell(realRow, aColumn);
617 if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
618 nsGkAtoms::_false, eCaseMatters)) {
619 return false;
623 return true;
626 NS_IMETHODIMP
627 nsTreeContentView::IsEditable(int32_t aRow, nsTreeColumn* aCol, bool* _retval) {
628 NS_ENSURE_ARG(aCol);
630 ErrorResult rv;
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);
640 return;
643 Row* row = mRows[aRow].get();
645 nsIContent* realRow =
646 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
647 if (realRow) {
648 Element* cell = GetCell(realRow, aColumn);
649 if (cell) cell->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, true);
653 NS_IMETHODIMP
654 nsTreeContentView::SetCellValue(int32_t aRow, nsTreeColumn* aCol,
655 const nsAString& aValue) {
656 NS_ENSURE_ARG(aCol);
658 ErrorResult rv;
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);
668 return;
671 Row* row = mRows[aRow].get();
673 nsIContent* realRow =
674 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
675 if (realRow) {
676 Element* cell = GetCell(realRow, aColumn);
677 if (cell) cell->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aValue, true);
681 NS_IMETHODIMP
682 nsTreeContentView::SetCellText(int32_t aRow, nsTreeColumn* aCol,
683 const nsAString& aValue) {
684 NS_ENSURE_ARG(aCol);
686 ErrorResult rv;
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);
695 return nullptr;
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();
716 mTree->Invalidate();
719 // We don't consider non-XUL nodes.
720 nsIContent* parent = nullptr;
721 if (!aElement->IsXULElement() ||
722 ((parent = aElement->GetParent()) && !parent->IsXULElement())) {
723 return;
725 if (!aElement->IsAnyOfXULElements(nsGkAtoms::treecol, nsGkAtoms::treeitem,
726 nsGkAtoms::treeseparator,
727 nsGkAtoms::treerow, nsGkAtoms::treecell)) {
728 return;
731 // If we have a legal tag, go up to the tree/select and make sure
732 // that it's ours.
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();
755 if (parent) {
756 InsertRowFor(parent, aElement);
760 return;
763 if (aElement->IsXULElement(nsGkAtoms::treecol)) {
764 if (aAttribute == nsGkAtoms::properties) {
765 if (mTree) {
766 RefPtr<nsTreeColumns> cols = mTree->GetColumns();
767 if (cols) {
768 RefPtr<nsTreeColumn> col = cols->GetColumnFor(aElement);
769 mTree->InvalidateColumn(col);
773 } else if (aElement->IsXULElement(nsGkAtoms::treeitem)) {
774 int32_t index = FindContent(aElement);
775 if (index >= 0) {
776 Row* row = mRows[index].get();
777 if (aAttribute == nsGkAtoms::container) {
778 bool isContainer =
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) {
792 bool isEmpty =
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);
801 if (index >= 0) {
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();
809 if (parent) {
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();
821 if (parent) {
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)) {
855 return;
858 // If we have a legal tag, go up to the tree/select and make sure
859 // that it's ours.
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);
872 if (index >= 0) {
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();
889 if (parent) {
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)) {
910 return;
913 // If we have a legal tag, go up to the tree/select and make sure
914 // that it's ours.
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);
927 if (index >= 0) {
928 Row* row = mRows[index].get();
929 row->SetEmpty(true);
930 int32_t count = RemoveSubtree(index);
931 // Invalidate also the row to update twisty.
932 if (mTree) {
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);
940 if (index >= 0) {
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();
949 if (parent) {
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);
959 ClearRows();
962 // Recursively serialize content, starting with aContent.
963 void nsTreeContentView::Serialize(nsIContent* aContent, int32_t aParentIndex,
964 int32_t* aIndex,
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,
985 int32_t* aIndex,
986 nsTArray<UniquePtr<Row>>& aRows) {
987 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
988 nsGkAtoms::_true, eCaseMatters))
989 return;
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)) {
999 row->SetOpen(true);
1000 nsIContent* child =
1001 nsTreeUtils::GetImmediateChild(aContent, nsGkAtoms::treechildren);
1002 if (child && child->IsXULElement()) {
1003 // Now, recursively serialize our child.
1004 int32_t count = aRows.Length();
1005 int32_t index = 0;
1006 Serialize(child, aParentIndex + *aIndex + 1, &index, aRows);
1007 row->mSubtreeSize += aRows.Length() - count;
1008 } else
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,
1019 int32_t* aIndex,
1020 nsTArray<UniquePtr<Row>>& aRows) {
1021 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
1022 nsGkAtoms::_true, eCaseMatters))
1023 return;
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,
1032 int32_t* aIndex) {
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,
1041 nsGkAtoms::hidden,
1042 nsGkAtoms::_true, eCaseMatters)) {
1043 (*aIndex)++;
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,
1049 eCaseMatters)) {
1050 nsIContent* child =
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,
1058 nsGkAtoms::hidden,
1059 nsGkAtoms::_true, eCaseMatters))
1060 (*aIndex)++;
1065 int32_t nsTreeContentView::EnsureSubtree(int32_t aIndex) {
1066 Row* row = mRows[aIndex].get();
1068 nsIContent* child;
1069 child =
1070 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treechildren);
1071 if (!child || !child->IsXULElement()) {
1072 return 0;
1075 AutoTArray<UniquePtr<Row>, 8> rows;
1076 int32_t index = 0;
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);
1095 return 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);
1109 return 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.
1120 insertRow = true;
1121 } else {
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;
1133 if (insertRow) {
1134 int32_t index = 0;
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);
1164 return 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);
1178 return count;
1181 void nsTreeContentView::ClearRows() {
1182 mRows.Clear();
1183 mBody = nullptr;
1184 // Remove ourselves from mDocument's observers.
1185 if (mDocument) {
1186 mDocument->RemoveObserver(this);
1187 mDocument = nullptr;
1191 void nsTreeContentView::OpenContainer(int32_t aIndex) {
1192 Row* row = mRows[aIndex].get();
1193 row->SetOpen(true);
1195 int32_t count = EnsureSubtree(aIndex);
1196 if (mTree) {
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);
1207 if (mTree) {
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) {
1216 return i;
1220 return -1;
1223 void nsTreeContentView::UpdateSubtreeSizes(int32_t aParentIndex,
1224 int32_t count) {
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,
1233 int32_t aCount) {
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;
1249 int32_t j = 0;
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();
1256 break;
1258 j++;
1262 return result;
1265 bool nsTreeContentView::IsValidRowIndex(int32_t aRowIndex) {
1266 return aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length());