forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1269 lines
		
	
	
	
		
			39 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1269 lines
		
	
	
	
		
			39 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- 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(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->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());
 | 
						|
}
 |