summaryrefslogtreecommitdiffstats
path: root/layout/xul/tree/nsTreeBodyFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--layout/xul/tree/nsTreeBodyFrame.cpp4363
1 files changed, 4363 insertions, 0 deletions
diff --git a/layout/xul/tree/nsTreeBodyFrame.cpp b/layout/xul/tree/nsTreeBodyFrame.cpp
new file mode 100644
index 0000000000..c7e8272aa0
--- /dev/null
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -0,0 +1,4363 @@
+/* -*- 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 "SimpleXULLeafFrame.h"
+#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/LookAndFeel.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/intl/Segmenter.h"
+
+#include "gfxUtils.h"
+#include "nsAlgorithm.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsFontMetrics.h"
+#include "nsITreeView.h"
+#include "nsPresContext.h"
+#include "nsNameSpaceManager.h"
+
+#include "nsTreeBodyFrame.h"
+#include "nsTreeSelection.h"
+#include "nsTreeImageListener.h"
+
+#include "nsGkAtoms.h"
+#include "nsCSSAnonBoxes.h"
+
+#include "gfxContext.h"
+#include "nsIContent.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/intl/Segmenter.h"
+#include "nsCSSRendering.h"
+#include "nsString.h"
+#include "nsContainerFrame.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsVariant.h"
+#include "nsWidgetsCID.h"
+#include "nsIFrameInlines.h"
+#include "nsTreeContentView.h"
+#include "nsTreeUtils.h"
+#include "nsStyleConsts.h"
+#include "nsITheme.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsDisplayList.h"
+#include "mozilla/dom/CustomEvent.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/TreeColumnBinding.h"
+#include <algorithm>
+#include "ScrollbarActivity.h"
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+# include "nsIWritablePropertyBag2.h"
+#endif
+#include "nsBidiUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::layout;
+
+enum CroppingStyle { CropNone, CropLeft, CropRight, CropCenter, CropAuto };
+
+// FIXME: Maybe unify with MiddleCroppingBlockFrame?
+static void CropStringForWidth(nsAString& aText, gfxContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics, nscoord aWidth,
+ CroppingStyle aCropType) {
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // 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);
+
+ if (ellipsisWidth > aWidth) {
+ aText.Truncate(0);
+ return;
+ }
+ if (ellipsisWidth == aWidth) {
+ aText.Assign(kEllipsis);
+ return;
+ }
+
+ // 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.
+ aWidth -= ellipsisWidth;
+
+ using mozilla::intl::GraphemeClusterBreakIteratorUtf16;
+ using mozilla::intl::GraphemeClusterBreakReverseIteratorUtf16;
+
+ // Now we crop. This is quite basic: it will not be really accurate in the
+ // presence of complex scripts with contextual shaping, etc., as it measures
+ // each grapheme cluster in isolation, not in its proper context.
+ switch (aCropType) {
+ case CropAuto:
+ case CropNone:
+ case CropRight: {
+ const Span text(aText);
+ GraphemeClusterBreakIteratorUtf16 iter(text);
+ uint32_t pos = 0;
+ nscoord totalWidth = 0;
+
+ while (Maybe<uint32_t> nextPos = iter.Next()) {
+ const nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
+ text.FromTo(pos, *nextPos), aFontMetrics, drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+ pos = *nextPos;
+ totalWidth += charWidth;
+ }
+
+ if (pos < aText.Length()) {
+ aText.Replace(pos, aText.Length() - pos, kEllipsis);
+ }
+ } break;
+
+ case CropLeft: {
+ const Span text(aText);
+ GraphemeClusterBreakReverseIteratorUtf16 iter(text);
+ uint32_t pos = text.Length();
+ nscoord totalWidth = 0;
+
+ // nextPos is decreasing since we use a reverse iterator.
+ while (Maybe<uint32_t> nextPos = iter.Next()) {
+ const nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
+ text.FromTo(*nextPos, pos), aFontMetrics, drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ pos = *nextPos;
+ totalWidth += charWidth;
+ }
+
+ if (pos > 0) {
+ aText.Replace(0, pos, kEllipsis);
+ }
+ } break;
+
+ case CropCenter: {
+ const Span text(aText);
+ nscoord totalWidth = 0;
+ GraphemeClusterBreakIteratorUtf16 leftIter(text);
+ GraphemeClusterBreakReverseIteratorUtf16 rightIter(text);
+ uint32_t leftPos = 0;
+ uint32_t rightPos = text.Length();
+
+ while (leftPos < rightPos) {
+ Maybe<uint32_t> nextPos = leftIter.Next();
+ nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
+ text.FromTo(leftPos, *nextPos), aFontMetrics, drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ leftPos = *nextPos;
+ totalWidth += charWidth;
+
+ if (leftPos >= rightPos) {
+ break;
+ }
+
+ nextPos = rightIter.Next();
+ charWidth = nsLayoutUtils::AppUnitWidthOfString(
+ text.FromTo(*nextPos, rightPos), aFontMetrics, drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ rightPos = *nextPos;
+ totalWidth += charWidth;
+ }
+
+ if (leftPos < rightPos) {
+ aText.Replace(leftPos, rightPos - leftPos, kEllipsis);
+ }
+ } break;
+ }
+}
+
+// Function that cancels all the image requests in our cache.
+void nsTreeBodyFrame::CancelImageRequests() {
+ for (nsTreeImageCacheEntry entry : mImageCache.Values()) {
+ // If our imgIRequest object was registered with the refresh driver
+ // then we need to deregister it.
+ nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request,
+ nullptr);
+ entry.request->UnlockImage();
+ entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ }
+}
+
+//
+// NS_NewTreeFrame
+//
+// Creates a new tree frame
+//
+nsIFrame* NS_NewTreeBodyFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsTreeBodyFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTreeBodyFrame)
+
+NS_QUERYFRAME_HEAD(nsTreeBodyFrame)
+ NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
+ NS_QUERYFRAME_ENTRY(nsTreeBodyFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SimpleXULLeafFrame)
+
+// Constructor
+nsTreeBodyFrame::nsTreeBodyFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : SimpleXULLeafFrame(aStyle, aPresContext, kClassID),
+ mImageCache(),
+ mTopRowIndex(0),
+ mPageLength(0),
+ mHorzPosition(0),
+ mOriginalHorzWidth(-1),
+ mHorzWidth(0),
+ mAdjustWidth(0),
+ mRowHeight(0),
+ mIndentation(0),
+ 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();
+}
+
+static void GetBorderPadding(ComputedStyle* aStyle, nsMargin& aMargin) {
+ aMargin.SizeTo(0, 0, 0, 0);
+ aStyle->StylePadding()->GetPadding(aMargin);
+ aMargin += aStyle->StyleBorder()->GetComputedBorder();
+}
+
+static void AdjustForBorderPadding(ComputedStyle* aStyle, nsRect& aRect) {
+ nsMargin borderPadding(0, 0, 0, 0);
+ GetBorderPadding(aStyle, borderPadding);
+ aRect.Deflate(borderPadding);
+}
+
+void nsTreeBodyFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ SimpleXULLeafFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mIndentation = GetIndentation();
+ mRowHeight = GetRowHeight();
+
+ // Call GetBaseElement so that mTree is assigned.
+ RefPtr<XULTreeElement> tree(GetBaseElement());
+ if (MOZ_LIKELY(tree)) {
+ nsAutoString rows;
+ if (tree->GetAttr(nsGkAtoms::rows, rows)) {
+ nsresult err;
+ mPageLength = rows.ToInteger(&err);
+ mHasFixedRowCount = true;
+ }
+ }
+
+ if (PresContext()->UseOverlayScrollbars()) {
+ mScrollbarActivity =
+ new ScrollbarActivity(static_cast<nsIScrollbarMediator*>(this));
+ }
+}
+
+void nsTreeBodyFrame::Destroy(DestroyContext& aContext) {
+ if (mScrollbarActivity) {
+ mScrollbarActivity->Destroy();
+ mScrollbarActivity = nullptr;
+ }
+
+ mScrollEvent.Revoke();
+ // Make sure we cancel any posted callbacks.
+ if (mReflowCallbackPosted) {
+ PresShell()->CancelReflowCallback(this);
+ mReflowCallbackPosted = false;
+ }
+
+ if (mColumns) mColumns->SetTree(nullptr);
+
+ RefPtr tree = mTree;
+
+ if (nsCOMPtr<nsITreeView> view = std::move(mView)) {
+ nsCOMPtr<nsITreeSelection> sel;
+ view->GetSelection(getter_AddRefs(sel));
+ if (sel) {
+ sel->SetTree(nullptr);
+ }
+ view->SetTree(nullptr);
+ }
+
+ // Make this call now because view->SetTree can run js which can undo this
+ // call.
+ if (tree) {
+ tree->BodyDestroyed(mTopRowIndex);
+ }
+ if (mTree && mTree != tree) {
+ mTree->BodyDestroyed(mTopRowIndex);
+ }
+
+ SimpleXULLeafFrame::Destroy(aContext);
+}
+
+void nsTreeBodyFrame::EnsureView() {
+ if (mView) {
+ return;
+ }
+
+ if (PresShell()->IsReflowLocked()) {
+ if (!mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresShell()->PostReflowCallback(this);
+ }
+ return;
+ }
+
+ AutoWeakFrame weakFrame(this);
+
+ RefPtr<XULTreeElement> tree = GetBaseElement();
+ if (!tree) {
+ return;
+ }
+ nsCOMPtr<nsITreeView> treeView = tree->GetView();
+ if (!treeView || !weakFrame.IsAlive()) {
+ return;
+ }
+ int32_t rowIndex = tree->GetCachedTopVisibleRow();
+
+ // Set our view.
+ SetView(treeView);
+ NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
+
+ // Scroll to the given row.
+ // XXX is this optimal if we haven't laid out yet?
+ ScrollToRow(rowIndex);
+ NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
+}
+
+void nsTreeBodyFrame::ManageReflowCallback() {
+ const nscoord horzWidth = CalcHorzWidth(GetScrollParts());
+ if (!mReflowCallbackPosted) {
+ if (!mLastReflowRect || !mLastReflowRect->IsEqualEdges(mRect) ||
+ mHorzWidth != horzWidth) {
+ PresShell()->PostReflowCallback(this);
+ mReflowCallbackPosted = true;
+ mOriginalHorzWidth = mHorzWidth;
+ }
+ } else if (mHorzWidth != horzWidth && mOriginalHorzWidth == horzWidth) {
+ // FIXME(emilio): This doesn't seem sound to me, if the rect changes in the
+ // block axis.
+ PresShell()->CancelReflowCallback(this);
+ mReflowCallbackPosted = false;
+ mOriginalHorzWidth = -1;
+ }
+ mLastReflowRect = Some(mRect);
+ mHorzWidth = horzWidth;
+}
+
+nscoord nsTreeBodyFrame::GetIntrinsicBSize() {
+ return mHasFixedRowCount ? mRowHeight * mPageLength : 0;
+}
+
+void nsTreeBodyFrame::DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput) {
+ ManageReflowCallback();
+ SimpleXULLeafFrame::DidReflow(aPresContext, aReflowInput);
+}
+
+bool nsTreeBodyFrame::ReflowFinished() {
+ if (!mView) {
+ AutoWeakFrame weakFrame(this);
+ EnsureView();
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+ }
+ if (mView) {
+ CalcInnerBox();
+ ScrollParts parts = GetScrollParts();
+ mHorzWidth = CalcHorzWidth(parts);
+ if (!mHasFixedRowCount) {
+ mPageLength =
+ (mRowHeight > 0) ? (mInnerBox.height / mRowHeight) : mRowCount;
+ }
+
+ int32_t lastPageTopRow = std::max(0, mRowCount - mPageLength);
+ if (mTopRowIndex > lastPageTopRow)
+ ScrollToRowInternal(parts, lastPageTopRow);
+
+ XULTreeElement* 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.
+ if (nsCOMPtr<nsITreeSelection> sel = GetSelection()) {
+ int32_t currentIndex;
+ sel->GetCurrentIndex(&currentIndex);
+ if (currentIndex != -1) {
+ EnsureRowIsVisibleInternal(parts, currentIndex);
+ }
+ }
+ }
+
+ if (!FullScrollbarsUpdate(false)) {
+ return false;
+ }
+ }
+
+ mReflowCallbackPosted = false;
+ return false;
+}
+
+void nsTreeBodyFrame::ReflowCallbackCanceled() {
+ mReflowCallbackPosted = false;
+}
+
+nsresult nsTreeBodyFrame::GetView(nsITreeView** aView) {
+ *aView = nullptr;
+ AutoWeakFrame weakFrame(this);
+ EnsureView();
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ NS_IF_ADDREF(*aView = mView);
+ return NS_OK;
+}
+
+nsresult nsTreeBodyFrame::SetView(nsITreeView* aView) {
+ if (aView == mView) {
+ return NS_OK;
+ }
+
+ // First clear out the old view.
+ nsCOMPtr<nsITreeView> oldView = std::move(mView);
+ if (oldView) {
+ AutoWeakFrame weakFrame(this);
+
+ nsCOMPtr<nsITreeSelection> sel;
+ oldView->GetSelection(getter_AddRefs(sel));
+ if (sel) {
+ sel->SetTree(nullptr);
+ }
+ oldView->SetTree(nullptr);
+
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+
+ // 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();
+
+ RefPtr<XULTreeElement> treeContent = GetBaseElement();
+ if (treeContent) {
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->TreeViewChanged(PresContext()->GetPresShell(), treeContent,
+ mView);
+ }
+#endif // #ifdef ACCESSIBILITY
+ FireDOMEvent(u"TreeViewChanged"_ns, treeContent);
+ }
+
+ if (aView) {
+ // Give the view a new empty selection object to play with, but only if it
+ // doesn't have one already.
+ nsCOMPtr<nsITreeSelection> sel;
+ aView->GetSelection(getter_AddRefs(sel));
+ if (sel) {
+ sel->SetTree(treeContent);
+ } else {
+ NS_NewTreeSelection(treeContent, getter_AddRefs(sel));
+ aView->SetSelection(sel);
+ }
+
+ // View, meet the tree.
+ AutoWeakFrame weakFrame(this);
+ aView->SetTree(treeContent);
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ aView->GetRowCount(&mRowCount);
+
+ if (!PresShell()->IsReflowLocked()) {
+ // The scrollbar will need to be updated.
+ FullScrollbarsUpdate(false);
+ } else if (!mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresShell()->PostReflowCallback(this);
+ }
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsITreeSelection> nsTreeBodyFrame::GetSelection() const {
+ nsCOMPtr<nsITreeSelection> sel;
+ if (nsCOMPtr<nsITreeView> view = GetExistingView()) {
+ view->GetSelection(getter_AddRefs(sel));
+ }
+ return sel.forget();
+}
+
+nsresult nsTreeBodyFrame::SetFocused(bool aFocused) {
+ if (mFocused != aFocused) {
+ mFocused = aFocused;
+ if (nsCOMPtr<nsITreeSelection> sel = GetSelection()) {
+ sel->InvalidateSelection();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsTreeBodyFrame::GetTreeBody(Element** aElement) {
+ // NS_ASSERTION(mContent, "no content, see bug #104878");
+ if (!mContent) return NS_ERROR_NULL_POINTER;
+
+ RefPtr<Element> element = mContent->AsElement();
+ element.forget(aElement);
+ return NS_OK;
+}
+
+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);
+}
+
+Maybe<CSSIntRegion> nsTreeBodyFrame::GetSelectionRegion() {
+ if (!mView) {
+ return Nothing();
+ }
+
+ AutoWeakFrame wf(this);
+ nsCOMPtr<nsITreeSelection> selection = GetSelection();
+ if (!selection || !wf.IsAlive()) {
+ return Nothing();
+ }
+
+ RefPtr<nsPresContext> presContext = PresContext();
+ nsIntRect rect = mRect.ToOutsidePixels(AppUnitsPerCSSPixel());
+
+ nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame();
+ nsPoint origin = GetOffsetTo(rootFrame);
+
+ CSSIntRegion region;
+
+ // 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.OrWith(CSSIntRect(x, y, rect.width, rowHeight));
+ }
+ y += rowHeight;
+ }
+
+ // clip to the tree boundary in case one row extends past it
+ region.AndWith(CSSIntRect(x, top, rect.width, rect.height));
+
+ return Some(region);
+}
+
+nsresult nsTreeBodyFrame::Invalidate() {
+ if (mUpdateBatchNest) return NS_OK;
+
+ InvalidateFrame();
+
+ return NS_OK;
+}
+
+nsresult nsTreeBodyFrame::InvalidateColumn(nsTreeColumn* aCol) {
+ if (mUpdateBatchNest) return NS_OK;
+
+ if (!aCol) return NS_ERROR_INVALID_ARG;
+
+#ifdef ACCESSIBILITY
+ if (GetAccService()) {
+ FireInvalidateEvent(-1, -1, aCol, aCol);
+ }
+#endif // #ifdef ACCESSIBILITY
+
+ nsRect columnRect;
+ nsresult rv = aCol->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 (GetAccService()) {
+ FireInvalidateEvent(aIndex, aIndex, nullptr, nullptr);
+ }
+#endif // #ifdef ACCESSIBILITY
+
+ 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, nsTreeColumn* aCol) {
+ if (mUpdateBatchNest) return NS_OK;
+
+#ifdef ACCESSIBILITY
+ if (GetAccService()) {
+ FireInvalidateEvent(aIndex, aIndex, aCol, aCol);
+ }
+#endif // #ifdef ACCESSIBILITY
+
+ aIndex -= mTopRowIndex;
+ if (aIndex < 0 || aIndex > mPageLength) return NS_OK;
+
+ if (!aCol) return NS_ERROR_INVALID_ARG;
+
+ nsRect cellRect;
+ nsresult rv = aCol->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 (GetAccService()) {
+ int32_t end =
+ mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0;
+ FireInvalidateEvent(aStart, end, nullptr, nullptr);
+ }
+#endif // #ifdef ACCESSIBILITY
+
+ nsRect rangeRect(mInnerBox.x,
+ mInnerBox.y + mRowHeight * (aStart - mTopRowIndex),
+ mInnerBox.width, mRowHeight * (aEnd - aStart + 1));
+ 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;
+ }
+ }
+
+ if (nsScrollbarFrame* sf = do_QueryFrame(aCurrFrame)) {
+ if (!sf->IsHorizontal()) {
+ 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};
+ XULTreeElement* tree = GetBaseElement();
+ if (nsIFrame* treeFrame = tree ? tree->GetPrimaryFrame() : nullptr) {
+ // 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());
+ result.mHScrollbarContent = result.mHScrollbar->GetContent()->AsElement();
+ }
+ if (result.mVScrollbar) {
+ result.mVScrollbar->SetScrollbarMediatorContent(GetContent());
+ result.mVScrollbarContent = result.mVScrollbar->GetContent()->AsElement();
+ }
+ }
+ return result;
+}
+
+void nsTreeBodyFrame::UpdateScrollbars(const ScrollParts& aParts) {
+ nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+
+ AutoWeakFrame 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;
+ }
+ }
+ }
+
+ if (!horizontalOverflowChanged && !verticalOverflowChanged) {
+ return;
+ }
+
+ AutoWeakFrame weakFrame(this);
+
+ RefPtr<nsPresContext> presContext = PresContext();
+ RefPtr<mozilla::PresShell> presShell = presContext->GetPresShell();
+ nsCOMPtr<nsIContent> content = mContent;
+
+ if (verticalOverflowChanged) {
+ InternalScrollPortEvent event(
+ true, mVerticalOverflow ? eScrollPortOverflow : eScrollPortUnderflow,
+ nullptr);
+ event.mOrient = InternalScrollPortEvent::eVertical;
+ EventDispatcher::Dispatch(content, presContext, &event);
+ }
+
+ if (horizontalOverflowChanged) {
+ InternalScrollPortEvent event(
+ true, mHorizontalOverflow ? eScrollPortOverflow : eScrollPortUnderflow,
+ nullptr);
+ event.mOrient = InternalScrollPortEvent::eHorizontal;
+ EventDispatcher::Dispatch(content, presContext, &event);
+ }
+
+ // The synchronous event dispatch above can trigger reflow notifications.
+ // Flush those explicitly now, so that we can guard against potential infinite
+ // recursion. See bug 905909.
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ NS_ASSERTION(!mCheckingOverflow,
+ "mCheckingOverflow should not already be set");
+ // Don't use AutoRestore since we want to not touch mCheckingOverflow if we
+ // fail the weakFrame.IsAlive() check below
+ mCheckingOverflow = true;
+ presShell->FlushPendingNotifications(FlushType::Layout);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ mCheckingOverflow = false;
+}
+
+void nsTreeBodyFrame::InvalidateScrollbars(const ScrollParts& aParts,
+ AutoWeakFrame& aWeakColumnsFrame) {
+ if (mUpdateBatchNest || !mView) return;
+ AutoWeakFrame 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);
+ NS_ENSURE_TRUE_VOID(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);
+ NS_ENSURE_TRUE_VOID(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);
+ NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
+
+ nsAutoString pageStr;
+ pageStr.AppendInt(bounds.width);
+ aParts.mHScrollbarContent->SetAttr(kNameSpaceID_None,
+ nsGkAtoms::pageincrement, pageStr, true);
+ NS_ENSURE_TRUE_VOID(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
+
+int32_t nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY) {
+ if (!mView) {
+ return 0;
+ }
+
+ nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);
+
+ // Check if the coordinates are above our visible space.
+ if (point.y < 0) {
+ return -1;
+ }
+
+ return GetRowAtInternal(point.x, point.y);
+}
+
+nsresult nsTreeBodyFrame::GetCellAt(int32_t aX, int32_t aY, int32_t* aRow,
+ nsTreeColumn** 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;
+ nsCSSAnonBoxPseudoStaticAtom* 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, nsTreeColumn* 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 == StyleDirection::Rtl;
+ nscoord currX = mInnerBox.x - mHorzPosition;
+
+ // The Rect for the requested item.
+ nsRect theRect;
+
+ nsPresContext* presContext = PresContext();
+
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+
+ 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;
+ view->GetCellProperties(aRow, currCol, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ ComputedStyle* rowContext =
+ GetPseudoComputedStyle(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);
+
+ ComputedStyle* cellContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
+
+ constexpr auto cell = "cell"_ns;
+ 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);
+
+ UniquePtr<gfxContext> 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;
+ view->GetLevel(aRow, &level);
+ if (!isRTL) cellX += mIndentation * level;
+ remainWidth -= mIndentation * level;
+
+ // Find the twisty rect by computing its size.
+ nsRect imageRect;
+ nsRect twistyRect(cellRect);
+ ComputedStyle* twistyContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
+ GetTwistyRect(aRow, currCol, imageRect, twistyRect, presContext,
+ twistyContext);
+
+ if ("twisty"_ns.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
+ ComputedStyle* imageContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeImage());
+
+ nsRect imageSize = GetImageSize(aRow, currCol, false, imageContext);
+ if ("image"_ns.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;
+ view->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.
+ ComputedStyle* textContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, presContext);
+ 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::GetRowAtInternal(nscoord aX, nscoord aY) {
+ if (mRowHeight <= 0) return -1;
+
+ // 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,
+ gfxContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics,
+ nsRect& aTextRect) {
+ MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ nscoord maxWidth = aTextRect.width;
+ bool widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(
+ aText, aFontMetrics, drawTarget, maxWidth);
+
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ if (aColumn->Overflow()) {
+ DebugOnly<nsresult> rv;
+ nsTreeColumn* nextColumn = aColumn->GetNext();
+ while (nextColumn && widthIsGreater) {
+ while (nextColumn) {
+ nscoord width;
+ rv = nextColumn->GetWidthInTwips(this, &width);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
+
+ if (width != 0) {
+ break;
+ }
+
+ nextColumn = nextColumn->GetNext();
+ }
+
+ if (nextColumn) {
+ nsAutoString nextText;
+ view->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;
+ }
+ }
+ }
+ }
+
+ CroppingStyle cropType = CroppingStyle::CropRight;
+ if (aColumn->GetCropStyle() == 1) {
+ cropType = CroppingStyle::CropCenter;
+ } else if (aColumn->GetCropStyle() == 2) {
+ cropType = CroppingStyle::CropLeft;
+ }
+ CropStringForWidth(aText, aRenderingContext, aFontMetrics, maxWidth,
+ cropType);
+
+ nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(
+ aText, this, aFontMetrics, aRenderingContext);
+
+ switch (aColumn->GetTextAlignment()) {
+ case mozilla::StyleTextAlign::Right:
+ aTextRect.x += aTextRect.width - width;
+ break;
+ case mozilla::StyleTextAlign::Center:
+ aTextRect.x += (aTextRect.width - width) / 2;
+ break;
+ default:
+ break;
+ }
+
+ aTextRect.width = width;
+}
+
+nsCSSAnonBoxPseudoStaticAtom* nsTreeBodyFrame::GetItemWithinCellAt(
+ nscoord aX, const nsRect& aCellRect, int32_t aRowIndex,
+ nsTreeColumn* aColumn) {
+ MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Obtain the properties for our cell.
+ PrefillPropertyArray(aRowIndex, aColumn);
+ nsAutoString properties;
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ view->GetCellProperties(aRowIndex, aColumn, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+
+ // Resolve style for the cell.
+ ComputedStyle* cellContext =
+ GetPseudoComputedStyle(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 == StyleDirection::Rtl;
+
+ nsPresContext* presContext = PresContext();
+ UniquePtr<gfxContext> rc =
+ presContext->PresShell()->CreateReferenceRenderingContext();
+
+ if (aColumn->IsPrimary()) {
+ // If we're the primary column, we have indentation and a twisty.
+ int32_t level;
+ view->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;
+ view->IsContainer(aRowIndex, &isContainer);
+ if (isContainer) {
+ bool isContainerEmpty = false;
+ view->IsContainerEmpty(aRowIndex, &isContainerEmpty);
+ if (!isContainerEmpty) hasTwisty = true;
+ }
+
+ // Resolve style for the twisty.
+ ComputedStyle* twistyContext =
+ GetPseudoComputedStyle(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.
+ ComputedStyle* imageContext =
+ GetPseudoComputedStyle(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;
+ view->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);
+
+ ComputedStyle* textContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
+
+ nsMargin textMargin;
+ textContext->StyleMargin()->GetMargin(textMargin);
+ textRect.Deflate(textMargin);
+
+ AdjustForBorderPadding(textContext, textRect);
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, presContext);
+ 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,
+ nsCSSAnonBoxPseudoStaticAtom** aChildElt) {
+ *aCol = nullptr;
+ *aChildElt = nullptr;
+
+ *aRow = GetRowAtInternal(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)) {
+ MOZ_ASSERT_UNREACHABLE("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,
+ gfxContext* aRenderingContext,
+ nscoord& aDesiredSize,
+ nscoord& aCurrentSize) {
+ MOZ_ASSERT(aCol, "aCol must not be null");
+ MOZ_ASSERT(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.
+ ComputedStyle* cellContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
+ nsMargin bp(0, 0, 0, 0);
+ GetBorderPadding(cellContext, bp);
+
+ aCurrentSize = cellRect.width;
+ aDesiredSize = bp.left + bp.right;
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+
+ 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;
+ view->GetLevel(aRow, &level);
+ aDesiredSize += mIndentation * level;
+
+ // Find the twisty rect by computing its size.
+ ComputedStyle* twistyContext =
+ GetPseudoComputedStyle(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;
+ }
+
+ ComputedStyle* imageContext =
+ GetPseudoComputedStyle(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;
+ view->GetCellText(aRow, aCol, cellText);
+ // We're going to measure this text so we need to ensure bidi is enabled if
+ // necessary
+ CheckTextForBidi(cellText);
+
+ ComputedStyle* textContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
+
+ // Get the borders and padding for the text.
+ GetBorderPadding(textContext, bp);
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, PresContext());
+ // 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, nsTreeColumn* aCol,
+ bool* _retval) {
+ nscoord currentSize, desiredSize;
+ nsresult rv;
+
+ if (!aCol) return NS_ERROR_INVALID_ARG;
+
+ UniquePtr<gfxContext> rc = PresShell()->CreateReferenceRenderingContext();
+
+ rv = GetCellWidth(aRow, aCol, rc.get(), desiredSize, currentSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = desiredSize > currentSize;
+
+ return NS_OK;
+}
+
+nsresult nsTreeBodyFrame::CreateTimer(const LookAndFeel::IntID aID,
+ nsTimerCallbackFunc aFunc, int32_t aType,
+ nsITimer** aTimer, const char* aName) {
+ // Get the delay from the look and feel service.
+ int32_t delay = LookAndFeel::GetInt(aID, 0);
+
+ nsCOMPtr<nsITimer> timer;
+
+ // Create a new timer only if the delay is greater than zero.
+ // Zero value means that this feature is completely disabled.
+ if (delay > 0) {
+ MOZ_TRY_VAR(timer,
+ NS_NewTimerWithFuncCallback(
+ aFunc, this, delay, aType, aName,
+ mContent->OwnerDoc()->EventTargetFor(TaskCategory::Other)));
+ }
+
+ timer.forget(aTimer);
+ 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 (GetAccService()) {
+ FireRowCountChangedEvent(aIndex, aCount);
+ }
+#endif // #ifdef ACCESSIBILITY
+
+ AutoWeakFrame weakFrame(this);
+
+ // Adjust our selection.
+ if (nsCOMPtr<nsITreeSelection> sel = GetSelection()) {
+ sel->AdjustSelection(aIndex, aCount);
+ }
+
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+
+ 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.
+ FullScrollbarsUpdate(false);
+ 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;
+ }
+ }
+
+ FullScrollbarsUpdate(needsInvalidation);
+ return NS_OK;
+}
+
+nsresult nsTreeBodyFrame::BeginUpdateBatch() {
+ ++mUpdateBatchNest;
+
+ return NS_OK;
+}
+
+nsresult nsTreeBodyFrame::EndUpdateBatch() {
+ NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch");
+
+ if (--mUpdateBatchNest != 0) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ if (!view) {
+ return NS_OK;
+ }
+
+ Invalidate();
+ int32_t countBeforeUpdate = mRowCount;
+ view->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) {
+ MOZ_ASSERT(!aCol || aCol->GetFrame(), "invalid column passed");
+ mScratchArray.Clear();
+
+ // focus
+ if (mFocused)
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::focus);
+ else
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::blur);
+
+ // sort
+ bool sorted = false;
+ mView->IsSorted(&sorted);
+ if (sorted) mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::sorted);
+
+ // drag session
+ if (mSlots && mSlots->mIsDragging)
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::dragSession);
+
+ if (aRowIndex != -1) {
+ if (aRowIndex == mMouseOverRow)
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::hover);
+
+ nsCOMPtr<nsITreeSelection> selection = GetSelection();
+ if (selection) {
+ // selected
+ bool isSelected;
+ selection->IsSelected(aRowIndex, &isSelected);
+ if (isSelected)
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::selected);
+
+ // current
+ int32_t currentIndex;
+ selection->GetCurrentIndex(&currentIndex);
+ if (aRowIndex == currentIndex)
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::current);
+ }
+
+ // container or leaf
+ bool isContainer = false;
+ mView->IsContainer(aRowIndex, &isContainer);
+ if (isContainer) {
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::container);
+
+ // open or closed
+ bool isOpen = false;
+ mView->IsContainerOpen(aRowIndex, &isOpen);
+ if (isOpen)
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::open);
+ else
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::closed);
+ } else {
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::leaf);
+ }
+
+ // drop orientation
+ if (mSlots && mSlots->mDropAllowed && mSlots->mDropRow == aRowIndex) {
+ if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE)
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::dropBefore);
+ else if (mSlots->mDropOrient == nsITreeView::DROP_ON)
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::dropOn);
+ else if (mSlots->mDropOrient == nsITreeView::DROP_AFTER)
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::dropAfter);
+ }
+
+ // odd or even
+ if (aRowIndex % 2)
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::odd);
+ else
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::even);
+
+ XULTreeElement* tree = GetBaseElement();
+ if (tree && tree->HasAttr(kNameSpaceID_None, nsGkAtoms::editing)) {
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::editing);
+ }
+
+ // multiple columns
+ if (mColumns->GetColumnAt(1))
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::multicol);
+ }
+
+ if (aCol) {
+ mScratchArray.AppendElement(aCol->GetAtom());
+
+ if (aCol->IsPrimary())
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::primary);
+
+ if (aCol->GetType() == TreeColumn_Binding::TYPE_CHECKBOX) {
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::checkbox);
+
+ if (aRowIndex != -1) {
+ nsAutoString value;
+ mView->GetCellValue(aRowIndex, aCol, value);
+ if (value.EqualsLiteral("true"))
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::checked);
+ }
+ }
+
+ // Read special properties from attributes on the column content node
+ if (aCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::insertbefore,
+ nsGkAtoms::_true, eCaseMatters))
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::insertbefore);
+ if (aCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::insertafter,
+ nsGkAtoms::_true, eCaseMatters))
+ mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::insertafter);
+ }
+}
+
+nsITheme* nsTreeBodyFrame::GetTwistyRect(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ nsRect& aImageRect,
+ nsRect& aTwistyRect,
+ nsPresContext* aPresContext,
+ ComputedStyle* 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 ComputedStyle 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;
+ StyleAppearance appearance =
+ aTwistyContext->StyleDisplay()->EffectiveAppearance();
+ if (appearance != StyleAppearance::None) {
+ theme = aPresContext->Theme();
+ if (theme->ThemeSupportsWidget(aPresContext, nullptr, appearance))
+ useTheme = true;
+ }
+
+ if (useTheme) {
+ LayoutDeviceIntSize minTwistySizePx =
+ theme->GetMinimumWidgetSize(aPresContext, this, appearance);
+
+ // 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) {
+ aTwistyRect.width = minTwistySize.width;
+ }
+ }
+
+ return useTheme ? theme : nullptr;
+}
+
+nsresult nsTreeBodyFrame::GetImage(int32_t aRowIndex, nsTreeColumn* aCol,
+ bool aUseContext,
+ ComputedStyle* aComputedStyle,
+ imgIContainer** aResult) {
+ *aResult = nullptr;
+
+ nsAutoString imageSrc;
+ mView->GetImageSrc(aRowIndex, aCol, imageSrc);
+ RefPtr<imgRequestProxy> styleRequest;
+ if (aUseContext || imageSrc.IsEmpty()) {
+ // Obtain the URL from the ComputedStyle.
+ styleRequest =
+ aComputedStyle->StyleList()->mListStyleImage.GetImageRequest();
+ if (!styleRequest) return NS_OK;
+ nsCOMPtr<nsIURI> uri;
+ styleRequest->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(spec, imageSrc);
+ }
+
+ // Look the image up in our cache.
+ nsTreeImageCacheEntry entry;
+ if (mImageCache.Get(imageSrc, &entry)) {
+ // Find out if the image has loaded.
+ uint32_t status;
+ imgIRequest* imgReq = entry.request;
+ imgReq->GetImageStatus(&status);
+ imgReq->GetImage(aResult); // We hand back the image here. The GetImage
+ // call addrefs *aResult.
+ bool animated = true; // Assuming animated is the safe option
+
+ // We can only call GetAnimated if we're decoded
+ if (*aResult && (status & imgIRequest::STATUS_DECODE_COMPLETE))
+ (*aResult)->GetAnimated(&animated);
+
+ if ((!(status & imgIRequest::STATUS_LOAD_COMPLETE)) || animated) {
+ // We either aren't done loading, or we're animating. Add our row as a
+ // listener for invalidations.
+ nsCOMPtr<imgINotificationObserver> obs;
+ imgReq->GetNotificationObserver(getter_AddRefs(obs));
+
+ if (obs) {
+ static_cast<nsTreeImageListener*>(obs.get())->AddCell(aRowIndex, aCol);
+ }
+
+ return NS_OK;
+ }
+ }
+
+ if (!*aResult) {
+ // Create a new nsTreeImageListener object and pass it our row and column
+ // information.
+ nsTreeImageListener* listener = new nsTreeImageListener(this);
+ if (!listener) return NS_ERROR_OUT_OF_MEMORY;
+
+ mCreatedListeners.Insert(listener);
+
+ listener->AddCell(aRowIndex, aCol);
+ nsCOMPtr<imgINotificationObserver> imgNotificationObserver = listener;
+
+ Document* doc = mContent->GetComposedDoc();
+ if (!doc)
+ // The page is currently being torn down. Why bother.
+ return NS_ERROR_FAILURE;
+
+ RefPtr<imgRequestProxy> imageRequest;
+ if (styleRequest) {
+ styleRequest->SyncClone(imgNotificationObserver, doc,
+ getter_AddRefs(imageRequest));
+ } else {
+ nsCOMPtr<nsIURI> srcURI;
+ nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(srcURI), imageSrc, doc, mContent->GetBaseURI());
+ if (!srcURI) return NS_ERROR_FAILURE;
+
+ auto referrerInfo = MakeRefPtr<mozilla::dom::ReferrerInfo>(*doc);
+
+ // 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(), 0, referrerInfo,
+ imgNotificationObserver, nsIRequest::LOAD_NORMAL, u""_ns,
+ getter_AddRefs(imageRequest));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // NOTE(heycam): If it's an SVG image, and we need to want the image to
+ // able to respond to media query changes, it needs to be added to the
+ // document's ImageTracker. For now, assume we don't need this.
+ }
+ listener->UnsuppressInvalidation();
+
+ if (!imageRequest) return NS_ERROR_FAILURE;
+
+ // We don't want discarding/decode-on-draw for xul images
+ imageRequest->StartDecoding(imgIContainer::FLAG_ASYNC_NOTIFY);
+ imageRequest->LockImage();
+
+ // In a case it was already cached.
+ imageRequest->GetImage(aResult);
+ nsTreeImageCacheEntry cacheEntry(imageRequest, imgNotificationObserver);
+ mImageCache.InsertOrUpdate(imageSrc, cacheEntry);
+ }
+ return NS_OK;
+}
+
+nsRect nsTreeBodyFrame::GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol,
+ bool aUseContext,
+ ComputedStyle* aComputedStyle) {
+ // 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 ComputedStyle 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(aComputedStyle, 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 awry.
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aCol, aUseContext, aComputedStyle, getter_AddRefs(image));
+
+ const nsStylePosition* myPosition = aComputedStyle->StylePosition();
+ if (myPosition->mWidth.ConvertsToLength()) {
+ int32_t val = myPosition->mWidth.ToLength();
+ r.width += val;
+ } else {
+ needWidth = true;
+ }
+
+ if (myPosition->mHeight.ConvertsToLength()) {
+ int32_t val = myPosition->mHeight.ToLength();
+ r.height += val;
+ } 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(ComputedStyle* aComputedStyle,
+ 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 = aComputedStyle->StylePosition();
+
+ if (myPosition->mWidth.ConvertsToLength()) {
+ // CSS has specified the destination width.
+ size.width = myPosition->mWidth.ToLength();
+ } else {
+ // We'll need to get the width of the image/region.
+ needWidth = true;
+ }
+
+ if (myPosition->mHeight.ConvertsToLength()) {
+ // CSS has specified the destination height.
+ size.height = myPosition->mHeight.ToLength();
+ } 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);
+ if (image) {
+ nscoord width;
+ image->GetWidth(&width);
+ imageSize.width = nsPresContext::CSSPixelsToAppUnits(width);
+ 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(ComputedStyle* aComputedStyle,
+ imgIContainer* image) {
+ if (!image) {
+ return nsRect();
+ }
+
+ nsRect r;
+ // Use the actual image size.
+ nscoord coord;
+ if (NS_SUCCEEDED(image->GetWidth(&coord))) {
+ r.width = nsPresContext::CSSPixelsToAppUnits(coord);
+ }
+ if (NS_SUCCEEDED(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();
+ ComputedStyle* rowContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
+ if (rowContext) {
+ const nsStylePosition* myPosition = rowContext->StylePosition();
+
+ nscoord minHeight = 0;
+ if (myPosition->mMinHeight.ConvertsToLength()) {
+ minHeight = myPosition->mMinHeight.ToLength();
+ }
+
+ nscoord height = 0;
+ if (myPosition->mHeight.ConvertsToLength()) {
+ height = myPosition->mHeight.ToLength();
+ }
+
+ 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();
+ ComputedStyle* indentContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeIndentation());
+ if (indentContext) {
+ const nsStylePosition* myPosition = indentContext->StylePosition();
+ if (myPosition->mWidth.ConvertsToLength()) {
+ return myPosition->mWidth.ToLength();
+ }
+ }
+
+ return nsPresContext::CSSPixelsToAppUnits(16); // As good a default as any.
+}
+
+void nsTreeBodyFrame::CalcInnerBox() {
+ mInnerBox.SetRect(0, 0, mRect.width, mRect.height);
+ AdjustForBorderPadding(mComputedStyle, 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;
+}
+
+Maybe<nsIFrame::Cursor> nsTreeBodyFrame::GetCursor(const nsPoint& aPoint) {
+ // 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;
+ nsCSSAnonBoxPseudoStaticAtom* child;
+ GetCellAt(aPoint.x, aPoint.y, &row, &col, &child);
+
+ if (child) {
+ // Our scratch array is already prefilled.
+ RefPtr<ComputedStyle> childContext = GetPseudoComputedStyle(child);
+ StyleCursorKind kind = childContext->StyleUI()->Cursor().keyword;
+ if (kind == StyleCursorKind::Auto) {
+ kind = StyleCursorKind::Default;
+ }
+ return Some(
+ Cursor{kind, AllowCustomCursorImage::Yes, std::move(childContext)});
+ }
+ }
+ return SimpleXULLeafFrame::GetCursor(aPoint);
+}
+
+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) {
+ action = dragEvent->mDataTransfer->DropEffectInt();
+ }
+ return action;
+}
+
+nsresult nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ if (aEvent->mMessage == eMouseOver || aEvent->mMessage == eMouseMove) {
+ nsPoint pt =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this});
+ int32_t xTwips = pt.x - mInnerBox.x;
+ int32_t yTwips = pt.y - mInnerBox.y;
+ int32_t newrow = GetRowAtInternal(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 = MakeUnique<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::IntID::TreeLazyScrollDelay, LazyScrollCallback,
+ nsITimer::TYPE_ONE_SHOT, getter_AddRefs(mSlots->mTimer),
+ "nsTreeBodyFrame::LazyScrollCallback");
+ }
+#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::IntID::TreeOpenDelay, OpenCallback,
+ nsITimer::TYPE_ONE_SHOT,
+ getter_AddRefs(mSlots->mTimer),
+ "nsTreeBodyFrame::OpenCallback");
+ }
+ }
+ }
+
+ // 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::IntID::TreeCloseDelay, CloseCallback,
+ nsITimer::TYPE_ONE_SHOT, getter_AddRefs(mSlots->mTimer),
+ "nsTreeBodyFrame::CloseCallback");
+ }
+ }
+
+ return NS_OK;
+}
+
+namespace mozilla {
+
+class nsDisplayTreeBody final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayTreeBody(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayTreeBody);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTreeBody)
+
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayTreeBodyGeometry(this, aBuilder, IsWindowActive());
+ }
+
+ void Destroy(nsDisplayListBuilder* aBuilder) override {
+ aBuilder->UnregisterThemeGeometry(this);
+ nsPaintedDisplayItem::Destroy(aBuilder);
+ }
+
+ bool IsWindowActive() const {
+ DocumentState docState =
+ mFrame->PresContext()->Document()->GetDocumentState();
+ return !docState.HasState(DocumentState::WINDOW_INACTIVE);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override {
+ auto geometry = static_cast<const nsDisplayTreeBodyGeometry*>(aGeometry);
+
+ if (IsWindowActive() != geometry->mWindowIsActive) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsPaintedDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry,
+ aInvalidRegion);
+ }
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
+ MOZ_ASSERT(aBuilder);
+ Unused << static_cast<nsTreeBodyFrame*>(mFrame)->PaintTreeBody(
+ *aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame(), aBuilder);
+ }
+
+ NS_DISPLAY_DECL_NAME("XULTreeBody", TYPE_XUL_TREE_BODY)
+
+ nsRect GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const override {
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+};
+
+} // namespace mozilla
+
+#ifdef XP_MACOSX
+static bool IsInSourceList(nsIFrame* aFrame) {
+ for (nsIFrame* frame = aFrame; frame;
+ frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) {
+ if (frame->StyleDisplay()->EffectiveAppearance() ==
+ StyleAppearance::MozMacSourceList) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+// Painting routines
+void nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ // REVIEW: why did we paint if we were collapsed? that makes no sense!
+ if (!IsVisibleForPainting()) return; // We're invisible. Don't paint.
+
+ // Handles painting our background, border, and outline.
+ SimpleXULLeafFrame::BuildDisplayList(aBuilder, 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;
+
+ nsDisplayItem* item = MakeDisplayItem<nsDisplayTreeBody>(aBuilder, this);
+ aLists.Content()->AppendToTop(item);
+
+#ifdef XP_MACOSX
+ XULTreeElement* tree = GetBaseElement();
+ nsIFrame* treeFrame = tree ? tree->GetPrimaryFrame() : nullptr;
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ nsCOMPtr<nsITreeSelection> selection = GetSelection();
+ nsITheme* theme = PresContext()->Theme();
+ // 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 an ancestor frame to be themed as a source list.
+ if (selection && theme && IsInSourceList(treeFrame)) {
+ // 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;
+ view->GetRowProperties(i, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+ ComputedStyle* rowContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
+ auto appearance = rowContext->StyleDisplay()->EffectiveAppearance();
+ if (appearance != StyleAppearance::None) {
+ 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, item,
+ LayoutDeviceIntRect::FromUnknownRect(
+ (rowRect + aBuilder->ToReferenceFrame(this))
+ .ToNearestPixels(
+ PresContext()->AppUnitsPerDevPixel())));
+ }
+ }
+ }
+ }
+ }
+ }
+#endif
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintTreeBody(gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt,
+ nsDisplayListBuilder* aBuilder) {
+ // Update our available height and our page count.
+ CalcInnerBox();
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ aRenderingContext.Save();
+ aRenderingContext.Clip(NSRectToSnappedRect(
+ mInnerBox + aPt, PresContext()->AppUnitsPerDevPixel(), *drawTarget));
+ int32_t oldPageCount = mPageLength;
+ if (!mHasFixedRowCount) {
+ mPageLength =
+ (mRowHeight > 0) ? (mInnerBox.height / mRowHeight) : mRowCount;
+ }
+
+ if (oldPageCount != mPageLength ||
+ mHorzWidth != CalcHorzWidth(GetScrollParts())) {
+ // Schedule a ResizeReflow that will update our info properly.
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_IS_DIRTY);
+ }
+#ifdef DEBUG
+ int32_t rowCount = mRowCount;
+ mView->GetRowCount(&rowCount);
+ NS_WARNING_ASSERTION(mRowCount == rowCount, "row count changed unexpectedly");
+#endif
+
+ ImgDrawResult result = ImgDrawResult::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, aBuilder);
+ }
+ }
+
+ 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);
+ }
+ }
+ aRenderingContext.Restore();
+
+ return result;
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintColumn(nsTreeColumn* aColumn,
+ const nsRect& aColumnRect,
+ nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect) {
+ MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Now obtain the properties for our cell.
+ PrefillPropertyArray(-1, aColumn);
+ nsAutoString properties;
+
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ view->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.
+ ComputedStyle* colContext =
+ GetPseudoComputedStyle(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);
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintRow(int32_t aRowIndex,
+ const nsRect& aRowRect,
+ nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, nsPoint aPt,
+ nsDisplayListBuilder* aBuilder) {
+ // 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.
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ if (!view) {
+ return ImgDrawResult::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;
+ view->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.
+ ComputedStyle* rowContext =
+ GetPseudoComputedStyle(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);
+
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+ // Paint our borders and background for our row rect.
+ nsITheme* theme = nullptr;
+ auto appearance = rowContext->StyleDisplay()->EffectiveAppearance();
+ if (appearance != StyleAppearance::None) {
+ theme = aPresContext->Theme();
+ }
+
+ if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, appearance)) {
+ 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;
+ view->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)) {
+ MOZ_ASSERT_UNREACHABLE("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, aBuilder);
+ }
+ }
+
+ // 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 {
+ MOZ_ASSERT_UNREACHABLE(
+ "The column before the primary column is "
+ "invalid");
+ currX = rowRect.x;
+ }
+ } else {
+ currX = rowRect.x;
+ }
+
+ int32_t level;
+ view->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, aBuilder);
+ }
+ }
+ }
+
+ return result;
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintSeparator(int32_t aRowIndex,
+ const nsRect& aSeparatorRect,
+ nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect) {
+ // Resolve style for the separator.
+ ComputedStyle* separatorContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeSeparator());
+ bool useTheme = false;
+ nsITheme* theme = nullptr;
+ StyleAppearance appearance =
+ separatorContext->StyleDisplay()->EffectiveAppearance();
+ if (appearance != StyleAppearance::None) {
+ theme = aPresContext->Theme();
+ if (theme->ThemeSupportsWidget(aPresContext, nullptr, appearance))
+ useTheme = true;
+ }
+
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+ // use -moz-appearance if provided.
+ if (useTheme) {
+ nsRect dirty;
+ dirty.IntersectRect(aSeparatorRect, aDirtyRect);
+ theme->DrawWidgetBackground(&aRenderingContext, this, appearance,
+ aSeparatorRect, dirty);
+ } else {
+ const nsStylePosition* stylePosition = separatorContext->StylePosition();
+
+ // Obtain the height for the separator or use the default value.
+ nscoord height;
+ if (stylePosition->mHeight.ConvertsToLength()) {
+ height = stylePosition->mHeight.ToLength();
+ } 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;
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintCell(
+ int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aCellRect,
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, nscoord& aCurrX, nsPoint aPt,
+ nsDisplayListBuilder* aBuilder) {
+ MOZ_ASSERT(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;
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ view->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.
+ ComputedStyle* cellContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
+
+ bool isRTL = StyleVisibility()->mDirection == StyleDirection::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.
+ ImgDrawResult 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.
+ // StyleDirection::Ltr means paint from left to right.
+ // StyleDirection::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;
+ view->GetLevel(aRowIndex, &level);
+
+ if (!isRTL) currX += mIndentation * level;
+ remainingWidth -= mIndentation * level;
+
+ // Resolve the style to use for the connecting lines.
+ ComputedStyle* lineContext =
+ GetPseudoComputedStyle(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.
+ ComputedStyle* twistyContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
+
+ nsRect imageSize;
+ nsRect twistyRect(aCellRect);
+ GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, aPresContext,
+ twistyContext);
+
+ nsMargin twistyMargin;
+ twistyContext->StyleMargin()->GetMargin(twistyMargin);
+ twistyRect.Inflate(twistyMargin);
+
+ const nsStyleBorder* borderStyle = lineContext->StyleBorder();
+ // Resolve currentcolor values against the treeline context
+ nscolor color = borderStyle->mBorderLeftColor.CalcColor(*lineContext);
+ ColorPattern colorPatt(ToDeviceColor(color));
+
+ StyleBorderStyle style = borderStyle->GetBorderStyle(eSideLeft);
+ 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;
+ view->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(view->GetParentIndex(currentParent, &parent)) ||
+ parent < 0)
+ break;
+ currentParent = parent;
+ srcX -= mIndentation;
+ }
+ }
+
+ // 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,
+ aBuilder);
+ }
+
+ // 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 TreeColumn_Binding::TYPE_TEXT:
+ result &= PaintText(aRowIndex, aColumn, elementRect, aPresContext,
+ aRenderingContext, aDirtyRect, currX);
+ break;
+ case TreeColumn_Binding::TYPE_CHECKBOX:
+ result &= PaintCheckbox(aRowIndex, aColumn, elementRect, aPresContext,
+ aRenderingContext, aDirtyRect);
+ break;
+ }
+ }
+ }
+
+ aCurrX = currX;
+
+ return result;
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintTwisty(
+ int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aTwistyRect,
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, nscoord& aRemainingWidth, nscoord& aCurrX) {
+ MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
+ nscoord rightEdge = aCurrX + aRemainingWidth;
+ // Paint the twisty, but only if we are a non-empty container.
+ bool shouldPaint = false;
+ bool isContainer = false;
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ view->IsContainer(aRowIndex, &isContainer);
+ if (isContainer) {
+ bool isContainerEmpty = false;
+ view->IsContainerEmpty(aRowIndex, &isContainerEmpty);
+ if (!isContainerEmpty) shouldPaint = true;
+ }
+
+ // Resolve style for the twisty.
+ ComputedStyle* twistyContext =
+ GetPseudoComputedStyle(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;
+
+ ImgDrawResult result = ImgDrawResult::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()->EffectiveAppearance(), twistyRect,
+ dirty);
+ } else {
+ // Time to paint the twisty.
+ // Adjust the rect for its border and padding.
+ nsMargin bp(0, 0, 0, 0);
+ GetBorderPadding(twistyContext, bp);
+ twistyRect.Deflate(bp);
+ if (isRTL) twistyRect.x = rightEdge - twistyRect.width;
+ imageSize.Deflate(bp);
+
+ // Get the image for drawing.
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aColumn, true, twistyContext, getter_AddRefs(image));
+ if (image) {
+ nsPoint anchorPoint = twistyRect.TopLeft();
+
+ // Center the image. XXX Obey vertical-align style prop?
+ if (imageSize.height < twistyRect.height) {
+ anchorPoint.y += (twistyRect.height - imageSize.height) / 2;
+ }
+
+ // Apply context paint if applicable
+ SVGImageContext svgContext;
+ SVGImageContext::MaybeStoreContextPaint(svgContext, *aPresContext,
+ *twistyContext, image);
+
+ // Paint the image.
+ result &= nsLayoutUtils::DrawSingleUnscaledImage(
+ aRenderingContext, aPresContext, image, SamplingFilter::POINT,
+ anchorPoint, &aDirtyRect, svgContext, imgIContainer::FLAG_NONE,
+ &imageSize);
+ }
+ }
+ }
+
+ return result;
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintImage(
+ int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aImageRect,
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, nscoord& aRemainingWidth, nscoord& aCurrX,
+ nsDisplayListBuilder* aBuilder) {
+ MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
+ nscoord rightEdge = aCurrX + aRemainingWidth;
+ // Resolve style for the image.
+ ComputedStyle* imageContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeImage());
+
+ // 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.
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aColumn, false, imageContext, getter_AddRefs(image));
+
+ // Get the image destination size.
+ nsSize imageDestSize = GetImageDestSize(imageContext, image);
+ if (!imageDestSize.width || !imageDestSize.height) {
+ return ImgDrawResult::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;
+ }
+ }
+
+ ImgDrawResult result = ImgDrawResult::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, 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;
+ }
+ }
+
+ const auto* styleEffects = imageContext->StyleEffects();
+ gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aRenderingContext);
+ if (!styleEffects->IsOpaque()) {
+ autoGroupForBlend.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
+ styleEffects->mOpacity);
+ }
+
+ uint32_t drawFlags = aBuilder && aBuilder->UseHighQualityScaling()
+ ? imgIContainer::FLAG_HIGH_QUALITY_SCALING
+ : imgIContainer::FLAG_NONE;
+ result &= nsLayoutUtils::DrawImage(
+ aRenderingContext, imageContext, aPresContext, image,
+ nsLayoutUtils::GetSamplingFilterForFrame(this), wholeImageDest,
+ destRect, destRect.TopLeft(), aDirtyRect, drawFlags);
+ }
+
+ // Update the aRemainingWidth and aCurrX values.
+ imageRect.Inflate(imageMargin);
+ aRemainingWidth -= imageRect.width;
+ if (!isRTL) {
+ aCurrX += imageRect.width;
+ }
+
+ return result;
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintText(
+ int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aTextRect,
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, nscoord& aCurrX) {
+ MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
+
+ // Now obtain the text for our cell.
+ nsAutoString text;
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ view->GetCellText(aRowIndex, aColumn, text);
+
+ // We're going to paint this text so we need to ensure bidi is enabled if
+ // necessary
+ CheckTextForBidi(text);
+
+ ImgDrawResult result = ImgDrawResult::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.
+ ComputedStyle* textContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
+
+ // Obtain the margins for the text and then deflate our rect by that
+ // amount. The text is assumed to be contained within the deflated rect.
+ nsRect textRect(aTextRect);
+ nsMargin textMargin;
+ textContext->StyleMargin()->GetMargin(textMargin);
+ textRect.Deflate(textMargin);
+
+ // Adjust the rect for its border and padding.
+ nsMargin bp(0, 0, 0, 0);
+ GetBorderPadding(textContext, bp);
+ textRect.Deflate(bp);
+
+ // Compute our text size.
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, PresContext());
+
+ 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->StyleText()->mColor));
+
+ // Draw decorations.
+ StyleTextDecorationLine decorations =
+ textContext->StyleTextReset()->mTextDecorationLine;
+
+ nscoord offset;
+ nscoord size;
+ if (decorations & (StyleTextDecorationLine::OVERLINE |
+ StyleTextDecorationLine::UNDERLINE)) {
+ fontMet->GetUnderline(offset, size);
+ if (decorations & StyleTextDecorationLine::OVERLINE) {
+ nsRect r(textRect.x, textRect.y, textRect.width, size);
+ Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, color);
+ }
+ if (decorations & StyleTextDecorationLine::UNDERLINE) {
+ nsRect r(textRect.x, textRect.y + baseline - offset, textRect.width,
+ size);
+ Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, color);
+ }
+ }
+ if (decorations & StyleTextDecorationLine::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);
+ }
+ ComputedStyle* cellContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
+
+ const auto* styleEffects = textContext->StyleEffects();
+ gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aRenderingContext);
+ if (!styleEffects->IsOpaque()) {
+ autoGroupForBlend.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
+ styleEffects->mOpacity);
+ }
+
+ aRenderingContext.SetColor(
+ sRGBColor::FromABGR(textContext->StyleText()->mColor.ToColor()));
+ nsLayoutUtils::DrawString(
+ this, *fontMet, &aRenderingContext, text.get(), text.Length(),
+ textRect.TopLeft() + nsPoint(0, baseline), cellContext);
+
+ return result;
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintCheckbox(int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsRect& aCheckboxRect,
+ nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect) {
+ MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
+
+ // Resolve style for the checkbox.
+ ComputedStyle* checkboxContext =
+ GetPseudoComputedStyle(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 == StyleDirection::Rtl) {
+ checkboxRect.x = rightEdge - checkboxRect.width;
+ }
+
+ // Paint our borders and background for our image rect.
+ ImgDrawResult result =
+ PaintBackgroundLayer(checkboxContext, aPresContext, aRenderingContext,
+ checkboxRect, aDirtyRect);
+
+ // Time to paint the checkbox.
+ // Adjust the rect for its border and padding.
+ nsMargin bp(0, 0, 0, 0);
+ GetBorderPadding(checkboxContext, bp);
+ checkboxRect.Deflate(bp);
+
+ // Get the image for drawing.
+ nsCOMPtr<imgIContainer> image;
+ GetImage(aRowIndex, aColumn, true, checkboxContext, 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;
+ }
+
+ // Apply context paint if applicable
+ SVGImageContext svgContext;
+ SVGImageContext::MaybeStoreContextPaint(svgContext, *aPresContext,
+ *checkboxContext, image);
+ // Paint the image.
+ result &= nsLayoutUtils::DrawSingleUnscaledImage(
+ aRenderingContext, aPresContext, image, SamplingFilter::POINT, pt,
+ &aDirtyRect, svgContext, imgIContainer::FLAG_NONE, &imageSize);
+ }
+
+ return result;
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintDropFeedback(
+ const nsRect& aDropFeedbackRect, nsPresContext* aPresContext,
+ gfxContext& 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.
+ ComputedStyle* feedbackContext =
+ GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeDropFeedback());
+
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+ // Paint only if it is visible.
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ if (feedbackContext->StyleVisibility()->IsVisibleOrCollapsed()) {
+ int32_t level;
+ view->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;
+ view->GetLevel(mSlots->mDropRow - 1, &previousLevel);
+ if (previousLevel > level) level = previousLevel;
+ }
+ } else {
+ if (mSlots->mDropRow < mRowCount - 1) {
+ int32_t nextLevel;
+ view->GetLevel(mSlots->mDropRow + 1, &nextLevel);
+ if (nextLevel > level) level = nextLevel;
+ }
+ }
+
+ currX += mIndentation * level;
+
+ if (primaryCol) {
+ ComputedStyle* twistyContext =
+ GetPseudoComputedStyle(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.ConvertsToLength()) {
+ width = stylePosition->mWidth.ToLength();
+ } 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.ConvertsToLength()) {
+ height = stylePosition->mHeight.ToLength();
+ } 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;
+}
+
+ImgDrawResult nsTreeBodyFrame::PaintBackgroundLayer(
+ ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
+ gfxContext& aRenderingContext, const nsRect& aRect,
+ const nsRect& aDirtyRect) {
+ const nsStyleBorder* myBorder = aComputedStyle->StyleBorder();
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForAllLayers(
+ *aPresContext, aDirtyRect, aRect, this,
+ nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES);
+ ImgDrawResult result = nsCSSRendering::PaintStyleImageLayerWithSC(
+ params, aRenderingContext, aComputedStyle, *myBorder);
+
+ result &= nsCSSRendering::PaintBorderWithStyleBorder(
+ aPresContext, aRenderingContext, this, aDirtyRect, aRect, *myBorder,
+ mComputedStyle, PaintBorderFlags::SyncDecodeImages);
+
+ nsCSSRendering::PaintNonThemedOutline(aPresContext, aRenderingContext, this,
+ aDirtyRect, aRect, aComputedStyle);
+
+ 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,
+ nsTreeColumn* aCol) {
+ if (!aCol) return NS_ERROR_INVALID_ARG;
+
+ ScrollParts parts = GetScrollParts();
+
+ nscoord result = -1;
+ nsresult rv;
+
+ nscoord columnPos;
+ rv = aCol->GetXInTwips(this, &columnPos);
+ if (NS_FAILED(rv)) return rv;
+
+ nscoord columnWidth;
+ rv = aCol->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;
+}
+
+void nsTreeBodyFrame::ScrollToRow(int32_t aRow) {
+ ScrollParts parts = GetScrollParts();
+ ScrollToRowInternal(parts, aRow);
+ UpdateScrollbars(parts);
+}
+
+nsresult nsTreeBodyFrame::ScrollToRowInternal(const ScrollParts& aParts,
+ int32_t aRow) {
+ ScrollInternal(aParts, aRow);
+
+ return NS_OK;
+}
+
+void nsTreeBodyFrame::ScrollByLines(int32_t aNumLines) {
+ if (!mView) {
+ return;
+ }
+ int32_t newIndex = mTopRowIndex + aNumLines;
+ ScrollToRow(newIndex);
+}
+
+void nsTreeBodyFrame::ScrollByPages(int32_t aNumPages) {
+ if (!mView) {
+ return;
+ }
+ int32_t newIndex = mTopRowIndex + aNumPages * mPageLength;
+ ScrollToRow(newIndex);
+}
+
+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
+ AutoWeakFrame weakFrame(this);
+ aParts.mColumnsScrollFrame->ScrollTo(nsPoint(mHorzPosition, 0),
+ ScrollMode::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,
+ ScrollSnapFlags aSnapFlags) {
+ // 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,
+ ScrollSnapFlags aSnapFlags) {
+ // 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,
+ ScrollSnapFlags aSnapFlags) {
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ ScrollByLines(aDirection);
+}
+
+void nsTreeBodyFrame::ScrollByUnit(
+ nsScrollbarFrame* aScrollbar, ScrollMode aMode, int32_t aDirection,
+ ScrollUnit aUnit, ScrollSnapFlags aSnapFlags /* = Disabled */) {
+ MOZ_ASSERT_UNREACHABLE("Can't get here, we pass false to MoveToNewPosition");
+}
+
+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->IsHorizontal();
+
+ AutoWeakFrame weakFrame(this);
+ if (isHorizontal) {
+ int32_t curpos = aScrollbar->MoveToNewPosition(
+ nsScrollbarFrame::ImplementsScrollByUnit::No);
+ 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;
+
+ AutoWeakFrame weakFrame(this);
+
+ // Vertical Scrollbar
+ if (parts.mVScrollbar == aScrollbar) {
+ nscoord rh = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
+ nscoord newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
+ nscoord newrow = (rh > 0) ? (newIndex / rh) : 0;
+ 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.
+ComputedStyle* nsTreeBodyFrame::GetPseudoComputedStyle(
+ nsCSSAnonBoxPseudoStaticAtom* aPseudoElement) {
+ return mStyleCache.GetComputedStyle(PresContext(), mContent, mComputedStyle,
+ aPseudoElement, mScratchArray);
+}
+
+XULTreeElement* nsTreeBodyFrame::GetBaseElement() {
+ if (!mTree) {
+ nsIFrame* parent = GetParent();
+ while (parent) {
+ nsIContent* content = parent->GetContent();
+ if (content && content->IsXULElement(nsGkAtoms::tree)) {
+ mTree = XULTreeElement::FromNodeOrNull(content->AsElement());
+ break;
+ }
+
+ parent = parent->GetInFlowParent();
+ }
+ }
+
+ return mTree;
+}
+
+nsresult nsTreeBodyFrame::ClearStyleAndImageCaches() {
+ mStyleCache.Clear();
+ CancelImageRequests();
+ mImageCache.Clear();
+ return NS_OK;
+}
+
+void nsTreeBodyFrame::RemoveImageCacheEntry(int32_t aRowIndex,
+ nsTreeColumn* aCol) {
+ nsAutoString imageSrc;
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ if (NS_FAILED(view->GetImageSrc(aRowIndex, aCol, imageSrc))) {
+ return;
+ }
+ nsTreeImageCacheEntry entry;
+ if (!mImageCache.Get(imageSrc, &entry)) {
+ return;
+ }
+ nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request, nullptr);
+ entry.request->UnlockImage();
+ entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ mImageCache.Remove(imageSrc);
+}
+
+/* virtual */
+void nsTreeBodyFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+ SimpleXULLeafFrame::DidSetComputedStyle(aOldComputedStyle);
+
+ // 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();
+}
+
+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, RelativeTo{this});
+ int32_t xTwips = pt.x - mInnerBox.x;
+ int32_t yTwips = pt.y - mInnerBox.y;
+
+ nsCOMPtr<nsITreeView> view = GetExistingView();
+ *aRow = GetRowAtInternal(xTwips, yTwips);
+ if (*aRow >= 0) {
+ // Compute the top/bottom of the row in question.
+ int32_t yOffset = yTwips - mRowHeight * (*aRow - mTopRowIndex);
+
+ bool isContainer = false;
+ view->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::IntID::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) {
+ auto* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (!self) {
+ return;
+ }
+
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+
+ nsCOMPtr<nsITreeView> view = self->GetExistingView();
+ if (self->mSlots->mDropRow >= 0) {
+ self->mSlots->mArray.AppendElement(self->mSlots->mDropRow);
+ view->ToggleOpenState(self->mSlots->mDropRow);
+ }
+}
+
+void nsTreeBodyFrame::CloseCallback(nsITimer* aTimer, void* aClosure) {
+ auto* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (!self) {
+ return;
+ }
+
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+
+ nsCOMPtr<nsITreeView> view = self->GetExistingView();
+ auto array = std::move(self->mSlots->mArray);
+ if (!view) {
+ return;
+ }
+ for (auto elem : Reversed(array)) {
+ view->ToggleOpenState(elem);
+ }
+}
+
+void nsTreeBodyFrame::LazyScrollCallback(nsITimer* aTimer, void* aClosure) {
+ nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (self) {
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+
+ if (self->mView) {
+ // Set a new timer to scroll the tree repeatedly.
+ self->CreateTimer(LookAndFeel::IntID::TreeScrollDelay, ScrollCallback,
+ nsITimer::TYPE_REPEATING_SLACK,
+ getter_AddRefs(self->mSlots->mTimer),
+ "nsTreeBodyFrame::ScrollCallback");
+ self->ScrollByLines(self->mSlots->mScrollLines);
+ // ScrollByLines may have deleted |self|.
+ }
+ }
+}
+
+void nsTreeBodyFrame::ScrollCallback(nsITimer* aTimer, void* aClosure) {
+ nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
+ if (self) {
+ // Don't scroll if we are already at the top or bottom of the view.
+ if (self->mView && self->CanAutoScroll(self->mSlots->mDropRow)) {
+ self->ScrollByLines(self->mSlots->mScrollLines);
+ } else {
+ aTimer->Cancel();
+ self->mSlots->mTimer = nullptr;
+ }
+ }
+}
+
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY 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;
+ RefPtr<nsIContent> content = GetContent();
+ RefPtr<nsPresContext> presContext = PresContext();
+ EventDispatcher::Dispatch(content, presContext, &event);
+}
+
+void nsTreeBodyFrame::PostScrollEvent() {
+ if (mScrollEvent.IsPending()) return;
+
+ RefPtr<ScrollEvent> event = new ScrollEvent(this);
+ nsresult rv =
+ mContent->OwnerDoc()->Dispatch(TaskCategory::Other, do_AddRef(event));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to dispatch ScrollEvent");
+ } else {
+ mScrollEvent = std::move(event);
+ }
+}
+
+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.Remove(aListener);
+ }
+}
+
+#ifdef ACCESSIBILITY
+static void InitCustomEvent(CustomEvent* aEvent, const nsAString& aType,
+ nsIWritablePropertyBag2* aDetail) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aEvent->GetParentObject())) {
+ return;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> detail(cx);
+ if (!ToJSValue(cx, aDetail, &detail)) {
+ jsapi.ClearException();
+ return;
+ }
+
+ aEvent->InitCustomEvent(cx, aType, /* aCanBubble = */ true,
+ /* aCancelable = */ false, detail);
+}
+
+void nsTreeBodyFrame::FireRowCountChangedEvent(int32_t aIndex, int32_t aCount) {
+ RefPtr<XULTreeElement> tree(GetBaseElement());
+ if (!tree) return;
+
+ RefPtr<Document> doc = tree->OwnerDoc();
+ MOZ_ASSERT(doc);
+
+ RefPtr<Event> event =
+ doc->CreateEvent(u"customevent"_ns, CallerType::System, IgnoreErrors());
+
+ CustomEvent* treeEvent = event->AsCustomEvent();
+ if (!treeEvent) {
+ return;
+ }
+
+ nsCOMPtr<nsIWritablePropertyBag2> propBag(
+ do_CreateInstance("@mozilla.org/hash-property-bag;1"));
+ if (!propBag) {
+ return;
+ }
+
+ // Set 'index' data - the row index rows are changed from.
+ propBag->SetPropertyAsInt32(u"index"_ns, aIndex);
+
+ // Set 'count' data - the number of changed rows.
+ propBag->SetPropertyAsInt32(u"count"_ns, aCount);
+
+ InitCustomEvent(treeEvent, u"TreeRowCountChanged"_ns, propBag);
+
+ event->SetTrusted(true);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(tree, event);
+ asyncDispatcher->PostDOMEvent();
+}
+
+void nsTreeBodyFrame::FireInvalidateEvent(int32_t aStartRowIdx,
+ int32_t aEndRowIdx,
+ nsTreeColumn* aStartCol,
+ nsTreeColumn* aEndCol) {
+ RefPtr<XULTreeElement> tree(GetBaseElement());
+ if (!tree) return;
+
+ RefPtr<Document> doc = tree->OwnerDoc();
+
+ RefPtr<Event> event =
+ doc->CreateEvent(u"customevent"_ns, CallerType::System, IgnoreErrors());
+
+ CustomEvent* treeEvent = event->AsCustomEvent();
+ if (!treeEvent) {
+ return;
+ }
+
+ nsCOMPtr<nsIWritablePropertyBag2> propBag(
+ do_CreateInstance("@mozilla.org/hash-property-bag;1"));
+ if (!propBag) {
+ return;
+ }
+
+ if (aStartRowIdx != -1 && aEndRowIdx != -1) {
+ // Set 'startrow' data - the start index of invalidated rows.
+ propBag->SetPropertyAsInt32(u"startrow"_ns, aStartRowIdx);
+
+ // Set 'endrow' data - the end index of invalidated rows.
+ propBag->SetPropertyAsInt32(u"endrow"_ns, aEndRowIdx);
+ }
+
+ if (aStartCol && aEndCol) {
+ // Set 'startcolumn' data - the start index of invalidated rows.
+ int32_t startColIdx = aStartCol->GetIndex();
+
+ propBag->SetPropertyAsInt32(u"startcolumn"_ns, startColIdx);
+
+ // Set 'endcolumn' data - the start index of invalidated rows.
+ int32_t endColIdx = aEndCol->GetIndex();
+ propBag->SetPropertyAsInt32(u"endcolumn"_ns, endColIdx);
+ }
+
+ InitCustomEvent(treeEvent, u"TreeInvalidated"_ns, propBag);
+
+ event->SetTrusted(true);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(tree, event);
+ asyncDispatcher->PostDOMEvent();
+}
+#endif
+
+class nsOverflowChecker : public Runnable {
+ public:
+ explicit nsOverflowChecker(nsTreeBodyFrame* aFrame)
+ : mozilla::Runnable("nsOverflowChecker"), mFrame(aFrame) {}
+ NS_IMETHOD Run() override {
+ if (mFrame.IsAlive()) {
+ nsTreeBodyFrame* tree = static_cast<nsTreeBodyFrame*>(mFrame.GetFrame());
+ nsTreeBodyFrame::ScrollParts parts = tree->GetScrollParts();
+ tree->CheckOverflow(parts);
+ }
+ return NS_OK;
+ }
+
+ private:
+ WeakFrame mFrame;
+};
+
+bool nsTreeBodyFrame::FullScrollbarsUpdate(bool aNeedsFullInvalidation) {
+ ScrollParts parts = GetScrollParts();
+ AutoWeakFrame weakFrame(this);
+ AutoWeakFrame weakColumnsFrame(parts.mColumnsFrame);
+ UpdateScrollbars(parts);
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+ if (aNeedsFullInvalidation) {
+ Invalidate();
+ }
+ InvalidateScrollbars(parts, weakColumnsFrame);
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+
+ // Overflow checking dispatches synchronous events, which can cause infinite
+ // recursion during reflow. Do the first overflow check synchronously, but
+ // force any nested checks to round-trip through the event loop. See bug
+ // 905909.
+ RefPtr<nsOverflowChecker> checker = new nsOverflowChecker(this);
+ if (!mCheckingOverflow) {
+ nsContentUtils::AddScriptRunner(checker);
+ } else {
+ mContent->OwnerDoc()->Dispatch(TaskCategory::Other, checker.forget());
+ }
+ return weakFrame.IsAlive();
+}
+
+void nsTreeBodyFrame::OnImageIsAnimated(imgIRequest* aRequest) {
+ nsLayoutUtils::RegisterImageRequest(PresContext(), aRequest, nullptr);
+}