From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- layout/xul/tree/TreeBoxObject.cpp | 695 ++++ layout/xul/tree/TreeBoxObject.h | 128 + layout/xul/tree/crashtests/307298-1.xul | 21 + layout/xul/tree/crashtests/309732-1.xul | 30 + layout/xul/tree/crashtests/309732-2.xul | 31 + layout/xul/tree/crashtests/366583-1.xul | 43 + layout/xul/tree/crashtests/380217-1.xul | 24 + layout/xul/tree/crashtests/382444-1-inner.html | 15 + layout/xul/tree/crashtests/382444-1.html | 9 + layout/xul/tree/crashtests/391178-1.xhtml | 41 + layout/xul/tree/crashtests/391178-2.xul | 20 + layout/xul/tree/crashtests/393665-1.xul | 3 + layout/xul/tree/crashtests/399227-1.xul | 44 + layout/xul/tree/crashtests/399227-2.xul | 50 + layout/xul/tree/crashtests/399692-1.xhtml | 10 + layout/xul/tree/crashtests/399715-1.xhtml | 9 + layout/xul/tree/crashtests/409807-1.xul | 25 + layout/xul/tree/crashtests/414170-1.xul | 20 + layout/xul/tree/crashtests/430394-1.xul | 8 + layout/xul/tree/crashtests/454186-1.xul | 23 + layout/xul/tree/crashtests/479931-1.xhtml | 19 + layout/xul/tree/crashtests/509602-1-overlay.xul | 12 + layout/xul/tree/crashtests/509602-1.xul | 3 + layout/xul/tree/crashtests/585815-iframe.xul | 72 + layout/xul/tree/crashtests/585815.html | 18 + layout/xul/tree/crashtests/601427.html | 30 + layout/xul/tree/crashtests/730441-1.xul | 54 + layout/xul/tree/crashtests/730441-2.xul | 52 + layout/xul/tree/crashtests/730441-3.xul | 38 + layout/xul/tree/crashtests/crashtests.list | 24 + layout/xul/tree/moz.build | 53 + layout/xul/tree/nsITreeBoxObject.idl | 189 + layout/xul/tree/nsITreeColumns.idl | 96 + layout/xul/tree/nsITreeContentView.idl | 22 + layout/xul/tree/nsITreeSelection.idl | 130 + layout/xul/tree/nsITreeView.idl | 215 + layout/xul/tree/nsTreeBodyFrame.cpp | 4945 +++++++++++++++++++++++ layout/xul/tree/nsTreeBodyFrame.h | 651 +++ layout/xul/tree/nsTreeColFrame.cpp | 201 + layout/xul/tree/nsTreeColFrame.h | 55 + layout/xul/tree/nsTreeColumns.cpp | 765 ++++ layout/xul/tree/nsTreeColumns.h | 215 + layout/xul/tree/nsTreeContentView.cpp | 1372 +++++++ layout/xul/tree/nsTreeContentView.h | 101 + layout/xul/tree/nsTreeImageListener.cpp | 115 + layout/xul/tree/nsTreeImageListener.h | 66 + layout/xul/tree/nsTreeSelection.cpp | 866 ++++ layout/xul/tree/nsTreeSelection.h | 56 + layout/xul/tree/nsTreeStyleCache.cpp | 99 + layout/xul/tree/nsTreeStyleCache.h | 89 + layout/xul/tree/nsTreeUtils.cpp | 140 + layout/xul/tree/nsTreeUtils.h | 39 + 52 files changed, 12051 insertions(+) create mode 100644 layout/xul/tree/TreeBoxObject.cpp create mode 100644 layout/xul/tree/TreeBoxObject.h create mode 100644 layout/xul/tree/crashtests/307298-1.xul create mode 100644 layout/xul/tree/crashtests/309732-1.xul create mode 100644 layout/xul/tree/crashtests/309732-2.xul create mode 100644 layout/xul/tree/crashtests/366583-1.xul create mode 100644 layout/xul/tree/crashtests/380217-1.xul create mode 100644 layout/xul/tree/crashtests/382444-1-inner.html create mode 100644 layout/xul/tree/crashtests/382444-1.html create mode 100644 layout/xul/tree/crashtests/391178-1.xhtml create mode 100644 layout/xul/tree/crashtests/391178-2.xul create mode 100644 layout/xul/tree/crashtests/393665-1.xul create mode 100644 layout/xul/tree/crashtests/399227-1.xul create mode 100644 layout/xul/tree/crashtests/399227-2.xul create mode 100644 layout/xul/tree/crashtests/399692-1.xhtml create mode 100644 layout/xul/tree/crashtests/399715-1.xhtml create mode 100644 layout/xul/tree/crashtests/409807-1.xul create mode 100644 layout/xul/tree/crashtests/414170-1.xul create mode 100644 layout/xul/tree/crashtests/430394-1.xul create mode 100644 layout/xul/tree/crashtests/454186-1.xul create mode 100644 layout/xul/tree/crashtests/479931-1.xhtml create mode 100644 layout/xul/tree/crashtests/509602-1-overlay.xul create mode 100644 layout/xul/tree/crashtests/509602-1.xul create mode 100644 layout/xul/tree/crashtests/585815-iframe.xul create mode 100644 layout/xul/tree/crashtests/585815.html create mode 100644 layout/xul/tree/crashtests/601427.html create mode 100644 layout/xul/tree/crashtests/730441-1.xul create mode 100644 layout/xul/tree/crashtests/730441-2.xul create mode 100644 layout/xul/tree/crashtests/730441-3.xul create mode 100644 layout/xul/tree/crashtests/crashtests.list create mode 100644 layout/xul/tree/moz.build create mode 100644 layout/xul/tree/nsITreeBoxObject.idl create mode 100644 layout/xul/tree/nsITreeColumns.idl create mode 100644 layout/xul/tree/nsITreeContentView.idl create mode 100644 layout/xul/tree/nsITreeSelection.idl create mode 100644 layout/xul/tree/nsITreeView.idl create mode 100644 layout/xul/tree/nsTreeBodyFrame.cpp create mode 100644 layout/xul/tree/nsTreeBodyFrame.h create mode 100644 layout/xul/tree/nsTreeColFrame.cpp create mode 100644 layout/xul/tree/nsTreeColFrame.h create mode 100644 layout/xul/tree/nsTreeColumns.cpp create mode 100644 layout/xul/tree/nsTreeColumns.h create mode 100644 layout/xul/tree/nsTreeContentView.cpp create mode 100644 layout/xul/tree/nsTreeContentView.h create mode 100644 layout/xul/tree/nsTreeImageListener.cpp create mode 100644 layout/xul/tree/nsTreeImageListener.h create mode 100644 layout/xul/tree/nsTreeSelection.cpp create mode 100644 layout/xul/tree/nsTreeSelection.h create mode 100644 layout/xul/tree/nsTreeStyleCache.cpp create mode 100644 layout/xul/tree/nsTreeStyleCache.h create mode 100644 layout/xul/tree/nsTreeUtils.cpp create mode 100644 layout/xul/tree/nsTreeUtils.h (limited to 'layout/xul/tree') 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 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 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 xulele = do_QueryInterface(mContent); + if (xulele) { + // See if there is a XUL tree builder associated with the element + nsCOMPtr 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 +TreeBoxObject::GetView() { + nsCOMPtr 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 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 +TreeBoxObject::GetTreeBody() +{ + nsCOMPtr el; + GetTreeBody(getter_AddRefs(el)); + nsCOMPtr ret(do_QueryInterface(el)); + return ret.forget(); +} + +already_AddRefed +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 +TreeBoxObject::SelectionRegion() +{ + nsCOMPtr 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 col; + GetCellAt(x, y, &aRetVal.mRow, getter_AddRefs(col), aRetVal.mChildElt); + aRetVal.mCol = col.forget().downcast(); +} + +void +TreeBoxObject::GetCellAt(JSContext* cx, + int32_t x, int32_t y, + JS::Handle rowOut, + JS::Handle colOut, + JS::Handle childEltOut, + ErrorResult& aRv) +{ + int32_t row; + nsITreeColumn* col; + nsAutoString childElt; + GetCellAt(x, y, &row, &col, childElt); + + JS::Rooted 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 +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 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 xOut, + JS::Handle yOut, + JS::Handle widthOut, + JS::Handle heightOut, + ErrorResult& aRv) +{ + int32_t x, y, w, h; + GetCoordsForCellItem(row, &col, element, &x, &y, &w, &h); + JS::Rooted 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 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 aGivenProto) override; + + already_AddRefed GetColumns(); + + already_AddRefed GetView(); + + void SetView(nsITreeView* arg, ErrorResult& aRv); + + bool Focused(); + + already_AddRefed GetTreeBody(); + + int32_t RowHeight(); + + int32_t RowWidth(); + + int32_t HorizontalPosition(); + + already_AddRefed 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 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 rowOut, + JS::Handle colOut, + JS::Handle childEltOut, + ErrorResult& aRv); + + void GetCoordsForCellItem(JSContext* cx, + int32_t row, + nsTreeColumn& col, + const nsAString& element, + JS::Handle xOut, + JS::Handle yOut, + JS::Handle widthOut, + JS::Handle 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 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 @@ + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + +* { position: fixed; } + + + + + + + + + + + + + + + + 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 @@ + + +Testcase bug - Crash [@ nsINodeInfo::Equals] with underflow event, tree stuff and removing window + + + + + + + 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 @@ + + + + + + + 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 @@ + + + + + + + +
+
+ + + 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 @@ + + + + + + + + + + + + + + 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 @@ + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + 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 @@ + + + + + + + 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 @@ + + + + + + + + + + + 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 @@ + + + + + + + + \ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + +
+ + + 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 @@ + + + + + + + + + + + + \ 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 @@ + + + \ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + Testcase for bug 585815 + + + + + + + + + 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 @@ + + + + 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 @@ + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + 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 @@ + + + + + + + + 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 +#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(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 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 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 box = + nsDoc->GetBoxObjectFor(parent->AsElement(), ignored); + // Ensure that we got a native box object. + nsCOMPtr pBox = do_QueryInterface(box); + if (pBox) { + nsCOMPtr realTreeBoxObject = do_QueryInterface(pBox); + if (realTreeBoxObject) { + nsTreeBodyFrame* innerTreeBoxObject = + static_cast(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 box = do_QueryInterface(mTreeBoxObject); + if (box) { + nsWeakFrame weakFrame(this); + nsCOMPtr 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 sel; + mView->GetSelection(getter_AddRefs(sel)); + if (sel) { + int32_t currentIndex; + sel->GetCurrentIndex(¤tIndex); + 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 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 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 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 selection; + mView->GetSelection(getter_AddRefs(selection)); + NS_ENSURE_TRUE(selection, NS_OK); + + nsCOMPtr region = do_CreateInstance("@mozilla.org/gfx/region;1"); + NS_ENSURE_TRUE(region, NS_ERROR_FAILURE); + region->Init(); + + RefPtr 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 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 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 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 presContext = PresContext(); + nsCOMPtr presShell = presContext->GetPresShell(); + nsCOMPtr 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 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 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 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 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 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 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 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 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(¤tIndex); + if (aRowIndex == currentIndex) + mScratchArray.AppendElement(nsGkAtoms::current); + + // active + if (aCol) { + nsCOMPtr 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 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 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 obs; + imgReq->GetNotificationObserver(getter_AddRefs(obs)); + + if (obs) { + static_cast (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 imgNotificationObserver = listener; + + RefPtr 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 baseURI = mContent->GetBaseURI(); + + nsCOMPtr 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 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(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(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 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 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 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 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 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 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 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 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 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 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(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(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(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(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 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 content(GetBaseElement()); + if (!content) + return; + + nsCOMPtr domDoc = do_QueryInterface(content->OwnerDoc()); + if (!domDoc) + return; + + nsCOMPtr event; + domDoc->CreateEvent(NS_LITERAL_STRING("customevent"), + getter_AddRefs(event)); + + nsCOMPtr treeEvent(do_QueryInterface(event)); + if (!treeEvent) + return; + + nsCOMPtr 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 detailVariant(new nsVariant()); + + detailVariant->SetAsISupports(propBag); + treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeRowCountChanged"), + true, false, detailVariant); + + event->SetTrusted(true); + + RefPtr asyncDispatcher = + new AsyncEventDispatcher(content, event); + asyncDispatcher->PostDOMEvent(); +} + +void +nsTreeBodyFrame::FireInvalidateEvent(int32_t aStartRowIdx, int32_t aEndRowIdx, + nsITreeColumn *aStartCol, + nsITreeColumn *aEndCol) +{ + nsCOMPtr content(GetBaseElement()); + if (!content) + return; + + nsCOMPtr domDoc = do_QueryInterface(content->OwnerDoc()); + if (!domDoc) + return; + + nsCOMPtr event; + domDoc->CreateEvent(NS_LITERAL_STRING("customevent"), + getter_AddRefs(event)); + + nsCOMPtr treeEvent(do_QueryInterface(event)); + if (!treeEvent) + return; + + nsCOMPtr 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 detailVariant(new nsVariant()); + + detailVariant->SetAsISupports(propBag); + treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeInvalidated"), + true, false, detailVariant); + + event->SetTrusted(true); + + RefPtr 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(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 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 request; + nsCOMPtr 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 Columns() const + { + RefPtr cols = mColumns; + return cols.forget(); + } + already_AddRefed GetExistingView() const + { + nsCOMPtr 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 mVScrollbarContent; + nsScrollbarFrame* mHScrollbar; + nsCOMPtr mHScrollbarContent; + nsIFrame* mColumnsFrame; + nsIScrollableFrame* mColumnsScrollFrame; + }; + + DrawResult PaintTreeBody(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, nsPoint aPt); + + nsITreeBoxObject* GetTreeBoxObject() const { return mTreeBoxObject; } + + // Get the base element, or