diff options
Diffstat (limited to 'layout/xul/tree/nsTreeContentView.cpp')
-rw-r--r-- | layout/xul/tree/nsTreeContentView.cpp | 1372 |
1 files changed, 1372 insertions, 0 deletions
diff --git a/layout/xul/tree/nsTreeContentView.cpp b/layout/xul/tree/nsTreeContentView.cpp new file mode 100644 index 000000000..66cc0072f --- /dev/null +++ b/layout/xul/tree/nsTreeContentView.cpp @@ -0,0 +1,1372 @@ +/* -*- 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<nsTreeColumn> 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<nsINativeTreeSelection> 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<nsIDOMElement> 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 <treeitem> + // 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<nsIBoxObject> boxObject = do_QueryInterface(mBoxObject); + if (!boxObject) { + mBoxObject = nullptr; + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIDOMElement> 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<nsIDOMElement> 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<nsIDOMElement> element; + aCol->GetElement(getter_AddRefs(element)); + if (element) { + nsCOMPtr<nsIContent> column = do_QueryInterface(element); + nsAutoString sort; + column->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort); + if (!sort.IsEmpty()) { + nsCOMPtr<nsIXULSortService> 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<nsIDOMNode> 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<nsIContent> 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<nsIMutationObserver> 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<nsIContent> parent = aElement->GetParent(); + if (parent) { + InsertRowFor(parent, aElement); + } + } + + return; + } + + if (aElement->IsXULElement(nsGkAtoms::treecol)) { + if (aAttribute == nsGkAtoms::properties) { + if (mBoxObject) { + nsCOMPtr<nsITreeColumns> cols; + mBoxObject->GetColumns(getter_AddRefs(cols)); + if (cols) { + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aElement); + nsCOMPtr<nsITreeColumn> 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<nsIContent> 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<nsIContent> 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<nsIMutationObserver> 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<nsIContent> 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<nsIMutationObserver> 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<nsIContent> 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<nsIMutationObserver> kungFuDeathGrip(this); + ClearRows(); +} + + +// Recursively serialize content, starting with aContent. +void +nsTreeContentView::Serialize(nsIContent* aContent, int32_t aParentIndex, + int32_t* aIndex, nsTArray<UniquePtr<Row>>& 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<UniquePtr<Row>>& aRows) +{ + if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) + return; + + aRows.AppendElement(MakeUnique<Row>(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<UniquePtr<Row>>& aRows) +{ + if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) + return; + + auto row = MakeUnique<Row>(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<UniquePtr<Row>, 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<Row>* newRows = mRows.InsertElementsAt(aIndex + 1, + rows.Length()); + for (nsTArray<Row>::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<nsIContent> 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<UniquePtr<Row>, 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<Row>::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<nsIAtom> 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; +} |