summaryrefslogtreecommitdiffstats
path: root/layout/xul/tree
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /layout/xul/tree
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/xul/tree')
-rw-r--r--layout/xul/tree/TreeBoxObject.cpp695
-rw-r--r--layout/xul/tree/TreeBoxObject.h128
-rw-r--r--layout/xul/tree/crashtests/307298-1.xul21
-rw-r--r--layout/xul/tree/crashtests/309732-1.xul30
-rw-r--r--layout/xul/tree/crashtests/309732-2.xul31
-rw-r--r--layout/xul/tree/crashtests/366583-1.xul43
-rw-r--r--layout/xul/tree/crashtests/380217-1.xul24
-rw-r--r--layout/xul/tree/crashtests/382444-1-inner.html15
-rw-r--r--layout/xul/tree/crashtests/382444-1.html9
-rw-r--r--layout/xul/tree/crashtests/391178-1.xhtml41
-rw-r--r--layout/xul/tree/crashtests/391178-2.xul20
-rw-r--r--layout/xul/tree/crashtests/393665-1.xul3
-rw-r--r--layout/xul/tree/crashtests/399227-1.xul44
-rw-r--r--layout/xul/tree/crashtests/399227-2.xul50
-rw-r--r--layout/xul/tree/crashtests/399692-1.xhtml10
-rw-r--r--layout/xul/tree/crashtests/399715-1.xhtml9
-rw-r--r--layout/xul/tree/crashtests/409807-1.xul25
-rw-r--r--layout/xul/tree/crashtests/414170-1.xul20
-rw-r--r--layout/xul/tree/crashtests/430394-1.xul8
-rw-r--r--layout/xul/tree/crashtests/454186-1.xul23
-rw-r--r--layout/xul/tree/crashtests/479931-1.xhtml19
-rw-r--r--layout/xul/tree/crashtests/509602-1-overlay.xul12
-rw-r--r--layout/xul/tree/crashtests/509602-1.xul3
-rw-r--r--layout/xul/tree/crashtests/585815-iframe.xul72
-rw-r--r--layout/xul/tree/crashtests/585815.html18
-rw-r--r--layout/xul/tree/crashtests/601427.html30
-rw-r--r--layout/xul/tree/crashtests/730441-1.xul54
-rw-r--r--layout/xul/tree/crashtests/730441-2.xul52
-rw-r--r--layout/xul/tree/crashtests/730441-3.xul38
-rw-r--r--layout/xul/tree/crashtests/crashtests.list24
-rw-r--r--layout/xul/tree/moz.build53
-rw-r--r--layout/xul/tree/nsITreeBoxObject.idl189
-rw-r--r--layout/xul/tree/nsITreeColumns.idl96
-rw-r--r--layout/xul/tree/nsITreeContentView.idl22
-rw-r--r--layout/xul/tree/nsITreeSelection.idl130
-rw-r--r--layout/xul/tree/nsITreeView.idl215
-rw-r--r--layout/xul/tree/nsTreeBodyFrame.cpp4945
-rw-r--r--layout/xul/tree/nsTreeBodyFrame.h651
-rw-r--r--layout/xul/tree/nsTreeColFrame.cpp201
-rw-r--r--layout/xul/tree/nsTreeColFrame.h55
-rw-r--r--layout/xul/tree/nsTreeColumns.cpp765
-rw-r--r--layout/xul/tree/nsTreeColumns.h215
-rw-r--r--layout/xul/tree/nsTreeContentView.cpp1372
-rw-r--r--layout/xul/tree/nsTreeContentView.h101
-rw-r--r--layout/xul/tree/nsTreeImageListener.cpp115
-rw-r--r--layout/xul/tree/nsTreeImageListener.h66
-rw-r--r--layout/xul/tree/nsTreeSelection.cpp866
-rw-r--r--layout/xul/tree/nsTreeSelection.h56
-rw-r--r--layout/xul/tree/nsTreeStyleCache.cpp99
-rw-r--r--layout/xul/tree/nsTreeStyleCache.h89
-rw-r--r--layout/xul/tree/nsTreeUtils.cpp140
-rw-r--r--layout/xul/tree/nsTreeUtils.h39
52 files changed, 12051 insertions, 0 deletions
diff --git a/layout/xul/tree/TreeBoxObject.cpp b/layout/xul/tree/TreeBoxObject.cpp
new file mode 100644
index 000000000..2265d9ee5
--- /dev/null
+++ b/layout/xul/tree/TreeBoxObject.cpp
@@ -0,0 +1,695 @@
+/* -*- 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 "mozilla/dom/TreeBoxObject.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMXULElement.h"
+#include "nsIScriptableRegion.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsTreeContentView.h"
+#include "nsITreeSelection.h"
+#include "ChildIterator.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsTreeBodyFrame.h"
+#include "mozilla/dom/TreeBoxObjectBinding.h"
+#include "nsITreeColumns.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ToJSValue.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(TreeBoxObject, BoxObject,
+ mView)
+
+NS_IMPL_ADDREF_INHERITED(TreeBoxObject, BoxObject)
+NS_IMPL_RELEASE_INHERITED(TreeBoxObject, BoxObject)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TreeBoxObject)
+ NS_INTERFACE_MAP_ENTRY(nsITreeBoxObject)
+NS_INTERFACE_MAP_END_INHERITING(BoxObject)
+
+void
+TreeBoxObject::Clear()
+{
+ ClearCachedValues();
+
+ // Drop the view's ref to us.
+ if (mView) {
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel)
+ sel->SetTree(nullptr);
+ mView->SetTree(nullptr); // Break the circular ref between the view and us.
+ }
+ mView = nullptr;
+
+ BoxObject::Clear();
+}
+
+
+TreeBoxObject::TreeBoxObject()
+ : mTreeBody(nullptr)
+{
+}
+
+TreeBoxObject::~TreeBoxObject()
+{
+}
+
+static nsIContent* FindBodyElement(nsIContent* aParent)
+{
+ mozilla::dom::FlattenedChildIterator iter(aParent);
+ for (nsIContent* content = iter.GetNextChild(); content; content = iter.GetNextChild()) {
+ mozilla::dom::NodeInfo *ni = content->NodeInfo();
+ if (ni->Equals(nsGkAtoms::treechildren, kNameSpaceID_XUL)) {
+ return content;
+ } else if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) {
+ // There are nesting tree elements. Only the innermost should
+ // find the treechilren.
+ return nullptr;
+ } else if (content->IsElement() &&
+ !ni->Equals(nsGkAtoms::_template, kNameSpaceID_XUL)) {
+ nsIContent* result = FindBodyElement(content);
+ if (result)
+ return result;
+ }
+ }
+
+ return nullptr;
+}
+
+nsTreeBodyFrame*
+TreeBoxObject::GetTreeBodyFrame(bool aFlushLayout)
+{
+ // Make sure our frames are up to date, and layout as needed. We
+ // have to do this before checking for our cached mTreeBody, since
+ // it might go away on style flush, and in any case if aFlushLayout
+ // is true we need to make sure to flush no matter what.
+ // XXXbz except that flushing style when we were not asked to flush
+ // layout here breaks things. See bug 585123.
+ nsIFrame* frame = nullptr;
+ if (aFlushLayout) {
+ frame = GetFrame(aFlushLayout);
+ if (!frame)
+ return nullptr;
+ }
+
+ if (mTreeBody) {
+ // Have one cached already.
+ return mTreeBody;
+ }
+
+ if (!aFlushLayout) {
+ frame = GetFrame(aFlushLayout);
+ if (!frame)
+ return nullptr;
+ }
+
+ // Iterate over our content model children looking for the body.
+ nsCOMPtr<nsIContent> content = FindBodyElement(frame->GetContent());
+ if (!content)
+ return nullptr;
+
+ frame = content->GetPrimaryFrame();
+ if (!frame)
+ return nullptr;
+
+ // Make sure that the treebodyframe has a pointer to |this|.
+ nsTreeBodyFrame *treeBody = do_QueryFrame(frame);
+ NS_ENSURE_TRUE(treeBody && treeBody->GetTreeBoxObject() == this, nullptr);
+
+ mTreeBody = treeBody;
+ return mTreeBody;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::GetView(nsITreeView * *aView)
+{
+ if (!mTreeBody) {
+ if (!GetTreeBodyFrame()) {
+ // Don't return an uninitialised view
+ *aView = nullptr;
+ return NS_OK;
+ }
+
+ if (mView)
+ // Our new frame needs to initialise itself
+ return mTreeBody->GetView(aView);
+ }
+ if (!mView) {
+ nsCOMPtr<nsIDOMXULElement> xulele = do_QueryInterface(mContent);
+ if (xulele) {
+ // See if there is a XUL tree builder associated with the element
+ nsCOMPtr<nsIXULTemplateBuilder> builder;
+ xulele->GetBuilder(getter_AddRefs(builder));
+ mView = do_QueryInterface(builder);
+
+ if (!mView) {
+ // No tree builder, create a tree content view.
+ nsresult rv = NS_NewTreeContentView(getter_AddRefs(mView));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Initialise the frame and view
+ mTreeBody->SetView(mView);
+ }
+ }
+ NS_IF_ADDREF(*aView = mView);
+ return NS_OK;
+}
+
+already_AddRefed<nsITreeView>
+TreeBoxObject::GetView() {
+ nsCOMPtr<nsITreeView> view;
+ GetView(getter_AddRefs(view));
+ return view.forget();
+}
+
+static bool
+CanTrustView(nsISupports* aValue)
+{
+ // Untrusted content is only allowed to specify known-good views
+ if (nsContentUtils::IsCallerChrome())
+ return true;
+ nsCOMPtr<nsINativeTreeView> nativeTreeView = do_QueryInterface(aValue);
+ if (!nativeTreeView || NS_FAILED(nativeTreeView->EnsureNative())) {
+ // XXX ERRMSG need a good error here for developers
+ return false;
+ }
+ return true;
+}
+
+NS_IMETHODIMP TreeBoxObject::SetView(nsITreeView * aView)
+{
+ if (!CanTrustView(aView))
+ return NS_ERROR_DOM_SECURITY_ERR;
+
+ mView = aView;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ body->SetView(aView);
+
+ return NS_OK;
+}
+
+void TreeBoxObject::SetView(nsITreeView* aView, ErrorResult& aRv)
+{
+ aRv = SetView(aView);
+}
+
+bool TreeBoxObject::Focused()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->GetFocused();
+ return false;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetFocused(bool* aFocused)
+{
+ *aFocused = Focused();
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeBoxObject::SetFocused(bool aFocused)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->SetFocused(aFocused);
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetTreeBody(nsIDOMElement** aElement)
+{
+ *aElement = nullptr;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->GetTreeBody(aElement);
+ return NS_OK;
+}
+
+already_AddRefed<Element>
+TreeBoxObject::GetTreeBody()
+{
+ nsCOMPtr<nsIDOMElement> el;
+ GetTreeBody(getter_AddRefs(el));
+ nsCOMPtr<Element> ret(do_QueryInterface(el));
+ return ret.forget();
+}
+
+already_AddRefed<nsTreeColumns>
+TreeBoxObject::GetColumns()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->Columns();
+ return nullptr;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetColumns(nsITreeColumns** aColumns)
+{
+ *aColumns = GetColumns().take();
+ return NS_OK;
+}
+
+int32_t TreeBoxObject::RowHeight()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->RowHeight();
+ return 0;
+}
+
+int32_t TreeBoxObject::RowWidth()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->RowWidth();
+ return 0;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetRowHeight(int32_t* aRowHeight)
+{
+ *aRowHeight = RowHeight();
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetRowWidth(int32_t *aRowWidth)
+{
+ *aRowWidth = RowWidth();
+ return NS_OK;
+}
+
+int32_t TreeBoxObject::GetFirstVisibleRow()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->FirstVisibleRow();
+ return 0;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetFirstVisibleRow(int32_t *aFirstVisibleRow)
+{
+ *aFirstVisibleRow = GetFirstVisibleRow();
+ return NS_OK;
+}
+
+int32_t TreeBoxObject::GetLastVisibleRow()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->LastVisibleRow();
+ return 0;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetLastVisibleRow(int32_t *aLastVisibleRow)
+{
+ *aLastVisibleRow = GetLastVisibleRow();
+ return NS_OK;
+}
+
+int32_t TreeBoxObject::HorizontalPosition()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->GetHorizontalPosition();
+ return 0;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetHorizontalPosition(int32_t *aHorizontalPosition)
+{
+ *aHorizontalPosition = HorizontalPosition();
+ return NS_OK;
+}
+
+int32_t TreeBoxObject::GetPageLength()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->PageLength();
+ return 0;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetPageLength(int32_t *aPageLength)
+{
+ *aPageLength = GetPageLength();
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeBoxObject::GetSelectionRegion(nsIScriptableRegion **aRegion)
+{
+ *aRegion = nullptr;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->GetSelectionRegion(aRegion);
+ return NS_OK;
+}
+
+already_AddRefed<nsIScriptableRegion>
+TreeBoxObject::SelectionRegion()
+{
+ nsCOMPtr<nsIScriptableRegion> region;
+ GetSelectionRegion(getter_AddRefs(region));
+ return region.forget();
+}
+
+NS_IMETHODIMP
+TreeBoxObject::EnsureRowIsVisible(int32_t aRow)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->EnsureRowIsVisible(aRow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::EnsureCellIsVisible(int32_t aRow, nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->EnsureCellIsVisible(aRow, aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollToRow(int32_t aRow)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame(true);
+ if (body)
+ return body->ScrollToRow(aRow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollByLines(int32_t aNumLines)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ScrollByLines(aNumLines);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollByPages(int32_t aNumPages)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ScrollByPages(aNumPages);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollToCell(int32_t aRow, nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ScrollToCell(aRow, aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollToColumn(nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ScrollToColumn(aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ScrollToHorizontalPosition(int32_t aHorizontalPosition)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ScrollToHorizontalPosition(aHorizontalPosition);
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeBoxObject::Invalidate()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->Invalidate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::InvalidateColumn(nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->InvalidateColumn(aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::InvalidateRow(int32_t aIndex)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->InvalidateRow(aIndex);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::InvalidateCell(int32_t aRow, nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->InvalidateCell(aRow, aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::InvalidateRange(int32_t aStart, int32_t aEnd)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->InvalidateRange(aStart, aEnd);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::InvalidateColumnRange(int32_t aStart, int32_t aEnd, nsITreeColumn* aCol)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->InvalidateColumnRange(aStart, aEnd, aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::GetRowAt(int32_t x, int32_t y, int32_t *aRow)
+{
+ *aRow = 0;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->GetRowAt(x, y, aRow);
+ return NS_OK;
+}
+
+int32_t
+TreeBoxObject::GetRowAt(int32_t x, int32_t y)
+{
+ int32_t row;
+ GetRowAt(x, y, &row);
+ return row;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::GetCellAt(int32_t aX, int32_t aY, int32_t *aRow,
+ nsITreeColumn** aCol, nsAString& aChildElt)
+{
+ *aRow = 0;
+ *aCol = nullptr;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ nsAutoCString element;
+ nsresult retval = body->GetCellAt(aX, aY, aRow, aCol, element);
+ CopyUTF8toUTF16(element, aChildElt);
+ return retval;
+ }
+ return NS_OK;
+}
+
+void
+TreeBoxObject::GetCellAt(int32_t x, int32_t y, TreeCellInfo& aRetVal, ErrorResult& aRv)
+{
+ nsCOMPtr<nsITreeColumn> col;
+ GetCellAt(x, y, &aRetVal.mRow, getter_AddRefs(col), aRetVal.mChildElt);
+ aRetVal.mCol = col.forget().downcast<nsTreeColumn>();
+}
+
+void
+TreeBoxObject::GetCellAt(JSContext* cx,
+ int32_t x, int32_t y,
+ JS::Handle<JSObject*> rowOut,
+ JS::Handle<JSObject*> colOut,
+ JS::Handle<JSObject*> childEltOut,
+ ErrorResult& aRv)
+{
+ int32_t row;
+ nsITreeColumn* col;
+ nsAutoString childElt;
+ GetCellAt(x, y, &row, &col, childElt);
+
+ JS::Rooted<JS::Value> v(cx);
+
+ if (!ToJSValue(cx, row, &v) ||
+ !JS_SetProperty(cx, rowOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ if (!dom::WrapObject(cx, col, &v) ||
+ !JS_SetProperty(cx, colOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ if (!ToJSValue(cx, childElt, &v) ||
+ !JS_SetProperty(cx, childEltOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+}
+
+NS_IMETHODIMP
+TreeBoxObject::GetCoordsForCellItem(int32_t aRow, nsITreeColumn* aCol, const nsAString& aElement,
+ int32_t *aX, int32_t *aY, int32_t *aWidth, int32_t *aHeight)
+{
+ *aX = *aY = *aWidth = *aHeight = 0;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ NS_ConvertUTF16toUTF8 element(aElement);
+ if (body)
+ return body->GetCoordsForCellItem(aRow, aCol, element, aX, aY, aWidth, aHeight);
+ return NS_OK;
+}
+
+already_AddRefed<DOMRect>
+TreeBoxObject::GetCoordsForCellItem(int32_t row, nsTreeColumn& col, const nsAString& element, ErrorResult& aRv)
+{
+ int32_t x, y, w, h;
+ GetCoordsForCellItem(row, &col, element, &x, &y, &w, &h);
+ RefPtr<DOMRect> rect = new DOMRect(mContent, x, y, w, h);
+ return rect.forget();
+}
+
+void
+TreeBoxObject::GetCoordsForCellItem(JSContext* cx,
+ int32_t row,
+ nsTreeColumn& col,
+ const nsAString& element,
+ JS::Handle<JSObject*> xOut,
+ JS::Handle<JSObject*> yOut,
+ JS::Handle<JSObject*> widthOut,
+ JS::Handle<JSObject*> heightOut,
+ ErrorResult& aRv)
+{
+ int32_t x, y, w, h;
+ GetCoordsForCellItem(row, &col, element, &x, &y, &w, &h);
+ JS::Rooted<JS::Value> v(cx, JS::Int32Value(x));
+ if (!JS_SetProperty(cx, xOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ v.setInt32(y);
+ if (!JS_SetProperty(cx, yOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ v.setInt32(w);
+ if (!JS_SetProperty(cx, widthOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+ v.setInt32(h);
+ if (!JS_SetProperty(cx, heightOut, "value", v)) {
+ aRv.Throw(NS_ERROR_XPC_CANT_SET_OUT_VAL);
+ return;
+ }
+}
+
+NS_IMETHODIMP
+TreeBoxObject::IsCellCropped(int32_t aRow, nsITreeColumn* aCol, bool *aIsCropped)
+{
+ *aIsCropped = false;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->IsCellCropped(aRow, aCol, aIsCropped);
+ return NS_OK;
+}
+
+bool
+TreeBoxObject::IsCellCropped(int32_t row, nsITreeColumn* col, ErrorResult& aRv)
+{
+ bool ret;
+ aRv = IsCellCropped(row, col, &ret);
+ return ret;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::RowCountChanged(int32_t aIndex, int32_t aDelta)
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->RowCountChanged(aIndex, aDelta);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::BeginUpdateBatch()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->BeginUpdateBatch();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::EndUpdateBatch()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->EndUpdateBatch();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TreeBoxObject::ClearStyleAndImageCaches()
+{
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body)
+ return body->ClearStyleAndImageCaches();
+ return NS_OK;
+}
+
+void
+TreeBoxObject::ClearCachedValues()
+{
+ mTreeBody = nullptr;
+}
+
+JSObject*
+TreeBoxObject::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return TreeBoxObjectBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+// Creation Routine ///////////////////////////////////////////////////////////////////////
+
+using namespace mozilla::dom;
+
+nsresult
+NS_NewTreeBoxObject(nsIBoxObject** aResult)
+{
+ NS_ADDREF(*aResult = new TreeBoxObject());
+ return NS_OK;
+}
diff --git a/layout/xul/tree/TreeBoxObject.h b/layout/xul/tree/TreeBoxObject.h
new file mode 100644
index 000000000..d997b5a63
--- /dev/null
+++ b/layout/xul/tree/TreeBoxObject.h
@@ -0,0 +1,128 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_TreeBoxObject_h
+#define mozilla_dom_TreeBoxObject_h
+
+#include "mozilla/dom/BoxObject.h"
+#include "nsITreeView.h"
+#include "nsITreeBoxObject.h"
+
+class nsTreeBodyFrame;
+class nsTreeColumn;
+class nsTreeColumns;
+
+namespace mozilla {
+namespace dom {
+
+struct TreeCellInfo;
+class DOMRect;
+
+class TreeBoxObject final : public BoxObject,
+ public nsITreeBoxObject
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TreeBoxObject, BoxObject)
+ NS_DECL_NSITREEBOXOBJECT
+
+ TreeBoxObject();
+
+ nsTreeBodyFrame* GetTreeBodyFrame(bool aFlushLayout = false);
+ nsTreeBodyFrame* GetCachedTreeBodyFrame() { return mTreeBody; }
+
+ //NS_PIBOXOBJECT interfaces
+ virtual void Clear() override;
+ virtual void ClearCachedValues() override;
+
+ // WebIDL
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<nsTreeColumns> GetColumns();
+
+ already_AddRefed<nsITreeView> GetView();
+
+ void SetView(nsITreeView* arg, ErrorResult& aRv);
+
+ bool Focused();
+
+ already_AddRefed<Element> GetTreeBody();
+
+ int32_t RowHeight();
+
+ int32_t RowWidth();
+
+ int32_t HorizontalPosition();
+
+ already_AddRefed<nsIScriptableRegion> SelectionRegion();
+
+ int32_t GetFirstVisibleRow();
+
+ int32_t GetLastVisibleRow();
+
+ int32_t GetPageLength();
+
+ int32_t GetRowAt(int32_t x, int32_t y);
+
+ void GetCellAt(int32_t x, int32_t y, TreeCellInfo& aRetVal, ErrorResult& aRv);
+
+ already_AddRefed<DOMRect> GetCoordsForCellItem(int32_t row,
+ nsTreeColumn& col,
+ const nsAString& element,
+ ErrorResult& aRv);
+
+ bool IsCellCropped(int32_t row, nsITreeColumn* col, ErrorResult& aRv);
+
+ // Deprecated APIs from old IDL
+ void GetCellAt(JSContext* cx,
+ int32_t x, int32_t y,
+ JS::Handle<JSObject*> rowOut,
+ JS::Handle<JSObject*> colOut,
+ JS::Handle<JSObject*> childEltOut,
+ ErrorResult& aRv);
+
+ void GetCoordsForCellItem(JSContext* cx,
+ int32_t row,
+ nsTreeColumn& col,
+ const nsAString& element,
+ JS::Handle<JSObject*> xOut,
+ JS::Handle<JSObject*> yOut,
+ JS::Handle<JSObject*> widthOut,
+ JS::Handle<JSObject*> heightOut,
+ ErrorResult& aRv);
+
+ // Same signature (except for nsresult return type) as the XPIDL impls
+ // void Invalidate();
+ // void BeginUpdateBatch();
+ // void EndUpdateBatch();
+ // void ClearStyleAndImageCaches();
+ // void SetFocused(bool arg);
+ // void EnsureRowIsVisible(int32_t index);
+ // void EnsureCellIsVisible(int32_t row, nsITreeColumn* col);
+ // void ScrollToRow(int32_t index);
+ // void ScrollByLines(int32_t numLines);
+ // void ScrollByPages(int32_t numPages);
+ // void ScrollToCell(int32_t row, nsITreeColumn* col);
+ // void ScrollToColumn(nsITreeColumn* col);
+ // void ScrollToHorizontalPosition(int32_t horizontalPosition);
+ // void InvalidateColumn(nsITreeColumn* col);
+ // void InvalidateRow(int32_t index);
+ // void InvalidateCell(int32_t row, nsITreeColumn* col);
+ // void InvalidateRange(int32_t startIndex, int32_t endIndex);
+ // void InvalidateColumnRange(int32_t startIndex, int32_t endIndex, nsITreeColumn* col);
+ // void RowCountChanged(int32_t index, int32_t count);
+
+protected:
+ nsTreeBodyFrame* mTreeBody;
+ nsCOMPtr<nsITreeView> mView;
+
+private:
+ ~TreeBoxObject();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/layout/xul/tree/crashtests/307298-1.xul b/layout/xul/tree/crashtests/307298-1.xul
new file mode 100644
index 000000000..57396755a
--- /dev/null
+++ b/layout/xul/tree/crashtests/307298-1.xul
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="var tree = document.getElementById('tree'), treeitem = document.getElementById('treeitem'); tree.parentNode.insertBefore(treeitem, tree);">
+
+<tree flex="1" id="tree">
+ <treecols>
+ <treecol id="name" label="Name" primary="true" flex="1"/>
+ </treecols>
+
+ <treechildren>
+ <treeitem id="treeitem">
+ <treerow>
+ <treecell label="Click the button below to crash"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+</window>
diff --git a/layout/xul/tree/crashtests/309732-1.xul b/layout/xul/tree/crashtests/309732-1.xul
new file mode 100644
index 000000000..df955e14e
--- /dev/null
+++ b/layout/xul/tree/crashtests/309732-1.xul
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30)">
+
+
+ <script>
+ function boom()
+ {
+ document.documentElement.appendChild(document.getElementById("TC"));
+ document.documentElement.appendChild(document.getElementById("TI"));
+
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+
+<tree flex="1">
+ <treecols>
+ <treecol label="Name"/>
+ </treecols>
+ <treechildren id="TC">
+ <treeitem id="TI">
+ <treerow>
+ <treecell label="First treecell"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </tree>
+</window>
diff --git a/layout/xul/tree/crashtests/309732-2.xul b/layout/xul/tree/crashtests/309732-2.xul
new file mode 100644
index 000000000..9c65e7379
--- /dev/null
+++ b/layout/xul/tree/crashtests/309732-2.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30)">
+
+ <script>
+ function boom()
+ {
+ document.documentElement.appendChild(document.getElementById('TC'));
+ document.getElementById('TI').hidden = false;
+
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+
+
+ <tree flex="1">
+ <treecols>
+ <treecol label="Name" flex="1"/>
+ </treecols>
+ <treechildren id="TC">
+ <treeitem>
+ <treerow>
+ <treecell label="First treecell"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </tree>
+ <treeitem id="TI" hidden="true"/>
+</window>
diff --git a/layout/xul/tree/crashtests/366583-1.xul b/layout/xul/tree/crashtests/366583-1.xul
new file mode 100644
index 000000000..db37b444e
--- /dev/null
+++ b/layout/xul/tree/crashtests/366583-1.xul
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="boom1();"
+ class="reftest-wait">
+
+<script>
+
+var tree;
+
+function boom1()
+{
+ tree = document.getElementById("tree");
+ tree.style.position = "fixed";
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ tree.style.overflow = "visible";
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+<tree rows="6" id="tree" style="display: list-item; overflow: auto; visibility: collapse;">
+ <treecols>
+ <treecol id="firstname" label="First Name" primary="true" flex="3"/>
+ <treecol id="lastname" label="Last Name" flex="7"/>
+ </treecols>
+
+ <treechildren>
+ <treeitem container="true" open="true">
+ <treerow>
+ <treecell label="Foo"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+
+</window>
diff --git a/layout/xul/tree/crashtests/380217-1.xul b/layout/xul/tree/crashtests/380217-1.xul
new file mode 100644
index 000000000..b834f7e1f
--- /dev/null
+++ b/layout/xul/tree/crashtests/380217-1.xul
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.documentElement.style.content = '\'a\'';">
+
+<html:style>
+* { position: fixed; }
+</html:style>
+
+<tree rows="6">
+ <treecols>
+ <treecol id="firstname" label="First Name" primary="true"/>
+ </treecols>
+ <treechildren>
+ <treeitem>
+ <treerow>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+</window>
diff --git a/layout/xul/tree/crashtests/382444-1-inner.html b/layout/xul/tree/crashtests/382444-1-inner.html
new file mode 100644
index 000000000..d59a2e787
--- /dev/null
+++ b/layout/xul/tree/crashtests/382444-1-inner.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>Testcase bug - Crash [@ nsINodeInfo::Equals] with underflow event, tree stuff and removing window</title>
+</head>
+<body>
+<iframe src="data:application/vnd.mozilla.xul+xml;charset=utf-8,%3Cwindow%20xmlns%3D%22http%3A//www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%3E%0A%3Ctree%20style%3D%22overflow%3A%20auto%3B%20display%3A%20-moz-inline-box%3B%22%3E%0A%3Ctreeitem%20style%3D%22overflow%3A%20scroll%3B%20display%3A%20table-cell%3B%22%3E%0A%3Ctreechildren%20style%3D%22%20display%3A%20table-row%3B%22%3E%0A%3Ctreeitem%20id%3D%22a%22%20style%3D%22display%3A%20table-cell%3B%22%3E%0A%3C/treeitem%3E%0A%3C/treechildren%3E%0A%3C/treeitem%3E%0A%0A%3C/tree%3E%0A%0A%3Cscript%20xmlns%3D%22http%3A//www.w3.org/1999/xhtml%22%3E%0Afunction%20doe%28%29%20%7B%0Adocument.getElementById%28%27a%27%29.parentNode.removeChild%28document.getElementById%28%27a%27%29%29%3B%0A%7D%0AsetTimeout%28doe%2C%20100%29%3B%0Adocument.addEventListener%28%27underflow%27%2C%20function%28e%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%20%7D%2C%20true%29%3B%0Awindow.addEventListener%28%27underflow%27%2C%20function%28e%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%20%7D%2C%20true%29%3B%0A%3C/script%3E%0A%3C/window%3E" id="content"></iframe>
+
+<script>
+function doe() {
+window.location.reload();
+}
+setTimeout(doe, 500);
+</script>
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/382444-1.html b/layout/xul/tree/crashtests/382444-1.html
new file mode 100644
index 000000000..8926cf16d
--- /dev/null
+++ b/layout/xul/tree/crashtests/382444-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="382444-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/391178-1.xhtml b/layout/xul/tree/crashtests/391178-1.xhtml
new file mode 100644
index 000000000..0f4b16cd9
--- /dev/null
+++ b/layout/xul/tree/crashtests/391178-1.xhtml
@@ -0,0 +1,41 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+var ccc;
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var hbox = document.createElementNS(XUL_NS, 'hbox');
+ var tree = document.createElementNS(XUL_NS, 'tree');
+ var treecol = document.createElementNS(XUL_NS, 'treecol');
+
+ ccc = document.getElementById("ccc");
+
+ ccc.style.position = "fixed";
+
+ hbox.appendChild(treecol);
+ tree.appendChild(hbox);
+ ccc.appendChild(tree);
+
+ setTimeout(boom2, 200);
+}
+
+function boom2()
+{
+ ccc.style.position = "";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div id="ccc">
+</div>
+
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/391178-2.xul b/layout/xul/tree/crashtests/391178-2.xul
new file mode 100644
index 000000000..491fbe77b
--- /dev/null
+++ b/layout/xul/tree/crashtests/391178-2.xul
@@ -0,0 +1,20 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<tree id="a" style="position: fixed;">
+ <box style=" display: -moz-box; position: fixed;">
+ <treecol style=" display: -moz-box;"/>
+ </box>
+ <box style="position: fixed;">
+ <treechildren style="display: -moz-box; position: absolute;"/>
+ </box>
+</tree>
+
+<script xmlns="http://www.w3.org/1999/xhtml">
+function removestyles(){
+ document.getElementById('a').removeAttribute('style');
+ document.documentElement.removeAttribute("class");
+}
+setTimeout(removestyles, 100);
+</script>
+</window>
diff --git a/layout/xul/tree/crashtests/393665-1.xul b/layout/xul/tree/crashtests/393665-1.xul
new file mode 100644
index 000000000..6fb5ec0c9
--- /dev/null
+++ b/layout/xul/tree/crashtests/393665-1.xul
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <treechildren style="display: block" />
+</window>
diff --git a/layout/xul/tree/crashtests/399227-1.xul b/layout/xul/tree/crashtests/399227-1.xul
new file mode 100644
index 000000000..bfc381892
--- /dev/null
+++ b/layout/xul/tree/crashtests/399227-1.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30)">
+
+
+ <script>
+ function boom()
+ {
+ var tree = document.getElementById("thetree");
+ var selection = tree.view.selection;
+
+ selection.select(0);
+ tree.parentNode.removeChild(tree);
+
+ // This is expected to throw an error (it used to crash).
+ try {
+ selection.rangedSelect(1, 1, false);
+ }
+ catch (ex) {}
+
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+
+<tree flex="1" id="thetree">
+ <treecols>
+ <treecol label="Name"/>
+ </treecols>
+ <treechildren id="TC">
+ <treeitem id="TI1">
+ <treerow>
+ <treecell label="First treecell"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="TI2">
+ <treerow>
+ <treecell label="Second treecell"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </tree>
+</window>
diff --git a/layout/xul/tree/crashtests/399227-2.xul b/layout/xul/tree/crashtests/399227-2.xul
new file mode 100644
index 000000000..55665ec47
--- /dev/null
+++ b/layout/xul/tree/crashtests/399227-2.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait" onload="setTimeout(boom, 30);">
+
+
+ <script>
+ function boom()
+ {
+ var tree = document.getElementById("thetree");
+ var selection = tree.view.selection;
+ var treecolumn0 = tree.columns[0];
+ var treecolumn1 = tree.columns[1];
+
+ selection.select(0);
+ selection.currentColumn = treecolumn0;
+ tree.parentNode.removeChild(tree);
+
+ // This is expected to throw an error (it used to crash).
+ try {
+ selection.currentColumn = treecolumn1;
+ }
+ catch (ex) {}
+
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+
+<tree flex="1" id="thetree" seltype="cell">
+ <treecols>
+ <treecol label="Name"/>
+ <treecol label="Test"/>
+ </treecols>
+ <treechildren id="TC">
+ <treeitem id="TI1">
+ <treerow>
+ <treecell label="First treecell"/>
+ <treecell label="Second treecell"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="TI2">
+ <treerow>
+ <treecell label="Third treecell"/>
+ <treecell label="Fourth treecell"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </tree>
+</window>
diff --git a/layout/xul/tree/crashtests/399692-1.xhtml b/layout/xul/tree/crashtests/399692-1.xhtml
new file mode 100644
index 000000000..97eec2674
--- /dev/null
+++ b/layout/xul/tree/crashtests/399692-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+</head>
+<body>
+
+<xul:treechildren style="display: inline;" />
+
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/399715-1.xhtml b/layout/xul/tree/crashtests/399715-1.xhtml
new file mode 100644
index 000000000..ea0a20cfa
--- /dev/null
+++ b/layout/xul/tree/crashtests/399715-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<body style="float: right;" onload="document.body.style.cssFloat = '';">
+
+<xul:tree><xul:hbox><xul:treecol /></xul:hbox></xul:tree>
+
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/409807-1.xul b/layout/xul/tree/crashtests/409807-1.xul
new file mode 100644
index 000000000..a3af3da41
--- /dev/null
+++ b/layout/xul/tree/crashtests/409807-1.xul
@@ -0,0 +1,25 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var tree = document.getElementById("tree");
+ var tc = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "treechildren");
+
+ document.addEventListener("DOMAttrModified", m, false);
+
+ tree.appendChild(tc);
+
+ function m()
+ {
+ document.removeEventListener("DOMAttrModified", m, false);
+ tree.removeChild(tc);
+ }
+}
+
+</script>
+
+<tree id="tree" />
+
+</window>
diff --git a/layout/xul/tree/crashtests/414170-1.xul b/layout/xul/tree/crashtests/414170-1.xul
new file mode 100644
index 000000000..f3bc1d134
--- /dev/null
+++ b/layout/xul/tree/crashtests/414170-1.xul
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var option = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
+ document.getElementById("tc").appendChild(option);
+}
+
+</script>
+
+<tree><treechildren id="tc"><hbox/></treechildren></tree>
+
+</window>
diff --git a/layout/xul/tree/crashtests/430394-1.xul b/layout/xul/tree/crashtests/430394-1.xul
new file mode 100644
index 000000000..63f4c3780
--- /dev/null
+++ b/layout/xul/tree/crashtests/430394-1.xul
@@ -0,0 +1,8 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<tree id="a" style="content: 't';" rows="-2">
+ <menuitem id="b" onoverflow="event.currentTarget.parentNode.removeAttribute('style')">
+ <treechildren style="display: block;" onoverflow="event.target.parentNode.removeChild(event.target)" />
+ <treechildren style="display: block;" ordinal="0.5"/>
+ </menuitem>
+</tree>
+</window> \ No newline at end of file
diff --git a/layout/xul/tree/crashtests/454186-1.xul b/layout/xul/tree/crashtests/454186-1.xul
new file mode 100644
index 000000000..edf266e43
--- /dev/null
+++ b/layout/xul/tree/crashtests/454186-1.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<tree flex="1">
+ <treecols>
+ <treecol label="test" flex="1" type="progressmeter" />
+ </treecols>
+ <treechildren>
+ <treeitem>
+ <treerow>
+ <treecell value="50" mode="normal" />
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell mode="undetermined" />
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+</window>
diff --git a/layout/xul/tree/crashtests/479931-1.xhtml b/layout/xul/tree/crashtests/479931-1.xhtml
new file mode 100644
index 000000000..458a19250
--- /dev/null
+++ b/layout/xul/tree/crashtests/479931-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var o = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
+ var q = document.getElementById("q");
+ q.appendChild(o);
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<xul:tree><xul:treechildren id="q"><div/></xul:treechildren></xul:tree>
+
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/509602-1-overlay.xul b/layout/xul/tree/crashtests/509602-1-overlay.xul
new file mode 100644
index 000000000..f5cecd40e
--- /dev/null
+++ b/layout/xul/tree/crashtests/509602-1-overlay.xul
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<box id="b">
+<box onDOMAttrModified="event.target.parentNode.removeChild(event.target)" id="d"/>
+<tree/>
+</box>
+
+<tree>
+<box id="b" observes="d"/>
+<treechildren observes="b"/>
+</tree>
+</overlay> \ No newline at end of file
diff --git a/layout/xul/tree/crashtests/509602-1.xul b/layout/xul/tree/crashtests/509602-1.xul
new file mode 100644
index 000000000..a1cdcf1cc
--- /dev/null
+++ b/layout/xul/tree/crashtests/509602-1.xul
@@ -0,0 +1,3 @@
+<?xul-overlay href="509602-1-overlay.xul"?>
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/> \ No newline at end of file
diff --git a/layout/xul/tree/crashtests/585815-iframe.xul b/layout/xul/tree/crashtests/585815-iframe.xul
new file mode 100644
index 000000000..ce9d29448
--- /dev/null
+++ b/layout/xul/tree/crashtests/585815-iframe.xul
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setInterval(run, 25)">
+
+<tree flex="1" rows="2">
+ <treecols>
+ <treecol id="sender" label="Sender" flex="1"/>
+ <treecol id="subject" label="Subject" flex="2"/>
+ </treecols>
+ <treechildren>
+ <treeitem>
+ <treerow>
+ <treecell label="joe@somewhere.com"/>
+ <treecell label="Top secret plans"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ <treeitem>
+ <treerow>
+ <treecell label="mel@whereever.com"/>
+ <treecell label="Let's do lunch"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+</tree>
+
+<script type="text/javascript"><![CDATA[
+function run() {
+ var tree = document.getElementsByTagName("tree")[0];
+ var sel = tree.view.selection;
+ sel.rangedSelect(0, 0, true);
+ sel.rangedSelect(1000, 1001, true);
+ sel.adjustSelection(1, 0x7fffffff);
+}
+]]></script>
+
+</window>
diff --git a/layout/xul/tree/crashtests/585815.html b/layout/xul/tree/crashtests/585815.html
new file mode 100644
index 000000000..0b8b01827
--- /dev/null
+++ b/layout/xul/tree/crashtests/585815.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 585815</title>
+<script>
+function done()
+{
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(done,1000)">
+
+<iframe src="585815-iframe.xul"></iframe>
+
+
+</body>
+</html>
diff --git a/layout/xul/tree/crashtests/601427.html b/layout/xul/tree/crashtests/601427.html
new file mode 100644
index 000000000..cd9574eef
--- /dev/null
+++ b/layout/xul/tree/crashtests/601427.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+
+var onPaintFunctions =
+[
+ function() { document.documentElement.style.MozAppearance = "treeheadersortarrow"; },
+ function() { document.documentElement.style.position = "fixed"; },
+ function() { document.documentElement.removeAttribute("class"); }
+];
+
+var i = 0;
+
+function advance()
+{
+ var f = onPaintFunctions[i++];
+ if (f)
+ f();
+}
+
+function start()
+{
+ window.addEventListener("MozAfterPaint", advance, true);
+ advance();
+}
+
+window.addEventListener("load", start, false);
+
+</script>
+</html>
diff --git a/layout/xul/tree/crashtests/730441-1.xul b/layout/xul/tree/crashtests/730441-1.xul
new file mode 100644
index 000000000..a31c3b63f
--- /dev/null
+++ b/layout/xul/tree/crashtests/730441-1.xul
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<!--
+Program received signal SIGSEGV, Segmentation fault.
+0xb6457185 in nsIContent::SetAttr (this=0x0, aNameSpaceID=0, aName=0xb0cb064c, aValue=..., aNotify=1) at ../../dist/include/nsIContent.h:285
+285 return SetAttr(aNameSpaceID, aName, nsnull, aValue, aNotify);
+(gdb) p this
+$6 = (nsIContent * const) 0x0
+(gdb) bt 3
+#0 0xb6457185 in nsIContent::SetAttr (this=0x0, aNameSpaceID=0, aName=0xb0cb064c, aValue=..., aNotify=1) at ../../dist/include/nsIContent.h:285
+#1 0xb6b72072 in nsTreeColumns::RestoreNaturalOrder (this=0xaaf83cc0) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:605
+#2 0xb736c76f in NS_InvokeByIndex_P () at xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp:69
+(More stack frames follow...)
+(gdb) frame 1
+#1 0xb6b72072 in nsTreeColumns::RestoreNaturalOrder (this=0xaaf83cc0) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:605
+605 child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, PR_TRUE);
+(gdb) list
+600 PRUint32 numChildren = colsContent->GetChildCount();
+601 for (PRUint32 i = 0; i < numChildren; ++i) {
+602 nsIContent *child = colsContent->GetChildAt(i);
+603 nsAutoString ordinal;
+604 ordinal.AppendInt(i);
+605 child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, PR_TRUE);
+606 }
+(gdb) p child
+$7 = (nsIContent *) 0x0
+
+First loop iteration: |child->SetAttr()| dispatches "DOMAttrModified" event.
+Event listener removes next column. Second loop iteration: |colsContent->GetChildAt(i)|
+returns null. Then we have |null->SetAttr()|.
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="run();">
+<tree id="tree">
+ <treecols>
+ <treecol id="col1"/>
+ <treecol id="col2"/>
+ </treecols>
+ <treechildren/>
+</tree>
+<script type="text/javascript"><![CDATA[
+function listener() {
+ var col2 = document.getElementById("col2");
+ col2.parentNode.removeChild(col2);
+}
+
+function run() {
+ var col1 = document.getElementById("col1");
+ col1.addEventListener("DOMAttrModified", listener, true);
+ var tree = document.getElementById("tree");
+ tree.columns.restoreNaturalOrder();
+}
+]]></script>
+</window>
+
diff --git a/layout/xul/tree/crashtests/730441-2.xul b/layout/xul/tree/crashtests/730441-2.xul
new file mode 100644
index 000000000..02b3a307e
--- /dev/null
+++ b/layout/xul/tree/crashtests/730441-2.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+Program received signal SIGSEGV, Segmentation fault.
+0xb6b720a6 in nsTreeColumns::RestoreNaturalOrder (this=0xa947a580) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:610
+610 mTree->Invalidate();
+(gdb) bt 3
+#0 0xb6b720a6 in nsTreeColumns::RestoreNaturalOrder (this=0xa947a580) at layout/xul/base/src/tree/src/nsTreeColumns.cpp:610
+#1 0xb736c76f in NS_InvokeByIndex_P () at xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp:69
+#2 0xb6171901 in XPCWrappedNative::CallMethod (ccx=..., mode=XPCWrappedNative::CALL_METHOD)
+ at js/src/xpconnect/src/xpcwrappednative.cpp:2722
+(More stack frames follow...)
+(gdb) list
+605 child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, PR_TRUE);
+606 }
+607
+608 nsTreeColumns::InvalidateColumns();
+609
+610 mTree->Invalidate();
+611
+612 return NS_OK;
+613 }
+614
+(gdb) p mTree
+$9 = (nsITreeBoxObject *) 0x0
+
+|child->SetAttr()| dispatches "DOMAttrModified" event. Event listener removes
+whole tree, |mTree| is being set to null. Then we have |null->Invalidate()|.
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="run();">
+<tree id="tree">
+ <treecols>
+ <treecol id="col"/>
+ </treecols>
+ <treechildren/>
+</tree>
+<script type="text/javascript"><![CDATA[
+var tree = null;
+
+function listener() {
+ tree.parentNode.removeChild(tree);
+}
+
+function run() {
+ col = document.getElementById("col");
+ col.addEventListener("DOMAttrModified", listener, true);
+ tree = document.getElementById("tree");
+ tree.columns.restoreNaturalOrder();
+}
+]]></script>
+</window>
+
diff --git a/layout/xul/tree/crashtests/730441-3.xul b/layout/xul/tree/crashtests/730441-3.xul
new file mode 100644
index 000000000..2cf74d1b9
--- /dev/null
+++ b/layout/xul/tree/crashtests/730441-3.xul
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<!--
+###!!! ASSERTION: You can't dereference a NULL nsCOMPtr with operator->().: 'mRawPtr != 0', file ../../../../dist/include/nsCOMPtr.h, line 796
+
+Program received signal SIGSEGV, Segmentation fault.
+0xb6b7463a in nsTreeContentView::SetTree (this=0xb0ba2510, aTree=0xaaecece0) at layout/xul/base/src/tree/src/nsTreeContentView.cpp:571
+571 boxObject->GetElement(getter_AddRefs(element));
+(gdb) bt 3
+#0 0xb6b7463a in nsTreeContentView::SetTree (this=0xb0ba2510, aTree=0xaaecece0) at layout/xul/base/src/tree/src/nsTreeContentView.cpp:571
+#1 0xb736c76f in NS_InvokeByIndex_P () at xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp:69
+#2 0xb6171901 in XPCWrappedNative::CallMethod (ccx=..., mode=XPCWrappedNative::CALL_METHOD)
+ at js/src/xpconnect/src/xpcwrappednative.cpp:2722
+(More stack frames follow...)
+(gdb) list 566
+561 nsTreeContentView::SetTree(nsITreeBoxObject* aTree)
+562 {
+563 ClearRows();
+564
+565 mBoxObject = aTree;
+566
+567 if (aTree && !mRoot) {
+568 // Get our root element
+569 nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mBoxObject);
+570 nsCOMPtr<nsIDOMElement> element;
+571 boxObject->GetElement(getter_AddRefs(element));
+(gdb) p boxObject
+$16 = {mRawPtr = 0x0}
+
+|aTree| does not implement |nsIBoxObject|, so |do_QueryInterface(mBoxObject)|
+returns null. Then we have |null->GetElement()|.
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.getElementById('tree').view.setTree({});">
+<tree id="tree">
+ <treechildren/>
+</tree>
+</window>
+
diff --git a/layout/xul/tree/crashtests/crashtests.list b/layout/xul/tree/crashtests/crashtests.list
new file mode 100644
index 000000000..d462e9550
--- /dev/null
+++ b/layout/xul/tree/crashtests/crashtests.list
@@ -0,0 +1,24 @@
+load 307298-1.xul
+load 309732-1.xul
+load 309732-2.xul
+load 366583-1.xul
+load 380217-1.xul
+load 382444-1.html
+load 391178-1.xhtml
+load 391178-2.xul
+load 393665-1.xul
+load 399227-1.xul
+load 399227-2.xul
+load 399692-1.xhtml
+load 399715-1.xhtml
+load 409807-1.xul
+load 414170-1.xul
+load 430394-1.xul
+load 454186-1.xul
+load 479931-1.xhtml
+load 509602-1.xul
+load 585815.html
+load 601427.html
+load 730441-1.xul
+load 730441-2.xul
+load 730441-3.xul
diff --git a/layout/xul/tree/moz.build b/layout/xul/tree/moz.build
new file mode 100644
index 000000000..ccac5bde9
--- /dev/null
+++ b/layout/xul/tree/moz.build
@@ -0,0 +1,53 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files('**'):
+ BUG_COMPONENT = ('Core', 'XP Toolkit/Widgets: XUL')
+
+XPIDL_SOURCES += [
+ 'nsITreeBoxObject.idl',
+ 'nsITreeColumns.idl',
+ 'nsITreeContentView.idl',
+ 'nsITreeSelection.idl',
+ 'nsITreeView.idl',
+]
+
+XPIDL_MODULE = 'layout_xul_tree'
+
+EXPORTS += [
+ 'nsTreeColFrame.h',
+ 'nsTreeColumns.h',
+ 'nsTreeUtils.h',
+]
+
+EXPORTS.mozilla.dom += [
+ 'TreeBoxObject.h'
+]
+
+UNIFIED_SOURCES += [
+ 'nsTreeBodyFrame.cpp',
+ 'nsTreeColFrame.cpp',
+ 'nsTreeColumns.cpp',
+ 'nsTreeContentView.cpp',
+ 'nsTreeImageListener.cpp',
+ 'nsTreeSelection.cpp',
+ 'nsTreeStyleCache.cpp',
+ 'nsTreeUtils.cpp',
+ 'TreeBoxObject.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '..',
+ '../../base',
+ '../../forms',
+ '../../generic',
+ '../../style',
+ '/dom/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/layout/xul/tree/nsITreeBoxObject.idl b/layout/xul/tree/nsITreeBoxObject.idl
new file mode 100644
index 000000000..638c8a41c
--- /dev/null
+++ b/layout/xul/tree/nsITreeBoxObject.idl
@@ -0,0 +1,189 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsIDOMElement;
+interface nsITreeView;
+interface nsITreeSelection;
+interface nsITreeColumn;
+interface nsITreeColumns;
+interface nsIScriptableRegion;
+
+[scriptable, uuid(f3da0c5e-51f5-45f0-b2cd-6be3ab6847ae)]
+interface nsITreeBoxObject : nsISupports
+{
+ /**
+ * Obtain the columns.
+ */
+ readonly attribute nsITreeColumns columns;
+
+ /**
+ * The view that backs the tree and that supplies it with its data.
+ * It is dynamically settable, either using a view attribute on the
+ * tree tag or by setting this attribute to a new value.
+ */
+ attribute nsITreeView view;
+
+ /**
+ * Whether or not we are currently focused.
+ */
+ attribute boolean focused;
+
+ /**
+ * Obtain the treebody content node
+ */
+ readonly attribute nsIDOMElement treeBody;
+
+ /**
+ * Obtain the height of a row.
+ */
+ readonly attribute long rowHeight;
+
+ /**
+ * Obtain the width of a row.
+ */
+ readonly attribute long rowWidth;
+
+ /**
+ * Get the pixel position of the horizontal scrollbar.
+ */
+ readonly attribute long horizontalPosition;
+
+ /**
+ * Return the region for the visible parts of the selection, in device pixels.
+ */
+ readonly attribute nsIScriptableRegion selectionRegion;
+
+ /**
+ * Get the index of the first visible row.
+ */
+ long getFirstVisibleRow();
+
+ /**
+ * Get the index of the last visible row.
+ */
+ long getLastVisibleRow();
+
+ /**
+ * Gets the number of possible visible rows.
+ */
+ long getPageLength();
+
+ /**
+ * Ensures that a row at a given index is visible.
+ */
+ void ensureRowIsVisible(in long index);
+
+ /**
+ * Ensures that a given cell in the tree is visible.
+ */
+ void ensureCellIsVisible(in long row, in nsITreeColumn col);
+
+ /**
+ * Scrolls such that the row at index is at the top of the visible view.
+ */
+ void scrollToRow(in long index);
+
+ /**
+ * Scroll the tree up or down by numLines lines. Positive
+ * values move down in the tree. Prevents scrolling off the
+ * end of the tree.
+ */
+ void scrollByLines(in long numLines);
+
+ /**
+ * Scroll the tree up or down by numPages pages. A page
+ * is considered to be the amount displayed by the tree.
+ * Positive values move down in the tree. Prevents scrolling
+ * off the end of the tree.
+ */
+ void scrollByPages(in long numPages);
+
+ /**
+ * Scrolls such that a given cell is visible (if possible)
+ * at the top left corner of the visible view.
+ */
+ void scrollToCell(in long row, in nsITreeColumn col);
+
+ /**
+ * Scrolls horizontally so that the specified column is
+ * at the left of the view (if possible).
+ */
+ void scrollToColumn(in nsITreeColumn col);
+
+ /**
+ * Scroll to a specific horizontal pixel position.
+ */
+ void scrollToHorizontalPosition(in long horizontalPosition);
+
+ /**
+ * Invalidation methods for fine-grained painting control.
+ */
+ void invalidate();
+ void invalidateColumn(in nsITreeColumn col);
+ void invalidateRow(in long index);
+ void invalidateCell(in long row, in nsITreeColumn col);
+ void invalidateRange(in long startIndex, in long endIndex);
+ void invalidateColumnRange(in long startIndex, in long endIndex,
+ in nsITreeColumn col);
+
+ /**
+ * A hit test that can tell you what row the mouse is over.
+ * returns -1 for invalid mouse coordinates.
+ *
+ * The coordinate system is the client coordinate system for the
+ * document this boxObject lives in, and the units are CSS pixels.
+ */
+ long getRowAt(in long x, in long y);
+
+ /**
+ * A hit test that can tell you what cell the mouse is over. Row is the row index
+ * hit, returns -1 for invalid mouse coordinates. ColID is the column hit.
+ * ChildElt is the pseudoelement hit: this can have values of
+ * "cell", "twisty", "image", and "text".
+ *
+ * The coordinate system is the client coordinate system for the
+ * document this boxObject lives in, and the units are CSS pixels.
+ */
+ void getCellAt(in long x, in long y, out long row, out nsITreeColumn col, out AString childElt);
+
+ /**
+ * Find the coordinates of an element within a specific cell.
+ */
+ void getCoordsForCellItem(in long row, in nsITreeColumn col, in AString element,
+ out long x, out long y, out long width, out long height);
+
+ /**
+ * Determine if the text of a cell is being cropped or not.
+ */
+ boolean isCellCropped(in long row, in nsITreeColumn col);
+
+ /**
+ * The view is responsible for calling these notification methods when
+ * rows are added or removed. Index is the position at which the new
+ * rows were added or at which rows were removed. For
+ * non-contiguous additions/removals, this method should be called multiple times.
+ */
+ void rowCountChanged(in long index, in long count);
+
+ /**
+ * Notify the tree that the view is about to perform a batch
+ * update, that is, add, remove or invalidate several rows at once.
+ * This must be followed by calling endUpdateBatch(), otherwise the tree
+ * will get out of sync.
+ */
+ void beginUpdateBatch();
+
+ /**
+ * Notify the tree that the view has completed a batch update.
+ */
+ void endUpdateBatch();
+
+ /**
+ * Called on a theme switch to flush out the tree's style and image caches.
+ */
+ void clearStyleAndImageCaches();
+};
diff --git a/layout/xul/tree/nsITreeColumns.idl b/layout/xul/tree/nsITreeColumns.idl
new file mode 100644
index 000000000..a601f3776
--- /dev/null
+++ b/layout/xul/tree/nsITreeColumns.idl
@@ -0,0 +1,96 @@
+/* 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 "nsISupports.idl"
+
+interface nsITreeColumns;
+interface nsIDOMElement;
+interface nsIAtom;
+
+[scriptable, uuid(ae835ecf-6b32-4660-9b43-8a270df56e02)]
+interface nsITreeColumn : nsISupports
+{
+ readonly attribute nsIDOMElement element;
+
+ readonly attribute nsITreeColumns columns;
+
+ readonly attribute long x;
+ readonly attribute long width;
+
+ readonly attribute AString id;
+ [noscript] void getIdConst([shared] out wstring idConst);
+ [noscript] readonly attribute nsIAtom atom;
+
+ readonly attribute long index;
+
+ readonly attribute boolean primary;
+ readonly attribute boolean cycler;
+ readonly attribute boolean editable;
+ readonly attribute boolean selectable;
+
+ const short TYPE_TEXT = 1;
+ const short TYPE_CHECKBOX = 2;
+ const short TYPE_PROGRESSMETER = 3;
+ const short TYPE_PASSWORD = 4;
+ readonly attribute short type;
+
+ nsITreeColumn getNext();
+ nsITreeColumn getPrevious();
+
+ void invalidate();
+};
+
+interface nsITreeBoxObject;
+
+[scriptable, uuid(f8a8d6b4-6788-438d-9009-7142798767ab)]
+interface nsITreeColumns : nsISupports
+{
+ /**
+ * The tree widget for these columns.
+ */
+ readonly attribute nsITreeBoxObject tree;
+
+ /**
+ * The number of columns.
+ */
+ readonly attribute long count;
+
+ /**
+ * An alias for count (for the benefit of scripts which treat this as an
+ * array).
+ */
+ readonly attribute long length;
+
+ /**
+ * Get the first/last column.
+ */
+ nsITreeColumn getFirstColumn();
+ nsITreeColumn getLastColumn();
+
+ /**
+ * Attribute based column getters.
+ */
+ nsITreeColumn getPrimaryColumn();
+ nsITreeColumn getSortedColumn();
+ nsITreeColumn getKeyColumn();
+
+ /**
+ * Get the column for the given element.
+ */
+ nsITreeColumn getColumnFor(in nsIDOMElement element);
+
+ /**
+ * Parametric column getters.
+ */
+ nsITreeColumn getNamedColumn(in AString id);
+ nsITreeColumn getColumnAt(in long index);
+
+ /**
+ * This method is called whenever a treecol is added or removed and
+ * the column cache needs to be rebuilt.
+ */
+ void invalidateColumns();
+
+ void restoreNaturalOrder();
+};
diff --git a/layout/xul/tree/nsITreeContentView.idl b/layout/xul/tree/nsITreeContentView.idl
new file mode 100644
index 000000000..0b5d178aa
--- /dev/null
+++ b/layout/xul/tree/nsITreeContentView.idl
@@ -0,0 +1,22 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsIDOMElement;
+
+[scriptable, uuid(5ef62896-0c0a-41f1-bb3c-44a60f5dfdab)]
+interface nsITreeContentView : nsISupports
+{
+ /**
+ * Retrieve the content item associated with the specified index.
+ */
+ nsIDOMElement getItemAtIndex(in long index);
+
+ /**
+ * Retrieve the index associated with the specified content item.
+ */
+ long getIndexOfItem(in nsIDOMElement item);
+};
diff --git a/layout/xul/tree/nsITreeSelection.idl b/layout/xul/tree/nsITreeSelection.idl
new file mode 100644
index 000000000..6cc137f1e
--- /dev/null
+++ b/layout/xul/tree/nsITreeSelection.idl
@@ -0,0 +1,130 @@
+/* -*- 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/. */
+
+interface nsITreeBoxObject;
+interface nsITreeColumn;
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(ab6fe746-300b-4ab4-abb9-1c0e3977874c)]
+interface nsITreeSelection : nsISupports
+{
+ /**
+ * The tree widget for this selection.
+ */
+ attribute nsITreeBoxObject tree;
+
+ /**
+ * This attribute is a boolean indicating single selection.
+ */
+ readonly attribute boolean single;
+
+ /**
+ * The number of rows currently selected in this tree.
+ */
+ readonly attribute long count;
+
+ /**
+ * Indicates whether or not the row at the specified index is
+ * part of the selection.
+ */
+ boolean isSelected(in long index);
+
+ /**
+ * Deselect all rows and select the row at the specified index.
+ */
+ void select(in long index);
+
+ /**
+ * Perform a timed select.
+ */
+ void timedSelect(in long index, in long delay);
+
+ /**
+ * Toggle the selection state of the row at the specified index.
+ */
+ void toggleSelect(in long index);
+
+ /**
+ * Select the range specified by the indices. If augment is true,
+ * then we add the range to the selection without clearing out anything
+ * else. If augment is false, everything is cleared except for the specified range.
+ */
+ void rangedSelect(in long startIndex, in long endIndex, in boolean augment);
+
+ /**
+ * Clears the range.
+ */
+ void clearRange(in long startIndex, in long endIndex);
+
+ /**
+ * Clears the selection.
+ */
+ void clearSelection();
+
+ /**
+ * Inverts the selection.
+ */
+ void invertSelection();
+
+ /**
+ * Selects all rows.
+ */
+ void selectAll();
+
+ /**
+ * Iterate the selection using these methods.
+ */
+ long getRangeCount();
+ void getRangeAt(in long i, out long min, out long max);
+
+ /**
+ * Can be used to invalidate the selection.
+ */
+ void invalidateSelection();
+
+ /**
+ * Called when the row count changes to adjust selection indices.
+ */
+ void adjustSelection(in long index, in long count);
+
+ /**
+ * This attribute is a boolean indicating whether or not the
+ * "select" event should fire when the selection is changed using
+ * one of our methods. A view can use this to temporarily suppress
+ * the selection while manipulating all of the indices, e.g., on
+ * a sort.
+ * Note: setting this attribute to false will fire a select event.
+ */
+ attribute boolean selectEventsSuppressed;
+
+ /**
+ * The current item (the one that gets a focus rect in addition to being
+ * selected).
+ */
+ attribute long currentIndex;
+
+ /**
+ * The current column.
+ */
+ attribute nsITreeColumn currentColumn;
+
+ /**
+ * The selection "pivot". This is the first item the user selected as
+ * part of a ranged select.
+ */
+ readonly attribute long shiftSelectPivot;
+};
+
+/**
+ * The following interface is not scriptable and MUST NEVER BE MADE scriptable.
+ * Native treeselections implement it, and we use this to check whether a
+ * treeselection is native (and therefore suitable for use by untrusted content).
+ */
+[uuid(1bd59678-5cb3-4316-b246-31a91b19aabe)]
+interface nsINativeTreeSelection : nsITreeSelection
+{
+ [noscript] void ensureNative();
+};
diff --git a/layout/xul/tree/nsITreeView.idl b/layout/xul/tree/nsITreeView.idl
new file mode 100644
index 000000000..66920f81b
--- /dev/null
+++ b/layout/xul/tree/nsITreeView.idl
@@ -0,0 +1,215 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsITreeBoxObject;
+interface nsITreeSelection;
+interface nsITreeColumn;
+interface nsIDOMDataTransfer;
+
+[scriptable, uuid(091116f0-0bdc-4b32-b9c8-c8d5a37cb088)]
+interface nsITreeView : nsISupports
+{
+ /**
+ * The total number of rows in the tree (including the offscreen rows).
+ */
+ readonly attribute long rowCount;
+
+ /**
+ * The selection for this view.
+ */
+ attribute nsITreeSelection selection;
+
+ /**
+ * A whitespace delimited list of properties. For each property X the view
+ * gives back will cause the pseudoclasses ::-moz-tree-cell(x),
+ * ::-moz-tree-row(x), ::-moz-tree-twisty(x), ::-moz-tree-image(x),
+ * ::-moz-tree-cell-text(x). to be matched on the pseudoelement
+ * ::moz-tree-row.
+ */
+ AString getRowProperties(in long index);
+
+ /**
+ * A whitespace delimited list of properties for a given cell. Each
+ * property, x, that the view gives back will cause the pseudoclasses
+ * ::-moz-tree-cell(x), ::-moz-tree-row(x), ::-moz-tree-twisty(x),
+ * ::-moz-tree-image(x), ::-moz-tree-cell-text(x). to be matched on the
+ * cell.
+ */
+ AString getCellProperties(in long row, in nsITreeColumn col);
+
+ /**
+ * Called to get properties to paint a column background. For shading the sort
+ * column, etc.
+ */
+ AString getColumnProperties(in nsITreeColumn col);
+
+ /**
+ * Methods that can be used to test whether or not a twisty should be drawn,
+ * and if so, whether an open or closed twisty should be used.
+ */
+ boolean isContainer(in long index);
+ boolean isContainerOpen(in long index);
+ boolean isContainerEmpty(in long index);
+
+ /**
+ * isSeparator is used to determine if the row at index is a separator.
+ * A value of true will result in the tree drawing a horizontal separator.
+ * The tree uses the ::moz-tree-separator pseudoclass to draw the separator.
+ */
+ boolean isSeparator(in long index);
+
+ /**
+ * Specifies if there is currently a sort on any column. Used mostly by dragdrop
+ * to affect drop feedback.
+ */
+ boolean isSorted();
+
+ const short DROP_BEFORE = -1;
+ const short DROP_ON = 0;
+ const short DROP_AFTER = 1;
+ /**
+ * Methods used by the drag feedback code to determine if a drag is allowable at
+ * the current location. To get the behavior where drops are only allowed on
+ * items, such as the mailNews folder pane, always return false when
+ * the orientation is not DROP_ON.
+ */
+ boolean canDrop(in long index, in long orientation, in nsIDOMDataTransfer dataTransfer);
+
+ /**
+ * Called when the user drops something on this view. The |orientation| param
+ * specifies before/on/after the given |row|.
+ */
+ void drop(in long row, in long orientation, in nsIDOMDataTransfer dataTransfer);
+
+ /**
+ * Methods used by the tree to draw thread lines in the tree.
+ * getParentIndex is used to obtain the index of a parent row.
+ * If there is no parent row, getParentIndex returns -1.
+ */
+ long getParentIndex(in long rowIndex);
+
+ /**
+ * hasNextSibling is used to determine if the row at rowIndex has a nextSibling
+ * that occurs *after* the index specified by afterIndex. Code that is forced
+ * to march down the view looking at levels can optimize the march by starting
+ * at afterIndex+1.
+ */
+ boolean hasNextSibling(in long rowIndex, in long afterIndex);
+
+ /**
+ * The level is an integer value that represents
+ * the level of indentation. It is multiplied by the width specified in the
+ * :moz-tree-indentation pseudoelement to compute the exact indendation.
+ */
+ long getLevel(in long index);
+
+ /**
+ * The image path for a given cell. For defining an icon for a cell.
+ * If the empty string is returned, the :moz-tree-image pseudoelement
+ * will be used.
+ */
+ AString getImageSrc(in long row, in nsITreeColumn col);
+
+ /**
+ * The progress mode for a given cell. This method is only called for
+ * columns of type |progressmeter|.
+ */
+ const short PROGRESS_NORMAL = 1;
+ const short PROGRESS_UNDETERMINED = 2;
+ const short PROGRESS_NONE = 3;
+ long getProgressMode(in long row, in nsITreeColumn col);
+
+ /**
+ * The value for a given cell. This method is only called for columns
+ * of type other than |text|.
+ */
+ AString getCellValue(in long row, in nsITreeColumn col);
+
+ /**
+ * The text for a given cell. If a column consists only of an image, then
+ * the empty string is returned.
+ */
+ AString getCellText(in long row, in nsITreeColumn col);
+
+ /**
+ * Called during initialization to link the view to the front end box object.
+ */
+ void setTree(in nsITreeBoxObject tree);
+
+ /**
+ * Called on the view when an item is opened or closed.
+ */
+ void toggleOpenState(in long index);
+
+ /**
+ * Called on the view when a header is clicked.
+ */
+ void cycleHeader(in nsITreeColumn col);
+
+ /**
+ * Should be called from a XUL onselect handler whenever the selection changes.
+ */
+ void selectionChanged();
+
+ /**
+ * Called on the view when a cell in a non-selectable cycling column (e.g., unread/flag/etc.) is clicked.
+ */
+ void cycleCell(in long row, in nsITreeColumn col);
+
+ /**
+ * isEditable is called to ask the view if the cell contents are editable.
+ * A value of true will result in the tree popping up a text field when
+ * the user tries to inline edit the cell.
+ */
+ boolean isEditable(in long row, in nsITreeColumn col);
+
+ /**
+ * isSelectable is called to ask the view if the cell is selectable.
+ * This method is only called if the selection style is |cell| or |text|.
+ * XXXvarga shouldn't this be called isCellSelectable?
+ */
+ boolean isSelectable(in long row, in nsITreeColumn col);
+
+ /**
+ * setCellValue is called when the value of the cell has been set by the user.
+ * This method is only called for columns of type other than |text|.
+ */
+ void setCellValue(in long row, in nsITreeColumn col, in AString value);
+
+ /**
+ * setCellText is called when the contents of the cell have been edited by the user.
+ */
+ void setCellText(in long row, in nsITreeColumn col, in AString value);
+
+ /**
+ * A command API that can be used to invoke commands on the selection. The tree
+ * will automatically invoke this method when certain keys are pressed. For example,
+ * when the DEL key is pressed, performAction will be called with the "delete" string.
+ */
+ void performAction(in wstring action);
+
+ /**
+ * A command API that can be used to invoke commands on a specific row.
+ */
+ void performActionOnRow(in wstring action, in long row);
+
+ /**
+ * A command API that can be used to invoke commands on a specific cell.
+ */
+ void performActionOnCell(in wstring action, in long row, in nsITreeColumn col);
+};
+
+/**
+ * The following interface is not scriptable and MUST NEVER BE MADE scriptable.
+ * Native treeviews implement it, and we use this to check whether a treeview
+ * is native (and therefore suitable for use by untrusted content).
+ */
+[uuid(46c90265-6553-41ae-8d39-7022e7d09145)]
+interface nsINativeTreeView : nsITreeView
+{
+ [noscript] void ensureNative();
+};
diff --git a/layout/xul/tree/nsTreeBodyFrame.cpp b/layout/xul/tree/nsTreeBodyFrame.cpp
new file mode 100644
index 000000000..deba04a36
--- /dev/null
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -0,0 +1,4945 @@
+/* -*- 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 "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEditRules.h"
+
+#include "gfxUtils.h"
+#include "nsAlgorithm.h"
+#include "nsCOMPtr.h"
+#include "nsFontMetrics.h"
+#include "nsPresContext.h"
+#include "nsNameSpaceManager.h"
+
+#include "nsTreeBodyFrame.h"
+#include "nsTreeSelection.h"
+#include "nsTreeImageListener.h"
+
+#include "nsGkAtoms.h"
+#include "nsCSSAnonBoxes.h"
+
+#include "nsIContent.h"
+#include "nsStyleContext.h"
+#include "nsIBoxObject.h"
+#include "nsIDOMCustomEvent.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDocument.h"
+#include "mozilla/css/StyleRule.h"
+#include "nsCSSRendering.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsXPIDLString.h"
+#include "nsContainerFrame.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsVariant.h"
+#include "nsWidgetsCID.h"
+#include "nsBoxFrame.h"
+#include "nsIURL.h"
+#include "nsBoxLayoutState.h"
+#include "nsTreeContentView.h"
+#include "nsTreeUtils.h"
+#include "nsThemeConstants.h"
+#include "nsITheme.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "imgILoader.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsDisplayList.h"
+#include "mozilla/dom/TreeBoxObject.h"
+#include "nsRenderingContext.h"
+#include "nsIScriptableRegion.h"
+#include <algorithm>
+#include "ScrollbarActivity.h"
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#include "nsIWritablePropertyBag2.h"
+#endif
+#include "nsBidiUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::layout;
+
+// Function that cancels all the image requests in our cache.
+void
+nsTreeBodyFrame::CancelImageRequests()
+{
+ for (auto iter = mImageCache.Iter(); !iter.Done(); iter.Next()) {
+ // If our imgIRequest object was registered with the refresh driver
+ // then we need to deregister it.
+ nsTreeImageCacheEntry entry = iter.UserData();
+ nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request,
+ nullptr);
+ entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ }
+}
+
+//
+// NS_NewTreeFrame
+//
+// Creates a new tree frame
+//
+nsIFrame*
+NS_NewTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTreeBodyFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTreeBodyFrame)
+
+NS_QUERYFRAME_HEAD(nsTreeBodyFrame)
+ NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
+ NS_QUERYFRAME_ENTRY(nsTreeBodyFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame)
+
+// Constructor
+nsTreeBodyFrame::nsTreeBodyFrame(nsStyleContext* aContext)
+:nsLeafBoxFrame(aContext),
+ mSlots(nullptr),
+ mImageCache(),
+ mTopRowIndex(0),
+ mPageLength(0),
+ mHorzPosition(0),
+ mOriginalHorzWidth(-1),
+ mHorzWidth(0),
+ mAdjustWidth(0),
+ mRowHeight(0),
+ mIndentation(0),
+ mStringWidth(-1),
+ mUpdateBatchNest(0),
+ mRowCount(0),
+ mMouseOverRow(-1),
+ mFocused(false),
+ mHasFixedRowCount(false),
+ mVerticalOverflow(false),
+ mHorizontalOverflow(false),
+ mReflowCallbackPosted(false),
+ mCheckingOverflow(false)
+{
+ mColumns = new nsTreeColumns(this);
+}
+
+// Destructor
+nsTreeBodyFrame::~nsTreeBodyFrame()
+{
+ CancelImageRequests();
+ DetachImageListeners();
+ delete mSlots;
+}
+
+static void
+GetBorderPadding(nsStyleContext* aContext, nsMargin& aMargin)
+{
+ aMargin.SizeTo(0, 0, 0, 0);
+ aContext->StylePadding()->GetPadding(aMargin);
+ aMargin += aContext->StyleBorder()->GetComputedBorder();
+}
+
+static void
+AdjustForBorderPadding(nsStyleContext* aContext, nsRect& aRect)
+{
+ nsMargin borderPadding(0, 0, 0, 0);
+ GetBorderPadding(aContext, borderPadding);
+ aRect.Deflate(borderPadding);
+}
+
+void
+nsTreeBodyFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mIndentation = GetIndentation();
+ mRowHeight = GetRowHeight();
+
+ EnsureBoxObject();
+
+ if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
+ mScrollbarActivity = new ScrollbarActivity(
+ static_cast<nsIScrollbarMediator*>(this));
+ }
+}
+
+nsSize
+nsTreeBodyFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ EnsureView();
+
+ nsIContent* baseElement = GetBaseElement();
+
+ nsSize min(0,0);
+ int32_t desiredRows;
+ if (MOZ_UNLIKELY(!baseElement)) {
+ desiredRows = 0;
+ }
+ else if (baseElement->IsHTMLElement(nsGkAtoms::select)) {
+ min.width = CalcMaxRowWidth();
+ nsAutoString size;
+ baseElement->GetAttr(kNameSpaceID_None, nsGkAtoms::size, size);
+ if (!size.IsEmpty()) {
+ nsresult err;
+ desiredRows = size.ToInteger(&err);
+ mHasFixedRowCount = true;
+ mPageLength = desiredRows;
+ }
+ else {
+ desiredRows = 1;
+ }
+ }
+ else {
+ // tree
+ nsAutoString rows;
+ baseElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows);
+ if (!rows.IsEmpty()) {
+ nsresult err;
+ desiredRows = rows.ToInteger(&err);
+ mPageLength = desiredRows;
+ }
+ else {
+ desiredRows = 0;
+ }
+ }
+
+ min.height = mRowHeight * desiredRows;
+
+ AddBorderAndPadding(min);
+ bool widthSet, heightSet;
+ nsIFrame::AddXULMinSize(aBoxLayoutState, this, min, widthSet, heightSet);
+
+ return min;
+}
+
+nscoord
+nsTreeBodyFrame::CalcMaxRowWidth()
+{
+ if (mStringWidth != -1)
+ return mStringWidth;
+
+ if (!mView)
+ return 0;
+
+ nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+ nsMargin rowMargin(0,0,0,0);
+ GetBorderPadding(rowContext, rowMargin);
+
+ nscoord rowWidth;
+ nsTreeColumn* col;
+
+ nsRenderingContext rc(
+ PresContext()->PresShell()->CreateReferenceRenderingContext());
+
+ for (int32_t row = 0; row < mRowCount; ++row) {
+ rowWidth = 0;
+
+ for (col = mColumns->GetFirstColumn(); col; col = col->GetNext()) {
+ nscoord desiredWidth, currentWidth;
+ nsresult rv = GetCellWidth(row, col, &rc, desiredWidth, currentWidth);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("invalid column");
+ continue;
+ }
+ rowWidth += desiredWidth;
+ }
+
+ if (rowWidth > mStringWidth)
+ mStringWidth = rowWidth;
+ }
+
+ mStringWidth += rowMargin.left + rowMargin.right;
+ return mStringWidth;
+}
+
+void
+nsTreeBodyFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (mScrollbarActivity) {
+ mScrollbarActivity->Destroy();
+ mScrollbarActivity = nullptr;
+ }
+
+ mScrollEvent.Revoke();
+ // Make sure we cancel any posted callbacks.
+ if (mReflowCallbackPosted) {
+ PresContext()->PresShell()->CancelReflowCallback(this);
+ mReflowCallbackPosted = false;
+ }
+
+ if (mColumns)
+ mColumns->SetTree(nullptr);
+
+ // Save off our info into the box object.
+ nsCOMPtr<nsPIBoxObject> box(do_QueryInterface(mTreeBoxObject));
+ if (box) {
+ if (mTopRowIndex > 0) {
+ nsAutoString topRowStr; topRowStr.AssignLiteral("topRow");
+ nsAutoString topRow;
+ topRow.AppendInt(mTopRowIndex);
+ box->SetProperty(topRowStr.get(), topRow.get());
+ }
+
+ // Always null out the cached tree body frame.
+ box->ClearCachedValues();
+
+ mTreeBoxObject = nullptr; // Drop our ref here.
+ }
+
+ if (mView) {
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel)
+ sel->SetTree(nullptr);
+ mView->SetTree(nullptr);
+ mView = nullptr;
+ }
+
+ nsLeafBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+nsTreeBodyFrame::EnsureBoxObject()
+{
+ if (!mTreeBoxObject) {
+ nsIContent* parent = GetBaseElement();
+ if (parent) {
+ nsIDocument* nsDoc = parent->GetComposedDoc();
+ if (!nsDoc) // there may be no document, if we're called from Destroy()
+ return;
+ ErrorResult ignored;
+ nsCOMPtr<nsIBoxObject> box =
+ nsDoc->GetBoxObjectFor(parent->AsElement(), ignored);
+ // Ensure that we got a native box object.
+ nsCOMPtr<nsPIBoxObject> pBox = do_QueryInterface(box);
+ if (pBox) {
+ nsCOMPtr<nsITreeBoxObject> realTreeBoxObject = do_QueryInterface(pBox);
+ if (realTreeBoxObject) {
+ nsTreeBodyFrame* innerTreeBoxObject =
+ static_cast<dom::TreeBoxObject*>(realTreeBoxObject.get())
+ ->GetCachedTreeBodyFrame();
+ ENSURE_TRUE(!innerTreeBoxObject || innerTreeBoxObject == this);
+ mTreeBoxObject = realTreeBoxObject;
+ }
+ }
+ }
+ }
+}
+
+void
+nsTreeBodyFrame::EnsureView()
+{
+ if (!mView) {
+ if (PresContext()->PresShell()->IsReflowLocked()) {
+ if (!mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresContext()->PresShell()->PostReflowCallback(this);
+ }
+ return;
+ }
+ nsCOMPtr<nsIBoxObject> box = do_QueryInterface(mTreeBoxObject);
+ if (box) {
+ nsWeakFrame weakFrame(this);
+ nsCOMPtr<nsITreeView> treeView;
+ mTreeBoxObject->GetView(getter_AddRefs(treeView));
+ if (treeView && weakFrame.IsAlive()) {
+ nsXPIDLString rowStr;
+ box->GetProperty(u"topRow", getter_Copies(rowStr));
+ nsAutoString rowStr2(rowStr);
+ nsresult error;
+ int32_t rowIndex = rowStr2.ToInteger(&error);
+
+ // Set our view.
+ SetView(treeView);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ // Scroll to the given row.
+ // XXX is this optimal if we haven't laid out yet?
+ ScrollToRow(rowIndex);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ // Clear out the property info for the top row, but we always keep the
+ // view current.
+ box->RemoveProperty(u"topRow");
+ }
+ }
+ }
+}
+
+void
+nsTreeBodyFrame::ManageReflowCallback(const nsRect& aRect, nscoord aHorzWidth)
+{
+ if (!mReflowCallbackPosted &&
+ (!aRect.IsEqualEdges(mRect) || mHorzWidth != aHorzWidth)) {
+ PresContext()->PresShell()->PostReflowCallback(this);
+ mReflowCallbackPosted = true;
+ mOriginalHorzWidth = mHorzWidth;
+ }
+ else if (mReflowCallbackPosted &&
+ mHorzWidth != aHorzWidth && mOriginalHorzWidth == aHorzWidth) {
+ PresContext()->PresShell()->CancelReflowCallback(this);
+ mReflowCallbackPosted = false;
+ mOriginalHorzWidth = -1;
+ }
+}
+
+void
+nsTreeBodyFrame::SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect,
+ bool aRemoveOverflowArea)
+{
+ nscoord horzWidth = CalcHorzWidth(GetScrollParts());
+ ManageReflowCallback(aRect, horzWidth);
+ mHorzWidth = horzWidth;
+
+ nsLeafBoxFrame::SetXULBounds(aBoxLayoutState, aRect, aRemoveOverflowArea);
+}
+
+
+bool
+nsTreeBodyFrame::ReflowFinished()
+{
+ if (!mView) {
+ nsWeakFrame weakFrame(this);
+ EnsureView();
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+ }
+ if (mView) {
+ CalcInnerBox();
+ ScrollParts parts = GetScrollParts();
+ mHorzWidth = CalcHorzWidth(parts);
+ if (!mHasFixedRowCount) {
+ mPageLength = mInnerBox.height / mRowHeight;
+ }
+
+ int32_t lastPageTopRow = std::max(0, mRowCount - mPageLength);
+ if (mTopRowIndex > lastPageTopRow)
+ ScrollToRowInternal(parts, lastPageTopRow);
+
+ nsIContent *treeContent = GetBaseElement();
+ if (treeContent &&
+ treeContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::keepcurrentinview,
+ nsGkAtoms::_true, eCaseMatters)) {
+ // make sure that the current selected item is still
+ // visible after the tree changes size.
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel) {
+ int32_t currentIndex;
+ sel->GetCurrentIndex(&currentIndex);
+ if (currentIndex != -1)
+ EnsureRowIsVisibleInternal(parts, currentIndex);
+ }
+ }
+
+ if (!FullScrollbarsUpdate(false)) {
+ return false;
+ }
+ }
+
+ mReflowCallbackPosted = false;
+ return false;
+}
+
+void
+nsTreeBodyFrame::ReflowCallbackCanceled()
+{
+ mReflowCallbackPosted = false;
+}
+
+nsresult
+nsTreeBodyFrame::GetView(nsITreeView * *aView)
+{
+ *aView = nullptr;
+ nsWeakFrame weakFrame(this);
+ EnsureView();
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ NS_IF_ADDREF(*aView = mView);
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::SetView(nsITreeView * aView)
+{
+ // First clear out the old view.
+ if (mView) {
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel)
+ sel->SetTree(nullptr);
+ mView->SetTree(nullptr);
+
+ // Only reset the top row index and delete the columns if we had an old non-null view.
+ mTopRowIndex = 0;
+ }
+
+ // Tree, meet the view.
+ mView = aView;
+
+ // Changing the view causes us to refetch our data. This will
+ // necessarily entail a full invalidation of the tree.
+ Invalidate();
+
+ nsIContent *treeContent = GetBaseElement();
+ if (treeContent) {
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService)
+ accService->TreeViewChanged(PresContext()->GetPresShell(), treeContent, mView);
+#endif
+ FireDOMEvent(NS_LITERAL_STRING("TreeViewChanged"), treeContent);
+ }
+
+ if (mView) {
+ // Give the view a new empty selection object to play with, but only if it
+ // doesn't have one already.
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel) {
+ sel->SetTree(mTreeBoxObject);
+ }
+ else {
+ NS_NewTreeSelection(mTreeBoxObject, getter_AddRefs(sel));
+ mView->SetSelection(sel);
+ }
+
+ // View, meet the tree.
+ nsWeakFrame weakFrame(this);
+ mView->SetTree(mTreeBoxObject);
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ mView->GetRowCount(&mRowCount);
+
+ if (!PresContext()->PresShell()->IsReflowLocked()) {
+ // The scrollbar will need to be updated.
+ FullScrollbarsUpdate(false);
+ } else if (!mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresContext()->PresShell()->PostReflowCallback(this);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::SetFocused(bool aFocused)
+{
+ if (mFocused != aFocused) {
+ mFocused = aFocused;
+ if (mView) {
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel)
+ sel->InvalidateSelection();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::GetTreeBody(nsIDOMElement** aElement)
+{
+ //NS_ASSERTION(mContent, "no content, see bug #104878");
+ if (!mContent)
+ return NS_ERROR_NULL_POINTER;
+
+ return mContent->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)aElement);
+}
+
+int32_t
+nsTreeBodyFrame::RowHeight() const
+{
+ return nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+}
+
+int32_t
+nsTreeBodyFrame::RowWidth()
+{
+ return nsPresContext::AppUnitsToIntCSSPixels(CalcHorzWidth(GetScrollParts()));
+}
+
+int32_t
+nsTreeBodyFrame::GetHorizontalPosition() const
+{
+ return nsPresContext::AppUnitsToIntCSSPixels(mHorzPosition);
+}
+
+nsresult
+nsTreeBodyFrame::GetSelectionRegion(nsIScriptableRegion **aRegion)
+{
+ *aRegion = nullptr;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mView->GetSelection(getter_AddRefs(selection));
+ NS_ENSURE_TRUE(selection, NS_OK);
+
+ nsCOMPtr<nsIScriptableRegion> region = do_CreateInstance("@mozilla.org/gfx/region;1");
+ NS_ENSURE_TRUE(region, NS_ERROR_FAILURE);
+ region->Init();
+
+ RefPtr<nsPresContext> presContext = PresContext();
+ nsIntRect rect = mRect.ToOutsidePixels(presContext->AppUnitsPerCSSPixel());
+
+ nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame();
+ nsPoint origin = GetOffsetTo(rootFrame);
+
+ // iterate through the visible rows and add the selected ones to the
+ // drag region
+ int32_t x = nsPresContext::AppUnitsToIntCSSPixels(origin.x);
+ int32_t y = nsPresContext::AppUnitsToIntCSSPixels(origin.y);
+ int32_t top = y;
+ int32_t end = LastVisibleRow();
+ int32_t rowHeight = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+ for (int32_t i = mTopRowIndex; i <= end; i++) {
+ bool isSelected;
+ selection->IsSelected(i, &isSelected);
+ if (isSelected)
+ region->UnionRect(x, y, rect.width, rowHeight);
+ y += rowHeight;
+ }
+
+ // clip to the tree boundary in case one row extends past it
+ region->IntersectRect(x, top, rect.width, rect.height);
+
+ region.forget(aRegion);
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::Invalidate()
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ InvalidateFrame();
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::InvalidateColumn(nsITreeColumn* aCol)
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive())
+ FireInvalidateEvent(-1, -1, aCol, aCol);
+#endif
+
+ nsRect columnRect;
+ nsresult rv = col->GetRect(this, mInnerBox.y, mInnerBox.height, &columnRect);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // When false then column is out of view
+ if (OffsetForHorzScroll(columnRect, true))
+ InvalidateFrameWithRect(columnRect);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::InvalidateRow(int32_t aIndex)
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive())
+ FireInvalidateEvent(aIndex, aIndex, nullptr, nullptr);
+#endif
+
+ aIndex -= mTopRowIndex;
+ if (aIndex < 0 || aIndex > mPageLength)
+ return NS_OK;
+
+ nsRect rowRect(mInnerBox.x, mInnerBox.y+mRowHeight*aIndex, mInnerBox.width, mRowHeight);
+ InvalidateFrameWithRect(rowRect);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::InvalidateCell(int32_t aIndex, nsITreeColumn* aCol)
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive())
+ FireInvalidateEvent(aIndex, aIndex, aCol, aCol);
+#endif
+
+ aIndex -= mTopRowIndex;
+ if (aIndex < 0 || aIndex > mPageLength)
+ return NS_OK;
+
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+ nsRect cellRect;
+ nsresult rv = col->GetRect(this, mInnerBox.y+mRowHeight*aIndex, mRowHeight,
+ &cellRect);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (OffsetForHorzScroll(cellRect, true))
+ InvalidateFrameWithRect(cellRect);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::InvalidateRange(int32_t aStart, int32_t aEnd)
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ if (aStart == aEnd)
+ return InvalidateRow(aStart);
+
+ int32_t last = LastVisibleRow();
+ if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last)
+ return NS_OK;
+
+ if (aStart < mTopRowIndex)
+ aStart = mTopRowIndex;
+
+ if (aEnd > last)
+ aEnd = last;
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive()) {
+ int32_t end =
+ mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0;
+ FireInvalidateEvent(aStart, end, nullptr, nullptr);
+ }
+#endif
+
+ nsRect rangeRect(mInnerBox.x, mInnerBox.y+mRowHeight*(aStart-mTopRowIndex), mInnerBox.width, mRowHeight*(aEnd-aStart+1));
+ InvalidateFrameWithRect(rangeRect);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::InvalidateColumnRange(int32_t aStart, int32_t aEnd, nsITreeColumn* aCol)
+{
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+ if (aStart == aEnd)
+ return InvalidateCell(aStart, col);
+
+ int32_t last = LastVisibleRow();
+ if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last)
+ return NS_OK;
+
+ if (aStart < mTopRowIndex)
+ aStart = mTopRowIndex;
+
+ if (aEnd > last)
+ aEnd = last;
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive()) {
+ int32_t end =
+ mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0;
+ FireInvalidateEvent(aStart, end, aCol, aCol);
+ }
+#endif
+
+ nsRect rangeRect;
+ nsresult rv = col->GetRect(this,
+ mInnerBox.y+mRowHeight*(aStart-mTopRowIndex),
+ mRowHeight*(aEnd-aStart+1),
+ &rangeRect);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ InvalidateFrameWithRect(rangeRect);
+
+ return NS_OK;
+}
+
+static void
+FindScrollParts(nsIFrame* aCurrFrame, nsTreeBodyFrame::ScrollParts* aResult)
+{
+ if (!aResult->mColumnsScrollFrame) {
+ nsIScrollableFrame* f = do_QueryFrame(aCurrFrame);
+ if (f) {
+ aResult->mColumnsFrame = aCurrFrame;
+ aResult->mColumnsScrollFrame = f;
+ }
+ }
+
+ nsScrollbarFrame *sf = do_QueryFrame(aCurrFrame);
+ if (sf) {
+ if (!aCurrFrame->IsXULHorizontal()) {
+ if (!aResult->mVScrollbar) {
+ aResult->mVScrollbar = sf;
+ }
+ } else {
+ if (!aResult->mHScrollbar) {
+ aResult->mHScrollbar = sf;
+ }
+ }
+ // don't bother searching inside a scrollbar
+ return;
+ }
+
+ nsIFrame* child = aCurrFrame->PrincipalChildList().FirstChild();
+ while (child &&
+ !child->GetContent()->IsRootOfNativeAnonymousSubtree() &&
+ (!aResult->mVScrollbar || !aResult->mHScrollbar ||
+ !aResult->mColumnsScrollFrame)) {
+ FindScrollParts(child, aResult);
+ child = child->GetNextSibling();
+ }
+}
+
+nsTreeBodyFrame::ScrollParts nsTreeBodyFrame::GetScrollParts()
+{
+ ScrollParts result = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
+ nsIContent* baseElement = GetBaseElement();
+ nsIFrame* treeFrame =
+ baseElement ? baseElement->GetPrimaryFrame() : nullptr;
+ if (treeFrame) {
+ // The way we do this, searching through the entire frame subtree, is pretty
+ // dumb! We should know where these frames are.
+ FindScrollParts(treeFrame, &result);
+ if (result.mHScrollbar) {
+ result.mHScrollbar->SetScrollbarMediatorContent(GetContent());
+ nsIFrame* f = do_QueryFrame(result.mHScrollbar);
+ result.mHScrollbarContent = f->GetContent();
+ }
+ if (result.mVScrollbar) {
+ result.mVScrollbar->SetScrollbarMediatorContent(GetContent());
+ nsIFrame* f = do_QueryFrame(result.mVScrollbar);
+ result.mVScrollbarContent = f->GetContent();
+ }
+ }
+ return result;
+}
+
+void
+nsTreeBodyFrame::UpdateScrollbars(const ScrollParts& aParts)
+{
+ nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+
+ nsWeakFrame weakFrame(this);
+
+ if (aParts.mVScrollbar) {
+ nsAutoString curPos;
+ curPos.AppendInt(mTopRowIndex*rowHeightAsPixels);
+ aParts.mVScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true);
+ // 'this' might be deleted here
+ }
+
+ if (weakFrame.IsAlive() && aParts.mHScrollbar) {
+ nsAutoString curPos;
+ curPos.AppendInt(mHorzPosition);
+ aParts.mHScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true);
+ // 'this' might be deleted here
+ }
+
+ if (weakFrame.IsAlive() && mScrollbarActivity) {
+ mScrollbarActivity->ActivityOccurred();
+ }
+}
+
+void
+nsTreeBodyFrame::CheckOverflow(const ScrollParts& aParts)
+{
+ bool verticalOverflowChanged = false;
+ bool horizontalOverflowChanged = false;
+
+ if (!mVerticalOverflow && mRowCount > mPageLength) {
+ mVerticalOverflow = true;
+ verticalOverflowChanged = true;
+ }
+ else if (mVerticalOverflow && mRowCount <= mPageLength) {
+ mVerticalOverflow = false;
+ verticalOverflowChanged = true;
+ }
+
+ if (aParts.mColumnsFrame) {
+ nsRect bounds = aParts.mColumnsFrame->GetRect();
+ if (bounds.width != 0) {
+ /* Ignore overflows that are less than half a pixel. Yes these happen
+ all over the place when flex boxes are compressed real small.
+ Probably a result of a rounding errors somewhere in the layout code. */
+ bounds.width += nsPresContext::CSSPixelsToAppUnits(0.5f);
+ if (!mHorizontalOverflow && bounds.width < mHorzWidth) {
+ mHorizontalOverflow = true;
+ horizontalOverflowChanged = true;
+ } else if (mHorizontalOverflow && bounds.width >= mHorzWidth) {
+ mHorizontalOverflow = false;
+ horizontalOverflowChanged = true;
+ }
+ }
+ }
+
+ nsWeakFrame weakFrame(this);
+
+ RefPtr<nsPresContext> presContext = PresContext();
+ nsCOMPtr<nsIPresShell> presShell = presContext->GetPresShell();
+ nsCOMPtr<nsIContent> content = mContent;
+
+ if (verticalOverflowChanged) {
+ InternalScrollPortEvent event(true,
+ mVerticalOverflow ? eScrollPortOverflow : eScrollPortUnderflow,
+ nullptr);
+ event.mOrient = InternalScrollPortEvent::eVertical;
+ EventDispatcher::Dispatch(content, presContext, &event);
+ }
+
+ if (horizontalOverflowChanged) {
+ InternalScrollPortEvent event(true,
+ mHorizontalOverflow ? eScrollPortOverflow : eScrollPortUnderflow,
+ nullptr);
+ event.mOrient = InternalScrollPortEvent::eHorizontal;
+ EventDispatcher::Dispatch(content, presContext, &event);
+ }
+
+ // The synchronous event dispatch above can trigger reflow notifications.
+ // Flush those explicitly now, so that we can guard against potential infinite
+ // recursion. See bug 905909.
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ NS_ASSERTION(!mCheckingOverflow, "mCheckingOverflow should not already be set");
+ // Don't use AutoRestore since we want to not touch mCheckingOverflow if we fail
+ // the weakFrame.IsAlive() check below
+ mCheckingOverflow = true;
+ presShell->FlushPendingNotifications(Flush_Layout);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ mCheckingOverflow = false;
+}
+
+void
+nsTreeBodyFrame::InvalidateScrollbars(const ScrollParts& aParts, nsWeakFrame& aWeakColumnsFrame)
+{
+ if (mUpdateBatchNest || !mView)
+ return;
+ nsWeakFrame weakFrame(this);
+
+ if (aParts.mVScrollbar) {
+ // Do Vertical Scrollbar
+ nsAutoString maxposStr;
+
+ nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+
+ int32_t size = rowHeightAsPixels * (mRowCount > mPageLength ? mRowCount - mPageLength : 0);
+ maxposStr.AppendInt(size);
+ aParts.mVScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos, maxposStr, true);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ // Also set our page increment and decrement.
+ nscoord pageincrement = mPageLength*rowHeightAsPixels;
+ nsAutoString pageStr;
+ pageStr.AppendInt(pageincrement);
+ aParts.mVScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true);
+ ENSURE_TRUE(weakFrame.IsAlive());
+ }
+
+ if (aParts.mHScrollbar && aParts.mColumnsFrame && aWeakColumnsFrame.IsAlive()) {
+ // And now Horizontal scrollbar
+ nsRect bounds = aParts.mColumnsFrame->GetRect();
+ nsAutoString maxposStr;
+
+ maxposStr.AppendInt(mHorzWidth > bounds.width ? mHorzWidth - bounds.width : 0);
+ aParts.mHScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos, maxposStr, true);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ nsAutoString pageStr;
+ pageStr.AppendInt(bounds.width);
+ aParts.mHScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true);
+ ENSURE_TRUE(weakFrame.IsAlive());
+
+ pageStr.Truncate();
+ pageStr.AppendInt(nsPresContext::CSSPixelsToAppUnits(16));
+ aParts.mHScrollbarContent->
+ SetAttr(kNameSpaceID_None, nsGkAtoms::increment, pageStr, true);
+ }
+
+ if (weakFrame.IsAlive() && mScrollbarActivity) {
+ mScrollbarActivity->ActivityOccurred();
+ }
+}
+
+// Takes client x/y in pixels, converts them to appunits, and converts into
+// values relative to this nsTreeBodyFrame frame.
+nsPoint
+nsTreeBodyFrame::AdjustClientCoordsToBoxCoordSpace(int32_t aX, int32_t aY)
+{
+ nsPoint point(nsPresContext::CSSPixelsToAppUnits(aX),
+ nsPresContext::CSSPixelsToAppUnits(aY));
+
+ nsPresContext* presContext = PresContext();
+ point -= GetOffsetTo(presContext->GetPresShell()->GetRootFrame());
+
+ // Adjust by the inner box coords, so that we're in the inner box's
+ // coordinate space.
+ point -= mInnerBox.TopLeft();
+ return point;
+} // AdjustClientCoordsToBoxCoordSpace
+
+nsresult
+nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY, int32_t* _retval)
+{
+ if (!mView)
+ return NS_OK;
+
+ nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);
+
+ // Check if the coordinates are above our visible space.
+ if (point.y < 0) {
+ *_retval = -1;
+ return NS_OK;
+ }
+
+ *_retval = GetRowAt(point.x, point.y);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::GetCellAt(int32_t aX, int32_t aY, int32_t* aRow, nsITreeColumn** aCol,
+ nsACString& aChildElt)
+{
+ if (!mView)
+ return NS_OK;
+
+ nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);
+
+ // Check if the coordinates are above our visible space.
+ if (point.y < 0) {
+ *aRow = -1;
+ return NS_OK;
+ }
+
+ nsTreeColumn* col;
+ nsIAtom* child;
+ GetCellAt(point.x, point.y, aRow, &col, &child);
+
+ if (col) {
+ NS_ADDREF(*aCol = col);
+ if (child == nsCSSAnonBoxes::moztreecell)
+ aChildElt.AssignLiteral("cell");
+ else if (child == nsCSSAnonBoxes::moztreetwisty)
+ aChildElt.AssignLiteral("twisty");
+ else if (child == nsCSSAnonBoxes::moztreeimage)
+ aChildElt.AssignLiteral("image");
+ else if (child == nsCSSAnonBoxes::moztreecelltext)
+ aChildElt.AssignLiteral("text");
+ }
+
+ return NS_OK;
+}
+
+
+//
+// GetCoordsForCellItem
+//
+// Find the x/y location and width/height (all in PIXELS) of the given object
+// in the given column.
+//
+// XXX IMPORTANT XXX:
+// Hyatt says in the bug for this, that the following needs to be done:
+// (1) You need to deal with overflow when computing cell rects. See other column
+// iteration examples... if you don't deal with this, you'll mistakenly extend the
+// cell into the scrollbar's rect.
+//
+// (2) You are adjusting the cell rect by the *row" border padding. That's
+// wrong. You need to first adjust a row rect by its border/padding, and then the
+// cell rect fits inside the adjusted row rect. It also can have border/padding
+// as well as margins. The vertical direction isn't that important, but you need
+// to get the horizontal direction right.
+//
+// (3) GetImageSize() does not include margins (but it does include border/padding).
+// You need to make sure to add in the image's margins as well.
+//
+nsresult
+nsTreeBodyFrame::GetCoordsForCellItem(int32_t aRow, nsITreeColumn* aCol, const nsACString& aElement,
+ int32_t *aX, int32_t *aY, int32_t *aWidth, int32_t *aHeight)
+{
+ *aX = 0;
+ *aY = 0;
+ *aWidth = 0;
+ *aHeight = 0;
+
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ nscoord currX = mInnerBox.x - mHorzPosition;
+
+ // The Rect for the requested item.
+ nsRect theRect;
+
+ nsPresContext* presContext = PresContext();
+
+ for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; currCol = currCol->GetNext()) {
+
+ // The Rect for the current cell.
+ nscoord colWidth;
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ currCol->GetWidthInTwips(this, &colWidth);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "invalid column");
+
+ nsRect cellRect(currX, mInnerBox.y + mRowHeight * (aRow - mTopRowIndex),
+ colWidth, mRowHeight);
+
+ // Check the ID of the current column to see if it matches. If it doesn't
+ // increment the current X value and continue to the next column.
+ if (currCol != aCol) {
+ currX += cellRect.width;
+ continue;
+ }
+ // Now obtain the properties for our cell.
+ PrefillPropertyArray(aRow, currCol);
+
+ nsAutoString properties;
+ mView->GetCellProperties(aRow, currCol, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+
+ // We don't want to consider any of the decorations that may be present
+ // on the current row, so we have to deflate the rect by the border and
+ // padding and offset its left and top coordinates appropriately.
+ AdjustForBorderPadding(rowContext, cellRect);
+
+ nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
+
+ NS_NAMED_LITERAL_CSTRING(cell, "cell");
+ if (currCol->IsCycler() || cell.Equals(aElement)) {
+ // If the current Column is a Cycler, then the Rect is just the cell - the margins.
+ // Similarly, if we're just being asked for the cell rect, provide it.
+
+ theRect = cellRect;
+ nsMargin cellMargin;
+ cellContext->StyleMargin()->GetMargin(cellMargin);
+ theRect.Deflate(cellMargin);
+ break;
+ }
+
+ // Since we're not looking for the cell, and since the cell isn't a cycler,
+ // we're looking for some subcomponent, and now we need to subtract the
+ // borders and padding of the cell from cellRect so this does not
+ // interfere with our computations.
+ AdjustForBorderPadding(cellContext, cellRect);
+
+ nsRenderingContext rc(
+ presContext->PresShell()->CreateReferenceRenderingContext());
+
+ // Now we'll start making our way across the cell, starting at the edge of
+ // the cell and proceeding until we hit the right edge. |cellX| is the
+ // working X value that we will increment as we crawl from left to right.
+ nscoord cellX = cellRect.x;
+ nscoord remainWidth = cellRect.width;
+
+ if (currCol->IsPrimary()) {
+ // If the current Column is a Primary, then we need to take into account the indentation
+ // and possibly a twisty.
+
+ // The amount of indentation is the indentation width (|mIndentation|) by the level.
+ int32_t level;
+ mView->GetLevel(aRow, &level);
+ if (!isRTL)
+ cellX += mIndentation * level;
+ remainWidth -= mIndentation * level;
+
+ // Find the twisty rect by computing its size.
+ nsRect imageRect;
+ nsRect twistyRect(cellRect);
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+ GetTwistyRect(aRow, currCol, imageRect, twistyRect, presContext,
+ twistyContext);
+
+ if (NS_LITERAL_CSTRING("twisty").Equals(aElement)) {
+ // If we're looking for the twisty Rect, just return the size
+ theRect = twistyRect;
+ break;
+ }
+
+ // Now we need to add in the margins of the twisty element, so that we
+ // can find the offset of the next element in the cell.
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+
+ // Adjust our working X value with the twisty width (image size, margins,
+ // borders, padding.
+ if (!isRTL)
+ cellX += twistyRect.width;
+ }
+
+ // Cell Image
+ nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
+
+ nsRect imageSize = GetImageSize(aRow, currCol, false, imageContext);
+ if (NS_LITERAL_CSTRING("image").Equals(aElement)) {
+ theRect = imageSize;
+ theRect.x = cellX;
+ theRect.y = cellRect.y;
+ break;
+ }
+
+ // Add in the margins of the cell image.
+ nsMargin imageMargin;
+ imageContext->StyleMargin()->GetMargin(imageMargin);
+ imageSize.Inflate(imageMargin);
+
+ // Increment cellX by the image width
+ if (!isRTL)
+ cellX += imageSize.width;
+
+ // Cell Text
+ nsAutoString cellText;
+ mView->GetCellText(aRow, currCol, cellText);
+ // We're going to measure this text so we need to ensure bidi is enabled if
+ // necessary
+ CheckTextForBidi(cellText);
+
+ // Create a scratch rect to represent the text rectangle, with the current
+ // X and Y coords, and a guess at the width and height. The width is the
+ // remaining width we have left to traverse in the cell, which will be the
+ // widest possible value for the text rect, and the row height.
+ nsRect textRect(cellX, cellRect.y, remainWidth, cellRect.height);
+
+ // Measure the width of the text. If the width of the text is greater than
+ // the remaining width available, then we just assume that the text has
+ // been cropped and use the remaining rect as the text Rect. Otherwise,
+ // we add in borders and padding to the text dimension and give that back.
+ nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForStyleContext(textContext);
+ nscoord height = fm->MaxHeight();
+
+ nsMargin textMargin;
+ textContext->StyleMargin()->GetMargin(textMargin);
+ textRect.Deflate(textMargin);
+
+ // Center the text. XXX Obey vertical-align style prop?
+ if (height < textRect.height) {
+ textRect.y += (textRect.height - height) / 2;
+ textRect.height = height;
+ }
+
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(textContext, bp);
+ textRect.height += bp.top + bp.bottom;
+
+ AdjustForCellText(cellText, aRow, currCol, rc, *fm, textRect);
+
+ theRect = textRect;
+ }
+
+ if (isRTL)
+ theRect.x = mInnerBox.width - theRect.x - theRect.width;
+
+ *aX = nsPresContext::AppUnitsToIntCSSPixels(theRect.x);
+ *aY = nsPresContext::AppUnitsToIntCSSPixels(theRect.y);
+ *aWidth = nsPresContext::AppUnitsToIntCSSPixels(theRect.width);
+ *aHeight = nsPresContext::AppUnitsToIntCSSPixels(theRect.height);
+
+ return NS_OK;
+}
+
+int32_t
+nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY)
+{
+ // Now just mod by our total inner box height and add to our top row index.
+ int32_t row = (aY/mRowHeight)+mTopRowIndex;
+
+ // Check if the coordinates are below our visible space (or within our visible
+ // space but below any row).
+ if (row > mTopRowIndex + mPageLength || row >= mRowCount)
+ return -1;
+
+ return row;
+}
+
+void
+nsTreeBodyFrame::CheckTextForBidi(nsAutoString& aText)
+{
+ // We could check to see whether the prescontext already has bidi enabled,
+ // but usually it won't, so it's probably faster to avoid the call to
+ // GetPresContext() when it's not needed.
+ if (HasRTLChars(aText)) {
+ PresContext()->SetBidiEnabled();
+ }
+}
+
+void
+nsTreeBodyFrame::AdjustForCellText(nsAutoString& aText,
+ int32_t aRowIndex, nsTreeColumn* aColumn,
+ nsRenderingContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics,
+ nsRect& aTextRect)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ nscoord maxWidth = aTextRect.width;
+ bool widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(aText,
+ aFontMetrics,
+ drawTarget,
+ maxWidth);
+
+ if (aColumn->Overflow()) {
+ DebugOnly<nsresult> rv;
+ nsTreeColumn* nextColumn = aColumn->GetNext();
+ while (nextColumn && widthIsGreater) {
+ while (nextColumn) {
+ nscoord width;
+ rv = nextColumn->GetWidthInTwips(this, &width);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
+
+ if (width != 0)
+ break;
+
+ nextColumn = nextColumn->GetNext();
+ }
+
+ if (nextColumn) {
+ nsAutoString nextText;
+ mView->GetCellText(aRowIndex, nextColumn, nextText);
+ // We don't measure or draw this text so no need to check it for
+ // bidi-ness
+
+ if (nextText.Length() == 0) {
+ nscoord width;
+ rv = nextColumn->GetWidthInTwips(this, &width);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
+
+ maxWidth += width;
+ widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(aText,
+ aFontMetrics,
+ drawTarget,
+ maxWidth);
+
+ nextColumn = nextColumn->GetNext();
+ }
+ else {
+ nextColumn = nullptr;
+ }
+ }
+ }
+ }
+
+ nscoord width;
+ if (widthIsGreater) {
+ // See if the width is even smaller than the ellipsis
+ // If so, clear the text completely.
+ const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
+ aFontMetrics.SetTextRunRTL(false);
+ nscoord ellipsisWidth =
+ nsLayoutUtils::AppUnitWidthOfString(kEllipsis, aFontMetrics, drawTarget);
+
+ width = maxWidth;
+ if (ellipsisWidth > width)
+ aText.SetLength(0);
+ else if (ellipsisWidth == width)
+ aText.Assign(kEllipsis);
+ else {
+ // We will be drawing an ellipsis, thank you very much.
+ // Subtract out the required width of the ellipsis.
+ // This is the total remaining width we have to play with.
+ width -= ellipsisWidth;
+
+ // Now we crop.
+ switch (aColumn->GetCropStyle()) {
+ default:
+ case 0: {
+ // Crop right.
+ nscoord cwidth;
+ nscoord twidth = 0;
+ uint32_t length = aText.Length();
+ uint32_t i;
+ for (i = 0; i < length; ++i) {
+ char16_t ch = aText[i];
+ // XXX this is horrible and doesn't handle clusters
+ cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
+ drawTarget);
+ if (twidth + cwidth > width)
+ break;
+ twidth += cwidth;
+ }
+ aText.Truncate(i);
+ aText.Append(kEllipsis);
+ }
+ break;
+
+ case 2: {
+ // Crop left.
+ nscoord cwidth;
+ nscoord twidth = 0;
+ int32_t length = aText.Length();
+ int32_t i;
+ for (i=length-1; i >= 0; --i) {
+ char16_t ch = aText[i];
+ cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
+ drawTarget);
+ if (twidth + cwidth > width)
+ break;
+ twidth += cwidth;
+ }
+
+ nsAutoString copy;
+ aText.Right(copy, length-1-i);
+ aText.Assign(kEllipsis);
+ aText += copy;
+ }
+ break;
+
+ case 1:
+ {
+ // Crop center.
+ nsAutoString leftStr, rightStr;
+ nscoord cwidth, twidth = 0;
+ int32_t length = aText.Length();
+ int32_t rightPos = length - 1;
+ for (int32_t leftPos = 0; leftPos < rightPos; ++leftPos) {
+ char16_t ch = aText[leftPos];
+ cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
+ drawTarget);
+ twidth += cwidth;
+ if (twidth > width)
+ break;
+ leftStr.Append(ch);
+
+ ch = aText[rightPos];
+ cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
+ drawTarget);
+ twidth += cwidth;
+ if (twidth > width)
+ break;
+ rightStr.Insert(ch, 0);
+ --rightPos;
+ }
+ aText = leftStr;
+ aText.Append(kEllipsis);
+ aText += rightStr;
+ }
+ break;
+ }
+ }
+ }
+
+ width = nsLayoutUtils::AppUnitWidthOfStringBidi(aText, this, aFontMetrics,
+ aRenderingContext);
+
+ switch (aColumn->GetTextAlignment()) {
+ case NS_STYLE_TEXT_ALIGN_RIGHT: {
+ aTextRect.x += aTextRect.width - width;
+ }
+ break;
+ case NS_STYLE_TEXT_ALIGN_CENTER: {
+ aTextRect.x += (aTextRect.width - width) / 2;
+ }
+ break;
+ }
+
+ aTextRect.width = width;
+}
+
+nsIAtom*
+nsTreeBodyFrame::GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect,
+ int32_t aRowIndex,
+ nsTreeColumn* aColumn)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Obtain the properties for our cell.
+ PrefillPropertyArray(aRowIndex, aColumn);
+ nsAutoString properties;
+ mView->GetCellProperties(aRowIndex, aColumn, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ // Resolve style for the cell.
+ nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
+
+ // Obtain the margins for the cell and then deflate our rect by that
+ // amount. The cell is assumed to be contained within the deflated rect.
+ nsRect cellRect(aCellRect);
+ nsMargin cellMargin;
+ cellContext->StyleMargin()->GetMargin(cellMargin);
+ cellRect.Deflate(cellMargin);
+
+ // Adjust the rect for its border and padding.
+ AdjustForBorderPadding(cellContext, cellRect);
+
+ if (aX < cellRect.x || aX >= cellRect.x + cellRect.width) {
+ // The user clicked within the cell's margins/borders/padding. This constitutes a click on the cell.
+ return nsCSSAnonBoxes::moztreecell;
+ }
+
+ nscoord currX = cellRect.x;
+ nscoord remainingWidth = cellRect.width;
+
+ // Handle right alignment hit testing.
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+
+ nsPresContext* presContext = PresContext();
+ nsRenderingContext rc(
+ presContext->PresShell()->CreateReferenceRenderingContext());
+
+ if (aColumn->IsPrimary()) {
+ // If we're the primary column, we have indentation and a twisty.
+ int32_t level;
+ mView->GetLevel(aRowIndex, &level);
+
+ if (!isRTL)
+ currX += mIndentation*level;
+ remainingWidth -= mIndentation*level;
+
+ if ((isRTL && aX > currX + remainingWidth) ||
+ (!isRTL && aX < currX)) {
+ // The user clicked within the indentation.
+ return nsCSSAnonBoxes::moztreecell;
+ }
+
+ // Always leave space for the twisty.
+ nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
+ bool hasTwisty = false;
+ bool isContainer = false;
+ mView->IsContainer(aRowIndex, &isContainer);
+ if (isContainer) {
+ bool isContainerEmpty = false;
+ mView->IsContainerEmpty(aRowIndex, &isContainerEmpty);
+ if (!isContainerEmpty)
+ hasTwisty = true;
+ }
+
+ // Resolve style for the twisty.
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+
+ nsRect imageSize;
+ GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, presContext,
+ twistyContext);
+
+ // We will treat a click as hitting the twisty if it happens on the margins, borders, padding,
+ // or content of the twisty object. By allowing a "slop" into the margin, we make it a little
+ // bit easier for a user to hit the twisty. (We don't want to be too picky here.)
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+ if (isRTL)
+ twistyRect.x = currX + remainingWidth - twistyRect.width;
+
+ // Now we test to see if aX is actually within the twistyRect. If it is, and if the item should
+ // have a twisty, then we return "twisty". If it is within the rect but we shouldn't have a twisty,
+ // then we return "cell".
+ if (aX >= twistyRect.x && aX < twistyRect.x + twistyRect.width) {
+ if (hasTwisty)
+ return nsCSSAnonBoxes::moztreetwisty;
+ else
+ return nsCSSAnonBoxes::moztreecell;
+ }
+
+ if (!isRTL)
+ currX += twistyRect.width;
+ remainingWidth -= twistyRect.width;
+ }
+
+ // Now test to see if the user hit the icon for the cell.
+ nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);
+
+ // Resolve style for the image.
+ nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
+
+ nsRect iconSize = GetImageSize(aRowIndex, aColumn, false, imageContext);
+ nsMargin imageMargin;
+ imageContext->StyleMargin()->GetMargin(imageMargin);
+ iconSize.Inflate(imageMargin);
+ iconRect.width = iconSize.width;
+ if (isRTL)
+ iconRect.x = currX + remainingWidth - iconRect.width;
+
+ if (aX >= iconRect.x && aX < iconRect.x + iconRect.width) {
+ // The user clicked on the image.
+ return nsCSSAnonBoxes::moztreeimage;
+ }
+
+ if (!isRTL)
+ currX += iconRect.width;
+ remainingWidth -= iconRect.width;
+
+ nsAutoString cellText;
+ mView->GetCellText(aRowIndex, aColumn, cellText);
+ // We're going to measure this text so we need to ensure bidi is enabled if
+ // necessary
+ CheckTextForBidi(cellText);
+
+ nsRect textRect(currX, cellRect.y, remainingWidth, cellRect.height);
+
+ nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
+
+ nsMargin textMargin;
+ textContext->StyleMargin()->GetMargin(textMargin);
+ textRect.Deflate(textMargin);
+
+ AdjustForBorderPadding(textContext, textRect);
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForStyleContext(textContext);
+ AdjustForCellText(cellText, aRowIndex, aColumn, rc, *fm, textRect);
+
+ if (aX >= textRect.x && aX < textRect.x + textRect.width)
+ return nsCSSAnonBoxes::moztreecelltext;
+ else
+ return nsCSSAnonBoxes::moztreecell;
+}
+
+void
+nsTreeBodyFrame::GetCellAt(nscoord aX, nscoord aY, int32_t* aRow,
+ nsTreeColumn** aCol, nsIAtom** aChildElt)
+{
+ *aCol = nullptr;
+ *aChildElt = nullptr;
+
+ *aRow = GetRowAt(aX, aY);
+ if (*aRow < 0)
+ return;
+
+ // Determine the column hit.
+ for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
+ currCol = currCol->GetNext()) {
+ nsRect cellRect;
+ nsresult rv = currCol->GetRect(this,
+ mInnerBox.y +
+ mRowHeight * (*aRow - mTopRowIndex),
+ mRowHeight,
+ &cellRect);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("column has no frame");
+ continue;
+ }
+
+ if (!OffsetForHorzScroll(cellRect, false))
+ continue;
+
+ if (aX >= cellRect.x && aX < cellRect.x + cellRect.width) {
+ // We know the column hit now.
+ *aCol = currCol;
+
+ if (currCol->IsCycler())
+ // Cyclers contain only images. Fill this in immediately and return.
+ *aChildElt = nsCSSAnonBoxes::moztreeimage;
+ else
+ *aChildElt = GetItemWithinCellAt(aX, cellRect, *aRow, currCol);
+ break;
+ }
+ }
+}
+
+nsresult
+nsTreeBodyFrame::GetCellWidth(int32_t aRow, nsTreeColumn* aCol,
+ nsRenderingContext* aRenderingContext,
+ nscoord& aDesiredSize, nscoord& aCurrentSize)
+{
+ NS_PRECONDITION(aCol, "aCol must not be null");
+ NS_PRECONDITION(aRenderingContext, "aRenderingContext must not be null");
+
+ // The rect for the current cell.
+ nscoord colWidth;
+ nsresult rv = aCol->GetWidthInTwips(this, &colWidth);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsRect cellRect(0, 0, colWidth, mRowHeight);
+
+ int32_t overflow = cellRect.x+cellRect.width-(mInnerBox.x+mInnerBox.width);
+ if (overflow > 0)
+ cellRect.width -= overflow;
+
+ // Adjust borders and padding for the cell.
+ nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(cellContext, bp);
+
+ aCurrentSize = cellRect.width;
+ aDesiredSize = bp.left + bp.right;
+
+ if (aCol->IsPrimary()) {
+ // If the current Column is a Primary, then we need to take into account
+ // the indentation and possibly a twisty.
+
+ // The amount of indentation is the indentation width (|mIndentation|) by the level.
+ int32_t level;
+ mView->GetLevel(aRow, &level);
+ aDesiredSize += mIndentation * level;
+
+ // Find the twisty rect by computing its size.
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+
+ nsRect imageSize;
+ nsRect twistyRect(cellRect);
+ GetTwistyRect(aRow, aCol, imageSize, twistyRect, PresContext(),
+ twistyContext);
+
+ // Add in the margins of the twisty element.
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+
+ aDesiredSize += twistyRect.width;
+ }
+
+ nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
+
+ // Account for the width of the cell image.
+ nsRect imageSize = GetImageSize(aRow, aCol, false, imageContext);
+ // Add in the margins of the cell image.
+ nsMargin imageMargin;
+ imageContext->StyleMargin()->GetMargin(imageMargin);
+ imageSize.Inflate(imageMargin);
+
+ aDesiredSize += imageSize.width;
+
+ // Get the cell text.
+ nsAutoString cellText;
+ mView->GetCellText(aRow, aCol, cellText);
+ // We're going to measure this text so we need to ensure bidi is enabled if
+ // necessary
+ CheckTextForBidi(cellText);
+
+ nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
+
+ // Get the borders and padding for the text.
+ GetBorderPadding(textContext, bp);
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForStyleContext(textContext);
+ // Get the width of the text itself
+ nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(cellText, this, *fm,
+ *aRenderingContext);
+ nscoord totalTextWidth = width + bp.left + bp.right;
+ aDesiredSize += totalTextWidth;
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::IsCellCropped(int32_t aRow, nsITreeColumn* aCol, bool *_retval)
+{
+ nscoord currentSize, desiredSize;
+ nsresult rv;
+
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+ nsRenderingContext rc(
+ PresContext()->PresShell()->CreateReferenceRenderingContext());
+
+ rv = GetCellWidth(aRow, col, &rc, desiredSize, currentSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = desiredSize > currentSize;
+
+ return NS_OK;
+}
+
+void
+nsTreeBodyFrame::MarkDirtyIfSelect()
+{
+ nsIContent* baseElement = GetBaseElement();
+
+ if (baseElement && baseElement->IsHTMLElement(nsGkAtoms::select)) {
+ // If we are an intrinsically sized select widget, we may need to
+ // resize, if the widest item was removed or a new item was added.
+ // XXX optimize this more
+
+ mStringWidth = -1;
+ PresContext()->PresShell()->FrameNeedsReflow(this,
+ nsIPresShell::eTreeChange,
+ NS_FRAME_IS_DIRTY);
+ }
+}
+
+nsresult
+nsTreeBodyFrame::CreateTimer(const LookAndFeel::IntID aID,
+ nsTimerCallbackFunc aFunc, int32_t aType,
+ nsITimer** aTimer)
+{
+ // Get the delay from the look and feel service.
+ int32_t delay = LookAndFeel::GetInt(aID, 0);
+
+ nsCOMPtr<nsITimer> timer;
+
+ // Create a new timer only if the delay is greater than zero.
+ // Zero value means that this feature is completely disabled.
+ if (delay > 0) {
+ timer = do_CreateInstance("@mozilla.org/timer;1");
+ if (timer)
+ timer->InitWithFuncCallback(aFunc, this, delay, aType);
+ }
+
+ NS_IF_ADDREF(*aTimer = timer);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::RowCountChanged(int32_t aIndex, int32_t aCount)
+{
+ if (aCount == 0 || !mView)
+ return NS_OK; // Nothing to do.
+
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive())
+ FireRowCountChangedEvent(aIndex, aCount);
+#endif
+
+ // Adjust our selection.
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel)
+ sel->AdjustSelection(aIndex, aCount);
+
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ mRowCount += aCount;
+#ifdef DEBUG
+ int32_t rowCount = mRowCount;
+ mView->GetRowCount(&rowCount);
+ NS_ASSERTION(rowCount == mRowCount, "row count did not change by the amount suggested, check caller");
+#endif
+
+ int32_t count = Abs(aCount);
+ int32_t last = LastVisibleRow();
+ if (aIndex >= mTopRowIndex && aIndex <= last)
+ InvalidateRange(aIndex, last);
+
+ ScrollParts parts = GetScrollParts();
+
+ if (mTopRowIndex == 0) {
+ // Just update the scrollbar and return.
+ if (FullScrollbarsUpdate(false)) {
+ MarkDirtyIfSelect();
+ }
+ return NS_OK;
+ }
+
+ bool needsInvalidation = false;
+ // Adjust our top row index.
+ if (aCount > 0) {
+ if (mTopRowIndex > aIndex) {
+ // Rows came in above us. Augment the top row index.
+ mTopRowIndex += aCount;
+ }
+ }
+ else if (aCount < 0) {
+ if (mTopRowIndex > aIndex+count-1) {
+ // No need to invalidate. The remove happened
+ // completely above us (offscreen).
+ mTopRowIndex -= count;
+ }
+ else if (mTopRowIndex >= aIndex) {
+ // This is a full-blown invalidate.
+ if (mTopRowIndex + mPageLength > mRowCount - 1) {
+ mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength);
+ }
+ needsInvalidation = true;
+ }
+ }
+
+ if (FullScrollbarsUpdate(needsInvalidation)) {
+ MarkDirtyIfSelect();
+ }
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::BeginUpdateBatch()
+{
+ ++mUpdateBatchNest;
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::EndUpdateBatch()
+{
+ NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch");
+
+ if (--mUpdateBatchNest == 0) {
+ if (mView) {
+ Invalidate();
+ int32_t countBeforeUpdate = mRowCount;
+ mView->GetRowCount(&mRowCount);
+ if (countBeforeUpdate != mRowCount) {
+ if (mTopRowIndex + mPageLength > mRowCount - 1) {
+ mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength);
+ }
+ FullScrollbarsUpdate(false);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsTreeBodyFrame::PrefillPropertyArray(int32_t aRowIndex, nsTreeColumn* aCol)
+{
+ NS_PRECONDITION(!aCol || aCol->GetFrame(), "invalid column passed");
+ mScratchArray.Clear();
+
+ // focus
+ if (mFocused)
+ mScratchArray.AppendElement(nsGkAtoms::focus);
+
+ // sort
+ bool sorted = false;
+ mView->IsSorted(&sorted);
+ if (sorted)
+ mScratchArray.AppendElement(nsGkAtoms::sorted);
+
+ // drag session
+ if (mSlots && mSlots->mIsDragging)
+ mScratchArray.AppendElement(nsGkAtoms::dragSession);
+
+ if (aRowIndex != -1) {
+ if (aRowIndex == mMouseOverRow)
+ mScratchArray.AppendElement(nsGkAtoms::hover);
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mView->GetSelection(getter_AddRefs(selection));
+
+ if (selection) {
+ // selected
+ bool isSelected;
+ selection->IsSelected(aRowIndex, &isSelected);
+ if (isSelected)
+ mScratchArray.AppendElement(nsGkAtoms::selected);
+
+ // current
+ int32_t currentIndex;
+ selection->GetCurrentIndex(&currentIndex);
+ if (aRowIndex == currentIndex)
+ mScratchArray.AppendElement(nsGkAtoms::current);
+
+ // active
+ if (aCol) {
+ nsCOMPtr<nsITreeColumn> currentColumn;
+ selection->GetCurrentColumn(getter_AddRefs(currentColumn));
+ if (aCol == currentColumn)
+ mScratchArray.AppendElement(nsGkAtoms::active);
+ }
+ }
+
+ // container or leaf
+ bool isContainer = false;
+ mView->IsContainer(aRowIndex, &isContainer);
+ if (isContainer) {
+ mScratchArray.AppendElement(nsGkAtoms::container);
+
+ // open or closed
+ bool isOpen = false;
+ mView->IsContainerOpen(aRowIndex, &isOpen);
+ if (isOpen)
+ mScratchArray.AppendElement(nsGkAtoms::open);
+ else
+ mScratchArray.AppendElement(nsGkAtoms::closed);
+ }
+ else {
+ mScratchArray.AppendElement(nsGkAtoms::leaf);
+ }
+
+ // drop orientation
+ if (mSlots && mSlots->mDropAllowed && mSlots->mDropRow == aRowIndex) {
+ if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE)
+ mScratchArray.AppendElement(nsGkAtoms::dropBefore);
+ else if (mSlots->mDropOrient == nsITreeView::DROP_ON)
+ mScratchArray.AppendElement(nsGkAtoms::dropOn);
+ else if (mSlots->mDropOrient == nsITreeView::DROP_AFTER)
+ mScratchArray.AppendElement(nsGkAtoms::dropAfter);
+ }
+
+ // odd or even
+ if (aRowIndex % 2)
+ mScratchArray.AppendElement(nsGkAtoms::odd);
+ else
+ mScratchArray.AppendElement(nsGkAtoms::even);
+
+ nsIContent* baseContent = GetBaseElement();
+ if (baseContent && baseContent->HasAttr(kNameSpaceID_None, nsGkAtoms::editing))
+ mScratchArray.AppendElement(nsGkAtoms::editing);
+
+ // multiple columns
+ if (mColumns->GetColumnAt(1))
+ mScratchArray.AppendElement(nsGkAtoms::multicol);
+ }
+
+ if (aCol) {
+ mScratchArray.AppendElement(aCol->GetAtom());
+
+ if (aCol->IsPrimary())
+ mScratchArray.AppendElement(nsGkAtoms::primary);
+
+ if (aCol->GetType() == nsITreeColumn::TYPE_CHECKBOX) {
+ mScratchArray.AppendElement(nsGkAtoms::checkbox);
+
+ if (aRowIndex != -1) {
+ nsAutoString value;
+ mView->GetCellValue(aRowIndex, aCol, value);
+ if (value.EqualsLiteral("true"))
+ mScratchArray.AppendElement(nsGkAtoms::checked);
+ }
+ }
+ else if (aCol->GetType() == nsITreeColumn::TYPE_PROGRESSMETER) {
+ mScratchArray.AppendElement(nsGkAtoms::progressmeter);
+
+ if (aRowIndex != -1) {
+ int32_t state;
+ mView->GetProgressMode(aRowIndex, aCol, &state);
+ if (state == nsITreeView::PROGRESS_NORMAL)
+ mScratchArray.AppendElement(nsGkAtoms::progressNormal);
+ else if (state == nsITreeView::PROGRESS_UNDETERMINED)
+ mScratchArray.AppendElement(nsGkAtoms::progressUndetermined);
+ }
+ }
+
+ // Read special properties from attributes on the column content node
+ if (aCol->mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::insertbefore,
+ nsGkAtoms::_true, eCaseMatters))
+ mScratchArray.AppendElement(nsGkAtoms::insertbefore);
+ if (aCol->mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::insertafter,
+ nsGkAtoms::_true, eCaseMatters))
+ mScratchArray.AppendElement(nsGkAtoms::insertafter);
+ }
+}
+
+nsITheme*
+nsTreeBodyFrame::GetTwistyRect(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ nsRect& aImageRect,
+ nsRect& aTwistyRect,
+ nsPresContext* aPresContext,
+ nsStyleContext* aTwistyContext)
+{
+ // The twisty rect extends all the way to the end of the cell. This is incorrect. We need to
+ // determine the twisty rect's true width. This is done by examining the style context for
+ // a width first. If it has one, we use that. If it doesn't, we use the image's natural width.
+ // If the image hasn't loaded and if no width is specified, then we just bail. If there is
+ // a -moz-appearance involved, adjust the rect by the minimum widget size provided by
+ // the theme implementation.
+ aImageRect = GetImageSize(aRowIndex, aColumn, true, aTwistyContext);
+ if (aImageRect.height > aTwistyRect.height)
+ aImageRect.height = aTwistyRect.height;
+ if (aImageRect.width > aTwistyRect.width)
+ aImageRect.width = aTwistyRect.width;
+ else
+ aTwistyRect.width = aImageRect.width;
+
+ bool useTheme = false;
+ nsITheme *theme = nullptr;
+ const nsStyleDisplay* twistyDisplayData = aTwistyContext->StyleDisplay();
+ if (twistyDisplayData->mAppearance) {
+ theme = aPresContext->GetTheme();
+ if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, twistyDisplayData->mAppearance))
+ useTheme = true;
+ }
+
+ if (useTheme) {
+ LayoutDeviceIntSize minTwistySizePx;
+ bool canOverride = true;
+ theme->GetMinimumWidgetSize(aPresContext, this, twistyDisplayData->mAppearance,
+ &minTwistySizePx, &canOverride);
+
+ // GMWS() returns size in pixels, we need to convert it back to app units
+ nsSize minTwistySize;
+ minTwistySize.width = aPresContext->DevPixelsToAppUnits(minTwistySizePx.width);
+ minTwistySize.height = aPresContext->DevPixelsToAppUnits(minTwistySizePx.height);
+
+ if (aTwistyRect.width < minTwistySize.width || !canOverride)
+ aTwistyRect.width = minTwistySize.width;
+ }
+
+ return useTheme ? theme : nullptr;
+}
+
+nsresult
+nsTreeBodyFrame::GetImage(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
+ nsStyleContext* aStyleContext, bool& aAllowImageRegions, imgIContainer** aResult)
+{
+ *aResult = nullptr;
+
+ nsAutoString imageSrc;
+ mView->GetImageSrc(aRowIndex, aCol, imageSrc);
+ RefPtr<imgRequestProxy> styleRequest;
+ if (!aUseContext && !imageSrc.IsEmpty()) {
+ aAllowImageRegions = false;
+ }
+ else {
+ // Obtain the URL from the style context.
+ aAllowImageRegions = true;
+ styleRequest = aStyleContext->StyleList()->GetListStyleImage();
+ if (!styleRequest)
+ return NS_OK;
+ nsCOMPtr<nsIURI> uri;
+ styleRequest->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(spec, imageSrc);
+ }
+
+ // Look the image up in our cache.
+ nsTreeImageCacheEntry entry;
+ if (mImageCache.Get(imageSrc, &entry)) {
+ // Find out if the image has loaded.
+ uint32_t status;
+ imgIRequest *imgReq = entry.request;
+ imgReq->GetImageStatus(&status);
+ imgReq->GetImage(aResult); // We hand back the image here. The GetImage call addrefs *aResult.
+ bool animated = true; // Assuming animated is the safe option
+
+ // We can only call GetAnimated if we're decoded
+ if (*aResult && (status & imgIRequest::STATUS_DECODE_COMPLETE))
+ (*aResult)->GetAnimated(&animated);
+
+ if ((!(status & imgIRequest::STATUS_LOAD_COMPLETE)) || animated) {
+ // We either aren't done loading, or we're animating. Add our row as a listener for invalidations.
+ nsCOMPtr<imgINotificationObserver> obs;
+ imgReq->GetNotificationObserver(getter_AddRefs(obs));
+
+ if (obs) {
+ static_cast<nsTreeImageListener*> (obs.get())->AddCell(aRowIndex, aCol);
+ }
+
+ return NS_OK;
+ }
+ }
+
+ if (!*aResult) {
+ // Create a new nsTreeImageListener object and pass it our row and column
+ // information.
+ nsTreeImageListener* listener = new nsTreeImageListener(this);
+ if (!listener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (!mCreatedListeners.PutEntry(listener)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ listener->AddCell(aRowIndex, aCol);
+ nsCOMPtr<imgINotificationObserver> imgNotificationObserver = listener;
+
+ RefPtr<imgRequestProxy> imageRequest;
+ if (styleRequest) {
+ styleRequest->Clone(imgNotificationObserver, getter_AddRefs(imageRequest));
+ } else {
+ nsIDocument* doc = mContent->GetComposedDoc();
+ if (!doc)
+ // The page is currently being torn down. Why bother.
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI();
+
+ nsCOMPtr<nsIURI> srcURI;
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcURI),
+ imageSrc,
+ doc,
+ baseURI);
+ if (!srcURI)
+ return NS_ERROR_FAILURE;
+
+ // XXXbz what's the origin principal for this stuff that comes from our
+ // view? I guess we should assume that it's the node's principal...
+ nsresult rv = nsContentUtils::LoadImage(srcURI,
+ mContent,
+ doc,
+ mContent->NodePrincipal(),
+ doc->GetDocumentURI(),
+ doc->GetReferrerPolicy(),
+ imgNotificationObserver,
+ nsIRequest::LOAD_NORMAL,
+ EmptyString(),
+ getter_AddRefs(imageRequest));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ listener->UnsuppressInvalidation();
+
+ if (!imageRequest)
+ return NS_ERROR_FAILURE;
+
+ // We don't want discarding/decode-on-draw for xul images
+ imageRequest->StartDecoding();
+ imageRequest->LockImage();
+
+ // In a case it was already cached.
+ imageRequest->GetImage(aResult);
+ nsTreeImageCacheEntry cacheEntry(imageRequest, imgNotificationObserver);
+ mImageCache.Put(imageSrc, cacheEntry);
+ }
+ return NS_OK;
+}
+
+nsRect nsTreeBodyFrame::GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
+ nsStyleContext* aStyleContext)
+{
+ // XXX We should respond to visibility rules for collapsed vs. hidden.
+
+ // This method returns the width of the twisty INCLUDING borders and padding.
+ // It first checks the style context for a width. If none is found, it tries to
+ // use the default image width for the twisty. If no image is found, it defaults
+ // to border+padding.
+ nsRect r(0,0,0,0);
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(aStyleContext, bp);
+ r.Inflate(bp);
+
+ // Now r contains our border+padding info. We now need to get our width and
+ // height.
+ bool needWidth = false;
+ bool needHeight = false;
+
+ // We have to load image even though we already have a size.
+ // Don't change this, otherwise things start to go crazy.
+ bool useImageRegion = true;
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aCol, aUseContext, aStyleContext, useImageRegion, getter_AddRefs(image));
+
+ const nsStylePosition* myPosition = aStyleContext->StylePosition();
+ const nsStyleList* myList = aStyleContext->StyleList();
+
+ if (useImageRegion) {
+ r.x += myList->mImageRegion.x;
+ r.y += myList->mImageRegion.y;
+ }
+
+ if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) {
+ int32_t val = myPosition->mWidth.GetCoordValue();
+ r.width += val;
+ }
+ else if (useImageRegion && myList->mImageRegion.width > 0)
+ r.width += myList->mImageRegion.width;
+ else
+ needWidth = true;
+
+ if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) {
+ int32_t val = myPosition->mHeight.GetCoordValue();
+ r.height += val;
+ }
+ else if (useImageRegion && myList->mImageRegion.height > 0)
+ r.height += myList->mImageRegion.height;
+ else
+ needHeight = true;
+
+ if (image) {
+ if (needWidth || needHeight) {
+ // Get the natural image size.
+
+ if (needWidth) {
+ // Get the size from the image.
+ nscoord width;
+ image->GetWidth(&width);
+ r.width += nsPresContext::CSSPixelsToAppUnits(width);
+ }
+
+ if (needHeight) {
+ nscoord height;
+ image->GetHeight(&height);
+ r.height += nsPresContext::CSSPixelsToAppUnits(height);
+ }
+ }
+ }
+
+ return r;
+}
+
+// GetImageDestSize returns the destination size of the image.
+// The width and height do not include borders and padding.
+// The width and height have not been adjusted to fit in the row height
+// or cell width.
+// The width and height reflect the destination size specified in CSS,
+// or the image region specified in CSS, or the natural size of the
+// image.
+// If only the destination width has been specified in CSS, the height is
+// calculated to maintain the aspect ratio of the image.
+// If only the destination height has been specified in CSS, the width is
+// calculated to maintain the aspect ratio of the image.
+nsSize
+nsTreeBodyFrame::GetImageDestSize(nsStyleContext* aStyleContext,
+ bool useImageRegion,
+ imgIContainer* image)
+{
+ nsSize size(0,0);
+
+ // We need to get the width and height.
+ bool needWidth = false;
+ bool needHeight = false;
+
+ // Get the style position to see if the CSS has specified the
+ // destination width/height.
+ const nsStylePosition* myPosition = aStyleContext->StylePosition();
+
+ if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) {
+ // CSS has specified the destination width.
+ size.width = myPosition->mWidth.GetCoordValue();
+ }
+ else {
+ // We'll need to get the width of the image/region.
+ needWidth = true;
+ }
+
+ if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) {
+ // CSS has specified the destination height.
+ size.height = myPosition->mHeight.GetCoordValue();
+ }
+ else {
+ // We'll need to get the height of the image/region.
+ needHeight = true;
+ }
+
+ if (needWidth || needHeight) {
+ // We need to get the size of the image/region.
+ nsSize imageSize(0,0);
+
+ const nsStyleList* myList = aStyleContext->StyleList();
+
+ if (useImageRegion && myList->mImageRegion.width > 0) {
+ // CSS has specified an image region.
+ // Use the width of the image region.
+ imageSize.width = myList->mImageRegion.width;
+ }
+ else if (image) {
+ nscoord width;
+ image->GetWidth(&width);
+ imageSize.width = nsPresContext::CSSPixelsToAppUnits(width);
+ }
+
+ if (useImageRegion && myList->mImageRegion.height > 0) {
+ // CSS has specified an image region.
+ // Use the height of the image region.
+ imageSize.height = myList->mImageRegion.height;
+ }
+ else if (image) {
+ nscoord height;
+ image->GetHeight(&height);
+ imageSize.height = nsPresContext::CSSPixelsToAppUnits(height);
+ }
+
+ if (needWidth) {
+ if (!needHeight && imageSize.height != 0) {
+ // The CSS specified the destination height, but not the destination
+ // width. We need to calculate the width so that we maintain the
+ // image's aspect ratio.
+ size.width = imageSize.width * size.height / imageSize.height;
+ }
+ else {
+ size.width = imageSize.width;
+ }
+ }
+
+ if (needHeight) {
+ if (!needWidth && imageSize.width != 0) {
+ // The CSS specified the destination width, but not the destination
+ // height. We need to calculate the height so that we maintain the
+ // image's aspect ratio.
+ size.height = imageSize.height * size.width / imageSize.width;
+ }
+ else {
+ size.height = imageSize.height;
+ }
+ }
+ }
+
+ return size;
+}
+
+// GetImageSourceRect returns the source rectangle of the image to be
+// displayed.
+// The width and height reflect the image region specified in CSS, or
+// the natural size of the image.
+// The width and height do not include borders and padding.
+// The width and height do not reflect the destination size specified
+// in CSS.
+nsRect
+nsTreeBodyFrame::GetImageSourceRect(nsStyleContext* aStyleContext,
+ bool useImageRegion,
+ imgIContainer* image)
+{
+ nsRect r(0,0,0,0);
+
+ const nsStyleList* myList = aStyleContext->StyleList();
+
+ if (useImageRegion &&
+ (myList->mImageRegion.width > 0 || myList->mImageRegion.height > 0)) {
+ // CSS has specified an image region.
+ r = myList->mImageRegion;
+ }
+ else if (image) {
+ // Use the actual image size.
+ nscoord coord;
+ image->GetWidth(&coord);
+ r.width = nsPresContext::CSSPixelsToAppUnits(coord);
+ image->GetHeight(&coord);
+ r.height = nsPresContext::CSSPixelsToAppUnits(coord);
+ }
+
+ return r;
+}
+
+int32_t nsTreeBodyFrame::GetRowHeight()
+{
+ // Look up the correct height. It is equal to the specified height
+ // + the specified margins.
+ mScratchArray.Clear();
+ nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+ if (rowContext) {
+ const nsStylePosition* myPosition = rowContext->StylePosition();
+
+ nscoord minHeight = 0;
+ if (myPosition->mMinHeight.GetUnit() == eStyleUnit_Coord)
+ minHeight = myPosition->mMinHeight.GetCoordValue();
+
+ nscoord height = 0;
+ if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord)
+ height = myPosition->mHeight.GetCoordValue();
+
+ if (height < minHeight)
+ height = minHeight;
+
+ if (height > 0) {
+ height = nsPresContext::AppUnitsToIntCSSPixels(height);
+ height += height % 2;
+ height = nsPresContext::CSSPixelsToAppUnits(height);
+
+ // XXX Check box-sizing to determine if border/padding should augment the height
+ // Inflate the height by our margins.
+ nsRect rowRect(0,0,0,height);
+ nsMargin rowMargin;
+ rowContext->StyleMargin()->GetMargin(rowMargin);
+ rowRect.Inflate(rowMargin);
+ height = rowRect.height;
+ return height;
+ }
+ }
+
+ return nsPresContext::CSSPixelsToAppUnits(18); // As good a default as any.
+}
+
+int32_t nsTreeBodyFrame::GetIndentation()
+{
+ // Look up the correct indentation. It is equal to the specified indentation width.
+ mScratchArray.Clear();
+ nsStyleContext* indentContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeindentation);
+ if (indentContext) {
+ const nsStylePosition* myPosition = indentContext->StylePosition();
+ if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) {
+ nscoord val = myPosition->mWidth.GetCoordValue();
+ return val;
+ }
+ }
+
+ return nsPresContext::CSSPixelsToAppUnits(16); // As good a default as any.
+}
+
+void nsTreeBodyFrame::CalcInnerBox()
+{
+ mInnerBox.SetRect(0, 0, mRect.width, mRect.height);
+ AdjustForBorderPadding(mStyleContext, mInnerBox);
+}
+
+nscoord
+nsTreeBodyFrame::CalcHorzWidth(const ScrollParts& aParts)
+{
+ // Compute the adjustment to the last column. This varies depending on the
+ // visibility of the columnpicker and the scrollbar.
+ if (aParts.mColumnsFrame)
+ mAdjustWidth = mRect.width - aParts.mColumnsFrame->GetRect().width;
+ else
+ mAdjustWidth = 0;
+
+ nscoord width = 0;
+
+ // We calculate this from the scrollable frame, so that it
+ // properly covers all contingencies of what could be
+ // scrollable (columns, body, etc...)
+
+ if (aParts.mColumnsScrollFrame) {
+ width = aParts.mColumnsScrollFrame->GetScrollRange().width +
+ aParts.mColumnsScrollFrame->GetScrollPortRect().width;
+ }
+
+ // If no horz scrolling periphery is present, then just return our width
+ if (width == 0)
+ width = mRect.width;
+
+ return width;
+}
+
+nsresult
+nsTreeBodyFrame::GetCursor(const nsPoint& aPoint,
+ nsIFrame::Cursor& aCursor)
+{
+ // Check the GetScriptHandlingObject so we don't end up running code when
+ // the document is a zombie.
+ bool dummy;
+ if (mView && GetContent()->GetComposedDoc()->GetScriptHandlingObject(dummy)) {
+ int32_t row;
+ nsTreeColumn* col;
+ nsIAtom* child;
+ GetCellAt(aPoint.x, aPoint.y, &row, &col, &child);
+
+ if (child) {
+ // Our scratch array is already prefilled.
+ nsStyleContext* childContext = GetPseudoStyleContext(child);
+
+ FillCursorInformationFromStyle(childContext->StyleUserInterface(),
+ aCursor);
+ if (aCursor.mCursor == NS_STYLE_CURSOR_AUTO)
+ aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
+
+ return NS_OK;
+ }
+ }
+
+ return nsLeafBoxFrame::GetCursor(aPoint, aCursor);
+}
+
+static uint32_t GetDropEffect(WidgetGUIEvent* aEvent)
+{
+ NS_ASSERTION(aEvent->mClass == eDragEventClass, "wrong event type");
+ WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
+ nsContentUtils::SetDataTransferInEvent(dragEvent);
+
+ uint32_t action = 0;
+ if (dragEvent->mDataTransfer) {
+ dragEvent->mDataTransfer->GetDropEffectInt(&action);
+ }
+ return action;
+}
+
+nsresult
+nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ if (aEvent->mMessage == eMouseOver || aEvent->mMessage == eMouseMove) {
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
+ int32_t xTwips = pt.x - mInnerBox.x;
+ int32_t yTwips = pt.y - mInnerBox.y;
+ int32_t newrow = GetRowAt(xTwips, yTwips);
+ if (mMouseOverRow != newrow) {
+ // redraw the old and the new row
+ if (mMouseOverRow != -1)
+ InvalidateRow(mMouseOverRow);
+ mMouseOverRow = newrow;
+ if (mMouseOverRow != -1)
+ InvalidateRow(mMouseOverRow);
+ }
+ } else if (aEvent->mMessage == eMouseOut) {
+ if (mMouseOverRow != -1) {
+ InvalidateRow(mMouseOverRow);
+ mMouseOverRow = -1;
+ }
+ } else if (aEvent->mMessage == eDragEnter) {
+ if (!mSlots)
+ mSlots = new Slots();
+
+ // Cache several things we'll need throughout the course of our work. These
+ // will all get released on a drag exit.
+
+ if (mSlots->mTimer) {
+ mSlots->mTimer->Cancel();
+ mSlots->mTimer = nullptr;
+ }
+
+ // Cache the drag session.
+ mSlots->mIsDragging = true;
+ mSlots->mDropRow = -1;
+ mSlots->mDropOrient = -1;
+ mSlots->mDragAction = GetDropEffect(aEvent);
+ } else if (aEvent->mMessage == eDragOver) {
+ // The mouse is hovering over this tree. If we determine things are
+ // different from the last time, invalidate the drop feedback at the old
+ // position, query the view to see if the current location is droppable,
+ // and then invalidate the drop feedback at the new location if it is.
+ // The mouse may or may not have changed position from the last time
+ // we were called, so optimize out a lot of the extra notifications by
+ // checking if anything changed first. For drop feedback we use drop,
+ // dropBefore and dropAfter property.
+
+ if (!mView || !mSlots)
+ return NS_OK;
+
+ // Save last values, we will need them.
+ int32_t lastDropRow = mSlots->mDropRow;
+ int16_t lastDropOrient = mSlots->mDropOrient;
+#ifndef XP_MACOSX
+ int16_t lastScrollLines = mSlots->mScrollLines;
+#endif
+
+ // Find out the current drag action
+ uint32_t lastDragAction = mSlots->mDragAction;
+ mSlots->mDragAction = GetDropEffect(aEvent);
+
+ // Compute the row mouse is over and the above/below/on state.
+ // Below we'll use this to see if anything changed.
+ // Also check if we want to auto-scroll.
+ ComputeDropPosition(aEvent, &mSlots->mDropRow, &mSlots->mDropOrient, &mSlots->mScrollLines);
+
+ // While we're here, handle tracking of scrolling during a drag.
+ if (mSlots->mScrollLines) {
+ if (mSlots->mDropAllowed) {
+ // Invalidate primary cell at old location.
+ mSlots->mDropAllowed = false;
+ InvalidateDropFeedback(lastDropRow, lastDropOrient);
+ }
+#ifdef XP_MACOSX
+ ScrollByLines(mSlots->mScrollLines);
+#else
+ if (!lastScrollLines) {
+ // Cancel any previously initialized timer.
+ if (mSlots->mTimer) {
+ mSlots->mTimer->Cancel();
+ mSlots->mTimer = nullptr;
+ }
+
+ // Set a timer to trigger the tree scrolling.
+ CreateTimer(LookAndFeel::eIntID_TreeLazyScrollDelay,
+ LazyScrollCallback, nsITimer::TYPE_ONE_SHOT,
+ getter_AddRefs(mSlots->mTimer));
+ }
+#endif
+ // Bail out to prevent spring loaded timer and feedback line settings.
+ return NS_OK;
+ }
+
+ // If changed from last time, invalidate primary cell at the old location and if allowed,
+ // invalidate primary cell at the new location. If nothing changed, just bail.
+ if (mSlots->mDropRow != lastDropRow ||
+ mSlots->mDropOrient != lastDropOrient ||
+ mSlots->mDragAction != lastDragAction) {
+
+ // Invalidate row at the old location.
+ if (mSlots->mDropAllowed) {
+ mSlots->mDropAllowed = false;
+ InvalidateDropFeedback(lastDropRow, lastDropOrient);
+ }
+
+ if (mSlots->mTimer) {
+ // Timer is active but for a different row than the current one, kill it.
+ mSlots->mTimer->Cancel();
+ mSlots->mTimer = nullptr;
+ }
+
+ if (mSlots->mDropRow >= 0) {
+ if (!mSlots->mTimer && mSlots->mDropOrient == nsITreeView::DROP_ON) {
+ // Either there wasn't a timer running or it was just killed above.
+ // If over a folder, start up a timer to open the folder.
+ bool isContainer = false;
+ mView->IsContainer(mSlots->mDropRow, &isContainer);
+ if (isContainer) {
+ bool isOpen = false;
+ mView->IsContainerOpen(mSlots->mDropRow, &isOpen);
+ if (!isOpen) {
+ // This node isn't expanded, set a timer to expand it.
+ CreateTimer(LookAndFeel::eIntID_TreeOpenDelay,
+ OpenCallback, nsITimer::TYPE_ONE_SHOT,
+ getter_AddRefs(mSlots->mTimer));
+ }
+ }
+ }
+
+ // The dataTransfer was initialized by the call to GetDropEffect above.
+ bool canDropAtNewLocation = false;
+ mView->CanDrop(mSlots->mDropRow, mSlots->mDropOrient,
+ aEvent->AsDragEvent()->mDataTransfer,
+ &canDropAtNewLocation);
+
+ if (canDropAtNewLocation) {
+ // Invalidate row at the new location.
+ mSlots->mDropAllowed = canDropAtNewLocation;
+ InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient);
+ }
+ }
+ }
+
+ // Indicate that the drop is allowed by preventing the default behaviour.
+ if (mSlots->mDropAllowed)
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ } else if (aEvent->mMessage == eDrop) {
+ // this event was meant for another frame, so ignore it
+ if (!mSlots)
+ return NS_OK;
+
+ // Tell the view where the drop happened.
+
+ // Remove the drop folder and all its parents from the array.
+ int32_t parentIndex;
+ nsresult rv = mView->GetParentIndex(mSlots->mDropRow, &parentIndex);
+ while (NS_SUCCEEDED(rv) && parentIndex >= 0) {
+ mSlots->mArray.RemoveElement(parentIndex);
+ rv = mView->GetParentIndex(parentIndex, &parentIndex);
+ }
+
+ NS_ASSERTION(aEvent->mClass == eDragEventClass, "wrong event type");
+ WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
+ nsContentUtils::SetDataTransferInEvent(dragEvent);
+
+ mView->Drop(mSlots->mDropRow, mSlots->mDropOrient,
+ dragEvent->mDataTransfer);
+ mSlots->mDropRow = -1;
+ mSlots->mDropOrient = -1;
+ mSlots->mIsDragging = false;
+ *aEventStatus = nsEventStatus_eConsumeNoDefault; // already handled the drop
+ } else if (aEvent->mMessage == eDragExit) {
+ // this event was meant for another frame, so ignore it
+ if (!mSlots)
+ return NS_OK;
+
+ // Clear out all our tracking vars.
+
+ if (mSlots->mDropAllowed) {
+ mSlots->mDropAllowed = false;
+ InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient);
+ }
+ else
+ mSlots->mDropAllowed = false;
+ mSlots->mIsDragging = false;
+ mSlots->mScrollLines = 0;
+ // If a drop is occuring, the exit event will fire just before the drop
+ // event, so don't reset mDropRow or mDropOrient as these fields are used
+ // by the drop event.
+ if (mSlots->mTimer) {
+ mSlots->mTimer->Cancel();
+ mSlots->mTimer = nullptr;
+ }
+
+ if (!mSlots->mArray.IsEmpty()) {
+ // Close all spring loaded folders except the drop folder.
+ CreateTimer(LookAndFeel::eIntID_TreeCloseDelay,
+ CloseCallback, nsITimer::TYPE_ONE_SHOT,
+ getter_AddRefs(mSlots->mTimer));
+ }
+ }
+
+ return NS_OK;
+}
+
+class nsDisplayTreeBody final : public nsDisplayItem {
+public:
+ nsDisplayTreeBody(nsDisplayListBuilder* aBuilder, nsFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame),
+ mDisableSubpixelAA(false) {
+ MOZ_COUNT_CTOR(nsDisplayTreeBody);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayTreeBody() {
+ MOZ_COUNT_DTOR(nsDisplayTreeBody);
+ }
+#endif
+
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion) override
+ {
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+ }
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override
+ {
+ DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
+ mDisableSubpixelAA);
+
+ DrawResult result = static_cast<nsTreeBodyFrame*>(mFrame)
+ ->PaintTreeBody(*aCtx, mVisibleRect, ToReferenceFrame());
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+ }
+
+ NS_DISPLAY_DECL_NAME("XULTreeBody", TYPE_XUL_TREE_BODY)
+
+ virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+ {
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+ virtual void DisableComponentAlpha() override {
+ mDisableSubpixelAA = true;
+ }
+
+ bool mDisableSubpixelAA;
+};
+
+// Painting routines
+void
+nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // REVIEW: why did we paint if we were collapsed? that makes no sense!
+ if (!IsVisibleForPainting(aBuilder))
+ return; // We're invisible. Don't paint.
+
+ // Handles painting our background, border, and outline.
+ nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ // Bail out now if there's no view or we can't run script because the
+ // document is a zombie
+ if (!mView || !GetContent ()->GetComposedDoc()->GetWindow())
+ return;
+
+#ifdef XP_MACOSX
+ nsIContent* baseElement = GetBaseElement();
+ nsIFrame* treeFrame =
+ baseElement ? baseElement->GetPrimaryFrame() : nullptr;
+ nsCOMPtr<nsITreeSelection> selection;
+ mView->GetSelection(getter_AddRefs(selection));
+ nsITheme* theme = PresContext()->GetTheme();
+ // On Mac, we support native theming of selected rows. On 10.10 and higher,
+ // this means applying vibrancy which require us to register the theme
+ // geometrics for the row. In order to make the vibrancy effect to work
+ // properly, we also need the tree to be themed as a source list.
+ if (selection && treeFrame && theme &&
+ treeFrame->StyleDisplay()->mAppearance == NS_THEME_MAC_SOURCE_LIST) {
+ // Loop through our onscreen rows. If the row is selected and a
+ // -moz-appearance is provided, RegisterThemeGeometry might be necessary.
+ const auto end = std::min(mRowCount, LastVisibleRow() + 1);
+ for (auto i = FirstVisibleRow(); i < end; i++) {
+ bool isSelected;
+ selection->IsSelected(i, &isSelected);
+ if (isSelected) {
+ PrefillPropertyArray(i, nullptr);
+ nsAutoString properties;
+ mView->GetRowProperties(i, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+ nsStyleContext* rowContext =
+ GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+ auto appearance = rowContext->StyleDisplay()->mAppearance;
+ if (appearance) {
+ if (theme->ThemeSupportsWidget(PresContext(), this, appearance)) {
+ nsITheme::ThemeGeometryType type =
+ theme->ThemeGeometryTypeForWidget(this, appearance);
+ if (type != nsITheme::eThemeGeometryTypeUnknown) {
+ nsRect rowRect(mInnerBox.x, mInnerBox.y + mRowHeight *
+ (i - FirstVisibleRow()), mInnerBox.width,
+ mRowHeight);
+ aBuilder->RegisterThemeGeometry(type,
+ LayoutDeviceIntRect::FromUnknownRect(
+ (rowRect + aBuilder->ToReferenceFrame(this)).ToNearestPixels(
+ PresContext()->AppUnitsPerDevPixel())));
+ }
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayTreeBody(aBuilder, this));
+}
+
+DrawResult
+nsTreeBodyFrame::PaintTreeBody(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect, nsPoint aPt)
+{
+ // Update our available height and our page count.
+ CalcInnerBox();
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+ gfxContext* gfx = aRenderingContext.ThebesContext();
+
+ gfx->Save();
+ gfx->Clip(NSRectToSnappedRect(mInnerBox + aPt,
+ PresContext()->AppUnitsPerDevPixel(),
+ *drawTarget));
+ int32_t oldPageCount = mPageLength;
+ if (!mHasFixedRowCount)
+ mPageLength = mInnerBox.height/mRowHeight;
+
+ if (oldPageCount != mPageLength || mHorzWidth != CalcHorzWidth(GetScrollParts())) {
+ // Schedule a ResizeReflow that will update our info properly.
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+ }
+#ifdef DEBUG
+ int32_t rowCount = mRowCount;
+ mView->GetRowCount(&rowCount);
+ NS_WARNING_ASSERTION(mRowCount == rowCount, "row count changed unexpectedly");
+#endif
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // Loop through our columns and paint them (e.g., for sorting). This is only
+ // relevant when painting backgrounds, since columns contain no content. Content
+ // is contained in the rows.
+ for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
+ currCol = currCol->GetNext()) {
+ nsRect colRect;
+ nsresult rv = currCol->GetRect(this, mInnerBox.y, mInnerBox.height,
+ &colRect);
+ // Don't paint hidden columns.
+ if (NS_FAILED(rv) || colRect.width == 0) continue;
+
+ if (OffsetForHorzScroll(colRect, false)) {
+ nsRect dirtyRect;
+ colRect += aPt;
+ if (dirtyRect.IntersectRect(aDirtyRect, colRect)) {
+ result &=
+ PaintColumn(currCol, colRect, PresContext(), aRenderingContext, aDirtyRect);
+ }
+ }
+ }
+ // Loop through our on-screen rows.
+ for (int32_t i = mTopRowIndex; i < mRowCount && i <= mTopRowIndex+mPageLength; i++) {
+ nsRect rowRect(mInnerBox.x, mInnerBox.y+mRowHeight*(i-mTopRowIndex), mInnerBox.width, mRowHeight);
+ nsRect dirtyRect;
+ if (dirtyRect.IntersectRect(aDirtyRect, rowRect + aPt) &&
+ rowRect.y < (mInnerBox.y+mInnerBox.height)) {
+ result &=
+ PaintRow(i, rowRect + aPt, PresContext(), aRenderingContext, aDirtyRect, aPt);
+ }
+ }
+
+ if (mSlots && mSlots->mDropAllowed && (mSlots->mDropOrient == nsITreeView::DROP_BEFORE ||
+ mSlots->mDropOrient == nsITreeView::DROP_AFTER)) {
+ nscoord yPos = mInnerBox.y + mRowHeight * (mSlots->mDropRow - mTopRowIndex) - mRowHeight / 2;
+ nsRect feedbackRect(mInnerBox.x, yPos, mInnerBox.width, mRowHeight);
+ if (mSlots->mDropOrient == nsITreeView::DROP_AFTER)
+ feedbackRect.y += mRowHeight;
+
+ nsRect dirtyRect;
+ feedbackRect += aPt;
+ if (dirtyRect.IntersectRect(aDirtyRect, feedbackRect)) {
+ result &=
+ PaintDropFeedback(feedbackRect, PresContext(), aRenderingContext,
+ aDirtyRect, aPt);
+ }
+ }
+ gfx->Restore();
+
+ return result;
+}
+
+
+
+DrawResult
+nsTreeBodyFrame::PaintColumn(nsTreeColumn* aColumn,
+ const nsRect& aColumnRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Now obtain the properties for our cell.
+ PrefillPropertyArray(-1, aColumn);
+ nsAutoString properties;
+ mView->GetColumnProperties(aColumn, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ // Resolve style for the column. It contains all the info we need to lay ourselves
+ // out and to paint.
+ nsStyleContext* colContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecolumn);
+
+ // Obtain the margins for the cell and then deflate our rect by that
+ // amount. The cell is assumed to be contained within the deflated rect.
+ nsRect colRect(aColumnRect);
+ nsMargin colMargin;
+ colContext->StyleMargin()->GetMargin(colMargin);
+ colRect.Deflate(colMargin);
+
+ return PaintBackgroundLayer(colContext, aPresContext, aRenderingContext,
+ colRect, aDirtyRect);
+}
+
+DrawResult
+nsTreeBodyFrame::PaintRow(int32_t aRowIndex,
+ const nsRect& aRowRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ // We have been given a rect for our row. We treat this row like a full-blown
+ // frame, meaning that it can have borders, margins, padding, and a background.
+
+ // Without a view, we have no data. Check for this up front.
+ if (!mView) {
+ return DrawResult::SUCCESS;
+ }
+
+ nsresult rv;
+
+ // Now obtain the properties for our row.
+ // XXX Automatically fill in the following props: open, closed, container, leaf, selected, focused
+ PrefillPropertyArray(aRowIndex, nullptr);
+
+ nsAutoString properties;
+ mView->GetRowProperties(aRowIndex, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ // Resolve style for the row. It contains all the info we need to lay ourselves
+ // out and to paint.
+ nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+
+ // Obtain the margins for the row and then deflate our rect by that
+ // amount. The row is assumed to be contained within the deflated rect.
+ nsRect rowRect(aRowRect);
+ nsMargin rowMargin;
+ rowContext->StyleMargin()->GetMargin(rowMargin);
+ rowRect.Deflate(rowMargin);
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // Paint our borders and background for our row rect.
+ nsITheme* theme = nullptr;
+ auto appearance = rowContext->StyleDisplay()->mAppearance;
+ if (appearance) {
+ theme = aPresContext->GetTheme();
+ }
+ gfxContext* ctx = aRenderingContext.ThebesContext();
+ // Save the current font smoothing background color in case we change it.
+ Color originalColor(ctx->GetFontSmoothingBackgroundColor());
+ if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, appearance)) {
+ nscolor color;
+ if (theme->WidgetProvidesFontSmoothingBackgroundColor(this, appearance,
+ &color)) {
+ // Set the font smoothing background color provided by the widget.
+ ctx->SetFontSmoothingBackgroundColor(ToDeviceColor(color));
+ }
+ nsRect dirty;
+ dirty.IntersectRect(rowRect, aDirtyRect);
+ theme->DrawWidgetBackground(&aRenderingContext, this, appearance, rowRect,
+ dirty);
+ } else {
+ result &= PaintBackgroundLayer(rowContext, aPresContext, aRenderingContext,
+ rowRect, aDirtyRect);
+ }
+
+ // Adjust the rect for its border and padding.
+ nsRect originalRowRect = rowRect;
+ AdjustForBorderPadding(rowContext, rowRect);
+
+ bool isSeparator = false;
+ mView->IsSeparator(aRowIndex, &isSeparator);
+ if (isSeparator) {
+ // The row is a separator.
+
+ nscoord primaryX = rowRect.x;
+ nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn();
+ if (primaryCol) {
+ // Paint the primary cell.
+ nsRect cellRect;
+ rv = primaryCol->GetRect(this, rowRect.y, rowRect.height, &cellRect);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("primary column is invalid");
+ return result;
+ }
+
+ if (OffsetForHorzScroll(cellRect, false)) {
+ cellRect.x += aPt.x;
+ nsRect dirtyRect;
+ nsRect checkRect(cellRect.x, originalRowRect.y,
+ cellRect.width, originalRowRect.height);
+ if (dirtyRect.IntersectRect(aDirtyRect, checkRect)) {
+ result &= PaintCell(aRowIndex, primaryCol, cellRect, aPresContext,
+ aRenderingContext, aDirtyRect, primaryX, aPt);
+ }
+ }
+
+ // Paint the left side of the separator.
+ nscoord currX;
+ nsTreeColumn* previousCol = primaryCol->GetPrevious();
+ if (previousCol) {
+ nsRect prevColRect;
+ rv = previousCol->GetRect(this, 0, 0, &prevColRect);
+ if (NS_SUCCEEDED(rv)) {
+ currX = (prevColRect.x - mHorzPosition) + prevColRect.width + aPt.x;
+ } else {
+ NS_NOTREACHED("The column before the primary column is invalid");
+ currX = rowRect.x;
+ }
+ } else {
+ currX = rowRect.x;
+ }
+
+ int32_t level;
+ mView->GetLevel(aRowIndex, &level);
+ if (level == 0)
+ currX += mIndentation;
+
+ if (currX > rowRect.x) {
+ nsRect separatorRect(rowRect);
+ separatorRect.width -= rowRect.x + rowRect.width - currX;
+ result &= PaintSeparator(aRowIndex, separatorRect, aPresContext,
+ aRenderingContext, aDirtyRect);
+ }
+ }
+
+ // Paint the right side (whole) separator.
+ nsRect separatorRect(rowRect);
+ if (primaryX > rowRect.x) {
+ separatorRect.width -= primaryX - rowRect.x;
+ separatorRect.x += primaryX - rowRect.x;
+ }
+ result &= PaintSeparator(aRowIndex, separatorRect, aPresContext,
+ aRenderingContext, aDirtyRect);
+ }
+ else {
+ // Now loop over our cells. Only paint a cell if it intersects with our dirty rect.
+ for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
+ currCol = currCol->GetNext()) {
+ nsRect cellRect;
+ rv = currCol->GetRect(this, rowRect.y, rowRect.height, &cellRect);
+ // Don't paint cells in hidden columns.
+ if (NS_FAILED(rv) || cellRect.width == 0)
+ continue;
+
+ if (OffsetForHorzScroll(cellRect, false)) {
+ cellRect.x += aPt.x;
+
+ // for primary columns, use the row's vertical size so that the
+ // lines get drawn properly
+ nsRect checkRect = cellRect;
+ if (currCol->IsPrimary())
+ checkRect = nsRect(cellRect.x, originalRowRect.y,
+ cellRect.width, originalRowRect.height);
+
+ nsRect dirtyRect;
+ nscoord dummy;
+ if (dirtyRect.IntersectRect(aDirtyRect, checkRect))
+ result &= PaintCell(aRowIndex, currCol, cellRect, aPresContext,
+ aRenderingContext, aDirtyRect, dummy, aPt);
+ }
+ }
+ }
+ // If we've changed the font smoothing background color for this row, restore
+ // the color to the original one.
+ if (originalColor != ctx->GetFontSmoothingBackgroundColor()) {
+ ctx->SetFontSmoothingBackgroundColor(originalColor);
+ }
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintSeparator(int32_t aRowIndex,
+ const nsRect& aSeparatorRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect)
+{
+ // Resolve style for the separator.
+ nsStyleContext* separatorContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeseparator);
+ bool useTheme = false;
+ nsITheme *theme = nullptr;
+ const nsStyleDisplay* displayData = separatorContext->StyleDisplay();
+ if ( displayData->mAppearance ) {
+ theme = aPresContext->GetTheme();
+ if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, displayData->mAppearance))
+ useTheme = true;
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // use -moz-appearance if provided.
+ if (useTheme) {
+ nsRect dirty;
+ dirty.IntersectRect(aSeparatorRect, aDirtyRect);
+ theme->DrawWidgetBackground(&aRenderingContext, this,
+ displayData->mAppearance, aSeparatorRect, dirty);
+ }
+ else {
+ const nsStylePosition* stylePosition = separatorContext->StylePosition();
+
+ // Obtain the height for the separator or use the default value.
+ nscoord height;
+ if (stylePosition->mHeight.GetUnit() == eStyleUnit_Coord)
+ height = stylePosition->mHeight.GetCoordValue();
+ else {
+ // Use default height 2px.
+ height = nsPresContext::CSSPixelsToAppUnits(2);
+ }
+
+ // Obtain the margins for the separator and then deflate our rect by that
+ // amount. The separator is assumed to be contained within the deflated rect.
+ nsRect separatorRect(aSeparatorRect.x, aSeparatorRect.y, aSeparatorRect.width, height);
+ nsMargin separatorMargin;
+ separatorContext->StyleMargin()->GetMargin(separatorMargin);
+ separatorRect.Deflate(separatorMargin);
+
+ // Center the separator.
+ separatorRect.y += (aSeparatorRect.height - height) / 2;
+
+ result &= PaintBackgroundLayer(separatorContext, aPresContext,
+ aRenderingContext, separatorRect,
+ aDirtyRect);
+ }
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintCell(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aCellRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aCurrX,
+ nsPoint aPt)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Now obtain the properties for our cell.
+ // XXX Automatically fill in the following props: open, closed, container, leaf, selected, focused, and the col ID.
+ PrefillPropertyArray(aRowIndex, aColumn);
+ nsAutoString properties;
+ mView->GetCellProperties(aRowIndex, aColumn, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ // Resolve style for the cell. It contains all the info we need to lay ourselves
+ // out and to paint.
+ nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
+
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+
+ // Obtain the margins for the cell and then deflate our rect by that
+ // amount. The cell is assumed to be contained within the deflated rect.
+ nsRect cellRect(aCellRect);
+ nsMargin cellMargin;
+ cellContext->StyleMargin()->GetMargin(cellMargin);
+ cellRect.Deflate(cellMargin);
+
+ // Paint our borders and background for our row rect.
+ DrawResult result = PaintBackgroundLayer(cellContext, aPresContext,
+ aRenderingContext, cellRect,
+ aDirtyRect);
+
+ // Adjust the rect for its border and padding.
+ AdjustForBorderPadding(cellContext, cellRect);
+
+ nscoord currX = cellRect.x;
+ nscoord remainingWidth = cellRect.width;
+
+ // Now we paint the contents of the cells.
+ // Directionality of the tree determines the order in which we paint.
+ // NS_STYLE_DIRECTION_LTR means paint from left to right.
+ // NS_STYLE_DIRECTION_RTL means paint from right to left.
+
+ if (aColumn->IsPrimary()) {
+ // If we're the primary column, we need to indent and paint the twisty and any connecting lines
+ // between siblings.
+
+ int32_t level;
+ mView->GetLevel(aRowIndex, &level);
+
+ if (!isRTL)
+ currX += mIndentation * level;
+ remainingWidth -= mIndentation * level;
+
+ // Resolve the style to use for the connecting lines.
+ nsStyleContext* lineContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeline);
+
+ if (mIndentation && level &&
+ lineContext->StyleVisibility()->IsVisibleOrCollapsed()) {
+ // Paint the thread lines.
+
+ // Get the size of the twisty. We don't want to paint the twisty
+ // before painting of connecting lines since it would paint lines over
+ // the twisty. But we need to leave a place for it.
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+
+ nsRect imageSize;
+ nsRect twistyRect(aCellRect);
+ GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, aPresContext,
+ twistyContext);
+
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+
+ aRenderingContext.ThebesContext()->Save();
+
+ const nsStyleBorder* borderStyle = lineContext->StyleBorder();
+ // Resolve currentcolor values against the treeline context
+ nscolor color = lineContext->StyleColor()->
+ CalcComplexColor(borderStyle->mBorderLeftColor);
+ ColorPattern colorPatt(ToDeviceColor(color));
+
+ uint8_t style = borderStyle->GetBorderStyle(NS_SIDE_LEFT);
+ StrokeOptions strokeOptions;
+ nsLayoutUtils::InitDashPattern(strokeOptions, style);
+
+ nscoord srcX = currX + twistyRect.width - mIndentation / 2;
+ nscoord lineY = (aRowIndex - mTopRowIndex) * mRowHeight + aPt.y;
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+ nsPresContext* pc = PresContext();
+
+ // Don't paint off our cell.
+ if (srcX <= cellRect.x + cellRect.width) {
+ nscoord destX = currX + twistyRect.width;
+ if (destX > cellRect.x + cellRect.width)
+ destX = cellRect.x + cellRect.width;
+ if (isRTL) {
+ srcX = currX + remainingWidth - (srcX - cellRect.x);
+ destX = currX + remainingWidth - (destX - cellRect.x);
+ }
+ Point p1(pc->AppUnitsToGfxUnits(srcX),
+ pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2));
+ Point p2(pc->AppUnitsToGfxUnits(destX),
+ pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2));
+ SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget,
+ strokeOptions.mLineWidth);
+ drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions);
+ }
+
+ int32_t currentParent = aRowIndex;
+ for (int32_t i = level; i > 0; i--) {
+ if (srcX <= cellRect.x + cellRect.width) {
+ // Paint full vertical line only if we have next sibling.
+ bool hasNextSibling;
+ mView->HasNextSibling(currentParent, aRowIndex, &hasNextSibling);
+ if (hasNextSibling || i == level) {
+ Point p1(pc->AppUnitsToGfxUnits(srcX),
+ pc->AppUnitsToGfxUnits(lineY));
+ Point p2;
+ p2.x = pc->AppUnitsToGfxUnits(srcX);
+
+ if (hasNextSibling)
+ p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight);
+ else if (i == level)
+ p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2);
+
+ SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget,
+ strokeOptions.mLineWidth);
+ drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions);
+ }
+ }
+
+ int32_t parent;
+ if (NS_FAILED(mView->GetParentIndex(currentParent, &parent)) || parent < 0)
+ break;
+ currentParent = parent;
+ srcX -= mIndentation;
+ }
+
+ aRenderingContext.ThebesContext()->Restore();
+ }
+
+ // Always leave space for the twisty.
+ nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
+ result &= PaintTwisty(aRowIndex, aColumn, twistyRect, aPresContext,
+ aRenderingContext, aDirtyRect, remainingWidth,
+ currX);
+ }
+
+ // Now paint the icon for our cell.
+ nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);
+ nsRect dirtyRect;
+ if (dirtyRect.IntersectRect(aDirtyRect, iconRect)) {
+ result &= PaintImage(aRowIndex, aColumn, iconRect, aPresContext,
+ aRenderingContext, aDirtyRect, remainingWidth,
+ currX);
+ }
+
+ // Now paint our element, but only if we aren't a cycler column.
+ // XXX until we have the ability to load images, allow the view to
+ // insert text into cycler columns...
+ if (!aColumn->IsCycler()) {
+ nsRect elementRect(currX, cellRect.y, remainingWidth, cellRect.height);
+ nsRect dirtyRect;
+ if (dirtyRect.IntersectRect(aDirtyRect, elementRect)) {
+ switch (aColumn->GetType()) {
+ case nsITreeColumn::TYPE_TEXT:
+ case nsITreeColumn::TYPE_PASSWORD:
+ result &= PaintText(aRowIndex, aColumn, elementRect, aPresContext,
+ aRenderingContext, aDirtyRect, currX);
+ break;
+ case nsITreeColumn::TYPE_CHECKBOX:
+ result &= PaintCheckbox(aRowIndex, aColumn, elementRect, aPresContext,
+ aRenderingContext, aDirtyRect);
+ break;
+ case nsITreeColumn::TYPE_PROGRESSMETER:
+ int32_t state;
+ mView->GetProgressMode(aRowIndex, aColumn, &state);
+ switch (state) {
+ case nsITreeView::PROGRESS_NORMAL:
+ case nsITreeView::PROGRESS_UNDETERMINED:
+ result &= PaintProgressMeter(aRowIndex, aColumn, elementRect,
+ aPresContext, aRenderingContext,
+ aDirtyRect);
+ break;
+ case nsITreeView::PROGRESS_NONE:
+ default:
+ result &= PaintText(aRowIndex, aColumn, elementRect, aPresContext,
+ aRenderingContext, aDirtyRect, currX);
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ aCurrX = currX;
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintTwisty(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aTwistyRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aRemainingWidth,
+ nscoord& aCurrX)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ nscoord rightEdge = aCurrX + aRemainingWidth;
+ // Paint the twisty, but only if we are a non-empty container.
+ bool shouldPaint = false;
+ bool isContainer = false;
+ mView->IsContainer(aRowIndex, &isContainer);
+ if (isContainer) {
+ bool isContainerEmpty = false;
+ mView->IsContainerEmpty(aRowIndex, &isContainerEmpty);
+ if (!isContainerEmpty)
+ shouldPaint = true;
+ }
+
+ // Resolve style for the twisty.
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+
+ // Obtain the margins for the twisty and then deflate our rect by that
+ // amount. The twisty is assumed to be contained within the deflated rect.
+ nsRect twistyRect(aTwistyRect);
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Deflate(twistyMargin);
+
+ nsRect imageSize;
+ nsITheme* theme = GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect,
+ aPresContext, twistyContext);
+
+ // Subtract out the remaining width. This is done even when we don't actually paint a twisty in
+ // this cell, so that cells in different rows still line up.
+ nsRect copyRect(twistyRect);
+ copyRect.Inflate(twistyMargin);
+ aRemainingWidth -= copyRect.width;
+ if (!isRTL)
+ aCurrX += copyRect.width;
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ if (shouldPaint) {
+ // Paint our borders and background for our image rect.
+ result &= PaintBackgroundLayer(twistyContext, aPresContext,
+ aRenderingContext, twistyRect,
+ aDirtyRect);
+
+ if (theme) {
+ if (isRTL)
+ twistyRect.x = rightEdge - twistyRect.width;
+ // yeah, I know it says we're drawing a background, but a twisty is really a fg
+ // object since it doesn't have anything that gecko would want to draw over it. Besides,
+ // we have to prevent imagelib from drawing it.
+ nsRect dirty;
+ dirty.IntersectRect(twistyRect, aDirtyRect);
+ theme->DrawWidgetBackground(&aRenderingContext, this,
+ twistyContext->StyleDisplay()->mAppearance, twistyRect, dirty);
+ }
+ else {
+ // Time to paint the twisty.
+ // Adjust the rect for its border and padding.
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(twistyContext, bp);
+ twistyRect.Deflate(bp);
+ if (isRTL)
+ twistyRect.x = rightEdge - twistyRect.width;
+ imageSize.Deflate(bp);
+
+ // Get the image for drawing.
+ nsCOMPtr<imgIContainer> image;
+ bool useImageRegion = true;
+ GetImage(aRowIndex, aColumn, true, twistyContext, useImageRegion, getter_AddRefs(image));
+ if (image) {
+ nsPoint pt = twistyRect.TopLeft();
+
+ // Center the image. XXX Obey vertical-align style prop?
+ if (imageSize.height < twistyRect.height) {
+ pt.y += (twistyRect.height - imageSize.height)/2;
+ }
+
+ // Paint the image.
+ result &=
+ nsLayoutUtils::DrawSingleUnscaledImage(
+ *aRenderingContext.ThebesContext(), aPresContext, image,
+ SamplingFilter::POINT, pt, &aDirtyRect,
+ imgIContainer::FLAG_NONE, &imageSize);
+ }
+ }
+ }
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintImage(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aImageRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aRemainingWidth,
+ nscoord& aCurrX)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ nscoord rightEdge = aCurrX + aRemainingWidth;
+ // Resolve style for the image.
+ nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
+
+ // Obtain opacity value for the image.
+ float opacity = imageContext->StyleEffects()->mOpacity;
+
+ // Obtain the margins for the image and then deflate our rect by that
+ // amount. The image is assumed to be contained within the deflated rect.
+ nsRect imageRect(aImageRect);
+ nsMargin imageMargin;
+ imageContext->StyleMargin()->GetMargin(imageMargin);
+ imageRect.Deflate(imageMargin);
+
+ // Get the image.
+ bool useImageRegion = true;
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aColumn, false, imageContext, useImageRegion, getter_AddRefs(image));
+
+ // Get the image destination size.
+ nsSize imageDestSize = GetImageDestSize(imageContext, useImageRegion, image);
+ if (!imageDestSize.width || !imageDestSize.height) {
+ return DrawResult::SUCCESS;
+ }
+
+ // Get the borders and padding.
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(imageContext, bp);
+
+ // destRect will be passed as the aDestRect argument in the DrawImage method.
+ // Start with the imageDestSize width and height.
+ nsRect destRect(0, 0, imageDestSize.width, imageDestSize.height);
+ // Inflate destRect for borders and padding so that we can compare/adjust
+ // with respect to imageRect.
+ destRect.Inflate(bp);
+
+ // The destRect width and height have not been adjusted to fit within the
+ // cell width and height.
+ // We must adjust the width even if image is null, because the width is used
+ // to update the aRemainingWidth and aCurrX values.
+ // Since the height isn't used unless the image is not null, we will adjust
+ // the height inside the if (image) block below.
+
+ if (destRect.width > imageRect.width) {
+ // The destRect is too wide to fit within the cell width.
+ // Adjust destRect width to fit within the cell width.
+ destRect.width = imageRect.width;
+ }
+ else {
+ // The cell is wider than the destRect.
+ // In a cycler column, the image is centered horizontally.
+ if (!aColumn->IsCycler()) {
+ // If this column is not a cycler, we won't center the image horizontally.
+ // We adjust the imageRect width so that the image is placed at the start
+ // of the cell.
+ imageRect.width = destRect.width;
+ }
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ if (image) {
+ if (isRTL)
+ imageRect.x = rightEdge - imageRect.width;
+ // Paint our borders and background for our image rect
+ result &= PaintBackgroundLayer(imageContext, aPresContext,
+ aRenderingContext, imageRect,
+ aDirtyRect);
+
+ // The destRect x and y have not been set yet. Let's do that now.
+ // Initially, we use the imageRect x and y.
+ destRect.x = imageRect.x;
+ destRect.y = imageRect.y;
+
+ if (destRect.width < imageRect.width) {
+ // The destRect width is smaller than the cell width.
+ // Center the image horizontally in the cell.
+ // Adjust the destRect x accordingly.
+ destRect.x += (imageRect.width - destRect.width)/2;
+ }
+
+ // Now it's time to adjust the destRect height to fit within the cell height.
+ if (destRect.height > imageRect.height) {
+ // The destRect height is larger than the cell height.
+ // Adjust destRect height to fit within the cell height.
+ destRect.height = imageRect.height;
+ }
+ else if (destRect.height < imageRect.height) {
+ // The destRect height is smaller than the cell height.
+ // Center the image vertically in the cell.
+ // Adjust the destRect y accordingly.
+ destRect.y += (imageRect.height - destRect.height)/2;
+ }
+
+ // It's almost time to paint the image.
+ // Deflate destRect for the border and padding.
+ destRect.Deflate(bp);
+
+ // Compute the area where our whole image would be mapped, to get the
+ // desired subregion onto our actual destRect:
+ nsRect wholeImageDest;
+ CSSIntSize rawImageCSSIntSize;
+ if (NS_SUCCEEDED(image->GetWidth(&rawImageCSSIntSize.width)) &&
+ NS_SUCCEEDED(image->GetHeight(&rawImageCSSIntSize.height))) {
+ // Get the image source rectangle - the rectangle containing the part of
+ // the image that we are going to display. sourceRect will be passed as
+ // the aSrcRect argument in the DrawImage method.
+ nsRect sourceRect = GetImageSourceRect(imageContext, useImageRegion, image);
+
+ // Let's say that the image is 100 pixels tall and that the CSS has
+ // specified that the destination height should be 50 pixels tall. Let's
+ // say that the cell height is only 20 pixels. So, in those 20 visible
+ // pixels, we want to see the top 20/50ths of the image. So, the
+ // sourceRect.height should be 100 * 20 / 50, which is 40 pixels.
+ // Essentially, we are scaling the image as dictated by the CSS
+ // destination height and width, and we are then clipping the scaled
+ // image by the cell width and height.
+ nsSize rawImageSize(CSSPixel::ToAppUnits(rawImageCSSIntSize));
+ wholeImageDest =
+ nsLayoutUtils::GetWholeImageDestination(rawImageSize, sourceRect,
+ nsRect(destRect.TopLeft(),
+ imageDestSize));
+ } else {
+ // GetWidth/GetHeight failed, so we can't easily map a subregion of the
+ // source image onto the destination area.
+ // * If this happens with a RasterImage, it probably means the image is
+ // in an error state, and we shouldn't draw anything. Hence, we leave
+ // wholeImageDest as an empty rect (its initial state).
+ // * If this happens with a VectorImage, it probably means the image has
+ // no explicit width or height attribute -- but we can still proceed and
+ // just treat the destination area as our whole SVG image area. Hence, we
+ // set wholeImageDest to the full destRect.
+ if (image->GetType() == imgIContainer::TYPE_VECTOR) {
+ wholeImageDest = destRect;
+ }
+ }
+
+ gfxContext* ctx = aRenderingContext.ThebesContext();
+ if (opacity != 1.0f) {
+ ctx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
+ }
+
+ result &=
+ nsLayoutUtils::DrawImage(*ctx, aPresContext, image,
+ nsLayoutUtils::GetSamplingFilterForFrame(this),
+ wholeImageDest, destRect, destRect.TopLeft(), aDirtyRect,
+ imgIContainer::FLAG_NONE);
+
+ if (opacity != 1.0f) {
+ ctx->PopGroupAndBlend();
+ }
+ }
+
+ // Update the aRemainingWidth and aCurrX values.
+ imageRect.Inflate(imageMargin);
+ aRemainingWidth -= imageRect.width;
+ if (!isRTL) {
+ aCurrX += imageRect.width;
+ }
+
+ return result;
+}
+
+// Disable PGO for PaintText because MSVC 2015 seems to have decided
+// that it can null out the alreadyAddRefed<nsFontMetrics> used to
+// initialize fontMet after storing fontMet on the stack in the same
+// space, overwriting fontMet's stack storage with null.
+#ifdef _MSC_VER
+# pragma optimize("g", off)
+#endif
+DrawResult
+nsTreeBodyFrame::PaintText(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aTextRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aCurrX)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+
+ // Now obtain the text for our cell.
+ nsAutoString text;
+ mView->GetCellText(aRowIndex, aColumn, text);
+
+ if (aColumn->Type() == nsITreeColumn::TYPE_PASSWORD) {
+ TextEditRules::FillBufWithPWChars(&text, text.Length());
+ }
+
+ // We're going to paint this text so we need to ensure bidi is enabled if
+ // necessary
+ CheckTextForBidi(text);
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ if (text.Length() == 0) {
+ // Don't paint an empty string. XXX What about background/borders? Still paint?
+ return result;
+ }
+
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // Resolve style for the text. It contains all the info we need to lay ourselves
+ // out and to paint.
+ nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
+
+ // Obtain opacity value for the image.
+ float opacity = textContext->StyleEffects()->mOpacity;
+
+ // Obtain the margins for the text and then deflate our rect by that
+ // amount. The text is assumed to be contained within the deflated rect.
+ nsRect textRect(aTextRect);
+ nsMargin textMargin;
+ textContext->StyleMargin()->GetMargin(textMargin);
+ textRect.Deflate(textMargin);
+
+ // Adjust the rect for its border and padding.
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(textContext, bp);
+ textRect.Deflate(bp);
+
+ // Compute our text size.
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForStyleContext(textContext);
+
+ nscoord height = fontMet->MaxHeight();
+ nscoord baseline = fontMet->MaxAscent();
+
+ // Center the text. XXX Obey vertical-align style prop?
+ if (height < textRect.height) {
+ textRect.y += (textRect.height - height)/2;
+ textRect.height = height;
+ }
+
+ // Set our font.
+ AdjustForCellText(text, aRowIndex, aColumn, aRenderingContext, *fontMet, textRect);
+ textRect.Inflate(bp);
+
+ // Subtract out the remaining width.
+ if (!isRTL)
+ aCurrX += textRect.width + textMargin.LeftRight();
+
+ result &= PaintBackgroundLayer(textContext, aPresContext, aRenderingContext,
+ textRect, aDirtyRect);
+
+ // Time to paint our text.
+ textRect.Deflate(bp);
+
+ // Set our color.
+ ColorPattern color(ToDeviceColor(textContext->StyleColor()->mColor));
+
+ // Draw decorations.
+ uint8_t decorations = textContext->StyleTextReset()->mTextDecorationLine;
+
+ nscoord offset;
+ nscoord size;
+ if (decorations & (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE |
+ NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE)) {
+ fontMet->GetUnderline(offset, size);
+ if (decorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
+ nsRect r(textRect.x, textRect.y, textRect.width, size);
+ Rect devPxRect =
+ NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, color);
+ }
+ if (decorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
+ nsRect r(textRect.x, textRect.y + baseline - offset,
+ textRect.width, size);
+ Rect devPxRect =
+ NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, color);
+ }
+ }
+ if (decorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
+ fontMet->GetStrikeout(offset, size);
+ nsRect r(textRect.x, textRect.y + baseline - offset, textRect.width, size);
+ Rect devPxRect =
+ NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, color);
+ }
+ nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
+
+ gfxContext* ctx = aRenderingContext.ThebesContext();
+ if (opacity != 1.0f) {
+ ctx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
+ }
+
+ ctx->SetColor(Color::FromABGR(textContext->StyleColor()->mColor));
+ nsLayoutUtils::DrawString(this, *fontMet, &aRenderingContext, text.get(),
+ text.Length(),
+ textRect.TopLeft() + nsPoint(0, baseline),
+ cellContext);
+
+ if (opacity != 1.0f) {
+ ctx->PopGroupAndBlend();
+ }
+
+ return result;
+}
+#ifdef _MSC_VER
+# pragma optimize("", on)
+#endif
+
+DrawResult
+nsTreeBodyFrame::PaintCheckbox(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aCheckboxRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Resolve style for the checkbox.
+ nsStyleContext* checkboxContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecheckbox);
+
+ nscoord rightEdge = aCheckboxRect.XMost();
+
+ // Obtain the margins for the checkbox and then deflate our rect by that
+ // amount. The checkbox is assumed to be contained within the deflated rect.
+ nsRect checkboxRect(aCheckboxRect);
+ nsMargin checkboxMargin;
+ checkboxContext->StyleMargin()->GetMargin(checkboxMargin);
+ checkboxRect.Deflate(checkboxMargin);
+
+ nsRect imageSize = GetImageSize(aRowIndex, aColumn, true, checkboxContext);
+
+ if (imageSize.height > checkboxRect.height)
+ imageSize.height = checkboxRect.height;
+ if (imageSize.width > checkboxRect.width)
+ imageSize.width = checkboxRect.width;
+
+ if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL)
+ checkboxRect.x = rightEdge - checkboxRect.width;
+
+ // Paint our borders and background for our image rect.
+ DrawResult result = PaintBackgroundLayer(checkboxContext, aPresContext,
+ aRenderingContext, checkboxRect,
+ aDirtyRect);
+
+ // Time to paint the checkbox.
+ // Adjust the rect for its border and padding.
+ nsMargin bp(0,0,0,0);
+ GetBorderPadding(checkboxContext, bp);
+ checkboxRect.Deflate(bp);
+
+ // Get the image for drawing.
+ nsCOMPtr<imgIContainer> image;
+ bool useImageRegion = true;
+ GetImage(aRowIndex, aColumn, true, checkboxContext, useImageRegion, getter_AddRefs(image));
+ if (image) {
+ nsPoint pt = checkboxRect.TopLeft();
+
+ if (imageSize.height < checkboxRect.height) {
+ pt.y += (checkboxRect.height - imageSize.height)/2;
+ }
+
+ if (imageSize.width < checkboxRect.width) {
+ pt.x += (checkboxRect.width - imageSize.width)/2;
+ }
+
+ // Paint the image.
+ result &=
+ nsLayoutUtils::DrawSingleUnscaledImage(*aRenderingContext.ThebesContext(),
+ aPresContext,
+ image, SamplingFilter::POINT, pt, &aDirtyRect,
+ imgIContainer::FLAG_NONE, &imageSize);
+ }
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintProgressMeter(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aProgressMeterRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect)
+{
+ NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Resolve style for the progress meter. It contains all the info we need
+ // to lay ourselves out and to paint.
+ nsStyleContext* meterContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeprogressmeter);
+
+ // Obtain the margins for the progress meter and then deflate our rect by that
+ // amount. The progress meter is assumed to be contained within the deflated
+ // rect.
+ nsRect meterRect(aProgressMeterRect);
+ nsMargin meterMargin;
+ meterContext->StyleMargin()->GetMargin(meterMargin);
+ meterRect.Deflate(meterMargin);
+
+ // Paint our borders and background for our progress meter rect.
+ DrawResult result = PaintBackgroundLayer(meterContext, aPresContext,
+ aRenderingContext, meterRect,
+ aDirtyRect);
+
+ // Time to paint our progress.
+ int32_t state;
+ mView->GetProgressMode(aRowIndex, aColumn, &state);
+ if (state == nsITreeView::PROGRESS_NORMAL) {
+ // Adjust the rect for its border and padding.
+ AdjustForBorderPadding(meterContext, meterRect);
+
+ // Now obtain the value for our cell.
+ nsAutoString value;
+ mView->GetCellValue(aRowIndex, aColumn, value);
+
+ nsresult rv;
+ int32_t intValue = value.ToInteger(&rv);
+ if (intValue < 0)
+ intValue = 0;
+ else if (intValue > 100)
+ intValue = 100;
+
+ nscoord meterWidth = NSToCoordRound((float)intValue / 100 * meterRect.width);
+ if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL)
+ meterRect.x += meterRect.width - meterWidth; // right align
+ meterRect.width = meterWidth;
+ bool useImageRegion = true;
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aColumn, true, meterContext, useImageRegion, getter_AddRefs(image));
+ if (image) {
+ int32_t width, height;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ nsSize size(width*nsDeviceContext::AppUnitsPerCSSPixel(),
+ height*nsDeviceContext::AppUnitsPerCSSPixel());
+ result &=
+ nsLayoutUtils::DrawImage(*aRenderingContext.ThebesContext(),
+ aPresContext, image,
+ nsLayoutUtils::GetSamplingFilterForFrame(this),
+ nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(),
+ aDirtyRect, imgIContainer::FLAG_NONE);
+ } else {
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ Rect rect =
+ NSRectToSnappedRect(meterRect, appUnitsPerDevPixel, *drawTarget);
+ ColorPattern color(ToDeviceColor(meterContext->StyleColor()->mColor));
+ drawTarget->FillRect(rect, color);
+ }
+ }
+ else if (state == nsITreeView::PROGRESS_UNDETERMINED) {
+ // Adjust the rect for its border and padding.
+ AdjustForBorderPadding(meterContext, meterRect);
+
+ bool useImageRegion = true;
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aColumn, true, meterContext, useImageRegion, getter_AddRefs(image));
+ if (image) {
+ int32_t width, height;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ nsSize size(width*nsDeviceContext::AppUnitsPerCSSPixel(),
+ height*nsDeviceContext::AppUnitsPerCSSPixel());
+ result &=
+ nsLayoutUtils::DrawImage(*aRenderingContext.ThebesContext(),
+ aPresContext, image,
+ nsLayoutUtils::GetSamplingFilterForFrame(this),
+ nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(),
+ aDirtyRect, imgIContainer::FLAG_NONE);
+ }
+ }
+
+ return result;
+}
+
+
+DrawResult
+nsTreeBodyFrame::PaintDropFeedback(const nsRect& aDropFeedbackRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ // Paint the drop feedback in between rows.
+
+ nscoord currX;
+
+ // Adjust for the primary cell.
+ nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn();
+
+ if (primaryCol) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ primaryCol->GetXInTwips(this, &currX);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "primary column is invalid?");
+
+ currX += aPt.x - mHorzPosition;
+ } else {
+ currX = aDropFeedbackRect.x;
+ }
+
+ PrefillPropertyArray(mSlots->mDropRow, primaryCol);
+
+ // Resolve the style to use for the drop feedback.
+ nsStyleContext* feedbackContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreedropfeedback);
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ // Paint only if it is visible.
+ if (feedbackContext->StyleVisibility()->IsVisibleOrCollapsed()) {
+ int32_t level;
+ mView->GetLevel(mSlots->mDropRow, &level);
+
+ // If our previous or next row has greater level use that for
+ // correct visual indentation.
+ if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) {
+ if (mSlots->mDropRow > 0) {
+ int32_t previousLevel;
+ mView->GetLevel(mSlots->mDropRow - 1, &previousLevel);
+ if (previousLevel > level)
+ level = previousLevel;
+ }
+ }
+ else {
+ if (mSlots->mDropRow < mRowCount - 1) {
+ int32_t nextLevel;
+ mView->GetLevel(mSlots->mDropRow + 1, &nextLevel);
+ if (nextLevel > level)
+ level = nextLevel;
+ }
+ }
+
+ currX += mIndentation * level;
+
+ if (primaryCol){
+ nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
+ nsRect imageSize;
+ nsRect twistyRect;
+ GetTwistyRect(mSlots->mDropRow, primaryCol, imageSize, twistyRect,
+ aPresContext, twistyContext);
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+ currX += twistyRect.width;
+ }
+
+ const nsStylePosition* stylePosition = feedbackContext->StylePosition();
+
+ // Obtain the width for the drop feedback or use default value.
+ nscoord width;
+ if (stylePosition->mWidth.GetUnit() == eStyleUnit_Coord)
+ width = stylePosition->mWidth.GetCoordValue();
+ else {
+ // Use default width 50px.
+ width = nsPresContext::CSSPixelsToAppUnits(50);
+ }
+
+ // Obtain the height for the drop feedback or use default value.
+ nscoord height;
+ if (stylePosition->mHeight.GetUnit() == eStyleUnit_Coord)
+ height = stylePosition->mHeight.GetCoordValue();
+ else {
+ // Use default height 2px.
+ height = nsPresContext::CSSPixelsToAppUnits(2);
+ }
+
+ // Obtain the margins for the drop feedback and then deflate our rect
+ // by that amount.
+ nsRect feedbackRect(currX, aDropFeedbackRect.y, width, height);
+ nsMargin margin;
+ feedbackContext->StyleMargin()->GetMargin(margin);
+ feedbackRect.Deflate(margin);
+
+ feedbackRect.y += (aDropFeedbackRect.height - height) / 2;
+
+ // Finally paint the drop feedback.
+ result &= PaintBackgroundLayer(feedbackContext, aPresContext,
+ aRenderingContext, feedbackRect,
+ aDirtyRect);
+ }
+
+ return result;
+}
+
+DrawResult
+nsTreeBodyFrame::PaintBackgroundLayer(nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ const nsStyleBorder* myBorder = aStyleContext->StyleBorder();
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForAllLayers(*aPresContext, aRenderingContext,
+ aDirtyRect, aRect, this,
+ nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES);
+ DrawResult result =
+ nsCSSRendering::PaintBackgroundWithSC(params, aStyleContext, *myBorder);
+
+ result &=
+ nsCSSRendering::PaintBorderWithStyleBorder(aPresContext, aRenderingContext,
+ this, aDirtyRect, aRect,
+ *myBorder, mStyleContext,
+ PaintBorderFlags::SYNC_DECODE_IMAGES);
+
+ nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this,
+ aDirtyRect, aRect, aStyleContext);
+
+ return result;
+}
+
+// Scrolling
+nsresult
+nsTreeBodyFrame::EnsureRowIsVisible(int32_t aRow)
+{
+ ScrollParts parts = GetScrollParts();
+ nsresult rv = EnsureRowIsVisibleInternal(parts, aRow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateScrollbars(parts);
+ return rv;
+}
+
+nsresult nsTreeBodyFrame::EnsureRowIsVisibleInternal(const ScrollParts& aParts, int32_t aRow)
+{
+ if (!mView || !mPageLength)
+ return NS_OK;
+
+ if (mTopRowIndex <= aRow && mTopRowIndex+mPageLength > aRow)
+ return NS_OK;
+
+ if (aRow < mTopRowIndex)
+ ScrollToRowInternal(aParts, aRow);
+ else {
+ // Bring it just on-screen.
+ int32_t distance = aRow - (mTopRowIndex+mPageLength)+1;
+ ScrollToRowInternal(aParts, mTopRowIndex+distance);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::EnsureCellIsVisible(int32_t aRow, nsITreeColumn* aCol)
+{
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+ ScrollParts parts = GetScrollParts();
+
+ nscoord result = -1;
+ nsresult rv;
+
+ nscoord columnPos;
+ rv = col->GetXInTwips(this, &columnPos);
+ if(NS_FAILED(rv)) return rv;
+
+ nscoord columnWidth;
+ rv = col->GetWidthInTwips(this, &columnWidth);
+ if(NS_FAILED(rv)) return rv;
+
+ // If the start of the column is before the
+ // start of the horizontal view, then scroll
+ if (columnPos < mHorzPosition)
+ result = columnPos;
+ // If the end of the column is past the end of
+ // the horizontal view, then scroll
+ else if ((columnPos + columnWidth) > (mHorzPosition + mInnerBox.width))
+ result = ((columnPos + columnWidth) - (mHorzPosition + mInnerBox.width)) + mHorzPosition;
+
+ if (result != -1) {
+ rv = ScrollHorzInternal(parts, result);
+ if(NS_FAILED(rv)) return rv;
+ }
+
+ rv = EnsureRowIsVisibleInternal(parts, aRow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateScrollbars(parts);
+ return rv;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollToCell(int32_t aRow, nsITreeColumn* aCol)
+{
+ ScrollParts parts = GetScrollParts();
+ nsresult rv = ScrollToRowInternal(parts, aRow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ScrollToColumnInternal(parts, aCol);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UpdateScrollbars(parts);
+ return rv;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollToColumn(nsITreeColumn* aCol)
+{
+ ScrollParts parts = GetScrollParts();
+ nsresult rv = ScrollToColumnInternal(parts, aCol);
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateScrollbars(parts);
+ return rv;
+}
+
+nsresult nsTreeBodyFrame::ScrollToColumnInternal(const ScrollParts& aParts,
+ nsITreeColumn* aCol)
+{
+ RefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
+ if (!col)
+ return NS_ERROR_INVALID_ARG;
+
+ nscoord x;
+ nsresult rv = col->GetXInTwips(this, &x);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return ScrollHorzInternal(aParts, x);
+}
+
+nsresult
+nsTreeBodyFrame::ScrollToHorizontalPosition(int32_t aHorizontalPosition)
+{
+ ScrollParts parts = GetScrollParts();
+ int32_t position = nsPresContext::CSSPixelsToAppUnits(aHorizontalPosition);
+ nsresult rv = ScrollHorzInternal(parts, position);
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateScrollbars(parts);
+ return rv;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollToRow(int32_t aRow)
+{
+ ScrollParts parts = GetScrollParts();
+ ScrollToRowInternal(parts, aRow);
+ UpdateScrollbars(parts);
+ return NS_OK;
+}
+
+nsresult nsTreeBodyFrame::ScrollToRowInternal(const ScrollParts& aParts, int32_t aRow)
+{
+ ScrollInternal(aParts, aRow);
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollByLines(int32_t aNumLines)
+{
+ if (!mView) {
+ return NS_OK;
+ }
+ int32_t newIndex = mTopRowIndex + aNumLines;
+ ScrollToRow(newIndex);
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollByPages(int32_t aNumPages)
+{
+ if (!mView) {
+ return NS_OK;
+ }
+ int32_t newIndex = mTopRowIndex + aNumPages * mPageLength;
+ ScrollToRow(newIndex);
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollInternal(const ScrollParts& aParts, int32_t aRow)
+{
+ if (!mView) {
+ return NS_OK;
+ }
+
+ // Note that we may be "over scrolled" at this point; that is the
+ // current mTopRowIndex may be larger than mRowCount - mPageLength.
+ // This can happen when items are removed for example. (bug 1085050)
+
+ int32_t maxTopRowIndex = std::max(0, mRowCount - mPageLength);
+ aRow = mozilla::clamped(aRow, 0, maxTopRowIndex);
+ if (aRow == mTopRowIndex) {
+ return NS_OK;
+ }
+ mTopRowIndex = aRow;
+ Invalidate();
+ PostScrollEvent();
+ return NS_OK;
+}
+
+nsresult
+nsTreeBodyFrame::ScrollHorzInternal(const ScrollParts& aParts, int32_t aPosition)
+{
+ if (!mView || !aParts.mColumnsScrollFrame || !aParts.mHScrollbar)
+ return NS_OK;
+
+ if (aPosition == mHorzPosition)
+ return NS_OK;
+
+ if (aPosition < 0 || aPosition > mHorzWidth)
+ return NS_OK;
+
+ nsRect bounds = aParts.mColumnsFrame->GetRect();
+ if (aPosition > (mHorzWidth - bounds.width))
+ aPosition = mHorzWidth - bounds.width;
+
+ mHorzPosition = aPosition;
+
+ Invalidate();
+
+ // Update the column scroll view
+ nsWeakFrame weakFrame(this);
+ aParts.mColumnsScrollFrame->ScrollTo(nsPoint(mHorzPosition, 0),
+ nsIScrollableFrame::INSTANT);
+ if (!weakFrame.IsAlive()) {
+ return NS_ERROR_FAILURE;
+ }
+ // And fire off an event about it all
+ PostScrollEvent();
+ return NS_OK;
+}
+
+void
+nsTreeBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ ScrollByPages(aDirection);
+}
+
+void
+nsTreeBodyFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ int32_t newIndex = aDirection < 0 ? 0 : mTopRowIndex;
+ ScrollToRow(newIndex);
+}
+
+void
+nsTreeBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ ScrollByLines(aDirection);
+}
+
+void
+nsTreeBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar)
+{
+ ScrollParts parts = GetScrollParts();
+ int32_t increment = aScrollbar->GetIncrement();
+ int32_t direction = 0;
+ if (increment < 0) {
+ direction = -1;
+ } else if (increment > 0) {
+ direction = 1;
+ }
+ bool isHorizontal = aScrollbar->IsXULHorizontal();
+
+ nsWeakFrame weakFrame(this);
+ if (isHorizontal) {
+ int32_t curpos = aScrollbar->MoveToNewPosition();
+ if (weakFrame.IsAlive()) {
+ ScrollHorzInternal(parts, curpos);
+ }
+ } else {
+ ScrollToRowInternal(parts, mTopRowIndex+direction);
+ }
+
+ if (weakFrame.IsAlive() && mScrollbarActivity) {
+ mScrollbarActivity->ActivityOccurred();
+ }
+ if (weakFrame.IsAlive()) {
+ UpdateScrollbars(parts);
+ }
+}
+
+void
+nsTreeBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar,
+ nscoord aOldPos,
+ nscoord aNewPos)
+{
+ ScrollParts parts = GetScrollParts();
+
+ if (aOldPos == aNewPos)
+ return;
+
+ nsWeakFrame weakFrame(this);
+
+ // Vertical Scrollbar
+ if (parts.mVScrollbar == aScrollbar) {
+ nscoord rh = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+ nscoord newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
+ nscoord newrow = newIndex/rh;
+ ScrollInternal(parts, newrow);
+ // Horizontal Scrollbar
+ } else if (parts.mHScrollbar == aScrollbar) {
+ int32_t newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
+ ScrollHorzInternal(parts, newIndex);
+ }
+ if (weakFrame.IsAlive()) {
+ UpdateScrollbars(parts);
+ }
+}
+
+// The style cache.
+nsStyleContext*
+nsTreeBodyFrame::GetPseudoStyleContext(nsIAtom* aPseudoElement)
+{
+ return mStyleCache.GetStyleContext(this, PresContext(), mContent,
+ mStyleContext, aPseudoElement,
+ mScratchArray);
+}
+
+// Our comparator for resolving our complex pseudos
+bool
+nsTreeBodyFrame::PseudoMatches(nsCSSSelector* aSelector)
+{
+ // Iterate the class list. For each item in the list, see if
+ // it is contained in our scratch array. If we have a miss, then
+ // we aren't a match. If all items in the class list are
+ // present in the scratch array, then we have a match.
+ nsAtomList* curr = aSelector->mClassList;
+ while (curr) {
+ if (!mScratchArray.Contains(curr->mAtom))
+ return false;
+ curr = curr->mNext;
+ }
+ return true;
+}
+
+nsIContent*
+nsTreeBodyFrame::GetBaseElement()
+{
+ nsIFrame* parent = GetParent();
+ while (parent) {
+ nsIContent* content = parent->GetContent();
+ if (content) {
+ dom::NodeInfo* ni = content->NodeInfo();
+
+ if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL) ||
+ (ni->Equals(nsGkAtoms::select) &&
+ content->IsHTMLElement()))
+ return content;
+ }
+
+ parent = parent->GetParent();
+ }
+
+ return nullptr;
+}
+
+nsresult
+nsTreeBodyFrame::ClearStyleAndImageCaches()
+{
+ mStyleCache.Clear();
+ CancelImageRequests();
+ mImageCache.Clear();
+ return NS_OK;
+}
+
+/* virtual */ void
+nsTreeBodyFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsLeafBoxFrame::DidSetStyleContext(aOldStyleContext);
+
+ // Clear the style cache; the pointers are no longer even valid
+ mStyleCache.Clear();
+ // XXX The following is hacky, but it's not incorrect,
+ // and appears to fix a few bugs with style changes, like text zoom and
+ // dpi changes
+ mIndentation = GetIndentation();
+ mRowHeight = GetRowHeight();
+ mStringWidth = -1;
+}
+
+bool
+nsTreeBodyFrame::OffsetForHorzScroll(nsRect& rect, bool clip)
+{
+ rect.x -= mHorzPosition;
+
+ // Scrolled out before
+ if (rect.XMost() <= mInnerBox.x)
+ return false;
+
+ // Scrolled out after
+ if (rect.x > mInnerBox.XMost())
+ return false;
+
+ if (clip) {
+ nscoord leftEdge = std::max(rect.x, mInnerBox.x);
+ nscoord rightEdge = std::min(rect.XMost(), mInnerBox.XMost());
+ rect.x = leftEdge;
+ rect.width = rightEdge - leftEdge;
+
+ // Should have returned false above
+ NS_ASSERTION(rect.width >= 0, "horz scroll code out of sync");
+ }
+
+ return true;
+}
+
+bool
+nsTreeBodyFrame::CanAutoScroll(int32_t aRowIndex)
+{
+ // Check first for partially visible last row.
+ if (aRowIndex == mRowCount - 1) {
+ nscoord y = mInnerBox.y + (aRowIndex - mTopRowIndex) * mRowHeight;
+ if (y < mInnerBox.height && y + mRowHeight > mInnerBox.height)
+ return true;
+ }
+
+ if (aRowIndex > 0 && aRowIndex < mRowCount - 1)
+ return true;
+
+ return false;
+}
+
+// Given a dom event, figure out which row in the tree the mouse is over,
+// if we should drop before/after/on that row or we should auto-scroll.
+// Doesn't query the content about if the drag is allowable, that's done elsewhere.
+//
+// For containers, we break up the vertical space of the row as follows: if in
+// the topmost 25%, the drop is _before_ the row the mouse is over; if in the
+// last 25%, _after_; in the middle 50%, we consider it a drop _on_ the container.
+//
+// For non-containers, if the mouse is in the top 50% of the row, the drop is
+// _before_ and the bottom 50% _after_
+void
+nsTreeBodyFrame::ComputeDropPosition(WidgetGUIEvent* aEvent,
+ int32_t* aRow,
+ int16_t* aOrient,
+ int16_t* aScrollLines)
+{
+ *aOrient = -1;
+ *aScrollLines = 0;
+
+ // Convert the event's point to our coordinates. We want it in
+ // the coordinates of our inner box's coordinates.
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
+ int32_t xTwips = pt.x - mInnerBox.x;
+ int32_t yTwips = pt.y - mInnerBox.y;
+
+ *aRow = GetRowAt(xTwips, yTwips);
+ if (*aRow >=0) {
+ // Compute the top/bottom of the row in question.
+ int32_t yOffset = yTwips - mRowHeight * (*aRow - mTopRowIndex);
+
+ bool isContainer = false;
+ mView->IsContainer (*aRow, &isContainer);
+ if (isContainer) {
+ // for a container, use a 25%/50%/25% breakdown
+ if (yOffset < mRowHeight / 4)
+ *aOrient = nsITreeView::DROP_BEFORE;
+ else if (yOffset > mRowHeight - (mRowHeight / 4))
+ *aOrient = nsITreeView::DROP_AFTER;
+ else
+ *aOrient = nsITreeView::DROP_ON;
+ }
+ else {
+ // for a non-container use a 50%/50% breakdown
+ if (yOffset < mRowHeight / 2)
+ *aOrient = nsITreeView::DROP_BEFORE;
+ else
+ *aOrient = nsITreeView::DROP_AFTER;
+ }
+ }
+
+ if (CanAutoScroll(*aRow)) {
+ // Get the max value from the look and feel service.
+ int32_t scrollLinesMax =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_TreeScrollLinesMax, 0);
+ scrollLinesMax--;
+ if (scrollLinesMax < 0)
+ scrollLinesMax = 0;
+
+ // Determine if we're w/in a margin of the top/bottom of the tree during a drag.
+ // This will ultimately cause us to scroll, but that's done elsewhere.
+ nscoord height = (3 * mRowHeight) / 4;
+ if (yTwips < height) {
+ // scroll up
+ *aScrollLines = NSToIntRound(-scrollLinesMax * (1 - (float)yTwips / height) - 1);
+ }
+ else if (yTwips > mRect.height - height) {
+ // scroll down
+ *aScrollLines = NSToIntRound(scrollLinesMax * (1 - (float)(mRect.height - yTwips) / height) + 1);
+ }
+ }
+} // ComputeDropPosition
+
+void
+nsTreeBodyFrame::OpenCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (self) {
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+
+ if (self->mSlots->mDropRow >= 0) {
+ self->mSlots->mArray.AppendElement(self->mSlots->mDropRow);
+ self->mView->ToggleOpenState(self->mSlots->mDropRow);
+ }
+ }
+}
+
+void
+nsTreeBodyFrame::CloseCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (self) {
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+
+ for (uint32_t i = self->mSlots->mArray.Length(); i--; ) {
+ if (self->mView)
+ self->mView->ToggleOpenState(self->mSlots->mArray[i]);
+ }
+ self->mSlots->mArray.Clear();
+ }
+}
+
+void
+nsTreeBodyFrame::LazyScrollCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (self) {
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+
+ if (self->mView) {
+ // Set a new timer to scroll the tree repeatedly.
+ self->CreateTimer(LookAndFeel::eIntID_TreeScrollDelay,
+ ScrollCallback, nsITimer::TYPE_REPEATING_SLACK,
+ getter_AddRefs(self->mSlots->mTimer));
+ self->ScrollByLines(self->mSlots->mScrollLines);
+ // ScrollByLines may have deleted |self|.
+ }
+ }
+}
+
+void
+nsTreeBodyFrame::ScrollCallback(nsITimer *aTimer, void *aClosure)
+{
+ nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (self) {
+ // Don't scroll if we are already at the top or bottom of the view.
+ if (self->mView && self->CanAutoScroll(self->mSlots->mDropRow)) {
+ self->ScrollByLines(self->mSlots->mScrollLines);
+ }
+ else {
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsTreeBodyFrame::ScrollEvent::Run()
+{
+ if (mInner) {
+ mInner->FireScrollEvent();
+ }
+ return NS_OK;
+}
+
+
+void
+nsTreeBodyFrame::FireScrollEvent()
+{
+ mScrollEvent.Forget();
+ WidgetGUIEvent event(true, eScroll, nullptr);
+ // scroll events fired at elements don't bubble
+ event.mFlags.mBubbles = false;
+ EventDispatcher::Dispatch(GetContent(), PresContext(), &event);
+}
+
+void
+nsTreeBodyFrame::PostScrollEvent()
+{
+ if (mScrollEvent.IsPending())
+ return;
+
+ RefPtr<ScrollEvent> ev = new ScrollEvent(this);
+ if (NS_FAILED(NS_DispatchToCurrentThread(ev))) {
+ NS_WARNING("failed to dispatch ScrollEvent");
+ } else {
+ mScrollEvent = ev;
+ }
+}
+
+void
+nsTreeBodyFrame::ScrollbarActivityStarted() const
+{
+ if (mScrollbarActivity) {
+ mScrollbarActivity->ActivityStarted();
+ }
+}
+
+void
+nsTreeBodyFrame::ScrollbarActivityStopped() const
+{
+ if (mScrollbarActivity) {
+ mScrollbarActivity->ActivityStopped();
+ }
+}
+
+void
+nsTreeBodyFrame::DetachImageListeners()
+{
+ mCreatedListeners.Clear();
+}
+
+void
+nsTreeBodyFrame::RemoveTreeImageListener(nsTreeImageListener* aListener)
+{
+ if (aListener) {
+ mCreatedListeners.RemoveEntry(aListener);
+ }
+}
+
+#ifdef ACCESSIBILITY
+void
+nsTreeBodyFrame::FireRowCountChangedEvent(int32_t aIndex, int32_t aCount)
+{
+ nsCOMPtr<nsIContent> content(GetBaseElement());
+ if (!content)
+ return;
+
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(content->OwnerDoc());
+ if (!domDoc)
+ return;
+
+ nsCOMPtr<nsIDOMEvent> event;
+ domDoc->CreateEvent(NS_LITERAL_STRING("customevent"),
+ getter_AddRefs(event));
+
+ nsCOMPtr<nsIDOMCustomEvent> treeEvent(do_QueryInterface(event));
+ if (!treeEvent)
+ return;
+
+ nsCOMPtr<nsIWritablePropertyBag2> propBag(
+ do_CreateInstance("@mozilla.org/hash-property-bag;1"));
+ if (!propBag)
+ return;
+
+ // Set 'index' data - the row index rows are changed from.
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("index"), aIndex);
+
+ // Set 'count' data - the number of changed rows.
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("count"), aCount);
+
+ RefPtr<nsVariant> detailVariant(new nsVariant());
+
+ detailVariant->SetAsISupports(propBag);
+ treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeRowCountChanged"),
+ true, false, detailVariant);
+
+ event->SetTrusted(true);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(content, event);
+ asyncDispatcher->PostDOMEvent();
+}
+
+void
+nsTreeBodyFrame::FireInvalidateEvent(int32_t aStartRowIdx, int32_t aEndRowIdx,
+ nsITreeColumn *aStartCol,
+ nsITreeColumn *aEndCol)
+{
+ nsCOMPtr<nsIContent> content(GetBaseElement());
+ if (!content)
+ return;
+
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(content->OwnerDoc());
+ if (!domDoc)
+ return;
+
+ nsCOMPtr<nsIDOMEvent> event;
+ domDoc->CreateEvent(NS_LITERAL_STRING("customevent"),
+ getter_AddRefs(event));
+
+ nsCOMPtr<nsIDOMCustomEvent> treeEvent(do_QueryInterface(event));
+ if (!treeEvent)
+ return;
+
+ nsCOMPtr<nsIWritablePropertyBag2> propBag(
+ do_CreateInstance("@mozilla.org/hash-property-bag;1"));
+ if (!propBag)
+ return;
+
+ if (aStartRowIdx != -1 && aEndRowIdx != -1) {
+ // Set 'startrow' data - the start index of invalidated rows.
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("startrow"),
+ aStartRowIdx);
+
+ // Set 'endrow' data - the end index of invalidated rows.
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("endrow"),
+ aEndRowIdx);
+ }
+
+ if (aStartCol && aEndCol) {
+ // Set 'startcolumn' data - the start index of invalidated rows.
+ int32_t startColIdx = 0;
+ nsresult rv = aStartCol->GetIndex(&startColIdx);
+ if (NS_FAILED(rv))
+ return;
+
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("startcolumn"),
+ startColIdx);
+
+ // Set 'endcolumn' data - the start index of invalidated rows.
+ int32_t endColIdx = 0;
+ rv = aEndCol->GetIndex(&endColIdx);
+ if (NS_FAILED(rv))
+ return;
+
+ propBag->SetPropertyAsInt32(NS_LITERAL_STRING("endcolumn"),
+ endColIdx);
+ }
+
+ RefPtr<nsVariant> detailVariant(new nsVariant());
+
+ detailVariant->SetAsISupports(propBag);
+ treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeInvalidated"),
+ true, false, detailVariant);
+
+ event->SetTrusted(true);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(content, event);
+ asyncDispatcher->PostDOMEvent();
+}
+#endif
+
+class nsOverflowChecker : public Runnable
+{
+public:
+ explicit nsOverflowChecker(nsTreeBodyFrame* aFrame) : mFrame(aFrame) {}
+ NS_IMETHOD Run() override
+ {
+ if (mFrame.IsAlive()) {
+ nsTreeBodyFrame* tree = static_cast<nsTreeBodyFrame*>(mFrame.GetFrame());
+ nsTreeBodyFrame::ScrollParts parts = tree->GetScrollParts();
+ tree->CheckOverflow(parts);
+ }
+ return NS_OK;
+ }
+private:
+ nsWeakFrame mFrame;
+};
+
+bool
+nsTreeBodyFrame::FullScrollbarsUpdate(bool aNeedsFullInvalidation)
+{
+ ScrollParts parts = GetScrollParts();
+ nsWeakFrame weakFrame(this);
+ nsWeakFrame weakColumnsFrame(parts.mColumnsFrame);
+ UpdateScrollbars(parts);
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+ if (aNeedsFullInvalidation) {
+ Invalidate();
+ }
+ InvalidateScrollbars(parts, weakColumnsFrame);
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+
+ // Overflow checking dispatches synchronous events, which can cause infinite
+ // recursion during reflow. Do the first overflow check synchronously, but
+ // force any nested checks to round-trip through the event loop. See bug
+ // 905909.
+ RefPtr<nsOverflowChecker> checker = new nsOverflowChecker(this);
+ if (!mCheckingOverflow) {
+ nsContentUtils::AddScriptRunner(checker);
+ } else {
+ NS_DispatchToCurrentThread(checker);
+ }
+ return weakFrame.IsAlive();
+}
+
+nsresult
+nsTreeBodyFrame::OnImageIsAnimated(imgIRequest* aRequest)
+{
+ nsLayoutUtils::RegisterImageRequest(PresContext(),
+ aRequest, nullptr);
+
+ return NS_OK;
+}
diff --git a/layout/xul/tree/nsTreeBodyFrame.h b/layout/xul/tree/nsTreeBodyFrame.h
new file mode 100644
index 000000000..9620c8ccb
--- /dev/null
+++ b/layout/xul/tree/nsTreeBodyFrame.h
@@ -0,0 +1,651 @@
+/* -*- 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/. */
+
+#ifndef nsTreeBodyFrame_h
+#define nsTreeBodyFrame_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsLeafBoxFrame.h"
+#include "nsITreeView.h"
+#include "nsICSSPseudoComparator.h"
+#include "nsIScrollbarMediator.h"
+#include "nsITimer.h"
+#include "nsIReflowCallback.h"
+#include "nsTArray.h"
+#include "nsTreeStyleCache.h"
+#include "nsTreeColumns.h"
+#include "nsDataHashtable.h"
+#include "imgIRequest.h"
+#include "imgINotificationObserver.h"
+#include "nsScrollbarFrame.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LookAndFeel.h"
+
+class nsFontMetrics;
+class nsOverflowChecker;
+class nsTreeImageListener;
+
+namespace mozilla {
+namespace layout {
+class ScrollbarActivity;
+} // namespace layout
+} // namespace mozilla
+
+// An entry in the tree's image cache
+struct nsTreeImageCacheEntry
+{
+ nsTreeImageCacheEntry() {}
+ nsTreeImageCacheEntry(imgIRequest *aRequest, imgINotificationObserver *aListener)
+ : request(aRequest), listener(aListener) {}
+
+ nsCOMPtr<imgIRequest> request;
+ nsCOMPtr<imgINotificationObserver> listener;
+};
+
+// The actual frame that paints the cells and rows.
+class nsTreeBodyFrame final
+ : public nsLeafBoxFrame
+ , public nsICSSPseudoComparator
+ , public nsIScrollbarMediator
+ , public nsIReflowCallback
+{
+ typedef mozilla::layout::ScrollbarActivity ScrollbarActivity;
+ typedef mozilla::image::DrawResult DrawResult;
+
+public:
+ explicit nsTreeBodyFrame(nsStyleContext* aContext);
+ ~nsTreeBodyFrame();
+
+ NS_DECL_QUERYFRAME_TARGET(nsTreeBodyFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // Callback handler methods for refresh driver based animations.
+ // Calls to these functions are forwarded from nsTreeImageListener. These
+ // mirror how nsImageFrame works.
+ nsresult OnImageIsAnimated(imgIRequest* aRequest);
+
+ // non-virtual signatures like nsITreeBodyFrame
+ already_AddRefed<nsTreeColumns> Columns() const
+ {
+ RefPtr<nsTreeColumns> cols = mColumns;
+ return cols.forget();
+ }
+ already_AddRefed<nsITreeView> GetExistingView() const
+ {
+ nsCOMPtr<nsITreeView> view = mView;
+ return view.forget();
+ }
+ nsresult GetView(nsITreeView **aView);
+ nsresult SetView(nsITreeView *aView);
+ bool GetFocused() const { return mFocused; }
+ nsresult SetFocused(bool aFocused);
+ nsresult GetTreeBody(nsIDOMElement **aElement);
+ int32_t RowHeight() const;
+ int32_t RowWidth();
+ int32_t GetHorizontalPosition() const;
+ nsresult GetSelectionRegion(nsIScriptableRegion **aRegion);
+ int32_t FirstVisibleRow() const { return mTopRowIndex; }
+ int32_t LastVisibleRow() const { return mTopRowIndex + mPageLength; }
+ int32_t PageLength() const { return mPageLength; }
+ nsresult EnsureRowIsVisible(int32_t aRow);
+ nsresult EnsureCellIsVisible(int32_t aRow, nsITreeColumn *aCol);
+ nsresult ScrollToRow(int32_t aRow);
+ nsresult ScrollByLines(int32_t aNumLines);
+ nsresult ScrollByPages(int32_t aNumPages);
+ nsresult ScrollToCell(int32_t aRow, nsITreeColumn *aCol);
+ nsresult ScrollToColumn(nsITreeColumn *aCol);
+ nsresult ScrollToHorizontalPosition(int32_t aValue);
+ nsresult Invalidate();
+ nsresult InvalidateColumn(nsITreeColumn *aCol);
+ nsresult InvalidateRow(int32_t aRow);
+ nsresult InvalidateCell(int32_t aRow, nsITreeColumn *aCol);
+ nsresult InvalidateRange(int32_t aStart, int32_t aEnd);
+ nsresult InvalidateColumnRange(int32_t aStart, int32_t aEnd,
+ nsITreeColumn *aCol);
+ nsresult GetRowAt(int32_t aX, int32_t aY, int32_t *aValue);
+ nsresult GetCellAt(int32_t aX, int32_t aY, int32_t *aRow,
+ nsITreeColumn **aCol, nsACString &aChildElt);
+ nsresult GetCoordsForCellItem(int32_t aRow, nsITreeColumn *aCol,
+ const nsACString &aElt,
+ int32_t *aX, int32_t *aY,
+ int32_t *aWidth, int32_t *aHeight);
+ nsresult IsCellCropped(int32_t aRow, nsITreeColumn *aCol, bool *aResult);
+ nsresult RowCountChanged(int32_t aIndex, int32_t aCount);
+ nsresult BeginUpdateBatch();
+ nsresult EndUpdateBatch();
+ nsresult ClearStyleAndImageCaches();
+
+ void CancelImageRequests();
+
+ void ManageReflowCallback(const nsRect& aRect, nscoord aHorzWidth);
+
+ virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual void SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect,
+ bool aRemoveOverflowArea = false) override;
+
+ // nsIReflowCallback
+ virtual bool ReflowFinished() override;
+ virtual void ReflowCallbackCanceled() override;
+
+ // nsICSSPseudoComparator
+ virtual bool PseudoMatches(nsCSSSelector* aSelector) override;
+
+ // nsIScrollbarMediator
+ virtual void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap
+ = nsIScrollbarMediator::DISABLE_SNAP) override;
+ virtual void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap
+ = nsIScrollbarMediator::DISABLE_SNAP) override;
+ virtual void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap
+ = nsIScrollbarMediator::DISABLE_SNAP) override;
+ virtual void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) override;
+ virtual void ThumbMoved(nsScrollbarFrame* aScrollbar,
+ nscoord aOldPos,
+ nscoord aNewPos) override;
+ virtual void ScrollbarReleased(nsScrollbarFrame* aScrollbar) override {}
+ virtual void VisibilityChanged(bool aVisible) override { Invalidate(); }
+ virtual nsIFrame* GetScrollbarBox(bool aVertical) override {
+ ScrollParts parts = GetScrollParts();
+ return aVertical ? parts.mVScrollbar : parts.mHScrollbar;
+ }
+ virtual void ScrollbarActivityStarted() const override;
+ virtual void ScrollbarActivityStopped() const override;
+ virtual bool IsScrollbarOnRight() const override {
+ return (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR);
+ }
+ virtual bool ShouldSuppressScrollbarRepaints() const override {
+ return false;
+ }
+
+ // Overridden from nsIFrame to cache our pres context.
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsresult GetCursor(const nsPoint& aPoint,
+ nsIFrame::Cursor& aCursor) override;
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ friend nsIFrame* NS_NewTreeBodyFrame(nsIPresShell* aPresShell);
+ friend class nsTreeColumn;
+
+ struct ScrollParts {
+ nsScrollbarFrame* mVScrollbar;
+ nsCOMPtr<nsIContent> mVScrollbarContent;
+ nsScrollbarFrame* mHScrollbar;
+ nsCOMPtr<nsIContent> mHScrollbarContent;
+ nsIFrame* mColumnsFrame;
+ nsIScrollableFrame* mColumnsScrollFrame;
+ };
+
+ DrawResult PaintTreeBody(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect, nsPoint aPt);
+
+ nsITreeBoxObject* GetTreeBoxObject() const { return mTreeBoxObject; }
+
+ // Get the base element, <tree> or <select>
+ nsIContent* GetBaseElement();
+
+ bool GetVerticalOverflow() const { return mVerticalOverflow; }
+ bool GetHorizontalOverflow() const {return mHorizontalOverflow; }
+
+protected:
+ friend class nsOverflowChecker;
+
+ // This method paints a specific column background of the tree.
+ DrawResult PaintColumn(nsTreeColumn* aColumn,
+ const nsRect& aColumnRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect);
+
+ // This method paints a single row in the tree.
+ DrawResult PaintRow(int32_t aRowIndex,
+ const nsRect& aRowRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt);
+
+ // This method paints a single separator in the tree.
+ DrawResult PaintSeparator(int32_t aRowIndex,
+ const nsRect& aSeparatorRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect);
+
+ // This method paints a specific cell in a given row of the tree.
+ DrawResult PaintCell(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aCellRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aCurrX,
+ nsPoint aPt);
+
+ // This method paints the twisty inside a cell in the primary column of an tree.
+ DrawResult PaintTwisty(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aTwistyRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aRemainingWidth,
+ nscoord& aCurrX);
+
+ // This method paints the image inside the cell of an tree.
+ DrawResult PaintImage(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aImageRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aRemainingWidth,
+ nscoord& aCurrX);
+
+ // This method paints the text string inside a particular cell of the tree.
+ DrawResult PaintText(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aTextRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nscoord& aCurrX);
+
+ // This method paints the checkbox inside a particular cell of the tree.
+ DrawResult PaintCheckbox(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aCheckboxRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect);
+
+ // This method paints the progress meter inside a particular cell of the tree.
+ DrawResult PaintProgressMeter(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aProgressMeterRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect);
+
+ // This method paints a drop feedback of the tree.
+ DrawResult PaintDropFeedback(const nsRect& aDropFeedbackRect,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt);
+
+ // This method is called with a specific style context and rect to
+ // paint the background rect as if it were a full-blown frame.
+ DrawResult PaintBackgroundLayer(nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect);
+
+
+ // An internal hit test. aX and aY are expected to be in twips in the
+ // coordinate system of this frame.
+ int32_t GetRowAt(nscoord aX, nscoord aY);
+
+ // Check for bidi characters in the text, and if there are any, ensure
+ // that the prescontext is in bidi mode.
+ void CheckTextForBidi(nsAutoString& aText);
+
+ void AdjustForCellText(nsAutoString& aText,
+ int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ nsRenderingContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics,
+ nsRect& aTextRect);
+
+ // A helper used when hit testing.
+ nsIAtom* GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect,
+ int32_t aRowIndex, nsTreeColumn* aColumn);
+
+ // An internal hit test. aX and aY are expected to be in twips in the
+ // coordinate system of this frame.
+ void GetCellAt(nscoord aX, nscoord aY, int32_t* aRow, nsTreeColumn** aCol,
+ nsIAtom** aChildElt);
+
+ // Retrieve the area for the twisty for a cell.
+ nsITheme* GetTwistyRect(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ nsRect& aImageRect,
+ nsRect& aTwistyRect,
+ nsPresContext* aPresContext,
+ nsStyleContext* aTwistyContext);
+
+ // Fetch an image from the image cache.
+ nsresult GetImage(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
+ nsStyleContext* aStyleContext, bool& aAllowImageRegions, imgIContainer** aResult);
+
+ // Returns the size of a given image. This size *includes* border and
+ // padding. It does not include margins.
+ nsRect GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext, nsStyleContext* aStyleContext);
+
+ // Returns the destination size of the image, not including borders and padding.
+ nsSize GetImageDestSize(nsStyleContext* aStyleContext, bool useImageRegion, imgIContainer* image);
+
+ // Returns the source rectangle of the image to be displayed.
+ nsRect GetImageSourceRect(nsStyleContext* aStyleContext, bool useImageRegion, imgIContainer* image);
+
+ // Returns the height of rows in the tree.
+ int32_t GetRowHeight();
+
+ // Returns our indentation width.
+ int32_t GetIndentation();
+
+ // Calculates our width/height once border and padding have been removed.
+ void CalcInnerBox();
+
+ // Calculate the total width of our scrollable portion
+ nscoord CalcHorzWidth(const ScrollParts& aParts);
+
+ // Looks up a style context in the style cache. On a cache miss we resolve
+ // the pseudo-styles passed in and place them into the cache.
+ nsStyleContext* GetPseudoStyleContext(nsIAtom* aPseudoElement);
+
+ // Retrieves the scrollbars and scrollview relevant to this treebody. We
+ // traverse the frame tree under our base element, in frame order, looking
+ // for the first relevant vertical scrollbar, horizontal scrollbar, and
+ // scrollable frame (with associated content and scrollable view). These
+ // are all volatile and should not be retained.
+ ScrollParts GetScrollParts();
+
+ // Update the curpos of the scrollbar.
+ void UpdateScrollbars(const ScrollParts& aParts);
+
+ // Update the maxpos of the scrollbar.
+ void InvalidateScrollbars(const ScrollParts& aParts, nsWeakFrame& aWeakColumnsFrame);
+
+ // Check overflow and generate events.
+ void CheckOverflow(const ScrollParts& aParts);
+
+ // Calls UpdateScrollbars, Invalidate aNeedsFullInvalidation if true,
+ // InvalidateScrollbars and finally CheckOverflow.
+ // returns true if the frame is still alive after the method call.
+ bool FullScrollbarsUpdate(bool aNeedsFullInvalidation);
+
+ // Use to auto-fill some of the common properties without the view having to do it.
+ // Examples include container, open, selected, and focus.
+ void PrefillPropertyArray(int32_t aRowIndex, nsTreeColumn* aCol);
+
+ // Our internal scroll method, used by all the public scroll methods.
+ nsresult ScrollInternal(const ScrollParts& aParts, int32_t aRow);
+ nsresult ScrollToRowInternal(const ScrollParts& aParts, int32_t aRow);
+ nsresult ScrollToColumnInternal(const ScrollParts& aParts, nsITreeColumn* aCol);
+ nsresult ScrollHorzInternal(const ScrollParts& aParts, int32_t aPosition);
+ nsresult EnsureRowIsVisibleInternal(const ScrollParts& aParts, int32_t aRow);
+
+ // Convert client pixels into appunits in our coordinate space.
+ nsPoint AdjustClientCoordsToBoxCoordSpace(int32_t aX, int32_t aY);
+
+ // Cache the box object
+ void EnsureBoxObject();
+
+ void EnsureView();
+
+ nsresult GetCellWidth(int32_t aRow, nsTreeColumn* aCol,
+ nsRenderingContext* aRenderingContext,
+ nscoord& aDesiredSize, nscoord& aCurrentSize);
+ nscoord CalcMaxRowWidth();
+
+ // Translate the given rect horizontally from tree coordinates into the
+ // coordinate system of our nsTreeBodyFrame. If clip is true, then clip the
+ // rect to its intersection with mInnerBox in the horizontal direction.
+ // Return whether the result has a nonempty intersection with mInnerBox
+ // after projecting both onto the horizontal coordinate axis.
+ bool OffsetForHorzScroll(nsRect& rect, bool clip);
+
+ bool CanAutoScroll(int32_t aRowIndex);
+
+ // Calc the row and above/below/on status given where the mouse currently is hovering.
+ // Also calc if we're in the region in which we want to auto-scroll the tree.
+ // A positive value of |aScrollLines| means scroll down, a negative value
+ // means scroll up, a zero value means that we aren't in drag scroll region.
+ void ComputeDropPosition(mozilla::WidgetGUIEvent* aEvent,
+ int32_t* aRow,
+ int16_t* aOrient,
+ int16_t* aScrollLines);
+
+ // Mark ourselves dirty if we're a select widget
+ void MarkDirtyIfSelect();
+
+ void InvalidateDropFeedback(int32_t aRow, int16_t aOrientation) {
+ InvalidateRow(aRow);
+ if (aOrientation != nsITreeView::DROP_ON)
+ InvalidateRow(aRow + aOrientation);
+ }
+
+public:
+ static
+ already_AddRefed<nsTreeColumn> GetColumnImpl(nsITreeColumn* aUnknownCol) {
+ if (!aUnknownCol)
+ return nullptr;
+
+ nsCOMPtr<nsTreeColumn> col = do_QueryInterface(aUnknownCol);
+ return col.forget();
+ }
+
+ /**
+ * Remove an nsITreeImageListener from being tracked by this frame. Only tree
+ * image listeners that are created by this frame are tracked.
+ *
+ * @param aListener A pointer to an nsTreeImageListener to no longer
+ * track.
+ */
+ void RemoveTreeImageListener(nsTreeImageListener* aListener);
+
+protected:
+
+ // Create a new timer. This method is used to delay various actions like
+ // opening/closing folders or tree scrolling.
+ // aID is type of the action, aFunc is the function to be called when
+ // the timer fires and aType is type of timer - one shot or repeating.
+ nsresult CreateTimer(const mozilla::LookAndFeel::IntID aID,
+ nsTimerCallbackFunc aFunc, int32_t aType,
+ nsITimer** aTimer);
+
+ static void OpenCallback(nsITimer *aTimer, void *aClosure);
+
+ static void CloseCallback(nsITimer *aTimer, void *aClosure);
+
+ static void LazyScrollCallback(nsITimer *aTimer, void *aClosure);
+
+ static void ScrollCallback(nsITimer *aTimer, void *aClosure);
+
+ class ScrollEvent : public mozilla::Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit ScrollEvent(nsTreeBodyFrame *aInner) : mInner(aInner) {}
+ void Revoke() { mInner = nullptr; }
+ private:
+ nsTreeBodyFrame* mInner;
+ };
+
+ void PostScrollEvent();
+ void FireScrollEvent();
+
+ /**
+ * Clear the pointer to this frame for all nsTreeImageListeners that were
+ * created by this frame.
+ */
+ void DetachImageListeners();
+
+#ifdef ACCESSIBILITY
+ /**
+ * Fires 'treeRowCountChanged' event asynchronously. The event supports
+ * nsIDOMCustomEvent interface that is used to expose the following
+ * information structures.
+ *
+ * @param aIndex the row index rows are added/removed from
+ * @param aCount the number of added/removed rows (the sign points to
+ * an operation, plus - addition, minus - removing)
+ */
+ void FireRowCountChangedEvent(int32_t aIndex, int32_t aCount);
+
+ /**
+ * Fires 'treeInvalidated' event asynchronously. The event supports
+ * nsIDOMCustomEvent interface that is used to expose the information
+ * structures described by method arguments.
+ *
+ * @param aStartRow the start index of invalidated rows, -1 means that
+ * columns have been invalidated only
+ * @param aEndRow the end index of invalidated rows, -1 means that columns
+ * have been invalidated only
+ * @param aStartCol the start invalidated column, nullptr means that only rows
+ * have been invalidated
+ * @param aEndCol the end invalidated column, nullptr means that rows have
+ * been invalidated only
+ */
+ void FireInvalidateEvent(int32_t aStartRow, int32_t aEndRow,
+ nsITreeColumn *aStartCol, nsITreeColumn *aEndCol);
+#endif
+
+protected: // Data Members
+
+ class Slots {
+ public:
+ Slots() {
+ }
+
+ ~Slots() {
+ if (mTimer)
+ mTimer->Cancel();
+ }
+
+ friend class nsTreeBodyFrame;
+
+ protected:
+ // If the drop is actually allowed here or not.
+ bool mDropAllowed;
+
+ // True while dragging over the tree.
+ bool mIsDragging;
+
+ // The row the mouse is hovering over during a drop.
+ int32_t mDropRow;
+
+ // Where we want to draw feedback (above/on this row/below) if allowed.
+ int16_t mDropOrient;
+
+ // Number of lines to be scrolled.
+ int16_t mScrollLines;
+
+ // The drag action that was received for this slot
+ uint32_t mDragAction;
+
+ // Timer for opening/closing spring loaded folders or scrolling the tree.
+ nsCOMPtr<nsITimer> mTimer;
+
+ // An array used to keep track of all spring loaded folders.
+ nsTArray<int32_t> mArray;
+ };
+
+ Slots* mSlots;
+
+ nsRevocableEventPtr<ScrollEvent> mScrollEvent;
+
+ RefPtr<ScrollbarActivity> mScrollbarActivity;
+
+ // The cached box object parent.
+ nsCOMPtr<nsITreeBoxObject> mTreeBoxObject;
+
+ // Cached column information.
+ RefPtr<nsTreeColumns> mColumns;
+
+ // The current view for this tree widget. We get all of our row and cell data
+ // from the view.
+ nsCOMPtr<nsITreeView> mView;
+
+ // A cache of all the style contexts we have seen for rows and cells of the tree. This is a mapping from
+ // a list of atoms to a corresponding style context. This cache stores every combination that
+ // occurs in the tree, so for n distinct properties, this cache could have 2 to the n entries
+ // (the power set of all row properties).
+ nsTreeStyleCache mStyleCache;
+
+ // A hashtable that maps from URLs to image request/listener pairs. The URL
+ // is provided by the view or by the style context. The style context
+ // represents a resolved :-moz-tree-cell-image (or twisty) pseudo-element.
+ // It maps directly to an imgIRequest.
+ nsDataHashtable<nsStringHashKey, nsTreeImageCacheEntry> mImageCache;
+
+ // A scratch array used when looking up cached style contexts.
+ AtomArray mScratchArray;
+
+ // The index of the first visible row and the # of rows visible onscreen.
+ // The tree only examines onscreen rows, starting from
+ // this index and going up to index+pageLength.
+ int32_t mTopRowIndex;
+ int32_t mPageLength;
+
+ // The horizontal scroll position
+ nscoord mHorzPosition;
+
+ // The original desired horizontal width before changing it and posting a
+ // reflow callback. In some cases, the desired horizontal width can first be
+ // different from the current desired horizontal width, only to return to
+ // the same value later during the same reflow. In this case, we can cancel
+ // the posted reflow callback and prevent an unnecessary reflow.
+ nscoord mOriginalHorzWidth;
+ // Our desired horizontal width (the width for which we actually have tree
+ // columns).
+ nscoord mHorzWidth;
+ // The amount by which to adjust the width of the last cell.
+ // This depends on whether or not the columnpicker and scrollbars are present.
+ nscoord mAdjustWidth;
+
+ // Cached heights and indent info.
+ nsRect mInnerBox; // 4-byte aligned
+ int32_t mRowHeight;
+ int32_t mIndentation;
+ nscoord mStringWidth;
+
+ int32_t mUpdateBatchNest;
+
+ // Cached row count.
+ int32_t mRowCount;
+
+ // The row the mouse is hovering over.
+ int32_t mMouseOverRow;
+
+ // Whether or not we're currently focused.
+ bool mFocused;
+
+ // Do we have a fixed number of onscreen rows?
+ bool mHasFixedRowCount;
+
+ bool mVerticalOverflow;
+ bool mHorizontalOverflow;
+
+ bool mReflowCallbackPosted;
+
+ // Set while we flush layout to take account of effects of
+ // overflow/underflow event handlers
+ bool mCheckingOverflow;
+
+ // Hash table to keep track of which listeners we created and thus
+ // have pointers to us.
+ nsTHashtable<nsPtrHashKey<nsTreeImageListener> > mCreatedListeners;
+
+}; // class nsTreeBodyFrame
+
+#endif
diff --git a/layout/xul/tree/nsTreeColFrame.cpp b/layout/xul/tree/nsTreeColFrame.cpp
new file mode 100644
index 000000000..649c0b0b4
--- /dev/null
+++ b/layout/xul/tree/nsTreeColFrame.cpp
@@ -0,0 +1,201 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsTreeColFrame.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsStyleContext.h"
+#include "nsNameSpaceManager.h"
+#include "nsIBoxObject.h"
+#include "mozilla/dom/TreeBoxObject.h"
+#include "nsIDOMElement.h"
+#include "nsITreeColumns.h"
+#include "nsIDOMXULTreeElement.h"
+#include "nsDisplayList.h"
+#include "nsTreeBodyFrame.h"
+
+//
+// NS_NewTreeColFrame
+//
+// Creates a new col frame
+//
+nsIFrame*
+NS_NewTreeColFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTreeColFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTreeColFrame)
+
+// Destructor
+nsTreeColFrame::~nsTreeColFrame()
+{
+}
+
+void
+nsTreeColFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+ InvalidateColumns();
+}
+
+void
+nsTreeColFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ InvalidateColumns(false);
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+class nsDisplayXULTreeColSplitterTarget : public nsDisplayItem {
+public:
+ nsDisplayXULTreeColSplitterTarget(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayXULTreeColSplitterTarget);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayXULTreeColSplitterTarget() {
+ MOZ_COUNT_DTOR(nsDisplayXULTreeColSplitterTarget);
+ }
+#endif
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) override;
+ NS_DISPLAY_DECL_NAME("XULTreeColSplitterTarget", TYPE_XUL_TREE_COL_SPLITTER_TARGET)
+};
+
+void
+nsDisplayXULTreeColSplitterTarget::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+ nsRect rect = aRect - ToReferenceFrame();
+ // If we are in either in the first 4 pixels or the last 4 pixels, we're going to
+ // do something really strange. Check for an adjacent splitter.
+ bool left = false;
+ bool right = false;
+ if (mFrame->GetSize().width - nsPresContext::CSSPixelsToAppUnits(4) <= rect.XMost()) {
+ right = true;
+ } else if (nsPresContext::CSSPixelsToAppUnits(4) > rect.x) {
+ left = true;
+ }
+
+ // Swap left and right for RTL trees in order to find the correct splitter
+ if (mFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
+ bool tmp = left;
+ left = right;
+ right = tmp;
+ }
+
+ if (left || right) {
+ // We are a header. Look for the correct splitter.
+ nsIFrame* child;
+ if (left)
+ child = mFrame->GetPrevSibling();
+ else
+ child = mFrame->GetNextSibling();
+
+ if (child && child->GetContent()->NodeInfo()->Equals(nsGkAtoms::splitter,
+ kNameSpaceID_XUL)) {
+ aOutFrames->AppendElement(child);
+ }
+ }
+
+}
+
+void
+nsTreeColFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (!aBuilder->IsForEventDelivery()) {
+ nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
+ return;
+ }
+
+ nsDisplayListCollection set;
+ nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set);
+
+ WrapListsInRedirector(aBuilder, set, aLists);
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayXULTreeColSplitterTarget(aBuilder, this));
+}
+
+nsresult
+nsTreeColFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+
+ if (aAttribute == nsGkAtoms::ordinal || aAttribute == nsGkAtoms::primary) {
+ InvalidateColumns();
+ }
+
+ return rv;
+}
+
+void
+nsTreeColFrame::SetXULBounds(nsBoxLayoutState& aBoxLayoutState,
+ const nsRect& aRect,
+ bool aRemoveOverflowArea)
+{
+ nscoord oldWidth = mRect.width;
+
+ nsBoxFrame::SetXULBounds(aBoxLayoutState, aRect, aRemoveOverflowArea);
+ if (mRect.width != oldWidth) {
+ nsITreeBoxObject* treeBoxObject = GetTreeBoxObject();
+ if (treeBoxObject) {
+ treeBoxObject->Invalidate();
+ }
+ }
+}
+
+nsITreeBoxObject*
+nsTreeColFrame::GetTreeBoxObject()
+{
+ nsITreeBoxObject* result = nullptr;
+
+ nsIContent* parent = mContent->GetParent();
+ if (parent) {
+ nsIContent* grandParent = parent->GetParent();
+ nsCOMPtr<nsIDOMXULElement> treeElement = do_QueryInterface(grandParent);
+ if (treeElement) {
+ nsCOMPtr<nsIBoxObject> boxObject;
+ treeElement->GetBoxObject(getter_AddRefs(boxObject));
+
+ nsCOMPtr<nsITreeBoxObject> treeBoxObject = do_QueryInterface(boxObject);
+ result = treeBoxObject.get();
+ }
+ }
+ return result;
+}
+
+void
+nsTreeColFrame::InvalidateColumns(bool aCanWalkFrameTree)
+{
+ nsITreeBoxObject* treeBoxObject = GetTreeBoxObject();
+ if (treeBoxObject) {
+ nsCOMPtr<nsITreeColumns> columns;
+
+ if (aCanWalkFrameTree) {
+ treeBoxObject->GetColumns(getter_AddRefs(columns));
+ } else {
+ nsTreeBodyFrame* body = static_cast<mozilla::dom::TreeBoxObject*>
+ (treeBoxObject)->GetCachedTreeBodyFrame();
+ if (body) {
+ columns = body->Columns();
+ }
+ }
+
+ if (columns)
+ columns->InvalidateColumns();
+ }
+}
diff --git a/layout/xul/tree/nsTreeColFrame.h b/layout/xul/tree/nsTreeColFrame.h
new file mode 100644
index 000000000..8fc3219d5
--- /dev/null
+++ b/layout/xul/tree/nsTreeColFrame.h
@@ -0,0 +1,55 @@
+/* -*- 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 "mozilla/Attributes.h"
+#include "nsBoxFrame.h"
+
+class nsITreeBoxObject;
+
+nsIFrame* NS_NewTreeColFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+class nsTreeColFrame : public nsBoxFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsTreeColFrame(nsStyleContext* aContext):
+ nsBoxFrame(aContext) {}
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual void BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void SetXULBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect,
+ bool aRemoveOverflowArea = false) override;
+
+ friend nsIFrame* NS_NewTreeColFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+protected:
+ virtual ~nsTreeColFrame();
+
+ /**
+ * @return the tree box object of the tree this column belongs to, or nullptr.
+ */
+ nsITreeBoxObject* GetTreeBoxObject();
+
+ /**
+ * Helper method that gets the nsITreeColumns object this column belongs to
+ * and calls InvalidateColumns() on it.
+ */
+ void InvalidateColumns(bool aCanWalkFrameTree = true);
+};
diff --git a/layout/xul/tree/nsTreeColumns.cpp b/layout/xul/tree/nsTreeColumns.cpp
new file mode 100644
index 000000000..c6ee19342
--- /dev/null
+++ b/layout/xul/tree/nsTreeColumns.cpp
@@ -0,0 +1,765 @@
+/* -*- 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 "nsIDOMElement.h"
+#include "nsIBoxObject.h"
+#include "nsTreeColumns.h"
+#include "nsTreeUtils.h"
+#include "nsStyleContext.h"
+#include "nsDOMClassInfoID.h"
+#include "nsContentUtils.h"
+#include "nsTreeBodyFrame.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TreeBoxObject.h"
+#include "mozilla/dom/TreeColumnBinding.h"
+#include "mozilla/dom/TreeColumnsBinding.h"
+
+using namespace mozilla;
+
+// Column class that caches all the info about our column.
+nsTreeColumn::nsTreeColumn(nsTreeColumns* aColumns, nsIContent* aContent)
+ : mContent(aContent),
+ mColumns(aColumns),
+ mPrevious(nullptr)
+{
+ NS_ASSERTION(aContent &&
+ aContent->NodeInfo()->Equals(nsGkAtoms::treecol,
+ kNameSpaceID_XUL),
+ "nsTreeColumn's content must be a <xul:treecol>");
+
+ Invalidate();
+}
+
+nsTreeColumn::~nsTreeColumn()
+{
+ if (mNext) {
+ mNext->SetPrevious(nullptr);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsTreeColumn)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTreeColumn)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
+ if (tmp->mNext) {
+ tmp->mNext->SetPrevious(nullptr);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNext)
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTreeColumn)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsTreeColumn)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumn)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumn)
+
+// QueryInterface implementation for nsTreeColumn
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumn)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsITreeColumn)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ if (aIID.Equals(NS_GET_IID(nsTreeColumn))) {
+ AddRef();
+ *aInstancePtr = this;
+ return NS_OK;
+ }
+ else
+NS_INTERFACE_MAP_END
+
+nsIFrame*
+nsTreeColumn::GetFrame()
+{
+ NS_ENSURE_TRUE(mContent, nullptr);
+
+ return mContent->GetPrimaryFrame();
+}
+
+bool
+nsTreeColumn::IsLastVisible(nsTreeBodyFrame* aBodyFrame)
+{
+ NS_ASSERTION(GetFrame(), "should have checked for this already");
+
+ // cyclers are fixed width, don't adjust them
+ if (IsCycler())
+ return false;
+
+ // we're certainly not the last visible if we're not visible
+ if (GetFrame()->GetRect().width == 0)
+ return false;
+
+ // try to find a visible successor
+ for (nsTreeColumn *next = GetNext(); next; next = next->GetNext()) {
+ nsIFrame* frame = next->GetFrame();
+ if (frame && frame->GetRect().width > 0)
+ return false;
+ }
+ return true;
+}
+
+nsresult
+nsTreeColumn::GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY, nscoord aHeight, nsRect* aResult)
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ *aResult = nsRect();
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isRTL = aBodyFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ *aResult = frame->GetRect();
+ aResult->y = aY;
+ aResult->height = aHeight;
+ if (isRTL)
+ aResult->x += aBodyFrame->mAdjustWidth;
+ else if (IsLastVisible(aBodyFrame))
+ aResult->width += aBodyFrame->mAdjustWidth;
+ return NS_OK;
+}
+
+nsresult
+nsTreeColumn::GetXInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult)
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ *aResult = 0;
+ return NS_ERROR_FAILURE;
+ }
+ *aResult = frame->GetRect().x;
+ return NS_OK;
+}
+
+nsresult
+nsTreeColumn::GetWidthInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult)
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ *aResult = 0;
+ return NS_ERROR_FAILURE;
+ }
+ *aResult = frame->GetRect().width;
+ if (IsLastVisible(aBodyFrame))
+ *aResult += aBodyFrame->mAdjustWidth;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsTreeColumn::GetElement(nsIDOMElement** aElement)
+{
+ if (mContent) {
+ return CallQueryInterface(mContent, aElement);
+ }
+ *aElement = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetColumns(nsITreeColumns** aColumns)
+{
+ NS_IF_ADDREF(*aColumns = mColumns);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetX(int32_t* aX)
+{
+ nsIFrame* frame = GetFrame();
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+ *aX = nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().x);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetWidth(int32_t* aWidth)
+{
+ nsIFrame* frame = GetFrame();
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+ *aWidth = nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().width);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetId(nsAString& aId)
+{
+ aId = GetId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetIdConst(const char16_t** aIdConst)
+{
+ *aIdConst = mId.get();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetAtom(nsIAtom** aAtom)
+{
+ NS_IF_ADDREF(*aAtom = GetAtom());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetIndex(int32_t* aIndex)
+{
+ *aIndex = GetIndex();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetPrimary(bool* aPrimary)
+{
+ *aPrimary = IsPrimary();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetCycler(bool* aCycler)
+{
+ *aCycler = IsCycler();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetEditable(bool* aEditable)
+{
+ *aEditable = IsEditable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetSelectable(bool* aSelectable)
+{
+ *aSelectable = IsSelectable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetType(int16_t* aType)
+{
+ *aType = GetType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetNext(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetNext());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::GetPrevious(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetPrevious());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumn::Invalidate()
+{
+ nsIFrame* frame = GetFrame();
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+ // Fetch the Id.
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, mId);
+
+ // If we have an Id, cache the Id as an atom.
+ if (!mId.IsEmpty()) {
+ mAtom = NS_Atomize(mId);
+ }
+
+ // Cache our index.
+ nsTreeUtils::GetColumnIndex(mContent, &mIndex);
+
+ const nsStyleVisibility* vis = frame->StyleVisibility();
+
+ // Cache our text alignment policy.
+ const nsStyleText* textStyle = frame->StyleText();
+
+ mTextAlignment = textStyle->mTextAlign;
+ // START or END alignment sometimes means RIGHT
+ if ((mTextAlignment == NS_STYLE_TEXT_ALIGN_START &&
+ vis->mDirection == NS_STYLE_DIRECTION_RTL) ||
+ (mTextAlignment == NS_STYLE_TEXT_ALIGN_END &&
+ vis->mDirection == NS_STYLE_DIRECTION_LTR)) {
+ mTextAlignment = NS_STYLE_TEXT_ALIGN_RIGHT;
+ } else if (mTextAlignment == NS_STYLE_TEXT_ALIGN_START ||
+ mTextAlignment == NS_STYLE_TEXT_ALIGN_END) {
+ mTextAlignment = NS_STYLE_TEXT_ALIGN_LEFT;
+ }
+
+ // Figure out if we're the primary column (that has to have indentation
+ // and twisties drawn.
+ mIsPrimary = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary,
+ nsGkAtoms::_true, eCaseMatters);
+
+ // Figure out if we're a cycling column (one that doesn't cause a selection
+ // to happen).
+ mIsCycler = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::cycler,
+ nsGkAtoms::_true, eCaseMatters);
+
+ mIsEditable = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
+ nsGkAtoms::_true, eCaseMatters);
+
+ mIsSelectable = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::selectable,
+ nsGkAtoms::_false, eCaseMatters);
+
+ mOverflow = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::overflow,
+ nsGkAtoms::_true, eCaseMatters);
+
+ // Figure out our column type. Default type is text.
+ mType = nsITreeColumn::TYPE_TEXT;
+ static nsIContent::AttrValuesArray typestrings[] =
+ {&nsGkAtoms::checkbox, &nsGkAtoms::progressmeter, &nsGkAtoms::password,
+ nullptr};
+ switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
+ typestrings, eCaseMatters)) {
+ case 0: mType = nsITreeColumn::TYPE_CHECKBOX; break;
+ case 1: mType = nsITreeColumn::TYPE_PROGRESSMETER; break;
+ case 2: mType = nsITreeColumn::TYPE_PASSWORD; break;
+ }
+
+ // Fetch the crop style.
+ mCropStyle = 0;
+ static nsIContent::AttrValuesArray cropstrings[] =
+ {&nsGkAtoms::center, &nsGkAtoms::left, &nsGkAtoms::start, nullptr};
+ switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop,
+ cropstrings, eCaseMatters)) {
+ case 0:
+ mCropStyle = 1;
+ break;
+ case 1:
+ case 2:
+ mCropStyle = 2;
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsIContent*
+nsTreeColumn::GetParentObject() const
+{
+ return mContent;
+}
+
+/* virtual */ JSObject*
+nsTreeColumn::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::TreeColumnBinding::Wrap(aCx, this, aGivenProto);
+}
+
+mozilla::dom::Element*
+nsTreeColumn::GetElement(mozilla::ErrorResult& aRv)
+{
+ nsCOMPtr<nsIDOMElement> element;
+ aRv = GetElement(getter_AddRefs(element));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ nsCOMPtr<nsINode> node = do_QueryInterface(element);
+ return node->AsElement();
+}
+
+int32_t
+nsTreeColumn::GetX(mozilla::ErrorResult& aRv)
+{
+ int32_t x;
+ aRv = GetX(&x);
+ return x;
+}
+
+int32_t
+nsTreeColumn::GetWidth(mozilla::ErrorResult& aRv)
+{
+ int32_t width;
+ aRv = GetWidth(&width);
+ return width;
+}
+
+void
+nsTreeColumn::Invalidate(mozilla::ErrorResult& aRv)
+{
+ aRv = Invalidate();
+}
+
+nsTreeColumns::nsTreeColumns(nsTreeBodyFrame* aTree)
+ : mTree(aTree)
+{
+}
+
+nsTreeColumns::~nsTreeColumns()
+{
+ nsTreeColumns::InvalidateColumns();
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsTreeColumns)
+
+// QueryInterface implementation for nsTreeColumns
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumns)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsITreeColumns)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumns)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumns)
+
+nsIContent*
+nsTreeColumns::GetParentObject() const
+{
+ return mTree ? mTree->GetBaseElement() : nullptr;
+}
+
+/* virtual */ JSObject*
+nsTreeColumns::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::TreeColumnsBinding::Wrap(aCx, this, aGivenProto);
+}
+
+dom::TreeBoxObject*
+nsTreeColumns::GetTree() const
+{
+ return mTree ? static_cast<mozilla::dom::TreeBoxObject*>(mTree->GetTreeBoxObject()) : nullptr;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetTree(nsITreeBoxObject** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetTree());
+ return NS_OK;
+}
+
+uint32_t
+nsTreeColumns::Count()
+{
+ EnsureColumns();
+ uint32_t count = 0;
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ ++count;
+ }
+ return count;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetCount(int32_t* _retval)
+{
+ *_retval = Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetLength(int32_t* _retval)
+{
+ *_retval = Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetFirstColumn(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetFirstColumn());
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetLastColumn()
+{
+ EnsureColumns();
+ nsTreeColumn* currCol = mFirstColumn;
+ while (currCol) {
+ nsTreeColumn* next = currCol->GetNext();
+ if (!next) {
+ return currCol;
+ }
+ currCol = next;
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetLastColumn(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetLastColumn());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetPrimaryColumn(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetPrimaryColumn());
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetSortedColumn()
+{
+ EnsureColumns();
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ if (currCol->mContent &&
+ nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None,
+ nsGkAtoms::sortDirection)) {
+ return currCol;
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetSortedColumn(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetSortedColumn());
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetKeyColumn()
+{
+ EnsureColumns();
+
+ nsTreeColumn* first = nullptr;
+ nsTreeColumn* primary = nullptr;
+ nsTreeColumn* sorted = nullptr;
+
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ // Skip hidden columns.
+ if (!currCol->mContent ||
+ currCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters))
+ continue;
+
+ // Skip non-text column
+ if (currCol->GetType() != nsITreeColumn::TYPE_TEXT)
+ continue;
+
+ if (!first)
+ first = currCol;
+
+ if (nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None,
+ nsGkAtoms::sortDirection)) {
+ // Use sorted column as the key.
+ sorted = currCol;
+ break;
+ }
+
+ if (currCol->IsPrimary())
+ if (!primary)
+ primary = currCol;
+ }
+
+ if (sorted)
+ return sorted;
+ if (primary)
+ return primary;
+ return first;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetKeyColumn(nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetKeyColumn());
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetColumnFor(dom::Element* aElement)
+{
+ EnsureColumns();
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ if (currCol->mContent == aElement) {
+ return currCol;
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetColumnFor(nsIDOMElement* aElement, nsITreeColumn** _retval)
+{
+ nsCOMPtr<dom::Element> element = do_QueryInterface(aElement);
+ NS_ADDREF(*_retval = GetColumnFor(element));
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::NamedGetter(const nsAString& aId, bool& aFound)
+{
+ EnsureColumns();
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ if (currCol->GetId().Equals(aId)) {
+ aFound = true;
+ return currCol;
+ }
+ }
+ aFound = false;
+ return nullptr;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetNamedColumn(const nsAString& aId)
+{
+ bool dummy;
+ return NamedGetter(aId, dummy);
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetNamedColumn(const nsAString& aId, nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetNamedColumn(aId));
+ return NS_OK;
+}
+
+void
+nsTreeColumns::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ aNames.AppendElement(currCol->GetId());
+ }
+}
+
+
+nsTreeColumn*
+nsTreeColumns::IndexedGetter(uint32_t aIndex, bool& aFound)
+{
+ EnsureColumns();
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ if (currCol->GetIndex() == static_cast<int32_t>(aIndex)) {
+ aFound = true;
+ return currCol;
+ }
+ }
+ aFound = false;
+ return nullptr;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetColumnAt(uint32_t aIndex)
+{
+ bool dummy;
+ return IndexedGetter(aIndex, dummy);
+}
+
+NS_IMETHODIMP
+nsTreeColumns::GetColumnAt(int32_t aIndex, nsITreeColumn** _retval)
+{
+ NS_IF_ADDREF(*_retval = GetColumnAt(static_cast<uint32_t>(aIndex)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::InvalidateColumns()
+{
+ for (nsTreeColumn* currCol = mFirstColumn; currCol;
+ currCol = currCol->GetNext()) {
+ currCol->SetColumns(nullptr);
+ }
+ mFirstColumn = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeColumns::RestoreNaturalOrder()
+{
+ if (!mTree)
+ return NS_OK;
+
+ nsIContent* content = mTree->GetBaseElement();
+
+ // Strong ref, since we'll be setting attributes
+ nsCOMPtr<nsIContent> colsContent =
+ nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treecols);
+ if (!colsContent)
+ return NS_OK;
+
+ for (uint32_t i = 0; i < colsContent->GetChildCount(); ++i) {
+ nsCOMPtr<nsIContent> child = colsContent->GetChildAt(i);
+ nsAutoString ordinal;
+ ordinal.AppendInt(i);
+ child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, true);
+ }
+
+ nsTreeColumns::InvalidateColumns();
+
+ if (mTree) {
+ mTree->Invalidate();
+ }
+ return NS_OK;
+}
+
+nsTreeColumn*
+nsTreeColumns::GetPrimaryColumn()
+{
+ EnsureColumns();
+ for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) {
+ if (currCol->IsPrimary()) {
+ return currCol;
+ }
+ }
+ return nullptr;
+}
+
+void
+nsTreeColumns::EnsureColumns()
+{
+ if (mTree && !mFirstColumn) {
+ nsIContent* treeContent = mTree->GetBaseElement();
+ nsIContent* colsContent =
+ nsTreeUtils::GetDescendantChild(treeContent, nsGkAtoms::treecols);
+ if (!colsContent)
+ return;
+
+ nsIContent* colContent =
+ nsTreeUtils::GetDescendantChild(colsContent, nsGkAtoms::treecol);
+ if (!colContent)
+ return;
+
+ nsIFrame* colFrame = colContent->GetPrimaryFrame();
+ if (!colFrame)
+ return;
+
+ colFrame = colFrame->GetParent();
+ if (!colFrame)
+ return;
+
+ colFrame = colFrame->PrincipalChildList().FirstChild();
+ if (!colFrame)
+ return;
+
+ // Now that we have the first visible column,
+ // we can enumerate the columns in visible order
+ nsTreeColumn* currCol = nullptr;
+ while (colFrame) {
+ nsIContent* colContent = colFrame->GetContent();
+
+ if (colContent->NodeInfo()->Equals(nsGkAtoms::treecol,
+ kNameSpaceID_XUL)) {
+ // Create a new column structure.
+ nsTreeColumn* col = new nsTreeColumn(this, colContent);
+ if (!col)
+ return;
+
+ if (currCol) {
+ currCol->SetNext(col);
+ col->SetPrevious(currCol);
+ }
+ else {
+ mFirstColumn = col;
+ }
+ currCol = col;
+ }
+
+ colFrame = colFrame->GetNextSibling();
+ }
+ }
+}
diff --git a/layout/xul/tree/nsTreeColumns.h b/layout/xul/tree/nsTreeColumns.h
new file mode 100644
index 000000000..0e27c6a03
--- /dev/null
+++ b/layout/xul/tree/nsTreeColumns.h
@@ -0,0 +1,215 @@
+/* -*- 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/. */
+
+#ifndef nsTreeColumns_h__
+#define nsTreeColumns_h__
+
+#include "nsITreeColumns.h"
+#include "nsITreeBoxObject.h"
+#include "mozilla/Attributes.h"
+#include "nsCoord.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+
+class nsTreeBodyFrame;
+class nsTreeColumns;
+class nsIFrame;
+class nsIContent;
+struct nsRect;
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+class Element;
+class TreeBoxObject;
+} // namespace dom
+} // namespace mozilla
+
+#define NS_TREECOLUMN_IMPL_CID \
+{ /* 02cd1963-4b5d-4a6c-9223-814d3ade93a3 */ \
+ 0x02cd1963, \
+ 0x4b5d, \
+ 0x4a6c, \
+ {0x92, 0x23, 0x81, 0x4d, 0x3a, 0xde, 0x93, 0xa3} \
+}
+
+// This class is our column info. We use it to iterate our columns and to obtain
+// information about each column.
+class nsTreeColumn final : public nsITreeColumn
+ , public nsWrapperCache
+{
+public:
+ nsTreeColumn(nsTreeColumns* aColumns, nsIContent* aContent);
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TREECOLUMN_IMPL_CID)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsTreeColumn)
+ NS_DECL_NSITREECOLUMN
+
+ // WebIDL
+ nsIContent* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ mozilla::dom::Element* GetElement(mozilla::ErrorResult& aRv);
+
+ nsTreeColumns* GetColumns() const { return mColumns; }
+
+ int32_t GetX(mozilla::ErrorResult& aRv);
+ int32_t GetWidth(mozilla::ErrorResult& aRv);
+
+ // GetId is fine
+ int32_t Index() const { return mIndex; }
+
+ bool Primary() const { return mIsPrimary; }
+ bool Cycler() const { return mIsCycler; }
+ bool Editable() const { return mIsEditable; }
+ bool Selectable() const { return mIsSelectable; }
+ int16_t Type() const { return mType; }
+
+ nsTreeColumn* GetNext() const { return mNext; }
+ nsTreeColumn* GetPrevious() const { return mPrevious; }
+
+ void Invalidate(mozilla::ErrorResult& aRv);
+
+ friend class nsTreeBodyFrame;
+ friend class nsTreeColumns;
+
+protected:
+ ~nsTreeColumn();
+ nsIFrame* GetFrame();
+ nsIFrame* GetFrame(nsTreeBodyFrame* aBodyFrame);
+ // Don't call this if GetWidthInTwips or GetRect fails
+ bool IsLastVisible(nsTreeBodyFrame* aBodyFrame);
+
+ /**
+ * Returns a rect with x and width taken from the frame's rect and specified
+ * y and height. May fail in case there's no frame for the column.
+ */
+ nsresult GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY, nscoord aHeight,
+ nsRect* aResult);
+
+ nsresult GetXInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult);
+ nsresult GetWidthInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult);
+
+ void SetColumns(nsTreeColumns* aColumns) { mColumns = aColumns; }
+
+ const nsAString& GetId() { return mId; }
+ nsIAtom* GetAtom() { return mAtom; }
+
+ int32_t GetIndex() { return mIndex; }
+
+ bool IsPrimary() { return mIsPrimary; }
+ bool IsCycler() { return mIsCycler; }
+ bool IsEditable() { return mIsEditable; }
+ bool IsSelectable() { return mIsSelectable; }
+ bool Overflow() { return mOverflow; }
+
+ int16_t GetType() { return mType; }
+
+ int8_t GetCropStyle() { return mCropStyle; }
+ int32_t GetTextAlignment() { return mTextAlignment; }
+
+ void SetNext(nsTreeColumn* aNext) {
+ NS_ASSERTION(!mNext, "already have a next sibling");
+ mNext = aNext;
+ }
+ void SetPrevious(nsTreeColumn* aPrevious) { mPrevious = aPrevious; }
+
+private:
+ /**
+ * Non-null nsIContent for the associated <treecol> element.
+ */
+ nsCOMPtr<nsIContent> mContent;
+
+ nsTreeColumns* mColumns;
+
+ nsString mId;
+ nsCOMPtr<nsIAtom> mAtom;
+
+ int32_t mIndex;
+
+ bool mIsPrimary;
+ bool mIsCycler;
+ bool mIsEditable;
+ bool mIsSelectable;
+ bool mOverflow;
+
+ int16_t mType;
+
+ int8_t mCropStyle;
+ int8_t mTextAlignment;
+
+ RefPtr<nsTreeColumn> mNext;
+ nsTreeColumn* mPrevious;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsTreeColumn, NS_TREECOLUMN_IMPL_CID)
+
+class nsTreeColumns final : public nsITreeColumns
+ , public nsWrapperCache
+{
+private:
+ ~nsTreeColumns();
+
+public:
+ explicit nsTreeColumns(nsTreeBodyFrame* aTree);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsTreeColumns)
+ NS_DECL_NSITREECOLUMNS
+
+ nsIContent* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL
+ mozilla::dom::TreeBoxObject* GetTree() const;
+ uint32_t Count();
+ uint32_t Length()
+ {
+ return Count();
+ }
+
+ nsTreeColumn* GetFirstColumn() { EnsureColumns(); return mFirstColumn; }
+ nsTreeColumn* GetLastColumn();
+
+ nsTreeColumn* GetPrimaryColumn();
+ nsTreeColumn* GetSortedColumn();
+ nsTreeColumn* GetKeyColumn();
+
+ nsTreeColumn* GetColumnFor(mozilla::dom::Element* aElement);
+
+ nsTreeColumn* IndexedGetter(uint32_t aIndex, bool& aFound);
+ nsTreeColumn* GetColumnAt(uint32_t aIndex);
+ nsTreeColumn* NamedGetter(const nsAString& aId, bool& aFound);
+ nsTreeColumn* GetNamedColumn(const nsAString& aId);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ // Uses XPCOM InvalidateColumns().
+ // Uses XPCOM RestoreNaturalOrder().
+
+ friend class nsTreeBodyFrame;
+protected:
+ void SetTree(nsTreeBodyFrame* aTree) { mTree = aTree; }
+
+ // Builds our cache of column info.
+ void EnsureColumns();
+
+private:
+ nsTreeBodyFrame* mTree;
+
+ /**
+ * The first column in the list of columns. All of the columns are supposed
+ * to be "alive", i.e. have a frame. This is achieved by clearing the columns
+ * list each time an nsTreeColFrame is destroyed.
+ *
+ * XXX this means that new nsTreeColumn objects are unnecessarily created
+ * for untouched columns.
+ */
+ RefPtr<nsTreeColumn> mFirstColumn;
+};
+
+#endif // nsTreeColumns_h__
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;
+}
diff --git a/layout/xul/tree/nsTreeContentView.h b/layout/xul/tree/nsTreeContentView.h
new file mode 100644
index 000000000..c2c3f5030
--- /dev/null
+++ b/layout/xul/tree/nsTreeContentView.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsTreeContentView_h__
+#define nsTreeContentView_h__
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsTArray.h"
+#include "nsStubDocumentObserver.h"
+#include "nsITreeBoxObject.h"
+#include "nsITreeColumns.h"
+#include "nsITreeView.h"
+#include "nsITreeContentView.h"
+#include "nsITreeSelection.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+class nsIDocument;
+class Row;
+
+nsresult NS_NewTreeContentView(nsITreeView** aResult);
+
+class nsTreeContentView final : public nsINativeTreeView,
+ public nsITreeContentView,
+ public nsStubDocumentObserver
+{
+ public:
+ nsTreeContentView(void);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTreeContentView,
+ nsINativeTreeView)
+
+ NS_DECL_NSITREEVIEW
+ // nsINativeTreeView: Untrusted code can use us
+ NS_IMETHOD EnsureNative() override { return NS_OK; }
+
+ NS_DECL_NSITREECONTENTVIEW
+
+ // nsIDocumentObserver
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ static bool CanTrustTreeSelection(nsISupports* aValue);
+
+ protected:
+ ~nsTreeContentView(void);
+
+ // Recursive methods which deal with serializing of nested content.
+ void Serialize(nsIContent* aContent, int32_t aParentIndex, int32_t* aIndex,
+ nsTArray<mozilla::UniquePtr<Row>>& aRows);
+
+ void SerializeItem(nsIContent* aContent, int32_t aParentIndex,
+ int32_t* aIndex, nsTArray<mozilla::UniquePtr<Row>>& aRows);
+
+ void SerializeSeparator(nsIContent* aContent, int32_t aParentIndex,
+ int32_t* aIndex, nsTArray<mozilla::UniquePtr<Row>>& aRows);
+
+ void GetIndexInSubtree(nsIContent* aContainer, nsIContent* aContent, int32_t* aResult);
+
+ // Helper methods which we use to manage our plain array of rows.
+ int32_t EnsureSubtree(int32_t aIndex);
+
+ int32_t RemoveSubtree(int32_t aIndex);
+
+ int32_t InsertRow(int32_t aParentIndex, int32_t aIndex, nsIContent* aContent);
+
+ void InsertRowFor(nsIContent* aParent, nsIContent* aChild);
+
+ int32_t RemoveRow(int32_t aIndex);
+
+ void ClearRows();
+
+ void OpenContainer(int32_t aIndex);
+
+ void CloseContainer(int32_t aIndex);
+
+ int32_t FindContent(nsIContent* aContent);
+
+ void UpdateSubtreeSizes(int32_t aIndex, int32_t aCount);
+
+ void UpdateParentIndexes(int32_t aIndex, int32_t aSkip, int32_t aCount);
+
+ // Content helpers.
+ nsIContent* GetCell(nsIContent* aContainer, nsITreeColumn* aCol);
+
+ private:
+ nsCOMPtr<nsITreeBoxObject> mBoxObject;
+ nsCOMPtr<nsITreeSelection> mSelection;
+ nsCOMPtr<nsIContent> mRoot;
+ nsCOMPtr<nsIContent> mBody;
+ nsIDocument* mDocument; // WEAK
+ nsTArray<mozilla::UniquePtr<Row>> mRows;
+};
+
+#endif // nsTreeContentView_h__
diff --git a/layout/xul/tree/nsTreeImageListener.cpp b/layout/xul/tree/nsTreeImageListener.cpp
new file mode 100644
index 000000000..f559be042
--- /dev/null
+++ b/layout/xul/tree/nsTreeImageListener.cpp
@@ -0,0 +1,115 @@
+/* -*- 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 "nsTreeImageListener.h"
+#include "nsITreeBoxObject.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "nsIContent.h"
+
+NS_IMPL_ISUPPORTS(nsTreeImageListener, imgINotificationObserver)
+
+nsTreeImageListener::nsTreeImageListener(nsTreeBodyFrame* aTreeFrame)
+ : mTreeFrame(aTreeFrame),
+ mInvalidationSuppressed(true),
+ mInvalidationArea(nullptr)
+{
+}
+
+nsTreeImageListener::~nsTreeImageListener()
+{
+ delete mInvalidationArea;
+}
+
+NS_IMETHODIMP
+nsTreeImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+{
+ if (aType == imgINotificationObserver::IS_ANIMATED) {
+ return mTreeFrame ? mTreeFrame->OnImageIsAnimated(aRequest) : NS_OK;
+ }
+
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ // Ensure the animation (if any) is started. Note: There is no
+ // corresponding call to Decrement for this. This Increment will be
+ // 'cleaned up' by the Request when it is destroyed, but only then.
+ aRequest->IncrementAnimationConsumers();
+ }
+
+ if (aType == imgINotificationObserver::FRAME_UPDATE) {
+ Invalidate();
+ }
+
+ return NS_OK;
+}
+
+void
+nsTreeImageListener::AddCell(int32_t aIndex, nsITreeColumn* aCol)
+{
+ if (!mInvalidationArea) {
+ mInvalidationArea = new InvalidationArea(aCol);
+ mInvalidationArea->AddRow(aIndex);
+ }
+ else {
+ InvalidationArea* currArea;
+ for (currArea = mInvalidationArea; currArea; currArea = currArea->GetNext()) {
+ if (currArea->GetCol() == aCol) {
+ currArea->AddRow(aIndex);
+ break;
+ }
+ }
+ if (!currArea) {
+ currArea = new InvalidationArea(aCol);
+ currArea->SetNext(mInvalidationArea);
+ mInvalidationArea = currArea;
+ mInvalidationArea->AddRow(aIndex);
+ }
+ }
+}
+
+
+void
+nsTreeImageListener::Invalidate()
+{
+ if (!mInvalidationSuppressed) {
+ for (InvalidationArea* currArea = mInvalidationArea; currArea;
+ currArea = currArea->GetNext()) {
+ // Loop from min to max, invalidating each cell that was listening for this image.
+ for (int32_t i = currArea->GetMin(); i <= currArea->GetMax(); ++i) {
+ if (mTreeFrame) {
+ nsITreeBoxObject* tree = mTreeFrame->GetTreeBoxObject();
+ if (tree) {
+ tree->InvalidateCell(i, currArea->GetCol());
+ }
+ }
+ }
+ }
+ }
+}
+
+nsTreeImageListener::InvalidationArea::InvalidationArea(nsITreeColumn* aCol)
+ : mCol(aCol),
+ mMin(-1), // min should start out "undefined"
+ mMax(0),
+ mNext(nullptr)
+{
+}
+
+void
+nsTreeImageListener::InvalidationArea::AddRow(int32_t aIndex)
+{
+ if (mMin == -1)
+ mMin = mMax = aIndex;
+ else if (aIndex < mMin)
+ mMin = aIndex;
+ else if (aIndex > mMax)
+ mMax = aIndex;
+}
+
+NS_IMETHODIMP
+nsTreeImageListener::ClearFrame()
+{
+ mTreeFrame = nullptr;
+ return NS_OK;
+}
diff --git a/layout/xul/tree/nsTreeImageListener.h b/layout/xul/tree/nsTreeImageListener.h
new file mode 100644
index 000000000..573b246e5
--- /dev/null
+++ b/layout/xul/tree/nsTreeImageListener.h
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+#ifndef nsTreeImageListener_h__
+#define nsTreeImageListener_h__
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsITreeColumns.h"
+#include "nsTreeBodyFrame.h"
+#include "mozilla/Attributes.h"
+
+// This class handles image load observation.
+class nsTreeImageListener final : public imgINotificationObserver
+{
+public:
+ explicit nsTreeImageListener(nsTreeBodyFrame *aTreeFrame);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ NS_IMETHOD ClearFrame();
+
+ friend class nsTreeBodyFrame;
+
+protected:
+ ~nsTreeImageListener();
+
+ void UnsuppressInvalidation() { mInvalidationSuppressed = false; }
+ void Invalidate();
+ void AddCell(int32_t aIndex, nsITreeColumn* aCol);
+
+private:
+ nsTreeBodyFrame* mTreeFrame;
+
+ // A guard that prevents us from recursive painting.
+ bool mInvalidationSuppressed;
+
+ class InvalidationArea {
+ public:
+ explicit InvalidationArea(nsITreeColumn* aCol);
+ ~InvalidationArea() { delete mNext; }
+
+ friend class nsTreeImageListener;
+
+ protected:
+ void AddRow(int32_t aIndex);
+ nsITreeColumn* GetCol() { return mCol.get(); }
+ int32_t GetMin() { return mMin; }
+ int32_t GetMax() { return mMax; }
+ InvalidationArea* GetNext() { return mNext; }
+ void SetNext(InvalidationArea* aNext) { mNext = aNext; }
+
+ private:
+ nsCOMPtr<nsITreeColumn> mCol;
+ int32_t mMin;
+ int32_t mMax;
+ InvalidationArea* mNext;
+ };
+
+ InvalidationArea* mInvalidationArea;
+};
+
+#endif // nsTreeImageListener_h__
diff --git a/layout/xul/tree/nsTreeSelection.cpp b/layout/xul/tree/nsTreeSelection.cpp
new file mode 100644
index 000000000..1d251a096
--- /dev/null
+++ b/layout/xul/tree/nsTreeSelection.cpp
@@ -0,0 +1,866 @@
+/* -*- 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 "mozilla/AsyncEventDispatcher.h"
+#include "nsCOMPtr.h"
+#include "nsTreeSelection.h"
+#include "nsIBoxObject.h"
+#include "nsITreeBoxObject.h"
+#include "nsITreeView.h"
+#include "nsString.h"
+#include "nsIDOMElement.h"
+#include "nsDOMClassInfoID.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla;
+
+// A helper class for managing our ranges of selection.
+struct nsTreeRange
+{
+ nsTreeSelection* mSelection;
+
+ nsTreeRange* mPrev;
+ nsTreeRange* mNext;
+
+ int32_t mMin;
+ int32_t mMax;
+
+ nsTreeRange(nsTreeSelection* aSel, int32_t aSingleVal)
+ :mSelection(aSel), mPrev(nullptr), mNext(nullptr), mMin(aSingleVal), mMax(aSingleVal) {}
+ nsTreeRange(nsTreeSelection* aSel, int32_t aMin, int32_t aMax)
+ :mSelection(aSel), mPrev(nullptr), mNext(nullptr), mMin(aMin), mMax(aMax) {}
+
+ ~nsTreeRange() { delete mNext; }
+
+ void Connect(nsTreeRange* aPrev = nullptr, nsTreeRange* aNext = nullptr) {
+ if (aPrev)
+ aPrev->mNext = this;
+ else
+ mSelection->mFirstRange = this;
+
+ if (aNext)
+ aNext->mPrev = this;
+
+ mPrev = aPrev;
+ mNext = aNext;
+ }
+
+ nsresult RemoveRange(int32_t aStart, int32_t aEnd) {
+ // This should so be a loop... sigh...
+ // We start past the range to remove, so no more to remove
+ if (aEnd < mMin)
+ return NS_OK;
+ // We are the last range to be affected
+ if (aEnd < mMax) {
+ if (aStart <= mMin) {
+ // Just chop the start of the range off
+ mMin = aEnd + 1;
+ } else {
+ // We need to split the range
+ nsTreeRange* range = new nsTreeRange(mSelection, aEnd + 1, mMax);
+ if (!range)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mMax = aStart - 1;
+ range->Connect(this, mNext);
+ }
+ return NS_OK;
+ }
+ nsTreeRange* next = mNext;
+ if (aStart <= mMin) {
+ // The remove includes us, remove ourselves from the list
+ if (mPrev)
+ mPrev->mNext = next;
+ else
+ mSelection->mFirstRange = next;
+
+ if (next)
+ next->mPrev = mPrev;
+ mPrev = mNext = nullptr;
+ delete this;
+ } else if (aStart <= mMax) {
+ // Just chop the end of the range off
+ mMax = aStart - 1;
+ }
+ return next ? next->RemoveRange(aStart, aEnd) : NS_OK;
+ }
+
+ nsresult Remove(int32_t aIndex) {
+ if (aIndex >= mMin && aIndex <= mMax) {
+ // We have found the range that contains us.
+ if (mMin == mMax) {
+ // Delete the whole range.
+ if (mPrev)
+ mPrev->mNext = mNext;
+ if (mNext)
+ mNext->mPrev = mPrev;
+ nsTreeRange* first = mSelection->mFirstRange;
+ if (first == this)
+ mSelection->mFirstRange = mNext;
+ mNext = mPrev = nullptr;
+ delete this;
+ }
+ else if (aIndex == mMin)
+ mMin++;
+ else if (aIndex == mMax)
+ mMax--;
+ else {
+ // We have to break this range.
+ nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex + 1, mMax);
+ if (!newRange)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ newRange->Connect(this, mNext);
+ mMax = aIndex - 1;
+ }
+ }
+ else if (mNext)
+ return mNext->Remove(aIndex);
+
+ return NS_OK;
+ }
+
+ nsresult Add(int32_t aIndex) {
+ if (aIndex < mMin) {
+ // We have found a spot to insert.
+ if (aIndex + 1 == mMin)
+ mMin = aIndex;
+ else if (mPrev && mPrev->mMax+1 == aIndex)
+ mPrev->mMax = aIndex;
+ else {
+ // We have to create a new range.
+ nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex);
+ if (!newRange)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ newRange->Connect(mPrev, this);
+ }
+ }
+ else if (mNext)
+ mNext->Add(aIndex);
+ else {
+ // Insert on to the end.
+ if (mMax+1 == aIndex)
+ mMax = aIndex;
+ else {
+ // We have to create a new range.
+ nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex);
+ if (!newRange)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ newRange->Connect(this, nullptr);
+ }
+ }
+ return NS_OK;
+ }
+
+ bool Contains(int32_t aIndex) {
+ if (aIndex >= mMin && aIndex <= mMax)
+ return true;
+
+ if (mNext)
+ return mNext->Contains(aIndex);
+
+ return false;
+ }
+
+ int32_t Count() {
+ int32_t total = mMax - mMin + 1;
+ if (mNext)
+ total += mNext->Count();
+ return total;
+ }
+
+ static void CollectRanges(nsTreeRange* aRange, nsTArray<int32_t>& aRanges)
+ {
+ nsTreeRange* cur = aRange;
+ while (cur) {
+ aRanges.AppendElement(cur->mMin);
+ aRanges.AppendElement(cur->mMax);
+ cur = cur->mNext;
+ }
+ }
+
+ static void InvalidateRanges(nsITreeBoxObject* aTree,
+ nsTArray<int32_t>& aRanges)
+ {
+ if (aTree) {
+ nsCOMPtr<nsITreeBoxObject> tree = aTree;
+ for (uint32_t i = 0; i < aRanges.Length(); i += 2) {
+ aTree->InvalidateRange(aRanges[i], aRanges[i + 1]);
+ }
+ }
+ }
+
+ void Invalidate() {
+ nsTArray<int32_t> ranges;
+ CollectRanges(this, ranges);
+ InvalidateRanges(mSelection->mTree, ranges);
+
+ }
+
+ void RemoveAllBut(int32_t aIndex) {
+ if (aIndex >= mMin && aIndex <= mMax) {
+
+ // Invalidate everything in this list.
+ nsTArray<int32_t> ranges;
+ CollectRanges(mSelection->mFirstRange, ranges);
+
+ mMin = aIndex;
+ mMax = aIndex;
+
+ nsTreeRange* first = mSelection->mFirstRange;
+ if (mPrev)
+ mPrev->mNext = mNext;
+ if (mNext)
+ mNext->mPrev = mPrev;
+ mNext = mPrev = nullptr;
+
+ if (first != this) {
+ delete mSelection->mFirstRange;
+ mSelection->mFirstRange = this;
+ }
+ InvalidateRanges(mSelection->mTree, ranges);
+ }
+ else if (mNext)
+ mNext->RemoveAllBut(aIndex);
+ }
+
+ void Insert(nsTreeRange* aRange) {
+ if (mMin >= aRange->mMax)
+ aRange->Connect(mPrev, this);
+ else if (mNext)
+ mNext->Insert(aRange);
+ else
+ aRange->Connect(this, nullptr);
+ }
+};
+
+nsTreeSelection::nsTreeSelection(nsITreeBoxObject* aTree)
+ : mTree(aTree),
+ mSuppressed(false),
+ mCurrentIndex(-1),
+ mShiftSelectPivot(-1),
+ mFirstRange(nullptr)
+{
+}
+
+nsTreeSelection::~nsTreeSelection()
+{
+ delete mFirstRange;
+ if (mSelectTimer)
+ mSelectTimer->Cancel();
+}
+
+NS_IMPL_CYCLE_COLLECTION(nsTreeSelection, mTree, mCurrentColumn)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeSelection)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeSelection)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeSelection)
+ NS_INTERFACE_MAP_ENTRY(nsITreeSelection)
+ NS_INTERFACE_MAP_ENTRY(nsINativeTreeSelection)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TreeSelection)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP nsTreeSelection::GetTree(nsITreeBoxObject * *aTree)
+{
+ NS_IF_ADDREF(*aTree = mTree);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::SetTree(nsITreeBoxObject * aTree)
+{
+ if (mSelectTimer) {
+ mSelectTimer->Cancel();
+ mSelectTimer = nullptr;
+ }
+
+ // Make sure aTree really implements nsITreeBoxObject and nsIBoxObject!
+ nsCOMPtr<nsIBoxObject> bo = do_QueryInterface(aTree);
+ mTree = do_QueryInterface(bo);
+ NS_ENSURE_STATE(mTree == aTree);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetSingle(bool* aSingle)
+{
+ if (!mTree)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree);
+
+ nsCOMPtr<nsIDOMElement> element;
+ boxObject->GetElement(getter_AddRefs(element));
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(element);
+
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::single, &nsGkAtoms::cell, &nsGkAtoms::text, nullptr};
+
+ *aSingle = content->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::seltype,
+ strings, eCaseMatters) >= 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::IsSelected(int32_t aIndex, bool* aResult)
+{
+ if (mFirstRange)
+ *aResult = mFirstRange->Contains(aIndex);
+ else
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::TimedSelect(int32_t aIndex, int32_t aMsec)
+{
+ bool suppressSelect = mSuppressed;
+
+ if (aMsec != -1)
+ mSuppressed = true;
+
+ nsresult rv = Select(aIndex);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (aMsec != -1) {
+ mSuppressed = suppressSelect;
+ if (!mSuppressed) {
+ if (mSelectTimer)
+ mSelectTimer->Cancel();
+
+ mSelectTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mSelectTimer->InitWithFuncCallback(SelectCallback, this, aMsec,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::Select(int32_t aIndex)
+{
+ mShiftSelectPivot = -1;
+
+ nsresult rv = SetCurrentIndex(aIndex);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mFirstRange) {
+ bool alreadySelected = mFirstRange->Contains(aIndex);
+
+ if (alreadySelected) {
+ int32_t count = mFirstRange->Count();
+ if (count > 1) {
+ // We need to deselect everything but our item.
+ mFirstRange->RemoveAllBut(aIndex);
+ FireOnSelectHandler();
+ }
+ return NS_OK;
+ }
+ else {
+ // Clear out our selection.
+ mFirstRange->Invalidate();
+ delete mFirstRange;
+ }
+ }
+
+ // Create our new selection.
+ mFirstRange = new nsTreeRange(this, aIndex);
+ if (!mFirstRange)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mFirstRange->Invalidate();
+
+ // Fire the select event
+ FireOnSelectHandler();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::ToggleSelect(int32_t aIndex)
+{
+ // There are six cases that can occur on a ToggleSelect with our
+ // range code.
+ // (1) A new range should be made for a selection.
+ // (2) A single range is removed from the selection.
+ // (3) The item is added to an existing range.
+ // (4) The item is removed from an existing range.
+ // (5) The addition of the item causes two ranges to be merged.
+ // (6) The removal of the item causes two ranges to be split.
+ mShiftSelectPivot = -1;
+ nsresult rv = SetCurrentIndex(aIndex);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!mFirstRange)
+ Select(aIndex);
+ else {
+ if (!mFirstRange->Contains(aIndex)) {
+ bool single;
+ rv = GetSingle(&single);
+ if (NS_SUCCEEDED(rv) && !single)
+ rv = mFirstRange->Add(aIndex);
+ }
+ else
+ rv = mFirstRange->Remove(aIndex);
+ if (NS_SUCCEEDED(rv)) {
+ if (mTree)
+ mTree->InvalidateRow(aIndex);
+
+ FireOnSelectHandler();
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsTreeSelection::RangedSelect(int32_t aStartIndex, int32_t aEndIndex, bool aAugment)
+{
+ bool single;
+ nsresult rv = GetSingle(&single);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if ((mFirstRange || (aStartIndex != aEndIndex)) && single)
+ return NS_OK;
+
+ if (!aAugment) {
+ // Clear our selection.
+ if (mFirstRange) {
+ mFirstRange->Invalidate();
+ delete mFirstRange;
+ mFirstRange = nullptr;
+ }
+ }
+
+ if (aStartIndex == -1) {
+ if (mShiftSelectPivot != -1)
+ aStartIndex = mShiftSelectPivot;
+ else if (mCurrentIndex != -1)
+ aStartIndex = mCurrentIndex;
+ else
+ aStartIndex = aEndIndex;
+ }
+
+ mShiftSelectPivot = aStartIndex;
+ rv = SetCurrentIndex(aEndIndex);
+ if (NS_FAILED(rv))
+ return rv;
+
+ int32_t start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex;
+ int32_t end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex;
+
+ if (aAugment && mFirstRange) {
+ // We need to remove all the items within our selected range from the selection,
+ // and then we insert our new range into the list.
+ nsresult rv = mFirstRange->RemoveRange(start, end);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ nsTreeRange* range = new nsTreeRange(this, start, end);
+ if (!range)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ range->Invalidate();
+
+ if (aAugment && mFirstRange)
+ mFirstRange->Insert(range);
+ else
+ mFirstRange = range;
+
+ FireOnSelectHandler();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::ClearRange(int32_t aStartIndex, int32_t aEndIndex)
+{
+ nsresult rv = SetCurrentIndex(aEndIndex);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mFirstRange) {
+ int32_t start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex;
+ int32_t end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex;
+
+ mFirstRange->RemoveRange(start, end);
+
+ if (mTree)
+ mTree->InvalidateRange(start, end);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::ClearSelection()
+{
+ if (mFirstRange) {
+ mFirstRange->Invalidate();
+ delete mFirstRange;
+ mFirstRange = nullptr;
+ }
+ mShiftSelectPivot = -1;
+
+ FireOnSelectHandler();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::InvertSelection()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsTreeSelection::SelectAll()
+{
+ if (!mTree)
+ return NS_OK;
+
+ nsCOMPtr<nsITreeView> view;
+ mTree->GetView(getter_AddRefs(view));
+ if (!view)
+ return NS_OK;
+
+ int32_t rowCount;
+ view->GetRowCount(&rowCount);
+ bool single;
+ nsresult rv = GetSingle(&single);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (rowCount == 0 || (rowCount > 1 && single))
+ return NS_OK;
+
+ mShiftSelectPivot = -1;
+
+ // Invalidate not necessary when clearing selection, since
+ // we're going to invalidate the world on the SelectAll.
+ delete mFirstRange;
+
+ mFirstRange = new nsTreeRange(this, 0, rowCount-1);
+ mFirstRange->Invalidate();
+
+ FireOnSelectHandler();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetRangeCount(int32_t* aResult)
+{
+ int32_t count = 0;
+ nsTreeRange* curr = mFirstRange;
+ while (curr) {
+ count++;
+ curr = curr->mNext;
+ }
+
+ *aResult = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetRangeAt(int32_t aIndex, int32_t* aMin, int32_t* aMax)
+{
+ *aMin = *aMax = -1;
+ int32_t i = -1;
+ nsTreeRange* curr = mFirstRange;
+ while (curr) {
+ i++;
+ if (i == aIndex) {
+ *aMin = curr->mMin;
+ *aMax = curr->mMax;
+ break;
+ }
+ curr = curr->mNext;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetCount(int32_t *count)
+{
+ if (mFirstRange)
+ *count = mFirstRange->Count();
+ else // No range available, so there's no selected row.
+ *count = 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetSelectEventsSuppressed(bool *aSelectEventsSuppressed)
+{
+ *aSelectEventsSuppressed = mSuppressed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::SetSelectEventsSuppressed(bool aSelectEventsSuppressed)
+{
+ mSuppressed = aSelectEventsSuppressed;
+ if (!mSuppressed)
+ FireOnSelectHandler();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::GetCurrentIndex(int32_t *aCurrentIndex)
+{
+ *aCurrentIndex = mCurrentIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::SetCurrentIndex(int32_t aIndex)
+{
+ if (!mTree) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mCurrentIndex == aIndex) {
+ return NS_OK;
+ }
+ if (mCurrentIndex != -1 && mTree)
+ mTree->InvalidateRow(mCurrentIndex);
+
+ mCurrentIndex = aIndex;
+ if (!mTree)
+ return NS_OK;
+
+ if (aIndex != -1)
+ mTree->InvalidateRow(aIndex);
+
+ // Fire DOMMenuItemActive or DOMMenuItemInactive event for tree.
+ nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree);
+ NS_ASSERTION(boxObject, "no box object!");
+ if (!boxObject)
+ return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIDOMElement> treeElt;
+ boxObject->GetElement(getter_AddRefs(treeElt));
+
+ nsCOMPtr<nsINode> treeDOMNode(do_QueryInterface(treeElt));
+ NS_ENSURE_STATE(treeDOMNode);
+
+ NS_NAMED_LITERAL_STRING(DOMMenuItemActive, "DOMMenuItemActive");
+ NS_NAMED_LITERAL_STRING(DOMMenuItemInactive, "DOMMenuItemInactive");
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(treeDOMNode,
+ (aIndex != -1 ? DOMMenuItemActive :
+ DOMMenuItemInactive),
+ true, false);
+ return asyncDispatcher->PostDOMEvent();
+}
+
+NS_IMETHODIMP nsTreeSelection::GetCurrentColumn(nsITreeColumn** aCurrentColumn)
+{
+ NS_IF_ADDREF(*aCurrentColumn = mCurrentColumn);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeSelection::SetCurrentColumn(nsITreeColumn* aCurrentColumn)
+{
+ if (!mTree) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mCurrentColumn == aCurrentColumn) {
+ return NS_OK;
+ }
+
+ if (mCurrentColumn) {
+ if (mFirstRange)
+ mTree->InvalidateCell(mFirstRange->mMin, mCurrentColumn);
+ if (mCurrentIndex != -1)
+ mTree->InvalidateCell(mCurrentIndex, mCurrentColumn);
+ }
+
+ mCurrentColumn = aCurrentColumn;
+
+ if (mCurrentColumn) {
+ if (mFirstRange)
+ mTree->InvalidateCell(mFirstRange->mMin, mCurrentColumn);
+ if (mCurrentIndex != -1)
+ mTree->InvalidateCell(mCurrentIndex, mCurrentColumn);
+ }
+
+ return NS_OK;
+}
+
+#define ADD_NEW_RANGE(macro_range, macro_selection, macro_start, macro_end) \
+ { \
+ int32_t start = macro_start; \
+ int32_t end = macro_end; \
+ if (start > end) { \
+ end = start; \
+ } \
+ nsTreeRange* macro_new_range = new nsTreeRange(macro_selection, start, end); \
+ if (macro_range) \
+ macro_range->Insert(macro_new_range); \
+ else \
+ macro_range = macro_new_range; \
+ }
+
+NS_IMETHODIMP
+nsTreeSelection::AdjustSelection(int32_t aIndex, int32_t aCount)
+{
+ NS_ASSERTION(aCount != 0, "adjusting by zero");
+ if (!aCount) return NS_OK;
+
+ // adjust mShiftSelectPivot, if necessary
+ if ((mShiftSelectPivot != 1) && (aIndex <= mShiftSelectPivot)) {
+ // if we are deleting and the delete includes the shift select pivot, reset it
+ if (aCount < 0 && (mShiftSelectPivot <= (aIndex -aCount -1))) {
+ mShiftSelectPivot = -1;
+ }
+ else {
+ mShiftSelectPivot += aCount;
+ }
+ }
+
+ // adjust mCurrentIndex, if necessary
+ if ((mCurrentIndex != -1) && (aIndex <= mCurrentIndex)) {
+ // if we are deleting and the delete includes the current index, reset it
+ if (aCount < 0 && (mCurrentIndex <= (aIndex -aCount -1))) {
+ mCurrentIndex = -1;
+ }
+ else {
+ mCurrentIndex += aCount;
+ }
+ }
+
+ // no selection, so nothing to do.
+ if (!mFirstRange) return NS_OK;
+
+ bool selChanged = false;
+ nsTreeRange* oldFirstRange = mFirstRange;
+ nsTreeRange* curr = mFirstRange;
+ mFirstRange = nullptr;
+ while (curr) {
+ if (aCount > 0) {
+ // inserting
+ if (aIndex > curr->mMax) {
+ // adjustment happens after the range, so no change
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax);
+ }
+ else if (aIndex <= curr->mMin) {
+ // adjustment happens before the start of the range, so shift down
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin + aCount, curr->mMax + aCount);
+ selChanged = true;
+ }
+ else {
+ // adjustment happen inside the range.
+ // break apart the range and create two ranges
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin, aIndex - 1);
+ ADD_NEW_RANGE(mFirstRange, this, aIndex + aCount, curr->mMax + aCount);
+ selChanged = true;
+ }
+ }
+ else {
+ // deleting
+ if (aIndex > curr->mMax) {
+ // adjustment happens after the range, so no change
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax);
+ }
+ else {
+ // remember, aCount is negative
+ selChanged = true;
+ int32_t lastIndexOfAdjustment = aIndex - aCount - 1;
+ if (aIndex <= curr->mMin) {
+ if (lastIndexOfAdjustment < curr->mMin) {
+ // adjustment happens before the start of the range, so shift up
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin + aCount, curr->mMax + aCount);
+ }
+ else if (lastIndexOfAdjustment >= curr->mMax) {
+ // adjustment contains the range. remove the range by not adding it to the newRange
+ }
+ else {
+ // adjustment starts before the range, and ends in the middle of it, so trim the range
+ ADD_NEW_RANGE(mFirstRange, this, aIndex, curr->mMax + aCount)
+ }
+ }
+ else if (lastIndexOfAdjustment >= curr->mMax) {
+ // adjustment starts in the middle of the current range, and contains the end of the range, so trim the range
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin, aIndex - 1)
+ }
+ else {
+ // range contains the adjustment, so shorten the range
+ ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax + aCount)
+ }
+ }
+ }
+ curr = curr->mNext;
+ }
+
+ delete oldFirstRange;
+
+ // Fire the select event
+ if (selChanged)
+ FireOnSelectHandler();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeSelection::InvalidateSelection()
+{
+ if (mFirstRange)
+ mFirstRange->Invalidate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTreeSelection::GetShiftSelectPivot(int32_t* aIndex)
+{
+ *aIndex = mShiftSelectPivot;
+ return NS_OK;
+}
+
+
+nsresult
+nsTreeSelection::FireOnSelectHandler()
+{
+ if (mSuppressed || !mTree)
+ return NS_OK;
+
+ nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree);
+ NS_ASSERTION(boxObject, "no box object!");
+ if (!boxObject)
+ return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIDOMElement> elt;
+ boxObject->GetElement(getter_AddRefs(elt));
+ NS_ENSURE_STATE(elt);
+
+ nsCOMPtr<nsINode> node(do_QueryInterface(elt));
+ NS_ENSURE_STATE(node);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(node, NS_LITERAL_STRING("select"), true, false);
+ asyncDispatcher->RunDOMEventWhenSafe();
+ return NS_OK;
+}
+
+void
+nsTreeSelection::SelectCallback(nsITimer *aTimer, void *aClosure)
+{
+ RefPtr<nsTreeSelection> self = static_cast<nsTreeSelection*>(aClosure);
+ if (self) {
+ self->FireOnSelectHandler();
+ aTimer->Cancel();
+ self->mSelectTimer = nullptr;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+NS_NewTreeSelection(nsITreeBoxObject* aTree, nsITreeSelection** aResult)
+{
+ *aResult = new nsTreeSelection(aTree);
+ if (!*aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
diff --git a/layout/xul/tree/nsTreeSelection.h b/layout/xul/tree/nsTreeSelection.h
new file mode 100644
index 000000000..41ffe31fb
--- /dev/null
+++ b/layout/xul/tree/nsTreeSelection.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 3; 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/. */
+
+#ifndef nsTreeSelection_h__
+#define nsTreeSelection_h__
+
+#include "nsITreeSelection.h"
+#include "nsITreeColumns.h"
+#include "nsITimer.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+class nsITreeBoxObject;
+struct nsTreeRange;
+
+class nsTreeSelection final : public nsINativeTreeSelection
+{
+public:
+ explicit nsTreeSelection(nsITreeBoxObject* aTree);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsTreeSelection)
+ NS_DECL_NSITREESELECTION
+
+ // nsINativeTreeSelection: Untrusted code can use us
+ NS_IMETHOD EnsureNative() override { return NS_OK; }
+
+ friend struct nsTreeRange;
+
+protected:
+ ~nsTreeSelection();
+
+ nsresult FireOnSelectHandler();
+ static void SelectCallback(nsITimer *aTimer, void *aClosure);
+
+protected:
+ // Members
+ nsCOMPtr<nsITreeBoxObject> mTree; // The tree will hold on to us through the view and let go when it dies.
+
+ bool mSuppressed; // Whether or not we should be firing onselect events.
+ int32_t mCurrentIndex; // The item to draw the rect around. The last one clicked, etc.
+ nsCOMPtr<nsITreeColumn> mCurrentColumn;
+ int32_t mShiftSelectPivot; // Used when multiple SHIFT+selects are performed to pivot on.
+
+ nsTreeRange* mFirstRange; // Our list of ranges.
+
+ nsCOMPtr<nsITimer> mSelectTimer;
+};
+
+nsresult
+NS_NewTreeSelection(nsITreeBoxObject* aTree, nsITreeSelection** aResult);
+
+#endif
diff --git a/layout/xul/tree/nsTreeStyleCache.cpp b/layout/xul/tree/nsTreeStyleCache.cpp
new file mode 100644
index 000000000..74b8a89c4
--- /dev/null
+++ b/layout/xul/tree/nsTreeStyleCache.cpp
@@ -0,0 +1,99 @@
+/* -*- 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 "nsTreeStyleCache.h"
+#include "nsStyleSet.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+
+nsTreeStyleCache::Transition::Transition(DFAState aState, nsIAtom* aSymbol)
+ : mState(aState), mInputSymbol(aSymbol)
+{
+}
+
+bool
+nsTreeStyleCache::Transition::operator==(const Transition& aOther) const
+{
+ return aOther.mState == mState && aOther.mInputSymbol == mInputSymbol;
+}
+
+uint32_t
+nsTreeStyleCache::Transition::Hash() const
+{
+ // Make a 32-bit integer that combines the low-order 16 bits of the state and the input symbol.
+ uint32_t hb = mState << 16;
+ uint32_t lb = (NS_PTR_TO_UINT32(mInputSymbol.get()) << 16) >> 16;
+ return hb+lb;
+}
+
+
+// The style context cache impl
+nsStyleContext*
+nsTreeStyleCache::GetStyleContext(nsICSSPseudoComparator* aComparator,
+ nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleContext* aContext,
+ nsIAtom* aPseudoElement,
+ const AtomArray & aInputWord)
+{
+ uint32_t count = aInputWord.Length();
+
+ // Go ahead and init the transition table.
+ if (!mTransitionTable) {
+ // Automatic miss. Build the table
+ mTransitionTable = new TransitionTable();
+ }
+
+ // The first transition is always made off the supplied pseudo-element.
+ Transition transition(0, aPseudoElement);
+ DFAState currState = mTransitionTable->Get(transition);
+
+ if (!currState) {
+ // We had a miss. Make a new state and add it to our hash.
+ currState = mNextState;
+ mNextState++;
+ mTransitionTable->Put(transition, currState);
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ Transition transition(currState, aInputWord[i]);
+ currState = mTransitionTable->Get(transition);
+
+ if (!currState) {
+ // We had a miss. Make a new state and add it to our hash.
+ currState = mNextState;
+ mNextState++;
+ mTransitionTable->Put(transition, currState);
+ }
+ }
+
+ // We're in a final state.
+ // Look up our style context for this state.
+ nsStyleContext* result = nullptr;
+ if (mCache) {
+ result = mCache->GetWeak(currState);
+ }
+ if (!result) {
+ // We missed the cache. Resolve this pseudo-style.
+ // XXXheycam ServoStyleSets do not support XUL tree styles.
+ if (aPresContext->StyleSet()->IsServo()) {
+ MOZ_CRASH("stylo: ServoStyleSets should not support XUL tree styles yet");
+ }
+ RefPtr<nsStyleContext> newResult = aPresContext->StyleSet()->AsGecko()->
+ ResolveXULTreePseudoStyle(aContent->AsElement(), aPseudoElement,
+ aContext, aComparator);
+
+ // Put the style context in our table, transferring the owning reference to the table.
+ if (!mCache) {
+ mCache = new StyleContextCache();
+ }
+ result = newResult.get();
+ mCache->Put(currState, newResult.forget());
+ }
+
+ return result;
+}
+
diff --git a/layout/xul/tree/nsTreeStyleCache.h b/layout/xul/tree/nsTreeStyleCache.h
new file mode 100644
index 000000000..1020b0435
--- /dev/null
+++ b/layout/xul/tree/nsTreeStyleCache.h
@@ -0,0 +1,89 @@
+/* -*- 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/. */
+
+#ifndef nsTreeStyleCache_h__
+#define nsTreeStyleCache_h__
+
+#include "mozilla/Attributes.h"
+#include "nsAutoPtr.h"
+#include "nsIAtom.h"
+#include "nsCOMArray.h"
+#include "nsICSSPseudoComparator.h"
+#include "nsRefPtrHashtable.h"
+#include "nsStyleContext.h"
+
+typedef nsCOMArray<nsIAtom> AtomArray;
+
+class nsTreeStyleCache
+{
+public:
+ nsTreeStyleCache()
+ : mNextState(0)
+ {
+ }
+
+ ~nsTreeStyleCache()
+ {
+ Clear();
+ }
+
+ void Clear()
+ {
+ mTransitionTable = nullptr;
+ mCache = nullptr;
+ mNextState = 0;
+ }
+
+ nsStyleContext* GetStyleContext(nsICSSPseudoComparator* aComparator,
+ nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleContext* aContext,
+ nsIAtom* aPseudoElement,
+ const AtomArray & aInputWord);
+
+protected:
+ typedef uint32_t DFAState;
+
+ class Transition final
+ {
+ public:
+ Transition(DFAState aState, nsIAtom* aSymbol);
+ bool operator==(const Transition& aOther) const;
+ uint32_t Hash() const;
+
+ private:
+ DFAState mState;
+ nsCOMPtr<nsIAtom> mInputSymbol;
+ };
+
+ typedef nsDataHashtable<nsGenericHashKey<Transition>, DFAState> TransitionTable;
+
+ // A transition table for a deterministic finite automaton. The DFA
+ // takes as its input a single pseudoelement and an ordered set of properties.
+ // It transitions on an input word that is the concatenation of the pseudoelement supplied
+ // with the properties in the array.
+ //
+ // It transitions from state to state by looking up entries in the transition table (which is
+ // a mapping from (S,i)->S', where S is the current state, i is the next
+ // property in the input word, and S' is the state to transition to.
+ //
+ // If S' is not found, it is constructed and entered into the hashtable
+ // under the key (S,i).
+ //
+ // Once the entire word has been consumed, the final state is used
+ // to reference the cache table to locate the style context.
+ nsAutoPtr<TransitionTable> mTransitionTable;
+
+ // The cache of all active style contexts. This is a hash from
+ // a final state in the DFA, Sf, to the resultant style context.
+ typedef nsRefPtrHashtable<nsUint32HashKey, nsStyleContext> StyleContextCache;
+ nsAutoPtr<StyleContextCache> mCache;
+
+ // An integer counter that is used when we need to make new states in the
+ // DFA.
+ DFAState mNextState;
+};
+
+#endif // nsTreeStyleCache_h__
diff --git a/layout/xul/tree/nsTreeUtils.cpp b/layout/xul/tree/nsTreeUtils.cpp
new file mode 100644
index 000000000..81a56f0e9
--- /dev/null
+++ b/layout/xul/tree/nsTreeUtils.cpp
@@ -0,0 +1,140 @@
+/* -*- 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 "nsReadableUtils.h"
+#include "nsTreeUtils.h"
+#include "ChildIterator.h"
+#include "nsCRT.h"
+#include "nsIAtom.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+
+using namespace mozilla;
+
+nsresult
+nsTreeUtils::TokenizeProperties(const nsAString& aProperties, AtomArray & aPropertiesArray)
+{
+ nsAString::const_iterator end;
+ aProperties.EndReading(end);
+
+ nsAString::const_iterator iter;
+ aProperties.BeginReading(iter);
+
+ do {
+ // Skip whitespace
+ while (iter != end && nsCRT::IsAsciiSpace(*iter))
+ ++iter;
+
+ // If only whitespace, we're done
+ if (iter == end)
+ break;
+
+ // Note the first non-whitespace character
+ nsAString::const_iterator first = iter;
+
+ // Advance to the next whitespace character
+ while (iter != end && ! nsCRT::IsAsciiSpace(*iter))
+ ++iter;
+
+ // XXX this would be nonsensical
+ NS_ASSERTION(iter != first, "eh? something's wrong here");
+ if (iter == first)
+ break;
+
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(Substring(first, iter));
+ aPropertiesArray.AppendElement(atom);
+ } while (iter != end);
+
+ return NS_OK;
+}
+
+nsIContent*
+nsTreeUtils::GetImmediateChild(nsIContent* aContainer, nsIAtom* aTag)
+{
+ dom::FlattenedChildIterator iter(aContainer);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(aTag)) {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+nsIContent*
+nsTreeUtils::GetDescendantChild(nsIContent* aContainer, nsIAtom* aTag)
+{
+ dom::FlattenedChildIterator iter(aContainer);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(aTag)) {
+ return child;
+ }
+
+ child = GetDescendantChild(child, aTag);
+ if (child) {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+nsresult
+nsTreeUtils::UpdateSortIndicators(nsIContent* aColumn, const nsAString& aDirection)
+{
+ aColumn->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, aDirection, true);
+ aColumn->SetAttr(kNameSpaceID_None, nsGkAtoms::sortActive, NS_LITERAL_STRING("true"), true);
+
+ // Unset sort attribute(s) on the other columns
+ nsCOMPtr<nsIContent> parentContent = aColumn->GetParent();
+ if (parentContent &&
+ parentContent->NodeInfo()->Equals(nsGkAtoms::treecols,
+ kNameSpaceID_XUL)) {
+ uint32_t i, numChildren = parentContent->GetChildCount();
+ for (i = 0; i < numChildren; ++i) {
+ nsCOMPtr<nsIContent> childContent = parentContent->GetChildAt(i);
+
+ if (childContent &&
+ childContent != aColumn &&
+ childContent->NodeInfo()->Equals(nsGkAtoms::treecol,
+ kNameSpaceID_XUL)) {
+ childContent->UnsetAttr(kNameSpaceID_None,
+ nsGkAtoms::sortDirection, true);
+ childContent->UnsetAttr(kNameSpaceID_None,
+ nsGkAtoms::sortActive, true);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsTreeUtils::GetColumnIndex(nsIContent* aColumn, int32_t* aResult)
+{
+ nsIContent* parentContent = aColumn->GetParent();
+ if (parentContent &&
+ parentContent->NodeInfo()->Equals(nsGkAtoms::treecols,
+ kNameSpaceID_XUL)) {
+ uint32_t i, numChildren = parentContent->GetChildCount();
+ int32_t colIndex = 0;
+ for (i = 0; i < numChildren; ++i) {
+ nsIContent *childContent = parentContent->GetChildAt(i);
+ if (childContent &&
+ childContent->NodeInfo()->Equals(nsGkAtoms::treecol,
+ kNameSpaceID_XUL)) {
+ if (childContent == aColumn) {
+ *aResult = colIndex;
+ return NS_OK;
+ }
+ ++colIndex;
+ }
+ }
+ }
+
+ *aResult = -1;
+ return NS_OK;
+}
diff --git a/layout/xul/tree/nsTreeUtils.h b/layout/xul/tree/nsTreeUtils.h
new file mode 100644
index 000000000..f2ed2df68
--- /dev/null
+++ b/layout/xul/tree/nsTreeUtils.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 3; 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/. */
+
+#ifndef nsTreeUtils_h__
+#define nsTreeUtils_h__
+
+#include "nsError.h"
+#include "nsString.h"
+#include "nsTreeStyleCache.h"
+
+class nsIAtom;
+class nsIContent;
+
+class nsTreeUtils
+{
+ public:
+ /**
+ * Parse a whitespace separated list of properties into an array
+ * of atoms.
+ */
+ static nsresult
+ TokenizeProperties(const nsAString& aProperties, AtomArray & aPropertiesArray);
+
+ static nsIContent*
+ GetImmediateChild(nsIContent* aContainer, nsIAtom* aTag);
+
+ static nsIContent*
+ GetDescendantChild(nsIContent* aContainer, nsIAtom* aTag);
+
+ static nsresult
+ UpdateSortIndicators(nsIContent* aColumn, const nsAString& aDirection);
+
+ static nsresult
+ GetColumnIndex(nsIContent* aColumn, int32_t* aResult);
+};
+
+#endif // nsTreeUtils_h__