/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsNameSpaceManager.h" #include "nsGkAtoms.h" #include "nsIBoxObject.h" #include "nsTreeUtils.h" #include "nsTreeContentView.h" #include "ChildIterator.h" #include "nsDOMClassInfoID.h" #include "nsError.h" #include "nsIXULSortService.h" #include "nsContentUtils.h" #include "nsTreeBodyFrame.h" #include "mozilla/dom/Element.h" #include "nsServiceManagerUtils.h" #include "nsIDocument.h" using namespace mozilla; #define NS_ENSURE_NATIVE_COLUMN(_col) \ RefPtr col = nsTreeBodyFrame::GetColumnImpl(_col); \ if (!col) { \ return NS_ERROR_INVALID_ARG; \ } // A content model view implementation for the tree. #define ROW_FLAG_CONTAINER 0x01 #define ROW_FLAG_OPEN 0x02 #define ROW_FLAG_EMPTY 0x04 #define ROW_FLAG_SEPARATOR 0x08 class Row { public: Row(nsIContent* aContent, int32_t aParentIndex) : mContent(aContent), mParentIndex(aParentIndex), mSubtreeSize(0), mFlags(0) { } ~Row() { } void SetContainer(bool aContainer) { aContainer ? mFlags |= ROW_FLAG_CONTAINER : mFlags &= ~ROW_FLAG_CONTAINER; } bool IsContainer() { return mFlags & ROW_FLAG_CONTAINER; } void SetOpen(bool aOpen) { aOpen ? mFlags |= ROW_FLAG_OPEN : mFlags &= ~ROW_FLAG_OPEN; } bool IsOpen() { return !!(mFlags & ROW_FLAG_OPEN); } void SetEmpty(bool aEmpty) { aEmpty ? mFlags |= ROW_FLAG_EMPTY : mFlags &= ~ROW_FLAG_EMPTY; } bool IsEmpty() { return !!(mFlags & ROW_FLAG_EMPTY); } void SetSeparator(bool aSeparator) { aSeparator ? mFlags |= ROW_FLAG_SEPARATOR : mFlags &= ~ROW_FLAG_SEPARATOR; } bool IsSeparator() { return !!(mFlags & ROW_FLAG_SEPARATOR); } // Weak reference to a content item. nsIContent* mContent; // The parent index of the item, set to -1 for the top level items. int32_t mParentIndex; // Subtree size for this item. int32_t mSubtreeSize; private: // State flags int8_t mFlags; }; // We don't reference count the reference to the document // If the document goes away first, we'll be informed and we // can drop our reference. // If we go away first, we'll get rid of ourselves from the // document's observer list. nsTreeContentView::nsTreeContentView(void) : mBoxObject(nullptr), mSelection(nullptr), mRoot(nullptr), mDocument(nullptr) { } nsTreeContentView::~nsTreeContentView(void) { // Remove ourselves from mDocument's observers. if (mDocument) mDocument->RemoveObserver(this); } nsresult NS_NewTreeContentView(nsITreeView** aResult) { *aResult = new nsTreeContentView; if (! *aResult) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*aResult); return NS_OK; } NS_IMPL_CYCLE_COLLECTION(nsTreeContentView, mBoxObject, mSelection, mRoot, mBody) NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeContentView) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeContentView) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeContentView) NS_INTERFACE_MAP_ENTRY(nsITreeView) NS_INTERFACE_MAP_ENTRY(nsITreeContentView) NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver) NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITreeContentView) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TreeContentView) NS_INTERFACE_MAP_END NS_IMETHODIMP nsTreeContentView::GetRowCount(int32_t* aRowCount) { *aRowCount = mRows.Length(); return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetSelection(nsITreeSelection** aSelection) { NS_IF_ADDREF(*aSelection = mSelection); return NS_OK; } bool nsTreeContentView::CanTrustTreeSelection(nsISupports* aValue) { // Untrusted content is only allowed to specify known-good views if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) return true; nsCOMPtr nativeTreeSel = do_QueryInterface(aValue); return nativeTreeSel && NS_SUCCEEDED(nativeTreeSel->EnsureNative()); } NS_IMETHODIMP nsTreeContentView::SetSelection(nsITreeSelection* aSelection) { NS_ENSURE_TRUE(!aSelection || CanTrustTreeSelection(aSelection), NS_ERROR_DOM_SECURITY_ERR); mSelection = aSelection; return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetRowProperties(int32_t aIndex, nsAString& aProps) { NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; Row* row = mRows[aIndex].get(); nsIContent* realRow; if (row->IsSeparator()) realRow = row->mContent; else realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); if (realRow) { realRow->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProps); } return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetCellProperties(int32_t aRow, nsITreeColumn* aCol, nsAString& aProps) { NS_ENSURE_NATIVE_COLUMN(aCol); NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); if (aRow < 0 || aRow >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; Row* row = mRows[aRow].get(); nsIContent* realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); if (realRow) { nsIContent* cell = GetCell(realRow, aCol); if (cell) { cell->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProps); } } return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetColumnProperties(nsITreeColumn* aCol, nsAString& aProps) { NS_ENSURE_NATIVE_COLUMN(aCol); nsCOMPtr element; aCol->GetElement(getter_AddRefs(element)); element->GetAttribute(NS_LITERAL_STRING("properties"), aProps); return NS_OK; } NS_IMETHODIMP nsTreeContentView::IsContainer(int32_t aIndex, bool* _retval) { NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; *_retval = mRows[aIndex]->IsContainer(); return NS_OK; } NS_IMETHODIMP nsTreeContentView::IsContainerOpen(int32_t aIndex, bool* _retval) { NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; *_retval = mRows[aIndex]->IsOpen(); return NS_OK; } NS_IMETHODIMP nsTreeContentView::IsContainerEmpty(int32_t aIndex, bool* _retval) { NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; *_retval = mRows[aIndex]->IsEmpty(); return NS_OK; } NS_IMETHODIMP nsTreeContentView::IsSeparator(int32_t aIndex, bool *_retval) { NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; *_retval = mRows[aIndex]->IsSeparator(); return NS_OK; } NS_IMETHODIMP nsTreeContentView::IsSorted(bool *_retval) { *_retval = false; return NS_OK; } NS_IMETHODIMP nsTreeContentView::CanDrop(int32_t aIndex, int32_t aOrientation, nsIDOMDataTransfer* aDataTransfer, bool *_retval) { NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; *_retval = false; return NS_OK; } NS_IMETHODIMP nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation, nsIDOMDataTransfer* aDataTransfer) { NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); if (aRow < 0 || aRow >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetParentIndex(int32_t aRowIndex, int32_t* _retval) { NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length()), "bad row index"); if (aRowIndex < 0 || aRowIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; *_retval = mRows[aRowIndex]->mParentIndex; return NS_OK; } NS_IMETHODIMP nsTreeContentView::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex, bool* _retval) { NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length()), "bad row index"); if (aRowIndex < 0 || aRowIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; // We have a next sibling if the row is not the last in the subtree. int32_t parentIndex = mRows[aRowIndex]->mParentIndex; if (parentIndex >= 0) { // Compute the last index in this subtree. int32_t lastIndex = parentIndex + (mRows[parentIndex])->mSubtreeSize; Row* row = mRows[lastIndex].get(); while (row->mParentIndex != parentIndex) { lastIndex = row->mParentIndex; row = mRows[lastIndex].get(); } *_retval = aRowIndex < lastIndex; } else { *_retval = uint32_t(aRowIndex) < mRows.Length() - 1; } return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetLevel(int32_t aIndex, int32_t* _retval) { NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; int32_t level = 0; Row* row = mRows[aIndex].get(); while (row->mParentIndex >= 0) { level++; row = mRows[row->mParentIndex].get(); } *_retval = level; return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetImageSrc(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) { _retval.Truncate(); NS_ENSURE_NATIVE_COLUMN(aCol); NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); if (aRow < 0 || aRow >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; Row* row = mRows[aRow].get(); nsIContent* realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); if (realRow) { nsIContent* cell = GetCell(realRow, aCol); if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::src, _retval); } return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetProgressMode(int32_t aRow, nsITreeColumn* aCol, int32_t* _retval) { NS_ENSURE_NATIVE_COLUMN(aCol); NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); if (aRow < 0 || aRow >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; *_retval = nsITreeView::PROGRESS_NONE; Row* row = mRows[aRow].get(); nsIContent* realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); if (realRow) { nsIContent* cell = GetCell(realRow, aCol); if (cell) { static nsIContent::AttrValuesArray strings[] = {&nsGkAtoms::normal, &nsGkAtoms::undetermined, nullptr}; switch (cell->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::mode, strings, eCaseMatters)) { case 0: *_retval = nsITreeView::PROGRESS_NORMAL; break; case 1: *_retval = nsITreeView::PROGRESS_UNDETERMINED; break; } } } return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetCellValue(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) { _retval.Truncate(); NS_ENSURE_NATIVE_COLUMN(aCol); NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); if (aRow < 0 || aRow >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; Row* row = mRows[aRow].get(); nsIContent* realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); if (realRow) { nsIContent* cell = GetCell(realRow, aCol); if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::value, _retval); } return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) { _retval.Truncate(); NS_ENSURE_NATIVE_COLUMN(aCol); NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); NS_PRECONDITION(aCol, "bad column"); if (aRow < 0 || aRow >= int32_t(mRows.Length()) || !aCol) return NS_ERROR_INVALID_ARG; Row* row = mRows[aRow].get(); // Check for a "label" attribute - this is valid on an // with a single implied column. if (row->mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, _retval) && !_retval.IsEmpty()) return NS_OK; if (row->mContent->IsXULElement(nsGkAtoms::treeitem)) { nsIContent* realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); if (realRow) { nsIContent* cell = GetCell(realRow, aCol); if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::label, _retval); } } return NS_OK; } NS_IMETHODIMP nsTreeContentView::SetTree(nsITreeBoxObject* aTree) { ClearRows(); mBoxObject = aTree; MOZ_ASSERT(!mRoot, "mRoot should have been cleared out by ClearRows"); if (aTree) { // Get our root element nsCOMPtr boxObject = do_QueryInterface(mBoxObject); if (!boxObject) { mBoxObject = nullptr; return NS_ERROR_INVALID_ARG; } nsCOMPtr element; boxObject->GetElement(getter_AddRefs(element)); mRoot = do_QueryInterface(element); NS_ENSURE_STATE(mRoot); // Add ourselves to document's observers. nsIDocument* document = mRoot->GetComposedDoc(); if (document) { document->AddObserver(this); mDocument = document; } nsCOMPtr bodyElement; mBoxObject->GetTreeBody(getter_AddRefs(bodyElement)); if (bodyElement) { mBody = do_QueryInterface(bodyElement); int32_t index = 0; Serialize(mBody, -1, &index, mRows); } } return NS_OK; } NS_IMETHODIMP nsTreeContentView::ToggleOpenState(int32_t aIndex) { NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; // We don't serialize content right here, since content might be generated // lazily. Row* row = mRows[aIndex].get(); if (row->IsOpen()) row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("false"), true); else row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true); return NS_OK; } NS_IMETHODIMP nsTreeContentView::CycleHeader(nsITreeColumn* aCol) { NS_ENSURE_NATIVE_COLUMN(aCol); if (!mRoot) return NS_OK; nsCOMPtr element; aCol->GetElement(getter_AddRefs(element)); if (element) { nsCOMPtr column = do_QueryInterface(element); nsAutoString sort; column->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort); if (!sort.IsEmpty()) { nsCOMPtr xs = do_GetService("@mozilla.org/xul/xul-sort-service;1"); if (xs) { nsAutoString sortdirection; static nsIContent::AttrValuesArray strings[] = {&nsGkAtoms::ascending, &nsGkAtoms::descending, nullptr}; switch (column->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sortDirection, strings, eCaseMatters)) { case 0: sortdirection.AssignLiteral("descending"); break; case 1: sortdirection.AssignLiteral("natural"); break; default: sortdirection.AssignLiteral("ascending"); break; } nsAutoString hints; column->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints); sortdirection.Append(' '); sortdirection += hints; nsCOMPtr rootnode = do_QueryInterface(mRoot); xs->Sort(rootnode, sort, sortdirection); } } } return NS_OK; } NS_IMETHODIMP nsTreeContentView::SelectionChanged() { return NS_OK; } NS_IMETHODIMP nsTreeContentView::CycleCell(int32_t aRow, nsITreeColumn* aCol) { return NS_OK; } NS_IMETHODIMP nsTreeContentView::IsEditable(int32_t aRow, nsITreeColumn* aCol, bool* _retval) { *_retval = false; NS_ENSURE_NATIVE_COLUMN(aCol); NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); if (aRow < 0 || aRow >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; *_retval = true; Row* row = mRows[aRow].get(); nsIContent* realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); if (realRow) { nsIContent* cell = GetCell(realRow, aCol); if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, nsGkAtoms::_false, eCaseMatters)) { *_retval = false; } } return NS_OK; } NS_IMETHODIMP nsTreeContentView::IsSelectable(int32_t aRow, nsITreeColumn* aCol, bool* _retval) { NS_ENSURE_NATIVE_COLUMN(aCol); NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); if (aRow < 0 || aRow >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; *_retval = true; Row* row = mRows[aRow].get(); nsIContent* realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); if (realRow) { nsIContent* cell = GetCell(realRow, aCol); if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::selectable, nsGkAtoms::_false, eCaseMatters)) { *_retval = false; } } return NS_OK; } NS_IMETHODIMP nsTreeContentView::SetCellValue(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue) { NS_ENSURE_NATIVE_COLUMN(aCol); NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); if (aRow < 0 || aRow >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; Row* row = mRows[aRow].get(); nsIContent* realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); if (realRow) { nsIContent* cell = GetCell(realRow, aCol); if (cell) cell->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, true); } return NS_OK; } NS_IMETHODIMP nsTreeContentView::SetCellText(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue) { NS_ENSURE_NATIVE_COLUMN(aCol); NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); if (aRow < 0 || aRow >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; Row* row = mRows[aRow].get(); nsIContent* realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); if (realRow) { nsIContent* cell = GetCell(realRow, aCol); if (cell) cell->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aValue, true); } return NS_OK; } NS_IMETHODIMP nsTreeContentView::PerformAction(const char16_t* aAction) { return NS_OK; } NS_IMETHODIMP nsTreeContentView::PerformActionOnRow(const char16_t* aAction, int32_t aRow) { return NS_OK; } NS_IMETHODIMP nsTreeContentView::PerformActionOnCell(const char16_t* aAction, int32_t aRow, nsITreeColumn* aCol) { return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetItemAtIndex(int32_t aIndex, nsIDOMElement** _retval) { NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) return NS_ERROR_INVALID_ARG; Row* row = mRows[aIndex].get(); row->mContent->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)_retval); return NS_OK; } NS_IMETHODIMP nsTreeContentView::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval) { nsCOMPtr content = do_QueryInterface(aItem); *_retval = FindContent(content); return NS_OK; } void nsTreeContentView::AttributeChanged(nsIDocument* aDocument, dom::Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { // Lots of codepaths under here that do all sorts of stuff, so be safe. nsCOMPtr kungFuDeathGrip(this); // Make sure this notification concerns us. // First check the tag to see if it's one that we care about. if (mBoxObject && (aElement == mRoot || aElement == mBody)) { mBoxObject->ClearStyleAndImageCaches(); mBoxObject->Invalidate(); } // We don't consider non-XUL nodes. nsIContent* parent = nullptr; if (!aElement->IsXULElement() || ((parent = aElement->GetParent()) && !parent->IsXULElement())) { return; } if (!aElement->IsAnyOfXULElements(nsGkAtoms::treecol, nsGkAtoms::treeitem, nsGkAtoms::treeseparator, nsGkAtoms::treerow, nsGkAtoms::treecell)) { return; } // If we have a legal tag, go up to the tree/select and make sure // that it's ours. for (nsIContent* element = aElement; element != mBody; element = element->GetParent()) { if (!element) return; // this is not for us if (element->IsXULElement(nsGkAtoms::tree)) return; // this is not for us } // Handle changes of the hidden attribute. if (aAttribute == nsGkAtoms::hidden && aElement->IsAnyOfXULElements(nsGkAtoms::treeitem, nsGkAtoms::treeseparator)) { bool hidden = aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, nsGkAtoms::_true, eCaseMatters); int32_t index = FindContent(aElement); if (hidden && index >= 0) { // Hide this row along with its children. int32_t count = RemoveRow(index); if (mBoxObject) mBoxObject->RowCountChanged(index, -count); } else if (!hidden && index < 0) { // Show this row along with its children. nsCOMPtr parent = aElement->GetParent(); if (parent) { InsertRowFor(parent, aElement); } } return; } if (aElement->IsXULElement(nsGkAtoms::treecol)) { if (aAttribute == nsGkAtoms::properties) { if (mBoxObject) { nsCOMPtr cols; mBoxObject->GetColumns(getter_AddRefs(cols)); if (cols) { nsCOMPtr element = do_QueryInterface(aElement); nsCOMPtr col; cols->GetColumnFor(element, getter_AddRefs(col)); mBoxObject->InvalidateColumn(col); } } } } else if (aElement->IsXULElement(nsGkAtoms::treeitem)) { int32_t index = FindContent(aElement); if (index >= 0) { Row* row = mRows[index].get(); if (aAttribute == nsGkAtoms::container) { bool isContainer = aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, nsGkAtoms::_true, eCaseMatters); row->SetContainer(isContainer); if (mBoxObject) mBoxObject->InvalidateRow(index); } else if (aAttribute == nsGkAtoms::open) { bool isOpen = aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, nsGkAtoms::_true, eCaseMatters); bool wasOpen = row->IsOpen(); if (! isOpen && wasOpen) CloseContainer(index); else if (isOpen && ! wasOpen) OpenContainer(index); } else if (aAttribute == nsGkAtoms::empty) { bool isEmpty = aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty, nsGkAtoms::_true, eCaseMatters); row->SetEmpty(isEmpty); if (mBoxObject) mBoxObject->InvalidateRow(index); } } } else if (aElement->IsXULElement(nsGkAtoms::treeseparator)) { int32_t index = FindContent(aElement); if (index >= 0) { if (aAttribute == nsGkAtoms::properties && mBoxObject) { mBoxObject->InvalidateRow(index); } } } else if (aElement->IsXULElement(nsGkAtoms::treerow)) { if (aAttribute == nsGkAtoms::properties) { nsCOMPtr parent = aElement->GetParent(); if (parent) { int32_t index = FindContent(parent); if (index >= 0 && mBoxObject) { mBoxObject->InvalidateRow(index); } } } } else if (aElement->IsXULElement(nsGkAtoms::treecell)) { if (aAttribute == nsGkAtoms::ref || aAttribute == nsGkAtoms::properties || aAttribute == nsGkAtoms::mode || aAttribute == nsGkAtoms::src || aAttribute == nsGkAtoms::value || aAttribute == nsGkAtoms::label) { nsIContent* parent = aElement->GetParent(); if (parent) { nsCOMPtr grandParent = parent->GetParent(); if (grandParent && grandParent->IsXULElement()) { int32_t index = FindContent(grandParent); if (index >= 0 && mBoxObject) { // XXX Should we make an effort to invalidate only cell ? mBoxObject->InvalidateRow(index); } } } } } } void nsTreeContentView::ContentAppended(nsIDocument *aDocument, nsIContent* aContainer, nsIContent* aFirstNewContent, int32_t /* unused */) { for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { // Our contentinserted doesn't use the index ContentInserted(aDocument, aContainer, cur, 0); } } void nsTreeContentView::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer, nsIContent* aChild, int32_t /* unused */) { NS_ASSERTION(aChild, "null ptr"); // Make sure this notification concerns us. // First check the tag to see if it's one that we care about. // Don't allow non-XUL nodes. if (!aChild->IsXULElement() || !aContainer->IsXULElement()) return; if (!aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, nsGkAtoms::treeseparator, nsGkAtoms::treechildren, nsGkAtoms::treerow, nsGkAtoms::treecell)) { return; } // If we have a legal tag, go up to the tree/select and make sure // that it's ours. for (nsIContent* element = aContainer; element != mBody; element = element->GetParent()) { if (!element) return; // this is not for us if (element->IsXULElement(nsGkAtoms::tree)) return; // this is not for us } // Lots of codepaths under here that do all sorts of stuff, so be safe. nsCOMPtr kungFuDeathGrip(this); if (aChild->IsXULElement(nsGkAtoms::treechildren)) { int32_t index = FindContent(aContainer); if (index >= 0) { Row* row = mRows[index].get(); row->SetEmpty(false); if (mBoxObject) mBoxObject->InvalidateRow(index); if (row->IsContainer() && row->IsOpen()) { int32_t count = EnsureSubtree(index); if (mBoxObject) mBoxObject->RowCountChanged(index + 1, count); } } } else if (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, nsGkAtoms::treeseparator)) { InsertRowFor(aContainer, aChild); } else if (aChild->IsXULElement(nsGkAtoms::treerow)) { int32_t index = FindContent(aContainer); if (index >= 0 && mBoxObject) mBoxObject->InvalidateRow(index); } else if (aChild->IsXULElement(nsGkAtoms::treecell)) { nsCOMPtr parent = aContainer->GetParent(); if (parent) { int32_t index = FindContent(parent); if (index >= 0 && mBoxObject) mBoxObject->InvalidateRow(index); } } } void nsTreeContentView::ContentRemoved(nsIDocument *aDocument, nsIContent* aContainer, nsIContent* aChild, int32_t aIndexInContainer, nsIContent* aPreviousSibling) { NS_ASSERTION(aChild, "null ptr"); // Make sure this notification concerns us. // First check the tag to see if it's one that we care about. // We don't consider non-XUL nodes. if (!aChild->IsXULElement() || !aContainer->IsXULElement()) return; if (!aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, nsGkAtoms::treeseparator, nsGkAtoms::treechildren, nsGkAtoms::treerow, nsGkAtoms::treecell)) { return; } // If we have a legal tag, go up to the tree/select and make sure // that it's ours. for (nsIContent* element = aContainer; element != mBody; element = element->GetParent()) { if (!element) return; // this is not for us if (element->IsXULElement(nsGkAtoms::tree)) return; // this is not for us } // Lots of codepaths under here that do all sorts of stuff, so be safe. nsCOMPtr kungFuDeathGrip(this); if (aChild->IsXULElement(nsGkAtoms::treechildren)) { int32_t index = FindContent(aContainer); if (index >= 0) { Row* row = mRows[index].get(); row->SetEmpty(true); int32_t count = RemoveSubtree(index); // Invalidate also the row to update twisty. if (mBoxObject) { mBoxObject->InvalidateRow(index); mBoxObject->RowCountChanged(index + 1, -count); } } } else if (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem, nsGkAtoms::treeseparator)) { int32_t index = FindContent(aChild); if (index >= 0) { int32_t count = RemoveRow(index); if (mBoxObject) mBoxObject->RowCountChanged(index, -count); } } else if (aChild->IsXULElement(nsGkAtoms::treerow)) { int32_t index = FindContent(aContainer); if (index >= 0 && mBoxObject) mBoxObject->InvalidateRow(index); } else if (aChild->IsXULElement(nsGkAtoms::treecell)) { nsCOMPtr parent = aContainer->GetParent(); if (parent) { int32_t index = FindContent(parent); if (index >= 0 && mBoxObject) mBoxObject->InvalidateRow(index); } } } void nsTreeContentView::NodeWillBeDestroyed(const nsINode* aNode) { // XXXbz do we need this strong ref? Do we drop refs to self in ClearRows? nsCOMPtr kungFuDeathGrip(this); ClearRows(); } // Recursively serialize content, starting with aContent. void nsTreeContentView::Serialize(nsIContent* aContent, int32_t aParentIndex, int32_t* aIndex, nsTArray>& aRows) { // Don't allow non-XUL nodes. if (!aContent->IsXULElement()) return; dom::FlattenedChildIterator iter(aContent); for (nsIContent* content = iter.GetNextChild(); content; content = iter.GetNextChild()) { int32_t count = aRows.Length(); if (content->IsXULElement(nsGkAtoms::treeitem)) { SerializeItem(content, aParentIndex, aIndex, aRows); } else if (content->IsXULElement(nsGkAtoms::treeseparator)) { SerializeSeparator(content, aParentIndex, aIndex, aRows); } *aIndex += aRows.Length() - count; } } void nsTreeContentView::SerializeItem(nsIContent* aContent, int32_t aParentIndex, int32_t* aIndex, nsTArray>& aRows) { if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, nsGkAtoms::_true, eCaseMatters)) return; aRows.AppendElement(MakeUnique(aContent, aParentIndex)); Row* row = aRows.LastElement().get(); if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, nsGkAtoms::_true, eCaseMatters)) { row->SetContainer(true); if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, nsGkAtoms::_true, eCaseMatters)) { row->SetOpen(true); nsIContent* child = nsTreeUtils::GetImmediateChild(aContent, nsGkAtoms::treechildren); if (child && child->IsXULElement()) { // Now, recursively serialize our child. int32_t count = aRows.Length(); int32_t index = 0; Serialize(child, aParentIndex + *aIndex + 1, &index, aRows); row->mSubtreeSize += aRows.Length() - count; } else row->SetEmpty(true); } else if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty, nsGkAtoms::_true, eCaseMatters)) { row->SetEmpty(true); } } } void nsTreeContentView::SerializeSeparator(nsIContent* aContent, int32_t aParentIndex, int32_t* aIndex, nsTArray>& aRows) { if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, nsGkAtoms::_true, eCaseMatters)) return; auto row = MakeUnique(aContent, aParentIndex); row->SetSeparator(true); aRows.AppendElement(Move(row)); } void nsTreeContentView::GetIndexInSubtree(nsIContent* aContainer, nsIContent* aContent, int32_t* aIndex) { uint32_t childCount = aContainer->GetChildCount(); if (!aContainer->IsXULElement()) return; for (uint32_t i = 0; i < childCount; i++) { nsIContent *content = aContainer->GetChildAt(i); if (content == aContent) break; if (content->IsXULElement(nsGkAtoms::treeitem)) { if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, nsGkAtoms::_true, eCaseMatters)) { (*aIndex)++; if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, nsGkAtoms::_true, eCaseMatters) && content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, nsGkAtoms::_true, eCaseMatters)) { nsIContent* child = nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treechildren); if (child && child->IsXULElement()) GetIndexInSubtree(child, aContent, aIndex); } } } else if (content->IsXULElement(nsGkAtoms::treeseparator)) { if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, nsGkAtoms::_true, eCaseMatters)) (*aIndex)++; } } } int32_t nsTreeContentView::EnsureSubtree(int32_t aIndex) { Row* row = mRows[aIndex].get(); nsIContent* child; child = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treechildren); if (!child || !child->IsXULElement()) { return 0; } AutoTArray, 8> rows; int32_t index = 0; Serialize(child, aIndex, &index, rows); // Insert |rows| into |mRows| at position |aIndex|, by first creating empty // UniquePtr entries and then Move'ing |rows|'s entries into them. (Note // that we can't simply use InsertElementsAt with an array argument, since // the destination can't steal ownership from its const source argument.) UniquePtr* newRows = mRows.InsertElementsAt(aIndex + 1, rows.Length()); for (nsTArray::index_type i = 0; i < rows.Length(); i++) { newRows[i] = Move(rows[i]); } int32_t count = rows.Length(); row->mSubtreeSize += count; UpdateSubtreeSizes(row->mParentIndex, count); // Update parent indexes, but skip newly added rows. // They already have correct values. UpdateParentIndexes(aIndex, count + 1, count); return count; } int32_t nsTreeContentView::RemoveSubtree(int32_t aIndex) { Row* row = mRows[aIndex].get(); int32_t count = row->mSubtreeSize; mRows.RemoveElementsAt(aIndex + 1, count); row->mSubtreeSize -= count; UpdateSubtreeSizes(row->mParentIndex, -count); UpdateParentIndexes(aIndex, 0, -count); return count; } void nsTreeContentView::InsertRowFor(nsIContent* aParent, nsIContent* aChild) { int32_t grandParentIndex = -1; bool insertRow = false; nsCOMPtr grandParent = aParent->GetParent(); if (grandParent->IsXULElement(nsGkAtoms::tree)) { // Allow insertion to the outermost container. insertRow = true; } else { // Test insertion to an inner container. // First try to find this parent in our array of rows, if we find one // we can be sure that all other parents are open too. grandParentIndex = FindContent(grandParent); if (grandParentIndex >= 0) { // Got it, now test if it is open. if (mRows[grandParentIndex]->IsOpen()) insertRow = true; } } if (insertRow) { int32_t index = 0; GetIndexInSubtree(aParent, aChild, &index); int32_t count = InsertRow(grandParentIndex, index, aChild); if (mBoxObject) mBoxObject->RowCountChanged(grandParentIndex + index + 1, count); } } int32_t nsTreeContentView::InsertRow(int32_t aParentIndex, int32_t aIndex, nsIContent* aContent) { AutoTArray, 8> rows; if (aContent->IsXULElement(nsGkAtoms::treeitem)) { SerializeItem(aContent, aParentIndex, &aIndex, rows); } else if (aContent->IsXULElement(nsGkAtoms::treeseparator)) { SerializeSeparator(aContent, aParentIndex, &aIndex, rows); } // We can't use InsertElementsAt since the destination can't steal // ownership from its const source argument. int32_t count = rows.Length(); for (nsTArray::index_type i = 0; i < size_t(count); i++) { mRows.InsertElementAt(aParentIndex + aIndex + i + 1, Move(rows[i])); } UpdateSubtreeSizes(aParentIndex, count); // Update parent indexes, but skip added rows. // They already have correct values. UpdateParentIndexes(aParentIndex + aIndex, count + 1, count); return count; } int32_t nsTreeContentView::RemoveRow(int32_t aIndex) { Row* row = mRows[aIndex].get(); int32_t count = row->mSubtreeSize + 1; int32_t parentIndex = row->mParentIndex; mRows.RemoveElementsAt(aIndex, count); UpdateSubtreeSizes(parentIndex, -count); UpdateParentIndexes(aIndex, 0, -count); return count; } void nsTreeContentView::ClearRows() { mRows.Clear(); mRoot = nullptr; mBody = nullptr; // Remove ourselves from mDocument's observers. if (mDocument) { mDocument->RemoveObserver(this); mDocument = nullptr; } } void nsTreeContentView::OpenContainer(int32_t aIndex) { Row* row = mRows[aIndex].get(); row->SetOpen(true); int32_t count = EnsureSubtree(aIndex); if (mBoxObject) { mBoxObject->InvalidateRow(aIndex); mBoxObject->RowCountChanged(aIndex + 1, count); } } void nsTreeContentView::CloseContainer(int32_t aIndex) { Row* row = mRows[aIndex].get(); row->SetOpen(false); int32_t count = RemoveSubtree(aIndex); if (mBoxObject) { mBoxObject->InvalidateRow(aIndex); mBoxObject->RowCountChanged(aIndex + 1, -count); } } int32_t nsTreeContentView::FindContent(nsIContent* aContent) { for (uint32_t i = 0; i < mRows.Length(); i++) { if (mRows[i]->mContent == aContent) { return i; } } return -1; } void nsTreeContentView::UpdateSubtreeSizes(int32_t aParentIndex, int32_t count) { while (aParentIndex >= 0) { Row* row = mRows[aParentIndex].get(); row->mSubtreeSize += count; aParentIndex = row->mParentIndex; } } void nsTreeContentView::UpdateParentIndexes(int32_t aIndex, int32_t aSkip, int32_t aCount) { int32_t count = mRows.Length(); for (int32_t i = aIndex + aSkip; i < count; i++) { Row* row = mRows[i].get(); if (row->mParentIndex > aIndex) { row->mParentIndex += aCount; } } } nsIContent* nsTreeContentView::GetCell(nsIContent* aContainer, nsITreeColumn* aCol) { nsCOMPtr colAtom; int32_t colIndex; aCol->GetAtom(getter_AddRefs(colAtom)); aCol->GetIndex(&colIndex); // Traverse through cells, try to find the cell by "ref" attribute or by cell // index in a row. "ref" attribute has higher priority. nsIContent* result = nullptr; int32_t j = 0; dom::FlattenedChildIterator iter(aContainer); for (nsIContent* cell = iter.GetNextChild(); cell; cell = iter.GetNextChild()) { if (cell->IsXULElement(nsGkAtoms::treecell)) { if (colAtom && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ref, colAtom, eCaseMatters)) { result = cell; break; } else if (j == colIndex) { result = cell; } j++; } } return result; }