summaryrefslogtreecommitdiffstats
path: root/layout/xul/tree/nsTreeContentView.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/xul/tree/nsTreeContentView.cpp')
-rw-r--r--layout/xul/tree/nsTreeContentView.cpp1269
1 files changed, 1269 insertions, 0 deletions
diff --git a/layout/xul/tree/nsTreeContentView.cpp b/layout/xul/tree/nsTreeContentView.cpp
new file mode 100644
index 0000000000..edb5368e2a
--- /dev/null
+++ b/layout/xul/tree/nsTreeContentView.cpp
@@ -0,0 +1,1269 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsTreeUtils.h"
+#include "nsTreeContentView.h"
+#include "ChildIterator.h"
+#include "nsError.h"
+#include "nsXULSortService.h"
+#include "nsTreeBodyFrame.h"
+#include "nsTreeColumns.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TreeContentViewBinding.h"
+#include "mozilla/dom/XULTreeElement.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/Document.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// 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(Element* aContent, int32_t aParentIndex)
+ : mContent(aContent),
+ mParentIndex(aParentIndex),
+ mSubtreeSize(0),
+ mFlags(0) {}
+
+ ~Row() = default;
+
+ 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.
+ Element* 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)
+ : mTree(nullptr), mSelection(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_WRAPPERCACHE(nsTreeContentView, mTree, mSelection,
+ 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(nsIDocumentObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITreeView)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END
+
+JSObject* nsTreeContentView::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return TreeContentView_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports* nsTreeContentView::GetParentObject() { return mTree; }
+
+NS_IMETHODIMP
+nsTreeContentView::GetRowCount(int32_t* aRowCount) {
+ *aRowCount = mRows.Length();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetSelection(nsITreeSelection** aSelection) {
+ NS_IF_ADDREF(*aSelection = GetSelection());
+
+ 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) {
+ ErrorResult rv;
+ SetSelection(aSelection, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::SetSelection(nsITreeSelection* aSelection,
+ ErrorResult& aError) {
+ if (aSelection && !CanTrustTreeSelection(aSelection)) {
+ aError.ThrowSecurityError("Not allowed to set tree selection");
+ return;
+ }
+
+ mSelection = aSelection;
+}
+
+void nsTreeContentView::GetRowProperties(int32_t aRow, nsAString& aProperties,
+ ErrorResult& aError) {
+ aProperties.Truncate();
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ Row* row = mRows[aRow].get();
+ nsIContent* realRow;
+ if (row->IsSeparator())
+ realRow = row->mContent;
+ else
+ realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+
+ if (realRow && realRow->IsElement()) {
+ realRow->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::properties,
+ aProperties);
+ }
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetRowProperties(int32_t aIndex, nsAString& aProps) {
+ ErrorResult rv;
+ GetRowProperties(aIndex, aProps, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::GetCellProperties(int32_t aRow, nsTreeColumn& aColumn,
+ nsAString& aProperties,
+ ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ Row* row = mRows[aRow].get();
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ Element* cell = GetCell(realRow, aColumn);
+ if (cell) {
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProperties);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetCellProperties(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& aProps) {
+ NS_ENSURE_ARG(aCol);
+
+ ErrorResult rv;
+ GetCellProperties(aRow, *aCol, aProps, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::GetColumnProperties(nsTreeColumn& aColumn,
+ nsAString& aProperties) {
+ RefPtr<Element> element = aColumn.Element();
+
+ if (element) {
+ element->GetAttr(nsGkAtoms::properties, aProperties);
+ }
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetColumnProperties(nsTreeColumn* aCol, nsAString& aProps) {
+ NS_ENSURE_ARG(aCol);
+
+ GetColumnProperties(*aCol, aProps);
+ return NS_OK;
+}
+
+bool nsTreeContentView::IsContainer(int32_t aRow, ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ return mRows[aRow]->IsContainer();
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsContainer(int32_t aIndex, bool* _retval) {
+ ErrorResult rv;
+ *_retval = IsContainer(aIndex, rv);
+ return rv.StealNSResult();
+}
+
+bool nsTreeContentView::IsContainerOpen(int32_t aRow, ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ return mRows[aRow]->IsOpen();
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsContainerOpen(int32_t aIndex, bool* _retval) {
+ ErrorResult rv;
+ *_retval = IsContainerOpen(aIndex, rv);
+ return rv.StealNSResult();
+}
+
+bool nsTreeContentView::IsContainerEmpty(int32_t aRow, ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ return mRows[aRow]->IsEmpty();
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsContainerEmpty(int32_t aIndex, bool* _retval) {
+ ErrorResult rv;
+ *_retval = IsContainerEmpty(aIndex, rv);
+ return rv.StealNSResult();
+}
+
+bool nsTreeContentView::IsSeparator(int32_t aRow, ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ return mRows[aRow]->IsSeparator();
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsSeparator(int32_t aIndex, bool* _retval) {
+ ErrorResult rv;
+ *_retval = IsSeparator(aIndex, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsSorted(bool* _retval) {
+ *_retval = IsSorted();
+
+ return NS_OK;
+}
+
+bool nsTreeContentView::CanDrop(int32_t aRow, int32_t aOrientation,
+ ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ }
+ return false;
+}
+
+bool nsTreeContentView::CanDrop(int32_t aRow, int32_t aOrientation,
+ DataTransfer* aDataTransfer,
+ ErrorResult& aError) {
+ return CanDrop(aRow, aOrientation, aError);
+}
+
+NS_IMETHODIMP
+nsTreeContentView::CanDrop(int32_t aIndex, int32_t aOrientation,
+ DataTransfer* aDataTransfer, bool* _retval) {
+ ErrorResult rv;
+ *_retval = CanDrop(aIndex, aOrientation, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation,
+ ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ }
+}
+
+void nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation,
+ DataTransfer* aDataTransfer, ErrorResult& aError) {
+ Drop(aRow, aOrientation, aError);
+}
+
+NS_IMETHODIMP
+nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation,
+ DataTransfer* aDataTransfer) {
+ ErrorResult rv;
+ Drop(aRow, aOrientation, rv);
+ return rv.StealNSResult();
+}
+
+int32_t nsTreeContentView::GetParentIndex(int32_t aRow, ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return 0;
+ }
+
+ return mRows[aRow]->mParentIndex;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetParentIndex(int32_t aRowIndex, int32_t* _retval) {
+ ErrorResult rv;
+ *_retval = GetParentIndex(aRowIndex, rv);
+ return rv.StealNSResult();
+}
+
+bool nsTreeContentView::HasNextSibling(int32_t aRow, int32_t aAfterIndex,
+ ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ // We have a next sibling if the row is not the last in the subtree.
+ int32_t parentIndex = mRows[aRow]->mParentIndex;
+ if (parentIndex < 0) {
+ return uint32_t(aRow) < mRows.Length() - 1;
+ }
+
+ // 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();
+ }
+
+ return aRow < lastIndex;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex,
+ bool* _retval) {
+ ErrorResult rv;
+ *_retval = HasNextSibling(aRowIndex, aAfterIndex, rv);
+ return rv.StealNSResult();
+}
+
+int32_t nsTreeContentView::GetLevel(int32_t aRow, ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return 0;
+ }
+
+ int32_t level = 0;
+ Row* row = mRows[aRow].get();
+ while (row->mParentIndex >= 0) {
+ level++;
+ row = mRows[row->mParentIndex].get();
+ }
+ return level;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetLevel(int32_t aIndex, int32_t* _retval) {
+ ErrorResult rv;
+ *_retval = GetLevel(aIndex, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::GetImageSrc(int32_t aRow, nsTreeColumn& aColumn,
+ nsAString& aSrc, ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ Element* cell = GetCell(realRow, aColumn);
+ if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::src, aSrc);
+ }
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetImageSrc(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& _retval) {
+ NS_ENSURE_ARG(aCol);
+
+ ErrorResult rv;
+ GetImageSrc(aRow, *aCol, _retval, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::GetCellValue(int32_t aRow, nsTreeColumn& aColumn,
+ nsAString& aValue, ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ Element* cell = GetCell(realRow, aColumn);
+ if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue);
+ }
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetCellValue(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& _retval) {
+ NS_ENSURE_ARG(aCol);
+
+ ErrorResult rv;
+ GetCellValue(aRow, *aCol, _retval, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::GetCellText(int32_t aRow, nsTreeColumn& aColumn,
+ nsAString& aText, ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ 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, aText) &&
+ !aText.IsEmpty()) {
+ return;
+ }
+
+ if (row->mContent->IsXULElement(nsGkAtoms::treeitem)) {
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ Element* cell = GetCell(realRow, aColumn);
+ if (cell) cell->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aText);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsTreeContentView::GetCellText(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& _retval) {
+ NS_ENSURE_ARG(aCol);
+
+ ErrorResult rv;
+ GetCellText(aRow, *aCol, _retval, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::SetTree(XULTreeElement* aTree, ErrorResult& aError) {
+ aError = SetTree(aTree);
+}
+
+NS_IMETHODIMP
+nsTreeContentView::SetTree(XULTreeElement* aTree) {
+ ClearRows();
+
+ mTree = aTree;
+
+ if (aTree) {
+ // Add ourselves to document's observers.
+ Document* document = mTree->GetComposedDoc();
+ if (document) {
+ document->AddObserver(this);
+ mDocument = document;
+ }
+
+ RefPtr<dom::Element> bodyElement = mTree->GetTreeBody();
+ if (bodyElement) {
+ mBody = std::move(bodyElement);
+ int32_t index = 0;
+ Serialize(mBody, -1, &index, mRows);
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsTreeContentView::ToggleOpenState(int32_t aRow, ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ // We don't serialize content right here, since content might be generated
+ // lazily.
+ Row* row = mRows[aRow].get();
+
+ if (row->IsOpen())
+ row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, u"false"_ns,
+ true);
+ else
+ row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, u"true"_ns,
+ true);
+}
+
+NS_IMETHODIMP
+nsTreeContentView::ToggleOpenState(int32_t aIndex) {
+ ErrorResult rv;
+ ToggleOpenState(aIndex, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::CycleHeader(nsTreeColumn& aColumn,
+ ErrorResult& aError) {
+ if (!mTree) return;
+
+ RefPtr<Element> column = aColumn.Element();
+ nsAutoString sort;
+ column->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
+ if (!sort.IsEmpty()) {
+ nsAutoString sortdirection;
+ static Element::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;
+
+ XULWidgetSort(mTree, sort, sortdirection);
+ }
+}
+
+NS_IMETHODIMP
+nsTreeContentView::CycleHeader(nsTreeColumn* aCol) {
+ NS_ENSURE_ARG(aCol);
+
+ ErrorResult rv;
+ CycleHeader(*aCol, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsTreeContentView::SelectionChangedXPCOM() { return NS_OK; }
+
+NS_IMETHODIMP
+nsTreeContentView::CycleCell(int32_t aRow, nsTreeColumn* aCol) { return NS_OK; }
+
+bool nsTreeContentView::IsEditable(int32_t aRow, nsTreeColumn& aColumn,
+ ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ Element* cell = GetCell(realRow, aColumn);
+ if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
+ nsGkAtoms::_false, eCaseMatters)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsTreeContentView::IsEditable(int32_t aRow, nsTreeColumn* aCol, bool* _retval) {
+ NS_ENSURE_ARG(aCol);
+
+ ErrorResult rv;
+ *_retval = IsEditable(aRow, *aCol, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::SetCellValue(int32_t aRow, nsTreeColumn& aColumn,
+ const nsAString& aValue,
+ ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ Element* cell = GetCell(realRow, aColumn);
+ if (cell) cell->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, true);
+ }
+}
+
+NS_IMETHODIMP
+nsTreeContentView::SetCellValue(int32_t aRow, nsTreeColumn* aCol,
+ const nsAString& aValue) {
+ NS_ENSURE_ARG(aCol);
+
+ ErrorResult rv;
+ SetCellValue(aRow, *aCol, aValue, rv);
+ return rv.StealNSResult();
+}
+
+void nsTreeContentView::SetCellText(int32_t aRow, nsTreeColumn& aColumn,
+ const nsAString& aValue,
+ ErrorResult& aError) {
+ if (!IsValidRowIndex(aRow)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ Row* row = mRows[aRow].get();
+
+ nsIContent* realRow =
+ nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow);
+ if (realRow) {
+ Element* cell = GetCell(realRow, aColumn);
+ if (cell) cell->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aValue, true);
+ }
+}
+
+NS_IMETHODIMP
+nsTreeContentView::SetCellText(int32_t aRow, nsTreeColumn* aCol,
+ const nsAString& aValue) {
+ NS_ENSURE_ARG(aCol);
+
+ ErrorResult rv;
+ SetCellText(aRow, *aCol, aValue, rv);
+ return rv.StealNSResult();
+}
+
+Element* nsTreeContentView::GetItemAtIndex(int32_t aIndex,
+ ErrorResult& aError) {
+ if (!IsValidRowIndex(aIndex)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return nullptr;
+ }
+
+ return mRows[aIndex]->mContent;
+}
+
+int32_t nsTreeContentView::GetIndexOfItem(Element* aItem) {
+ return FindContent(aItem);
+}
+
+void nsTreeContentView::AttributeChanged(dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* 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 (aElement == mTree || aElement == mBody) {
+ mTree->ClearStyleAndImageCaches();
+ mTree->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 (mTree) mTree->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 (mTree) {
+ RefPtr<nsTreeColumns> cols = mTree->GetColumns();
+ if (cols) {
+ RefPtr<nsTreeColumn> col = cols->GetColumnFor(aElement);
+ mTree->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 (mTree) mTree->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 (mTree) mTree->InvalidateRow(index);
+ }
+ }
+ } else if (aElement->IsXULElement(nsGkAtoms::treeseparator)) {
+ int32_t index = FindContent(aElement);
+ if (index >= 0) {
+ if (aAttribute == nsGkAtoms::properties && mTree) {
+ mTree->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 && mTree) {
+ mTree->InvalidateRow(index);
+ }
+ }
+ }
+ } else if (aElement->IsXULElement(nsGkAtoms::treecell)) {
+ if (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 && mTree) {
+ // XXX Should we make an effort to invalidate only cell ?
+ mTree->InvalidateRow(index);
+ }
+ }
+ }
+ }
+ }
+}
+
+void nsTreeContentView::ContentAppended(nsIContent* aFirstNewContent) {
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ // Our contentinserted doesn't use the index
+ ContentInserted(cur);
+ }
+}
+
+void nsTreeContentView::ContentInserted(nsIContent* aChild) {
+ NS_ASSERTION(aChild, "null ptr");
+ nsIContent* container = aChild->GetParent();
+
+ // 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() || !container->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 = container; 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(container);
+ if (index >= 0) {
+ Row* row = mRows[index].get();
+ row->SetEmpty(false);
+ if (mTree) mTree->InvalidateRow(index);
+ if (row->IsContainer() && row->IsOpen()) {
+ int32_t count = EnsureSubtree(index);
+ if (mTree) mTree->RowCountChanged(index + 1, count);
+ }
+ }
+ } else if (aChild->IsAnyOfXULElements(nsGkAtoms::treeitem,
+ nsGkAtoms::treeseparator)) {
+ InsertRowFor(container, aChild);
+ } else if (aChild->IsXULElement(nsGkAtoms::treerow)) {
+ int32_t index = FindContent(container);
+ if (index >= 0 && mTree) mTree->InvalidateRow(index);
+ } else if (aChild->IsXULElement(nsGkAtoms::treecell)) {
+ nsCOMPtr<nsIContent> parent = container->GetParent();
+ if (parent) {
+ int32_t index = FindContent(parent);
+ if (index >= 0 && mTree) mTree->InvalidateRow(index);
+ }
+ }
+}
+
+void nsTreeContentView::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ NS_ASSERTION(aChild, "null ptr");
+
+ nsIContent* container = aChild->GetParent();
+ // 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() || !container->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 = container; 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(container);
+ if (index >= 0) {
+ Row* row = mRows[index].get();
+ row->SetEmpty(true);
+ int32_t count = RemoveSubtree(index);
+ // Invalidate also the row to update twisty.
+ if (mTree) {
+ mTree->InvalidateRow(index);
+ mTree->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 (mTree) mTree->RowCountChanged(index, -count);
+ }
+ } else if (aChild->IsXULElement(nsGkAtoms::treerow)) {
+ int32_t index = FindContent(container);
+ if (index >= 0 && mTree) mTree->InvalidateRow(index);
+ } else if (aChild->IsXULElement(nsGkAtoms::treecell)) {
+ nsCOMPtr<nsIContent> parent = container->GetParent();
+ if (parent) {
+ int32_t index = FindContent(parent);
+ if (index >= 0 && mTree) mTree->InvalidateRow(index);
+ }
+ }
+}
+
+void nsTreeContentView::NodeWillBeDestroyed(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->AsElement(), aParentIndex, aIndex, aRows);
+ } else if (content->IsXULElement(nsGkAtoms::treeseparator)) {
+ SerializeSeparator(content->AsElement(), aParentIndex, aIndex, aRows);
+ }
+
+ *aIndex += aRows.Length() - count;
+ }
+}
+
+void nsTreeContentView::SerializeItem(Element* 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(Element* 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(std::move(row));
+}
+
+void nsTreeContentView::GetIndexInSubtree(nsIContent* aContainer,
+ nsIContent* aContent,
+ int32_t* aIndex) {
+ if (!aContainer->IsXULElement()) return;
+
+ for (nsIContent* content = aContainer->GetFirstChild(); content;
+ content = content->GetNextSibling()) {
+ if (content == aContent) break;
+
+ if (content->IsXULElement(nsGkAtoms::treeitem)) {
+ if (!content->AsElement()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters)) {
+ (*aIndex)++;
+ if (content->AsElement()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::container,
+ nsGkAtoms::_true, eCaseMatters) &&
+ content->AsElement()->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->AsElement()->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] = std::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 (mTree) mTree->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->AsElement(), aParentIndex, &aIndex, rows);
+ } else if (aContent->IsXULElement(nsGkAtoms::treeseparator)) {
+ SerializeSeparator(aContent->AsElement(), 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, std::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();
+ 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 (mTree) {
+ mTree->InvalidateRow(aIndex);
+ mTree->RowCountChanged(aIndex + 1, count);
+ }
+}
+
+void nsTreeContentView::CloseContainer(int32_t aIndex) {
+ Row* row = mRows[aIndex].get();
+ row->SetOpen(false);
+
+ int32_t count = RemoveSubtree(aIndex);
+ if (mTree) {
+ mTree->InvalidateRow(aIndex);
+ mTree->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;
+ }
+ }
+}
+
+Element* nsTreeContentView::GetCell(nsIContent* aContainer,
+ nsTreeColumn& aCol) {
+ int32_t colIndex(aCol.GetIndex());
+
+ // Traverse through cells, try to find the cell by index in a row.
+ Element* 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 (j == colIndex) {
+ result = cell->AsElement();
+ break;
+ }
+ j++;
+ }
+ }
+
+ return result;
+}
+
+bool nsTreeContentView::IsValidRowIndex(int32_t aRowIndex) {
+ return aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length());
+}