1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/AsyncEventDispatcher.h"
8 #include "mozilla/ContentEvents.h"
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/EventDispatcher.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/PathHelpers.h"
13 #include "mozilla/Likely.h"
14 #include "mozilla/LookAndFeel.h"
15 #include "mozilla/MathAlgorithms.h"
16 #include "mozilla/MouseEvents.h"
17 #include "mozilla/PresShell.h"
18 #include "mozilla/ResultExtensions.h"
19
20 #include "gfxUtils.h"
21 #include "nsAlgorithm.h"
22 #include "nsCOMPtr.h"
23 #include "nsComponentManagerUtils.h"
24 #include "nsFontMetrics.h"
25 #include "nsPresContext.h"
26 #include "nsNameSpaceManager.h"
27
28 #include "nsTreeBodyFrame.h"
29 #include "nsTreeSelection.h"
30 #include "nsTreeImageListener.h"
31
32 #include "nsGkAtoms.h"
33 #include "nsCSSAnonBoxes.h"
34
35 #include "gfxContext.h"
36 #include "nsIContent.h"
37 #include "mozilla/ComputedStyle.h"
38 #include "mozilla/dom/Document.h"
39 #include "nsCSSRendering.h"
40 #include "nsString.h"
41 #include "nsContainerFrame.h"
42 #include "nsView.h"
43 #include "nsViewManager.h"
44 #include "nsVariant.h"
45 #include "nsWidgetsCID.h"
46 #include "nsIFrameInlines.h"
47 #include "nsBoxFrame.h"
48 #include "nsBoxLayoutState.h"
49 #include "nsTreeContentView.h"
50 #include "nsTreeUtils.h"
51 #include "nsStyleConsts.h"
52 #include "nsITheme.h"
53 #include "imgIRequest.h"
54 #include "imgIContainer.h"
55 #include "mozilla/dom/NodeInfo.h"
56 #include "nsContentUtils.h"
57 #include "nsLayoutUtils.h"
58 #include "nsIScrollableFrame.h"
59 #include "nsDisplayList.h"
60 #include "mozilla/dom/CustomEvent.h"
61 #include "mozilla/dom/Event.h"
62 #include "mozilla/dom/ScriptSettings.h"
63 #include "mozilla/dom/ToJSValue.h"
64 #include "mozilla/dom/TreeColumnBinding.h"
65 #include <algorithm>
66 #include "ScrollbarActivity.h"
67
68 #ifdef ACCESSIBILITY
69 # include "nsAccessibilityService.h"
70 # include "nsIWritablePropertyBag2.h"
71 #endif
72 #include "nsBidiUtils.h"
73
74 using namespace mozilla;
75 using namespace mozilla::dom;
76 using namespace mozilla::gfx;
77 using namespace mozilla::image;
78 using namespace mozilla::layout;
79
80 // Function that cancels all the image requests in our cache.
CancelImageRequests()81 void nsTreeBodyFrame::CancelImageRequests() {
82 for (nsTreeImageCacheEntry entry : mImageCache.Values()) {
83 // If our imgIRequest object was registered with the refresh driver
84 // then we need to deregister it.
85 nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request,
86 nullptr);
87 entry.request->UnlockImage();
88 entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
89 }
90 }
91
92 //
93 // NS_NewTreeFrame
94 //
95 // Creates a new tree frame
96 //
NS_NewTreeBodyFrame(PresShell * aPresShell,ComputedStyle * aStyle)97 nsIFrame* NS_NewTreeBodyFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
98 return new (aPresShell) nsTreeBodyFrame(aStyle, aPresShell->GetPresContext());
99 }
100
101 NS_IMPL_FRAMEARENA_HELPERS(nsTreeBodyFrame)
102
NS_QUERYFRAME_HEAD(nsTreeBodyFrame)103 NS_QUERYFRAME_HEAD(nsTreeBodyFrame)
104 NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
105 NS_QUERYFRAME_ENTRY(nsTreeBodyFrame)
106 NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame)
107
108 // Constructor
109 nsTreeBodyFrame::nsTreeBodyFrame(ComputedStyle* aStyle,
110 nsPresContext* aPresContext)
111 : nsLeafBoxFrame(aStyle, aPresContext, kClassID),
112 mSlots(nullptr),
113 mImageCache(),
114 mTopRowIndex(0),
115 mPageLength(0),
116 mHorzPosition(0),
117 mOriginalHorzWidth(-1),
118 mHorzWidth(0),
119 mAdjustWidth(0),
120 mRowHeight(0),
121 mIndentation(0),
122 mStringWidth(-1),
123 mUpdateBatchNest(0),
124 mRowCount(0),
125 mMouseOverRow(-1),
126 mFocused(false),
127 mHasFixedRowCount(false),
128 mVerticalOverflow(false),
129 mHorizontalOverflow(false),
130 mReflowCallbackPosted(false),
131 mCheckingOverflow(false) {
132 mColumns = new nsTreeColumns(this);
133 }
134
135 // Destructor
~nsTreeBodyFrame()136 nsTreeBodyFrame::~nsTreeBodyFrame() {
137 CancelImageRequests();
138 DetachImageListeners();
139 delete mSlots;
140 }
141
GetBorderPadding(ComputedStyle * aStyle,nsMargin & aMargin)142 static void GetBorderPadding(ComputedStyle* aStyle, nsMargin& aMargin) {
143 aMargin.SizeTo(0, 0, 0, 0);
144 aStyle->StylePadding()->GetPadding(aMargin);
145 aMargin += aStyle->StyleBorder()->GetComputedBorder();
146 }
147
AdjustForBorderPadding(ComputedStyle * aStyle,nsRect & aRect)148 static void AdjustForBorderPadding(ComputedStyle* aStyle, nsRect& aRect) {
149 nsMargin borderPadding(0, 0, 0, 0);
150 GetBorderPadding(aStyle, borderPadding);
151 aRect.Deflate(borderPadding);
152 }
153
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)154 void nsTreeBodyFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
155 nsIFrame* aPrevInFlow) {
156 nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
157
158 mIndentation = GetIndentation();
159 mRowHeight = GetRowHeight();
160
161 // Call GetBaseElement so that mTree is assigned.
162 GetBaseElement();
163
164 if (LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0) {
165 mScrollbarActivity =
166 new ScrollbarActivity(static_cast<nsIScrollbarMediator*>(this));
167 }
168 }
169
GetXULMinSize(nsBoxLayoutState & aBoxLayoutState)170 nsSize nsTreeBodyFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) {
171 EnsureView();
172
173 RefPtr<XULTreeElement> tree(GetBaseElement());
174
175 nsSize min(0, 0);
176 int32_t desiredRows;
177 if (MOZ_UNLIKELY(!tree)) {
178 desiredRows = 0;
179 } else {
180 nsAutoString rows;
181 tree->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows);
182 if (!rows.IsEmpty()) {
183 nsresult err;
184 desiredRows = rows.ToInteger(&err);
185 mPageLength = desiredRows;
186 } else {
187 desiredRows = 0;
188 }
189 }
190
191 min.height = mRowHeight * desiredRows;
192
193 AddXULBorderAndPadding(min);
194 bool widthSet, heightSet;
195 nsIFrame::AddXULMinSize(this, min, widthSet, heightSet);
196
197 return min;
198 }
199
CalcMaxRowWidth()200 nscoord nsTreeBodyFrame::CalcMaxRowWidth() {
201 if (mStringWidth != -1) return mStringWidth;
202
203 if (!mView) return 0;
204
205 ComputedStyle* rowContext =
206 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
207 nsMargin rowMargin(0, 0, 0, 0);
208 GetBorderPadding(rowContext, rowMargin);
209
210 nscoord rowWidth;
211 nsTreeColumn* col;
212
213 RefPtr<gfxContext> rc = PresShell()->CreateReferenceRenderingContext();
214
215 for (int32_t row = 0; row < mRowCount; ++row) {
216 rowWidth = 0;
217
218 for (col = mColumns->GetFirstColumn(); col; col = col->GetNext()) {
219 nscoord desiredWidth, currentWidth;
220 nsresult rv = GetCellWidth(row, col, rc, desiredWidth, currentWidth);
221 if (NS_FAILED(rv)) {
222 MOZ_ASSERT_UNREACHABLE("invalid column");
223 continue;
224 }
225 rowWidth += desiredWidth;
226 }
227
228 if (rowWidth > mStringWidth) mStringWidth = rowWidth;
229 }
230
231 mStringWidth += rowMargin.left + rowMargin.right;
232 return mStringWidth;
233 }
234
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)235 void nsTreeBodyFrame::DestroyFrom(nsIFrame* aDestructRoot,
236 PostDestroyData& aPostDestroyData) {
237 if (mScrollbarActivity) {
238 mScrollbarActivity->Destroy();
239 mScrollbarActivity = nullptr;
240 }
241
242 mScrollEvent.Revoke();
243 // Make sure we cancel any posted callbacks.
244 if (mReflowCallbackPosted) {
245 PresShell()->CancelReflowCallback(this);
246 mReflowCallbackPosted = false;
247 }
248
249 if (mColumns) mColumns->SetTree(nullptr);
250
251 if (mTree) {
252 mTree->BodyDestroyed(mTopRowIndex);
253 }
254
255 if (mView) {
256 nsCOMPtr<nsITreeSelection> sel;
257 mView->GetSelection(getter_AddRefs(sel));
258 if (sel) sel->SetTree(nullptr);
259 mView->SetTree(nullptr);
260 mView = nullptr;
261 }
262
263 nsLeafBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
264 }
265
EnsureView()266 void nsTreeBodyFrame::EnsureView() {
267 if (!mView) {
268 if (PresShell()->IsReflowLocked()) {
269 if (!mReflowCallbackPosted) {
270 mReflowCallbackPosted = true;
271 PresShell()->PostReflowCallback(this);
272 }
273 return;
274 }
275
276 AutoWeakFrame weakFrame(this);
277
278 RefPtr<XULTreeElement> tree = GetBaseElement();
279 if (tree) {
280 nsCOMPtr<nsITreeView> treeView = tree->GetView();
281 if (treeView && weakFrame.IsAlive()) {
282 int32_t rowIndex = tree->GetCachedTopVisibleRow();
283
284 // Set our view.
285 SetView(treeView);
286 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
287
288 // Scroll to the given row.
289 // XXX is this optimal if we haven't laid out yet?
290 ScrollToRow(rowIndex);
291 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
292 }
293 }
294 }
295 }
296
ManageReflowCallback(const nsRect & aRect,nscoord aHorzWidth)297 void nsTreeBodyFrame::ManageReflowCallback(const nsRect& aRect,
298 nscoord aHorzWidth) {
299 if (!mReflowCallbackPosted &&
300 (!aRect.IsEqualEdges(mRect) || mHorzWidth != aHorzWidth)) {
301 PresShell()->PostReflowCallback(this);
302 mReflowCallbackPosted = true;
303 mOriginalHorzWidth = mHorzWidth;
304 } else if (mReflowCallbackPosted && mHorzWidth != aHorzWidth &&
305 mOriginalHorzWidth == aHorzWidth) {
306 PresShell()->CancelReflowCallback(this);
307 mReflowCallbackPosted = false;
308 mOriginalHorzWidth = -1;
309 }
310 }
311
SetXULBounds(nsBoxLayoutState & aBoxLayoutState,const nsRect & aRect,bool aRemoveOverflowArea)312 void nsTreeBodyFrame::SetXULBounds(nsBoxLayoutState& aBoxLayoutState,
313 const nsRect& aRect,
314 bool aRemoveOverflowArea) {
315 nscoord horzWidth = CalcHorzWidth(GetScrollParts());
316 ManageReflowCallback(aRect, horzWidth);
317 mHorzWidth = horzWidth;
318
319 nsLeafBoxFrame::SetXULBounds(aBoxLayoutState, aRect, aRemoveOverflowArea);
320 }
321
ReflowFinished()322 bool nsTreeBodyFrame::ReflowFinished() {
323 if (!mView) {
324 AutoWeakFrame weakFrame(this);
325 EnsureView();
326 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
327 }
328 if (mView) {
329 CalcInnerBox();
330 ScrollParts parts = GetScrollParts();
331 mHorzWidth = CalcHorzWidth(parts);
332 if (!mHasFixedRowCount) {
333 mPageLength =
334 (mRowHeight > 0) ? (mInnerBox.height / mRowHeight) : mRowCount;
335 }
336
337 int32_t lastPageTopRow = std::max(0, mRowCount - mPageLength);
338 if (mTopRowIndex > lastPageTopRow)
339 ScrollToRowInternal(parts, lastPageTopRow);
340
341 XULTreeElement* treeContent = GetBaseElement();
342 if (treeContent && treeContent->AttrValueIs(
343 kNameSpaceID_None, nsGkAtoms::keepcurrentinview,
344 nsGkAtoms::_true, eCaseMatters)) {
345 // make sure that the current selected item is still
346 // visible after the tree changes size.
347 nsCOMPtr<nsITreeSelection> sel;
348 mView->GetSelection(getter_AddRefs(sel));
349 if (sel) {
350 int32_t currentIndex;
351 sel->GetCurrentIndex(¤tIndex);
352 if (currentIndex != -1) EnsureRowIsVisibleInternal(parts, currentIndex);
353 }
354 }
355
356 if (!FullScrollbarsUpdate(false)) {
357 return false;
358 }
359 }
360
361 mReflowCallbackPosted = false;
362 return false;
363 }
364
ReflowCallbackCanceled()365 void nsTreeBodyFrame::ReflowCallbackCanceled() {
366 mReflowCallbackPosted = false;
367 }
368
GetView(nsITreeView ** aView)369 nsresult nsTreeBodyFrame::GetView(nsITreeView** aView) {
370 *aView = nullptr;
371 AutoWeakFrame weakFrame(this);
372 EnsureView();
373 NS_ENSURE_STATE(weakFrame.IsAlive());
374 NS_IF_ADDREF(*aView = mView);
375 return NS_OK;
376 }
377
SetView(nsITreeView * aView)378 nsresult nsTreeBodyFrame::SetView(nsITreeView* aView) {
379 // First clear out the old view.
380 if (mView) {
381 nsCOMPtr<nsITreeSelection> sel;
382 mView->GetSelection(getter_AddRefs(sel));
383 if (sel) sel->SetTree(nullptr);
384 mView->SetTree(nullptr);
385
386 // Only reset the top row index and delete the columns if we had an old
387 // non-null view.
388 mTopRowIndex = 0;
389 }
390
391 // Tree, meet the view.
392 mView = aView;
393
394 // Changing the view causes us to refetch our data. This will
395 // necessarily entail a full invalidation of the tree.
396 Invalidate();
397
398 RefPtr<XULTreeElement> treeContent = GetBaseElement();
399 if (treeContent) {
400 #ifdef ACCESSIBILITY
401 if (nsAccessibilityService* accService =
402 PresShell::GetAccessibilityService()) {
403 accService->TreeViewChanged(PresContext()->GetPresShell(), treeContent,
404 mView);
405 }
406 #endif // #ifdef ACCESSIBILITY
407 FireDOMEvent(u"TreeViewChanged"_ns, treeContent);
408 }
409
410 if (mView) {
411 // Give the view a new empty selection object to play with, but only if it
412 // doesn't have one already.
413 nsCOMPtr<nsITreeSelection> sel;
414 mView->GetSelection(getter_AddRefs(sel));
415 if (sel) {
416 sel->SetTree(treeContent);
417 } else {
418 NS_NewTreeSelection(treeContent, getter_AddRefs(sel));
419 mView->SetSelection(sel);
420 }
421
422 // View, meet the tree.
423 AutoWeakFrame weakFrame(this);
424 mView->SetTree(treeContent);
425 NS_ENSURE_STATE(weakFrame.IsAlive());
426 mView->GetRowCount(&mRowCount);
427
428 if (!PresShell()->IsReflowLocked()) {
429 // The scrollbar will need to be updated.
430 FullScrollbarsUpdate(false);
431 } else if (!mReflowCallbackPosted) {
432 mReflowCallbackPosted = true;
433 PresShell()->PostReflowCallback(this);
434 }
435 }
436
437 return NS_OK;
438 }
439
SetFocused(bool aFocused)440 nsresult nsTreeBodyFrame::SetFocused(bool aFocused) {
441 if (mFocused != aFocused) {
442 mFocused = aFocused;
443 if (mView) {
444 nsCOMPtr<nsITreeSelection> sel;
445 mView->GetSelection(getter_AddRefs(sel));
446 if (sel) sel->InvalidateSelection();
447 }
448 }
449 return NS_OK;
450 }
451
GetTreeBody(Element ** aElement)452 nsresult nsTreeBodyFrame::GetTreeBody(Element** aElement) {
453 // NS_ASSERTION(mContent, "no content, see bug #104878");
454 if (!mContent) return NS_ERROR_NULL_POINTER;
455
456 RefPtr<Element> element = mContent->AsElement();
457 element.forget(aElement);
458 return NS_OK;
459 }
460
RowHeight() const461 int32_t nsTreeBodyFrame::RowHeight() const {
462 return nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
463 }
464
RowWidth()465 int32_t nsTreeBodyFrame::RowWidth() {
466 return nsPresContext::AppUnitsToIntCSSPixels(CalcHorzWidth(GetScrollParts()));
467 }
468
GetHorizontalPosition() const469 int32_t nsTreeBodyFrame::GetHorizontalPosition() const {
470 return nsPresContext::AppUnitsToIntCSSPixels(mHorzPosition);
471 }
472
GetSelectionRegion()473 Maybe<CSSIntRegion> nsTreeBodyFrame::GetSelectionRegion() {
474 if (!mView) {
475 return Nothing();
476 }
477
478 nsCOMPtr<nsITreeSelection> selection;
479 mView->GetSelection(getter_AddRefs(selection));
480 if (!selection) {
481 return Nothing();
482 }
483
484 RefPtr<nsPresContext> presContext = PresContext();
485 nsIntRect rect = mRect.ToOutsidePixels(AppUnitsPerCSSPixel());
486
487 nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame();
488 nsPoint origin = GetOffsetTo(rootFrame);
489
490 CSSIntRegion region;
491
492 // iterate through the visible rows and add the selected ones to the
493 // drag region
494 int32_t x = nsPresContext::AppUnitsToIntCSSPixels(origin.x);
495 int32_t y = nsPresContext::AppUnitsToIntCSSPixels(origin.y);
496 int32_t top = y;
497 int32_t end = LastVisibleRow();
498 int32_t rowHeight = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
499 for (int32_t i = mTopRowIndex; i <= end; i++) {
500 bool isSelected;
501 selection->IsSelected(i, &isSelected);
502 if (isSelected) {
503 region.OrWith(CSSIntRect(x, y, rect.width, rowHeight));
504 }
505 y += rowHeight;
506 }
507
508 // clip to the tree boundary in case one row extends past it
509 region.AndWith(CSSIntRect(x, top, rect.width, rect.height));
510
511 return Some(region);
512 }
513
Invalidate()514 nsresult nsTreeBodyFrame::Invalidate() {
515 if (mUpdateBatchNest) return NS_OK;
516
517 InvalidateFrame();
518
519 return NS_OK;
520 }
521
InvalidateColumn(nsTreeColumn * aCol)522 nsresult nsTreeBodyFrame::InvalidateColumn(nsTreeColumn* aCol) {
523 if (mUpdateBatchNest) return NS_OK;
524
525 if (!aCol) return NS_ERROR_INVALID_ARG;
526
527 #ifdef ACCESSIBILITY
528 if (PresShell::IsAccessibilityActive()) {
529 FireInvalidateEvent(-1, -1, aCol, aCol);
530 }
531 #endif // #ifdef ACCESSIBILITY
532
533 nsRect columnRect;
534 nsresult rv = aCol->GetRect(this, mInnerBox.y, mInnerBox.height, &columnRect);
535 NS_ENSURE_SUCCESS(rv, rv);
536
537 // When false then column is out of view
538 if (OffsetForHorzScroll(columnRect, true))
539 InvalidateFrameWithRect(columnRect);
540
541 return NS_OK;
542 }
543
InvalidateRow(int32_t aIndex)544 nsresult nsTreeBodyFrame::InvalidateRow(int32_t aIndex) {
545 if (mUpdateBatchNest) return NS_OK;
546
547 #ifdef ACCESSIBILITY
548 if (PresShell::IsAccessibilityActive()) {
549 FireInvalidateEvent(aIndex, aIndex, nullptr, nullptr);
550 }
551 #endif // #ifdef ACCESSIBILITY
552
553 aIndex -= mTopRowIndex;
554 if (aIndex < 0 || aIndex > mPageLength) return NS_OK;
555
556 nsRect rowRect(mInnerBox.x, mInnerBox.y + mRowHeight * aIndex,
557 mInnerBox.width, mRowHeight);
558 InvalidateFrameWithRect(rowRect);
559
560 return NS_OK;
561 }
562
InvalidateCell(int32_t aIndex,nsTreeColumn * aCol)563 nsresult nsTreeBodyFrame::InvalidateCell(int32_t aIndex, nsTreeColumn* aCol) {
564 if (mUpdateBatchNest) return NS_OK;
565
566 #ifdef ACCESSIBILITY
567 if (PresShell::IsAccessibilityActive()) {
568 FireInvalidateEvent(aIndex, aIndex, aCol, aCol);
569 }
570 #endif // #ifdef ACCESSIBILITY
571
572 aIndex -= mTopRowIndex;
573 if (aIndex < 0 || aIndex > mPageLength) return NS_OK;
574
575 if (!aCol) return NS_ERROR_INVALID_ARG;
576
577 nsRect cellRect;
578 nsresult rv = aCol->GetRect(this, mInnerBox.y + mRowHeight * aIndex,
579 mRowHeight, &cellRect);
580 NS_ENSURE_SUCCESS(rv, rv);
581
582 if (OffsetForHorzScroll(cellRect, true)) InvalidateFrameWithRect(cellRect);
583
584 return NS_OK;
585 }
586
InvalidateRange(int32_t aStart,int32_t aEnd)587 nsresult nsTreeBodyFrame::InvalidateRange(int32_t aStart, int32_t aEnd) {
588 if (mUpdateBatchNest) return NS_OK;
589
590 if (aStart == aEnd) return InvalidateRow(aStart);
591
592 int32_t last = LastVisibleRow();
593 if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last) return NS_OK;
594
595 if (aStart < mTopRowIndex) aStart = mTopRowIndex;
596
597 if (aEnd > last) aEnd = last;
598
599 #ifdef ACCESSIBILITY
600 if (PresShell::IsAccessibilityActive()) {
601 int32_t end =
602 mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0;
603 FireInvalidateEvent(aStart, end, nullptr, nullptr);
604 }
605 #endif // #ifdef ACCESSIBILITY
606
607 nsRect rangeRect(mInnerBox.x,
608 mInnerBox.y + mRowHeight * (aStart - mTopRowIndex),
609 mInnerBox.width, mRowHeight * (aEnd - aStart + 1));
610 InvalidateFrameWithRect(rangeRect);
611
612 return NS_OK;
613 }
614
FindScrollParts(nsIFrame * aCurrFrame,nsTreeBodyFrame::ScrollParts * aResult)615 static void FindScrollParts(nsIFrame* aCurrFrame,
616 nsTreeBodyFrame::ScrollParts* aResult) {
617 if (!aResult->mColumnsScrollFrame) {
618 nsIScrollableFrame* f = do_QueryFrame(aCurrFrame);
619 if (f) {
620 aResult->mColumnsFrame = aCurrFrame;
621 aResult->mColumnsScrollFrame = f;
622 }
623 }
624
625 nsScrollbarFrame* sf = do_QueryFrame(aCurrFrame);
626 if (sf) {
627 if (!aCurrFrame->IsXULHorizontal()) {
628 if (!aResult->mVScrollbar) {
629 aResult->mVScrollbar = sf;
630 }
631 } else {
632 if (!aResult->mHScrollbar) {
633 aResult->mHScrollbar = sf;
634 }
635 }
636 // don't bother searching inside a scrollbar
637 return;
638 }
639
640 nsIFrame* child = aCurrFrame->PrincipalChildList().FirstChild();
641 while (child && !child->GetContent()->IsRootOfNativeAnonymousSubtree() &&
642 (!aResult->mVScrollbar || !aResult->mHScrollbar ||
643 !aResult->mColumnsScrollFrame)) {
644 FindScrollParts(child, aResult);
645 child = child->GetNextSibling();
646 }
647 }
648
GetScrollParts()649 nsTreeBodyFrame::ScrollParts nsTreeBodyFrame::GetScrollParts() {
650 ScrollParts result = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
651 XULTreeElement* tree = GetBaseElement();
652 nsIFrame* treeFrame = tree ? tree->GetPrimaryFrame() : nullptr;
653 if (treeFrame) {
654 // The way we do this, searching through the entire frame subtree, is pretty
655 // dumb! We should know where these frames are.
656 FindScrollParts(treeFrame, &result);
657 if (result.mHScrollbar) {
658 result.mHScrollbar->SetScrollbarMediatorContent(GetContent());
659 nsIFrame* f = do_QueryFrame(result.mHScrollbar);
660 result.mHScrollbarContent = f->GetContent()->AsElement();
661 }
662 if (result.mVScrollbar) {
663 result.mVScrollbar->SetScrollbarMediatorContent(GetContent());
664 nsIFrame* f = do_QueryFrame(result.mVScrollbar);
665 result.mVScrollbarContent = f->GetContent()->AsElement();
666 }
667 }
668 return result;
669 }
670
UpdateScrollbars(const ScrollParts & aParts)671 void nsTreeBodyFrame::UpdateScrollbars(const ScrollParts& aParts) {
672 nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
673
674 AutoWeakFrame weakFrame(this);
675
676 if (aParts.mVScrollbar) {
677 nsAutoString curPos;
678 curPos.AppendInt(mTopRowIndex * rowHeightAsPixels);
679 aParts.mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos,
680 curPos, true);
681 // 'this' might be deleted here
682 }
683
684 if (weakFrame.IsAlive() && aParts.mHScrollbar) {
685 nsAutoString curPos;
686 curPos.AppendInt(mHorzPosition);
687 aParts.mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos,
688 curPos, true);
689 // 'this' might be deleted here
690 }
691
692 if (weakFrame.IsAlive() && mScrollbarActivity) {
693 mScrollbarActivity->ActivityOccurred();
694 }
695 }
696
CheckOverflow(const ScrollParts & aParts)697 void nsTreeBodyFrame::CheckOverflow(const ScrollParts& aParts) {
698 bool verticalOverflowChanged = false;
699 bool horizontalOverflowChanged = false;
700
701 if (!mVerticalOverflow && mRowCount > mPageLength) {
702 mVerticalOverflow = true;
703 verticalOverflowChanged = true;
704 } else if (mVerticalOverflow && mRowCount <= mPageLength) {
705 mVerticalOverflow = false;
706 verticalOverflowChanged = true;
707 }
708
709 if (aParts.mColumnsFrame) {
710 nsRect bounds = aParts.mColumnsFrame->GetRect();
711 if (bounds.width != 0) {
712 /* Ignore overflows that are less than half a pixel. Yes these happen
713 all over the place when flex boxes are compressed real small.
714 Probably a result of a rounding errors somewhere in the layout code. */
715 bounds.width += nsPresContext::CSSPixelsToAppUnits(0.5f);
716 if (!mHorizontalOverflow && bounds.width < mHorzWidth) {
717 mHorizontalOverflow = true;
718 horizontalOverflowChanged = true;
719 } else if (mHorizontalOverflow && bounds.width >= mHorzWidth) {
720 mHorizontalOverflow = false;
721 horizontalOverflowChanged = true;
722 }
723 }
724 }
725
726 if (!horizontalOverflowChanged && !verticalOverflowChanged) {
727 return;
728 }
729
730 AutoWeakFrame weakFrame(this);
731
732 RefPtr<nsPresContext> presContext = PresContext();
733 RefPtr<mozilla::PresShell> presShell = presContext->GetPresShell();
734 nsCOMPtr<nsIContent> content = mContent;
735
736 if (verticalOverflowChanged) {
737 InternalScrollPortEvent event(
738 true, mVerticalOverflow ? eScrollPortOverflow : eScrollPortUnderflow,
739 nullptr);
740 event.mOrient = InternalScrollPortEvent::eVertical;
741 EventDispatcher::Dispatch(content, presContext, &event);
742 }
743
744 if (horizontalOverflowChanged) {
745 InternalScrollPortEvent event(
746 true, mHorizontalOverflow ? eScrollPortOverflow : eScrollPortUnderflow,
747 nullptr);
748 event.mOrient = InternalScrollPortEvent::eHorizontal;
749 EventDispatcher::Dispatch(content, presContext, &event);
750 }
751
752 // The synchronous event dispatch above can trigger reflow notifications.
753 // Flush those explicitly now, so that we can guard against potential infinite
754 // recursion. See bug 905909.
755 if (!weakFrame.IsAlive()) {
756 return;
757 }
758 NS_ASSERTION(!mCheckingOverflow,
759 "mCheckingOverflow should not already be set");
760 // Don't use AutoRestore since we want to not touch mCheckingOverflow if we
761 // fail the weakFrame.IsAlive() check below
762 mCheckingOverflow = true;
763 presShell->FlushPendingNotifications(FlushType::Layout);
764 if (!weakFrame.IsAlive()) {
765 return;
766 }
767 mCheckingOverflow = false;
768 }
769
InvalidateScrollbars(const ScrollParts & aParts,AutoWeakFrame & aWeakColumnsFrame)770 void nsTreeBodyFrame::InvalidateScrollbars(const ScrollParts& aParts,
771 AutoWeakFrame& aWeakColumnsFrame) {
772 if (mUpdateBatchNest || !mView) return;
773 AutoWeakFrame weakFrame(this);
774
775 if (aParts.mVScrollbar) {
776 // Do Vertical Scrollbar
777 nsAutoString maxposStr;
778
779 nscoord rowHeightAsPixels =
780 nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
781
782 int32_t size = rowHeightAsPixels *
783 (mRowCount > mPageLength ? mRowCount - mPageLength : 0);
784 maxposStr.AppendInt(size);
785 aParts.mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos,
786 maxposStr, true);
787 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
788
789 // Also set our page increment and decrement.
790 nscoord pageincrement = mPageLength * rowHeightAsPixels;
791 nsAutoString pageStr;
792 pageStr.AppendInt(pageincrement);
793 aParts.mVScrollbarContent->SetAttr(kNameSpaceID_None,
794 nsGkAtoms::pageincrement, pageStr, true);
795 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
796 }
797
798 if (aParts.mHScrollbar && aParts.mColumnsFrame &&
799 aWeakColumnsFrame.IsAlive()) {
800 // And now Horizontal scrollbar
801 nsRect bounds = aParts.mColumnsFrame->GetRect();
802 nsAutoString maxposStr;
803
804 maxposStr.AppendInt(mHorzWidth > bounds.width ? mHorzWidth - bounds.width
805 : 0);
806 aParts.mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos,
807 maxposStr, true);
808 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
809
810 nsAutoString pageStr;
811 pageStr.AppendInt(bounds.width);
812 aParts.mHScrollbarContent->SetAttr(kNameSpaceID_None,
813 nsGkAtoms::pageincrement, pageStr, true);
814 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
815
816 pageStr.Truncate();
817 pageStr.AppendInt(nsPresContext::CSSPixelsToAppUnits(16));
818 aParts.mHScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::increment,
819 pageStr, true);
820 }
821
822 if (weakFrame.IsAlive() && mScrollbarActivity) {
823 mScrollbarActivity->ActivityOccurred();
824 }
825 }
826
827 // Takes client x/y in pixels, converts them to appunits, and converts into
828 // values relative to this nsTreeBodyFrame frame.
AdjustClientCoordsToBoxCoordSpace(int32_t aX,int32_t aY)829 nsPoint nsTreeBodyFrame::AdjustClientCoordsToBoxCoordSpace(int32_t aX,
830 int32_t aY) {
831 nsPoint point(nsPresContext::CSSPixelsToAppUnits(aX),
832 nsPresContext::CSSPixelsToAppUnits(aY));
833
834 nsPresContext* presContext = PresContext();
835 point -= GetOffsetTo(presContext->GetPresShell()->GetRootFrame());
836
837 // Adjust by the inner box coords, so that we're in the inner box's
838 // coordinate space.
839 point -= mInnerBox.TopLeft();
840 return point;
841 } // AdjustClientCoordsToBoxCoordSpace
842
GetRowAt(int32_t aX,int32_t aY)843 int32_t nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY) {
844 if (!mView) {
845 return 0;
846 }
847
848 nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);
849
850 // Check if the coordinates are above our visible space.
851 if (point.y < 0) {
852 return -1;
853 }
854
855 return GetRowAtInternal(point.x, point.y);
856 }
857
GetCellAt(int32_t aX,int32_t aY,int32_t * aRow,nsTreeColumn ** aCol,nsACString & aChildElt)858 nsresult nsTreeBodyFrame::GetCellAt(int32_t aX, int32_t aY, int32_t* aRow,
859 nsTreeColumn** aCol,
860 nsACString& aChildElt) {
861 if (!mView) return NS_OK;
862
863 nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);
864
865 // Check if the coordinates are above our visible space.
866 if (point.y < 0) {
867 *aRow = -1;
868 return NS_OK;
869 }
870
871 nsTreeColumn* col;
872 nsCSSAnonBoxPseudoStaticAtom* child;
873 GetCellAt(point.x, point.y, aRow, &col, &child);
874
875 if (col) {
876 NS_ADDREF(*aCol = col);
877 if (child == nsCSSAnonBoxes::mozTreeCell())
878 aChildElt.AssignLiteral("cell");
879 else if (child == nsCSSAnonBoxes::mozTreeTwisty())
880 aChildElt.AssignLiteral("twisty");
881 else if (child == nsCSSAnonBoxes::mozTreeImage())
882 aChildElt.AssignLiteral("image");
883 else if (child == nsCSSAnonBoxes::mozTreeCellText())
884 aChildElt.AssignLiteral("text");
885 }
886
887 return NS_OK;
888 }
889
890 //
891 // GetCoordsForCellItem
892 //
893 // Find the x/y location and width/height (all in PIXELS) of the given object
894 // in the given column.
895 //
896 // XXX IMPORTANT XXX:
897 // Hyatt says in the bug for this, that the following needs to be done:
898 // (1) You need to deal with overflow when computing cell rects. See other
899 // column iteration examples... if you don't deal with this, you'll mistakenly
900 // extend the cell into the scrollbar's rect.
901 //
902 // (2) You are adjusting the cell rect by the *row" border padding. That's
903 // wrong. You need to first adjust a row rect by its border/padding, and then
904 // the cell rect fits inside the adjusted row rect. It also can have
905 // border/padding as well as margins. The vertical direction isn't that
906 // important, but you need to get the horizontal direction right.
907 //
908 // (3) GetImageSize() does not include margins (but it does include
909 // border/padding). You need to make sure to add in the image's margins as well.
910 //
GetCoordsForCellItem(int32_t aRow,nsTreeColumn * aCol,const nsACString & aElement,int32_t * aX,int32_t * aY,int32_t * aWidth,int32_t * aHeight)911 nsresult nsTreeBodyFrame::GetCoordsForCellItem(int32_t aRow, nsTreeColumn* aCol,
912 const nsACString& aElement,
913 int32_t* aX, int32_t* aY,
914 int32_t* aWidth,
915 int32_t* aHeight) {
916 *aX = 0;
917 *aY = 0;
918 *aWidth = 0;
919 *aHeight = 0;
920
921 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
922 nscoord currX = mInnerBox.x - mHorzPosition;
923
924 // The Rect for the requested item.
925 nsRect theRect;
926
927 nsPresContext* presContext = PresContext();
928
929 for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
930 currCol = currCol->GetNext()) {
931 // The Rect for the current cell.
932 nscoord colWidth;
933 #ifdef DEBUG
934 nsresult rv =
935 #endif
936 currCol->GetWidthInTwips(this, &colWidth);
937 NS_ASSERTION(NS_SUCCEEDED(rv), "invalid column");
938
939 nsRect cellRect(currX, mInnerBox.y + mRowHeight * (aRow - mTopRowIndex),
940 colWidth, mRowHeight);
941
942 // Check the ID of the current column to see if it matches. If it doesn't
943 // increment the current X value and continue to the next column.
944 if (currCol != aCol) {
945 currX += cellRect.width;
946 continue;
947 }
948 // Now obtain the properties for our cell.
949 PrefillPropertyArray(aRow, currCol);
950
951 nsAutoString properties;
952 mView->GetCellProperties(aRow, currCol, properties);
953 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
954
955 ComputedStyle* rowContext =
956 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
957
958 // We don't want to consider any of the decorations that may be present
959 // on the current row, so we have to deflate the rect by the border and
960 // padding and offset its left and top coordinates appropriately.
961 AdjustForBorderPadding(rowContext, cellRect);
962
963 ComputedStyle* cellContext =
964 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
965
966 constexpr auto cell = "cell"_ns;
967 if (currCol->IsCycler() || cell.Equals(aElement)) {
968 // If the current Column is a Cycler, then the Rect is just the cell - the
969 // margins. Similarly, if we're just being asked for the cell rect,
970 // provide it.
971
972 theRect = cellRect;
973 nsMargin cellMargin;
974 cellContext->StyleMargin()->GetMargin(cellMargin);
975 theRect.Deflate(cellMargin);
976 break;
977 }
978
979 // Since we're not looking for the cell, and since the cell isn't a cycler,
980 // we're looking for some subcomponent, and now we need to subtract the
981 // borders and padding of the cell from cellRect so this does not
982 // interfere with our computations.
983 AdjustForBorderPadding(cellContext, cellRect);
984
985 RefPtr<gfxContext> rc =
986 presContext->PresShell()->CreateReferenceRenderingContext();
987
988 // Now we'll start making our way across the cell, starting at the edge of
989 // the cell and proceeding until we hit the right edge. |cellX| is the
990 // working X value that we will increment as we crawl from left to right.
991 nscoord cellX = cellRect.x;
992 nscoord remainWidth = cellRect.width;
993
994 if (currCol->IsPrimary()) {
995 // If the current Column is a Primary, then we need to take into account
996 // the indentation and possibly a twisty.
997
998 // The amount of indentation is the indentation width (|mIndentation|) by
999 // the level.
1000 int32_t level;
1001 mView->GetLevel(aRow, &level);
1002 if (!isRTL) cellX += mIndentation * level;
1003 remainWidth -= mIndentation * level;
1004
1005 // Find the twisty rect by computing its size.
1006 nsRect imageRect;
1007 nsRect twistyRect(cellRect);
1008 ComputedStyle* twistyContext =
1009 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
1010 GetTwistyRect(aRow, currCol, imageRect, twistyRect, presContext,
1011 twistyContext);
1012
1013 if ("twisty"_ns.Equals(aElement)) {
1014 // If we're looking for the twisty Rect, just return the size
1015 theRect = twistyRect;
1016 break;
1017 }
1018
1019 // Now we need to add in the margins of the twisty element, so that we
1020 // can find the offset of the next element in the cell.
1021 nsMargin twistyMargin;
1022 twistyContext->StyleMargin()->GetMargin(twistyMargin);
1023 twistyRect.Inflate(twistyMargin);
1024
1025 // Adjust our working X value with the twisty width (image size, margins,
1026 // borders, padding.
1027 if (!isRTL) cellX += twistyRect.width;
1028 }
1029
1030 // Cell Image
1031 ComputedStyle* imageContext =
1032 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeImage());
1033
1034 nsRect imageSize = GetImageSize(aRow, currCol, false, imageContext);
1035 if ("image"_ns.Equals(aElement)) {
1036 theRect = imageSize;
1037 theRect.x = cellX;
1038 theRect.y = cellRect.y;
1039 break;
1040 }
1041
1042 // Add in the margins of the cell image.
1043 nsMargin imageMargin;
1044 imageContext->StyleMargin()->GetMargin(imageMargin);
1045 imageSize.Inflate(imageMargin);
1046
1047 // Increment cellX by the image width
1048 if (!isRTL) cellX += imageSize.width;
1049
1050 // Cell Text
1051 nsAutoString cellText;
1052 mView->GetCellText(aRow, currCol, cellText);
1053 // We're going to measure this text so we need to ensure bidi is enabled if
1054 // necessary
1055 CheckTextForBidi(cellText);
1056
1057 // Create a scratch rect to represent the text rectangle, with the current
1058 // X and Y coords, and a guess at the width and height. The width is the
1059 // remaining width we have left to traverse in the cell, which will be the
1060 // widest possible value for the text rect, and the row height.
1061 nsRect textRect(cellX, cellRect.y, remainWidth, cellRect.height);
1062
1063 // Measure the width of the text. If the width of the text is greater than
1064 // the remaining width available, then we just assume that the text has
1065 // been cropped and use the remaining rect as the text Rect. Otherwise,
1066 // we add in borders and padding to the text dimension and give that back.
1067 ComputedStyle* textContext =
1068 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
1069
1070 RefPtr<nsFontMetrics> fm =
1071 nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, presContext);
1072 nscoord height = fm->MaxHeight();
1073
1074 nsMargin textMargin;
1075 textContext->StyleMargin()->GetMargin(textMargin);
1076 textRect.Deflate(textMargin);
1077
1078 // Center the text. XXX Obey vertical-align style prop?
1079 if (height < textRect.height) {
1080 textRect.y += (textRect.height - height) / 2;
1081 textRect.height = height;
1082 }
1083
1084 nsMargin bp(0, 0, 0, 0);
1085 GetBorderPadding(textContext, bp);
1086 textRect.height += bp.top + bp.bottom;
1087
1088 AdjustForCellText(cellText, aRow, currCol, *rc, *fm, textRect);
1089
1090 theRect = textRect;
1091 }
1092
1093 if (isRTL) theRect.x = mInnerBox.width - theRect.x - theRect.width;
1094
1095 *aX = nsPresContext::AppUnitsToIntCSSPixels(theRect.x);
1096 *aY = nsPresContext::AppUnitsToIntCSSPixels(theRect.y);
1097 *aWidth = nsPresContext::AppUnitsToIntCSSPixels(theRect.width);
1098 *aHeight = nsPresContext::AppUnitsToIntCSSPixels(theRect.height);
1099
1100 return NS_OK;
1101 }
1102
GetRowAtInternal(nscoord aX,nscoord aY)1103 int32_t nsTreeBodyFrame::GetRowAtInternal(nscoord aX, nscoord aY) {
1104 if (mRowHeight <= 0) return -1;
1105
1106 // Now just mod by our total inner box height and add to our top row index.
1107 int32_t row = (aY / mRowHeight) + mTopRowIndex;
1108
1109 // Check if the coordinates are below our visible space (or within our visible
1110 // space but below any row).
1111 if (row > mTopRowIndex + mPageLength || row >= mRowCount) return -1;
1112
1113 return row;
1114 }
1115
CheckTextForBidi(nsAutoString & aText)1116 void nsTreeBodyFrame::CheckTextForBidi(nsAutoString& aText) {
1117 // We could check to see whether the prescontext already has bidi enabled,
1118 // but usually it won't, so it's probably faster to avoid the call to
1119 // GetPresContext() when it's not needed.
1120 if (HasRTLChars(aText)) {
1121 PresContext()->SetBidiEnabled();
1122 }
1123 }
1124
AdjustForCellText(nsAutoString & aText,int32_t aRowIndex,nsTreeColumn * aColumn,gfxContext & aRenderingContext,nsFontMetrics & aFontMetrics,nsRect & aTextRect)1125 void nsTreeBodyFrame::AdjustForCellText(nsAutoString& aText, int32_t aRowIndex,
1126 nsTreeColumn* aColumn,
1127 gfxContext& aRenderingContext,
1128 nsFontMetrics& aFontMetrics,
1129 nsRect& aTextRect) {
1130 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
1131
1132 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
1133
1134 nscoord maxWidth = aTextRect.width;
1135 bool widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(
1136 aText, aFontMetrics, drawTarget, maxWidth);
1137
1138 if (aColumn->Overflow()) {
1139 DebugOnly<nsresult> rv;
1140 nsTreeColumn* nextColumn = aColumn->GetNext();
1141 while (nextColumn && widthIsGreater) {
1142 while (nextColumn) {
1143 nscoord width;
1144 rv = nextColumn->GetWidthInTwips(this, &width);
1145 NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
1146
1147 if (width != 0) break;
1148
1149 nextColumn = nextColumn->GetNext();
1150 }
1151
1152 if (nextColumn) {
1153 nsAutoString nextText;
1154 mView->GetCellText(aRowIndex, nextColumn, nextText);
1155 // We don't measure or draw this text so no need to check it for
1156 // bidi-ness
1157
1158 if (nextText.Length() == 0) {
1159 nscoord width;
1160 rv = nextColumn->GetWidthInTwips(this, &width);
1161 NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
1162
1163 maxWidth += width;
1164 widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(
1165 aText, aFontMetrics, drawTarget, maxWidth);
1166
1167 nextColumn = nextColumn->GetNext();
1168 } else {
1169 nextColumn = nullptr;
1170 }
1171 }
1172 }
1173 }
1174
1175 nscoord width;
1176 if (widthIsGreater) {
1177 // See if the width is even smaller than the ellipsis
1178 // If so, clear the text completely.
1179 const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
1180 aFontMetrics.SetTextRunRTL(false);
1181 nscoord ellipsisWidth = nsLayoutUtils::AppUnitWidthOfString(
1182 kEllipsis, aFontMetrics, drawTarget);
1183
1184 width = maxWidth;
1185 if (ellipsisWidth > width)
1186 aText.SetLength(0);
1187 else if (ellipsisWidth == width)
1188 aText.Assign(kEllipsis);
1189 else {
1190 // We will be drawing an ellipsis, thank you very much.
1191 // Subtract out the required width of the ellipsis.
1192 // This is the total remaining width we have to play with.
1193 width -= ellipsisWidth;
1194
1195 // Now we crop.
1196 switch (aColumn->GetCropStyle()) {
1197 default:
1198 case 0: {
1199 // Crop right.
1200 nscoord cwidth;
1201 nscoord twidth = 0;
1202 uint32_t length = aText.Length();
1203 uint32_t i;
1204 for (i = 0; i < length; ++i) {
1205 char16_t ch = aText[i];
1206 // XXX this is horrible and doesn't handle clusters
1207 cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
1208 drawTarget);
1209 if (twidth + cwidth > width) break;
1210 twidth += cwidth;
1211 }
1212 aText.Truncate(i);
1213 aText.Append(kEllipsis);
1214 } break;
1215
1216 case 2: {
1217 // Crop left.
1218 nscoord cwidth;
1219 nscoord twidth = 0;
1220 int32_t length = aText.Length();
1221 int32_t i;
1222 for (i = length - 1; i >= 0; --i) {
1223 char16_t ch = aText[i];
1224 cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
1225 drawTarget);
1226 if (twidth + cwidth > width) break;
1227 twidth += cwidth;
1228 }
1229
1230 nsAutoString copy;
1231 aText.Right(copy, length - 1 - i);
1232 aText.Assign(kEllipsis);
1233 aText += copy;
1234 } break;
1235
1236 case 1: {
1237 // Crop center.
1238 nsAutoString leftStr, rightStr;
1239 nscoord cwidth, twidth = 0;
1240 int32_t length = aText.Length();
1241 int32_t rightPos = length - 1;
1242 for (int32_t leftPos = 0; leftPos < rightPos; ++leftPos) {
1243 char16_t ch = aText[leftPos];
1244 cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
1245 drawTarget);
1246 twidth += cwidth;
1247 if (twidth > width) break;
1248 leftStr.Append(ch);
1249
1250 ch = aText[rightPos];
1251 cwidth = nsLayoutUtils::AppUnitWidthOfString(ch, aFontMetrics,
1252 drawTarget);
1253 twidth += cwidth;
1254 if (twidth > width) break;
1255 rightStr.Insert(ch, 0);
1256 --rightPos;
1257 }
1258 aText = leftStr;
1259 aText.Append(kEllipsis);
1260 aText += rightStr;
1261 } break;
1262 }
1263 }
1264 }
1265
1266 width = nsLayoutUtils::AppUnitWidthOfStringBidi(aText, this, aFontMetrics,
1267 aRenderingContext);
1268
1269 switch (aColumn->GetTextAlignment()) {
1270 case mozilla::StyleTextAlign::Right:
1271 aTextRect.x += aTextRect.width - width;
1272 break;
1273 case mozilla::StyleTextAlign::Center:
1274 aTextRect.x += (aTextRect.width - width) / 2;
1275 break;
1276 default:
1277 break;
1278 }
1279
1280 aTextRect.width = width;
1281 }
1282
GetItemWithinCellAt(nscoord aX,const nsRect & aCellRect,int32_t aRowIndex,nsTreeColumn * aColumn)1283 nsCSSAnonBoxPseudoStaticAtom* nsTreeBodyFrame::GetItemWithinCellAt(
1284 nscoord aX, const nsRect& aCellRect, int32_t aRowIndex,
1285 nsTreeColumn* aColumn) {
1286 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
1287
1288 // Obtain the properties for our cell.
1289 PrefillPropertyArray(aRowIndex, aColumn);
1290 nsAutoString properties;
1291 mView->GetCellProperties(aRowIndex, aColumn, properties);
1292 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
1293
1294 // Resolve style for the cell.
1295 ComputedStyle* cellContext =
1296 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
1297
1298 // Obtain the margins for the cell and then deflate our rect by that
1299 // amount. The cell is assumed to be contained within the deflated rect.
1300 nsRect cellRect(aCellRect);
1301 nsMargin cellMargin;
1302 cellContext->StyleMargin()->GetMargin(cellMargin);
1303 cellRect.Deflate(cellMargin);
1304
1305 // Adjust the rect for its border and padding.
1306 AdjustForBorderPadding(cellContext, cellRect);
1307
1308 if (aX < cellRect.x || aX >= cellRect.x + cellRect.width) {
1309 // The user clicked within the cell's margins/borders/padding. This
1310 // constitutes a click on the cell.
1311 return nsCSSAnonBoxes::mozTreeCell();
1312 }
1313
1314 nscoord currX = cellRect.x;
1315 nscoord remainingWidth = cellRect.width;
1316
1317 // Handle right alignment hit testing.
1318 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
1319
1320 nsPresContext* presContext = PresContext();
1321 RefPtr<gfxContext> rc =
1322 presContext->PresShell()->CreateReferenceRenderingContext();
1323
1324 if (aColumn->IsPrimary()) {
1325 // If we're the primary column, we have indentation and a twisty.
1326 int32_t level;
1327 mView->GetLevel(aRowIndex, &level);
1328
1329 if (!isRTL) currX += mIndentation * level;
1330 remainingWidth -= mIndentation * level;
1331
1332 if ((isRTL && aX > currX + remainingWidth) || (!isRTL && aX < currX)) {
1333 // The user clicked within the indentation.
1334 return nsCSSAnonBoxes::mozTreeCell();
1335 }
1336
1337 // Always leave space for the twisty.
1338 nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
1339 bool hasTwisty = false;
1340 bool isContainer = false;
1341 mView->IsContainer(aRowIndex, &isContainer);
1342 if (isContainer) {
1343 bool isContainerEmpty = false;
1344 mView->IsContainerEmpty(aRowIndex, &isContainerEmpty);
1345 if (!isContainerEmpty) hasTwisty = true;
1346 }
1347
1348 // Resolve style for the twisty.
1349 ComputedStyle* twistyContext =
1350 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
1351
1352 nsRect imageSize;
1353 GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, presContext,
1354 twistyContext);
1355
1356 // We will treat a click as hitting the twisty if it happens on the margins,
1357 // borders, padding, or content of the twisty object. By allowing a "slop"
1358 // into the margin, we make it a little bit easier for a user to hit the
1359 // twisty. (We don't want to be too picky here.)
1360 nsMargin twistyMargin;
1361 twistyContext->StyleMargin()->GetMargin(twistyMargin);
1362 twistyRect.Inflate(twistyMargin);
1363 if (isRTL) twistyRect.x = currX + remainingWidth - twistyRect.width;
1364
1365 // Now we test to see if aX is actually within the twistyRect. If it is,
1366 // and if the item should have a twisty, then we return "twisty". If it is
1367 // within the rect but we shouldn't have a twisty, then we return "cell".
1368 if (aX >= twistyRect.x && aX < twistyRect.x + twistyRect.width) {
1369 if (hasTwisty)
1370 return nsCSSAnonBoxes::mozTreeTwisty();
1371 else
1372 return nsCSSAnonBoxes::mozTreeCell();
1373 }
1374
1375 if (!isRTL) currX += twistyRect.width;
1376 remainingWidth -= twistyRect.width;
1377 }
1378
1379 // Now test to see if the user hit the icon for the cell.
1380 nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);
1381
1382 // Resolve style for the image.
1383 ComputedStyle* imageContext =
1384 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeImage());
1385
1386 nsRect iconSize = GetImageSize(aRowIndex, aColumn, false, imageContext);
1387 nsMargin imageMargin;
1388 imageContext->StyleMargin()->GetMargin(imageMargin);
1389 iconSize.Inflate(imageMargin);
1390 iconRect.width = iconSize.width;
1391 if (isRTL) iconRect.x = currX + remainingWidth - iconRect.width;
1392
1393 if (aX >= iconRect.x && aX < iconRect.x + iconRect.width) {
1394 // The user clicked on the image.
1395 return nsCSSAnonBoxes::mozTreeImage();
1396 }
1397
1398 if (!isRTL) currX += iconRect.width;
1399 remainingWidth -= iconRect.width;
1400
1401 nsAutoString cellText;
1402 mView->GetCellText(aRowIndex, aColumn, cellText);
1403 // We're going to measure this text so we need to ensure bidi is enabled if
1404 // necessary
1405 CheckTextForBidi(cellText);
1406
1407 nsRect textRect(currX, cellRect.y, remainingWidth, cellRect.height);
1408
1409 ComputedStyle* textContext =
1410 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
1411
1412 nsMargin textMargin;
1413 textContext->StyleMargin()->GetMargin(textMargin);
1414 textRect.Deflate(textMargin);
1415
1416 AdjustForBorderPadding(textContext, textRect);
1417
1418 RefPtr<nsFontMetrics> fm =
1419 nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, presContext);
1420 AdjustForCellText(cellText, aRowIndex, aColumn, *rc, *fm, textRect);
1421
1422 if (aX >= textRect.x && aX < textRect.x + textRect.width)
1423 return nsCSSAnonBoxes::mozTreeCellText();
1424 else
1425 return nsCSSAnonBoxes::mozTreeCell();
1426 }
1427
GetCellAt(nscoord aX,nscoord aY,int32_t * aRow,nsTreeColumn ** aCol,nsCSSAnonBoxPseudoStaticAtom ** aChildElt)1428 void nsTreeBodyFrame::GetCellAt(nscoord aX, nscoord aY, int32_t* aRow,
1429 nsTreeColumn** aCol,
1430 nsCSSAnonBoxPseudoStaticAtom** aChildElt) {
1431 *aCol = nullptr;
1432 *aChildElt = nullptr;
1433
1434 *aRow = GetRowAtInternal(aX, aY);
1435 if (*aRow < 0) return;
1436
1437 // Determine the column hit.
1438 for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
1439 currCol = currCol->GetNext()) {
1440 nsRect cellRect;
1441 nsresult rv = currCol->GetRect(
1442 this, mInnerBox.y + mRowHeight * (*aRow - mTopRowIndex), mRowHeight,
1443 &cellRect);
1444 if (NS_FAILED(rv)) {
1445 MOZ_ASSERT_UNREACHABLE("column has no frame");
1446 continue;
1447 }
1448
1449 if (!OffsetForHorzScroll(cellRect, false)) continue;
1450
1451 if (aX >= cellRect.x && aX < cellRect.x + cellRect.width) {
1452 // We know the column hit now.
1453 *aCol = currCol;
1454
1455 if (currCol->IsCycler())
1456 // Cyclers contain only images. Fill this in immediately and return.
1457 *aChildElt = nsCSSAnonBoxes::mozTreeImage();
1458 else
1459 *aChildElt = GetItemWithinCellAt(aX, cellRect, *aRow, currCol);
1460 break;
1461 }
1462 }
1463 }
1464
GetCellWidth(int32_t aRow,nsTreeColumn * aCol,gfxContext * aRenderingContext,nscoord & aDesiredSize,nscoord & aCurrentSize)1465 nsresult nsTreeBodyFrame::GetCellWidth(int32_t aRow, nsTreeColumn* aCol,
1466 gfxContext* aRenderingContext,
1467 nscoord& aDesiredSize,
1468 nscoord& aCurrentSize) {
1469 MOZ_ASSERT(aCol, "aCol must not be null");
1470 MOZ_ASSERT(aRenderingContext, "aRenderingContext must not be null");
1471
1472 // The rect for the current cell.
1473 nscoord colWidth;
1474 nsresult rv = aCol->GetWidthInTwips(this, &colWidth);
1475 NS_ENSURE_SUCCESS(rv, rv);
1476
1477 nsRect cellRect(0, 0, colWidth, mRowHeight);
1478
1479 int32_t overflow =
1480 cellRect.x + cellRect.width - (mInnerBox.x + mInnerBox.width);
1481 if (overflow > 0) cellRect.width -= overflow;
1482
1483 // Adjust borders and padding for the cell.
1484 ComputedStyle* cellContext =
1485 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
1486 nsMargin bp(0, 0, 0, 0);
1487 GetBorderPadding(cellContext, bp);
1488
1489 aCurrentSize = cellRect.width;
1490 aDesiredSize = bp.left + bp.right;
1491
1492 if (aCol->IsPrimary()) {
1493 // If the current Column is a Primary, then we need to take into account
1494 // the indentation and possibly a twisty.
1495
1496 // The amount of indentation is the indentation width (|mIndentation|) by
1497 // the level.
1498 int32_t level;
1499 mView->GetLevel(aRow, &level);
1500 aDesiredSize += mIndentation * level;
1501
1502 // Find the twisty rect by computing its size.
1503 ComputedStyle* twistyContext =
1504 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
1505
1506 nsRect imageSize;
1507 nsRect twistyRect(cellRect);
1508 GetTwistyRect(aRow, aCol, imageSize, twistyRect, PresContext(),
1509 twistyContext);
1510
1511 // Add in the margins of the twisty element.
1512 nsMargin twistyMargin;
1513 twistyContext->StyleMargin()->GetMargin(twistyMargin);
1514 twistyRect.Inflate(twistyMargin);
1515
1516 aDesiredSize += twistyRect.width;
1517 }
1518
1519 ComputedStyle* imageContext =
1520 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeImage());
1521
1522 // Account for the width of the cell image.
1523 nsRect imageSize = GetImageSize(aRow, aCol, false, imageContext);
1524 // Add in the margins of the cell image.
1525 nsMargin imageMargin;
1526 imageContext->StyleMargin()->GetMargin(imageMargin);
1527 imageSize.Inflate(imageMargin);
1528
1529 aDesiredSize += imageSize.width;
1530
1531 // Get the cell text.
1532 nsAutoString cellText;
1533 mView->GetCellText(aRow, aCol, cellText);
1534 // We're going to measure this text so we need to ensure bidi is enabled if
1535 // necessary
1536 CheckTextForBidi(cellText);
1537
1538 ComputedStyle* textContext =
1539 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
1540
1541 // Get the borders and padding for the text.
1542 GetBorderPadding(textContext, bp);
1543
1544 RefPtr<nsFontMetrics> fm =
1545 nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, PresContext());
1546 // Get the width of the text itself
1547 nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(cellText, this, *fm,
1548 *aRenderingContext);
1549 nscoord totalTextWidth = width + bp.left + bp.right;
1550 aDesiredSize += totalTextWidth;
1551 return NS_OK;
1552 }
1553
IsCellCropped(int32_t aRow,nsTreeColumn * aCol,bool * _retval)1554 nsresult nsTreeBodyFrame::IsCellCropped(int32_t aRow, nsTreeColumn* aCol,
1555 bool* _retval) {
1556 nscoord currentSize, desiredSize;
1557 nsresult rv;
1558
1559 if (!aCol) return NS_ERROR_INVALID_ARG;
1560
1561 RefPtr<gfxContext> rc = PresShell()->CreateReferenceRenderingContext();
1562
1563 rv = GetCellWidth(aRow, aCol, rc, desiredSize, currentSize);
1564 NS_ENSURE_SUCCESS(rv, rv);
1565
1566 *_retval = desiredSize > currentSize;
1567
1568 return NS_OK;
1569 }
1570
CreateTimer(const LookAndFeel::IntID aID,nsTimerCallbackFunc aFunc,int32_t aType,nsITimer ** aTimer,const char * aName)1571 nsresult nsTreeBodyFrame::CreateTimer(const LookAndFeel::IntID aID,
1572 nsTimerCallbackFunc aFunc, int32_t aType,
1573 nsITimer** aTimer, const char* aName) {
1574 // Get the delay from the look and feel service.
1575 int32_t delay = LookAndFeel::GetInt(aID, 0);
1576
1577 nsCOMPtr<nsITimer> timer;
1578
1579 // Create a new timer only if the delay is greater than zero.
1580 // Zero value means that this feature is completely disabled.
1581 if (delay > 0) {
1582 MOZ_TRY_VAR(timer,
1583 NS_NewTimerWithFuncCallback(
1584 aFunc, this, delay, aType, aName,
1585 mContent->OwnerDoc()->EventTargetFor(TaskCategory::Other)));
1586 }
1587
1588 timer.forget(aTimer);
1589 return NS_OK;
1590 }
1591
RowCountChanged(int32_t aIndex,int32_t aCount)1592 nsresult nsTreeBodyFrame::RowCountChanged(int32_t aIndex, int32_t aCount) {
1593 if (aCount == 0 || !mView) return NS_OK; // Nothing to do.
1594
1595 #ifdef ACCESSIBILITY
1596 if (PresShell::IsAccessibilityActive()) {
1597 FireRowCountChangedEvent(aIndex, aCount);
1598 }
1599 #endif // #ifdef ACCESSIBILITY
1600
1601 AutoWeakFrame weakFrame(this);
1602
1603 // Adjust our selection.
1604 nsCOMPtr<nsITreeView> view = mView;
1605 nsCOMPtr<nsITreeSelection> sel;
1606 view->GetSelection(getter_AddRefs(sel));
1607 if (sel) {
1608 sel->AdjustSelection(aIndex, aCount);
1609 }
1610
1611 NS_ENSURE_STATE(weakFrame.IsAlive());
1612
1613 if (mUpdateBatchNest) return NS_OK;
1614
1615 mRowCount += aCount;
1616 #ifdef DEBUG
1617 int32_t rowCount = mRowCount;
1618 mView->GetRowCount(&rowCount);
1619 NS_ASSERTION(
1620 rowCount == mRowCount,
1621 "row count did not change by the amount suggested, check caller");
1622 #endif
1623
1624 int32_t count = Abs(aCount);
1625 int32_t last = LastVisibleRow();
1626 if (aIndex >= mTopRowIndex && aIndex <= last) InvalidateRange(aIndex, last);
1627
1628 ScrollParts parts = GetScrollParts();
1629
1630 if (mTopRowIndex == 0) {
1631 // Just update the scrollbar and return.
1632 FullScrollbarsUpdate(false);
1633 return NS_OK;
1634 }
1635
1636 bool needsInvalidation = false;
1637 // Adjust our top row index.
1638 if (aCount > 0) {
1639 if (mTopRowIndex > aIndex) {
1640 // Rows came in above us. Augment the top row index.
1641 mTopRowIndex += aCount;
1642 }
1643 } else if (aCount < 0) {
1644 if (mTopRowIndex > aIndex + count - 1) {
1645 // No need to invalidate. The remove happened
1646 // completely above us (offscreen).
1647 mTopRowIndex -= count;
1648 } else if (mTopRowIndex >= aIndex) {
1649 // This is a full-blown invalidate.
1650 if (mTopRowIndex + mPageLength > mRowCount - 1) {
1651 mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength);
1652 }
1653 needsInvalidation = true;
1654 }
1655 }
1656
1657 FullScrollbarsUpdate(needsInvalidation);
1658 return NS_OK;
1659 }
1660
BeginUpdateBatch()1661 nsresult nsTreeBodyFrame::BeginUpdateBatch() {
1662 ++mUpdateBatchNest;
1663
1664 return NS_OK;
1665 }
1666
EndUpdateBatch()1667 nsresult nsTreeBodyFrame::EndUpdateBatch() {
1668 NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch");
1669
1670 if (--mUpdateBatchNest == 0) {
1671 if (mView) {
1672 Invalidate();
1673 int32_t countBeforeUpdate = mRowCount;
1674 mView->GetRowCount(&mRowCount);
1675 if (countBeforeUpdate != mRowCount) {
1676 if (mTopRowIndex + mPageLength > mRowCount - 1) {
1677 mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength);
1678 }
1679 FullScrollbarsUpdate(false);
1680 }
1681 }
1682 }
1683
1684 return NS_OK;
1685 }
1686
PrefillPropertyArray(int32_t aRowIndex,nsTreeColumn * aCol)1687 void nsTreeBodyFrame::PrefillPropertyArray(int32_t aRowIndex,
1688 nsTreeColumn* aCol) {
1689 MOZ_ASSERT(!aCol || aCol->GetFrame(), "invalid column passed");
1690 mScratchArray.Clear();
1691
1692 // focus
1693 if (mFocused)
1694 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::focus);
1695 else
1696 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::blur);
1697
1698 // sort
1699 bool sorted = false;
1700 mView->IsSorted(&sorted);
1701 if (sorted) mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::sorted);
1702
1703 // drag session
1704 if (mSlots && mSlots->mIsDragging)
1705 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::dragSession);
1706
1707 if (aRowIndex != -1) {
1708 if (aRowIndex == mMouseOverRow)
1709 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::hover);
1710
1711 nsCOMPtr<nsITreeSelection> selection;
1712 mView->GetSelection(getter_AddRefs(selection));
1713
1714 if (selection) {
1715 // selected
1716 bool isSelected;
1717 selection->IsSelected(aRowIndex, &isSelected);
1718 if (isSelected)
1719 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::selected);
1720
1721 // current
1722 int32_t currentIndex;
1723 selection->GetCurrentIndex(¤tIndex);
1724 if (aRowIndex == currentIndex)
1725 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::current);
1726 }
1727
1728 // container or leaf
1729 bool isContainer = false;
1730 mView->IsContainer(aRowIndex, &isContainer);
1731 if (isContainer) {
1732 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::container);
1733
1734 // open or closed
1735 bool isOpen = false;
1736 mView->IsContainerOpen(aRowIndex, &isOpen);
1737 if (isOpen)
1738 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::open);
1739 else
1740 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::closed);
1741 } else {
1742 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::leaf);
1743 }
1744
1745 // drop orientation
1746 if (mSlots && mSlots->mDropAllowed && mSlots->mDropRow == aRowIndex) {
1747 if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE)
1748 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::dropBefore);
1749 else if (mSlots->mDropOrient == nsITreeView::DROP_ON)
1750 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::dropOn);
1751 else if (mSlots->mDropOrient == nsITreeView::DROP_AFTER)
1752 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::dropAfter);
1753 }
1754
1755 // odd or even
1756 if (aRowIndex % 2)
1757 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::odd);
1758 else
1759 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::even);
1760
1761 XULTreeElement* tree = GetBaseElement();
1762 if (tree && tree->HasAttr(kNameSpaceID_None, nsGkAtoms::editing)) {
1763 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::editing);
1764 }
1765
1766 // multiple columns
1767 if (mColumns->GetColumnAt(1))
1768 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::multicol);
1769 }
1770
1771 if (aCol) {
1772 mScratchArray.AppendElement(aCol->GetAtom());
1773
1774 if (aCol->IsPrimary())
1775 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::primary);
1776
1777 if (aCol->GetType() == TreeColumn_Binding::TYPE_CHECKBOX) {
1778 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::checkbox);
1779
1780 if (aRowIndex != -1) {
1781 nsAutoString value;
1782 mView->GetCellValue(aRowIndex, aCol, value);
1783 if (value.EqualsLiteral("true"))
1784 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::checked);
1785 }
1786 }
1787
1788 // Read special properties from attributes on the column content node
1789 if (aCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::insertbefore,
1790 nsGkAtoms::_true, eCaseMatters))
1791 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::insertbefore);
1792 if (aCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::insertafter,
1793 nsGkAtoms::_true, eCaseMatters))
1794 mScratchArray.AppendElement((nsStaticAtom*)nsGkAtoms::insertafter);
1795 }
1796 }
1797
GetTwistyRect(int32_t aRowIndex,nsTreeColumn * aColumn,nsRect & aImageRect,nsRect & aTwistyRect,nsPresContext * aPresContext,ComputedStyle * aTwistyContext)1798 nsITheme* nsTreeBodyFrame::GetTwistyRect(int32_t aRowIndex,
1799 nsTreeColumn* aColumn,
1800 nsRect& aImageRect,
1801 nsRect& aTwistyRect,
1802 nsPresContext* aPresContext,
1803 ComputedStyle* aTwistyContext) {
1804 // The twisty rect extends all the way to the end of the cell. This is
1805 // incorrect. We need to determine the twisty rect's true width. This is
1806 // done by examining the ComputedStyle for a width first. If it has one, we
1807 // use that. If it doesn't, we use the image's natural width. If the image
1808 // hasn't loaded and if no width is specified, then we just bail. If there is
1809 // a -moz-appearance involved, adjust the rect by the minimum widget size
1810 // provided by the theme implementation.
1811 aImageRect = GetImageSize(aRowIndex, aColumn, true, aTwistyContext);
1812 if (aImageRect.height > aTwistyRect.height)
1813 aImageRect.height = aTwistyRect.height;
1814 if (aImageRect.width > aTwistyRect.width)
1815 aImageRect.width = aTwistyRect.width;
1816 else
1817 aTwistyRect.width = aImageRect.width;
1818
1819 bool useTheme = false;
1820 nsITheme* theme = nullptr;
1821 StyleAppearance appearance =
1822 aTwistyContext->StyleDisplay()->EffectiveAppearance();
1823 if (appearance != StyleAppearance::None) {
1824 theme = aPresContext->Theme();
1825 if (theme->ThemeSupportsWidget(aPresContext, nullptr, appearance))
1826 useTheme = true;
1827 }
1828
1829 if (useTheme) {
1830 LayoutDeviceIntSize minTwistySizePx;
1831 bool canOverride = true;
1832 theme->GetMinimumWidgetSize(aPresContext, this, appearance,
1833 &minTwistySizePx, &canOverride);
1834
1835 // GMWS() returns size in pixels, we need to convert it back to app units
1836 nsSize minTwistySize;
1837 minTwistySize.width =
1838 aPresContext->DevPixelsToAppUnits(minTwistySizePx.width);
1839 minTwistySize.height =
1840 aPresContext->DevPixelsToAppUnits(minTwistySizePx.height);
1841
1842 if (aTwistyRect.width < minTwistySize.width || !canOverride)
1843 aTwistyRect.width = minTwistySize.width;
1844 }
1845
1846 return useTheme ? theme : nullptr;
1847 }
1848
GetImage(int32_t aRowIndex,nsTreeColumn * aCol,bool aUseContext,ComputedStyle * aComputedStyle,bool & aAllowImageRegions,imgIContainer ** aResult)1849 nsresult nsTreeBodyFrame::GetImage(int32_t aRowIndex, nsTreeColumn* aCol,
1850 bool aUseContext,
1851 ComputedStyle* aComputedStyle,
1852 bool& aAllowImageRegions,
1853 imgIContainer** aResult) {
1854 *aResult = nullptr;
1855
1856 nsAutoString imageSrc;
1857 mView->GetImageSrc(aRowIndex, aCol, imageSrc);
1858 RefPtr<imgRequestProxy> styleRequest;
1859 if (!aUseContext && !imageSrc.IsEmpty()) {
1860 aAllowImageRegions = false;
1861 } else {
1862 // Obtain the URL from the ComputedStyle.
1863 aAllowImageRegions = true;
1864 styleRequest =
1865 aComputedStyle->StyleList()->mListStyleImage.GetImageRequest();
1866 if (!styleRequest) return NS_OK;
1867 nsCOMPtr<nsIURI> uri;
1868 styleRequest->GetURI(getter_AddRefs(uri));
1869 nsAutoCString spec;
1870 nsresult rv = uri->GetSpec(spec);
1871 NS_ENSURE_SUCCESS(rv, rv);
1872 CopyUTF8toUTF16(spec, imageSrc);
1873 }
1874
1875 // Look the image up in our cache.
1876 nsTreeImageCacheEntry entry;
1877 if (mImageCache.Get(imageSrc, &entry)) {
1878 // Find out if the image has loaded.
1879 uint32_t status;
1880 imgIRequest* imgReq = entry.request;
1881 imgReq->GetImageStatus(&status);
1882 imgReq->GetImage(aResult); // We hand back the image here. The GetImage
1883 // call addrefs *aResult.
1884 bool animated = true; // Assuming animated is the safe option
1885
1886 // We can only call GetAnimated if we're decoded
1887 if (*aResult && (status & imgIRequest::STATUS_DECODE_COMPLETE))
1888 (*aResult)->GetAnimated(&animated);
1889
1890 if ((!(status & imgIRequest::STATUS_LOAD_COMPLETE)) || animated) {
1891 // We either aren't done loading, or we're animating. Add our row as a
1892 // listener for invalidations.
1893 nsCOMPtr<imgINotificationObserver> obs;
1894 imgReq->GetNotificationObserver(getter_AddRefs(obs));
1895
1896 if (obs) {
1897 static_cast<nsTreeImageListener*>(obs.get())->AddCell(aRowIndex, aCol);
1898 }
1899
1900 return NS_OK;
1901 }
1902 }
1903
1904 if (!*aResult) {
1905 // Create a new nsTreeImageListener object and pass it our row and column
1906 // information.
1907 nsTreeImageListener* listener = new nsTreeImageListener(this);
1908 if (!listener) return NS_ERROR_OUT_OF_MEMORY;
1909
1910 mCreatedListeners.Insert(listener);
1911
1912 listener->AddCell(aRowIndex, aCol);
1913 nsCOMPtr<imgINotificationObserver> imgNotificationObserver = listener;
1914
1915 Document* doc = mContent->GetComposedDoc();
1916 if (!doc)
1917 // The page is currently being torn down. Why bother.
1918 return NS_ERROR_FAILURE;
1919
1920 RefPtr<imgRequestProxy> imageRequest;
1921 if (styleRequest) {
1922 styleRequest->SyncClone(imgNotificationObserver, doc,
1923 getter_AddRefs(imageRequest));
1924 } else {
1925 nsCOMPtr<nsIURI> srcURI;
1926 nsContentUtils::NewURIWithDocumentCharset(
1927 getter_AddRefs(srcURI), imageSrc, doc, mContent->GetBaseURI());
1928 if (!srcURI) return NS_ERROR_FAILURE;
1929
1930 auto referrerInfo = MakeRefPtr<mozilla::dom::ReferrerInfo>(*doc);
1931
1932 // XXXbz what's the origin principal for this stuff that comes from our
1933 // view? I guess we should assume that it's the node's principal...
1934 nsresult rv = nsContentUtils::LoadImage(
1935 srcURI, mContent, doc, mContent->NodePrincipal(), 0, referrerInfo,
1936 imgNotificationObserver, nsIRequest::LOAD_NORMAL, u""_ns,
1937 getter_AddRefs(imageRequest));
1938 NS_ENSURE_SUCCESS(rv, rv);
1939
1940 // NOTE(heycam): If it's an SVG image, and we need to want the image to
1941 // able to respond to media query changes, it needs to be added to the
1942 // document's ImageTracker (like nsImageBoxFrame does). For now, assume
1943 // we don't need this.
1944 }
1945 listener->UnsuppressInvalidation();
1946
1947 if (!imageRequest) return NS_ERROR_FAILURE;
1948
1949 // We don't want discarding/decode-on-draw for xul images
1950 imageRequest->StartDecoding(imgIContainer::FLAG_ASYNC_NOTIFY);
1951 imageRequest->LockImage();
1952
1953 // In a case it was already cached.
1954 imageRequest->GetImage(aResult);
1955 nsTreeImageCacheEntry cacheEntry(imageRequest, imgNotificationObserver);
1956 mImageCache.InsertOrUpdate(imageSrc, cacheEntry);
1957 }
1958 return NS_OK;
1959 }
1960
GetImageSize(int32_t aRowIndex,nsTreeColumn * aCol,bool aUseContext,ComputedStyle * aComputedStyle)1961 nsRect nsTreeBodyFrame::GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol,
1962 bool aUseContext,
1963 ComputedStyle* aComputedStyle) {
1964 // XXX We should respond to visibility rules for collapsed vs. hidden.
1965
1966 // This method returns the width of the twisty INCLUDING borders and padding.
1967 // It first checks the ComputedStyle for a width. If none is found, it tries
1968 // to use the default image width for the twisty. If no image is found, it
1969 // defaults to border+padding.
1970 nsRect r(0, 0, 0, 0);
1971 nsMargin bp(0, 0, 0, 0);
1972 GetBorderPadding(aComputedStyle, bp);
1973 r.Inflate(bp);
1974
1975 // Now r contains our border+padding info. We now need to get our width and
1976 // height.
1977 bool needWidth = false;
1978 bool needHeight = false;
1979
1980 // We have to load image even though we already have a size.
1981 // Don't change this, otherwise things start to go awry.
1982 bool useImageRegion = true;
1983 nsCOMPtr<imgIContainer> image;
1984 GetImage(aRowIndex, aCol, aUseContext, aComputedStyle, useImageRegion,
1985 getter_AddRefs(image));
1986
1987 const nsStylePosition* myPosition = aComputedStyle->StylePosition();
1988 const nsStyleList* myList = aComputedStyle->StyleList();
1989 nsRect imageRegion = myList->GetImageRegion();
1990 if (useImageRegion) {
1991 r.x += imageRegion.x;
1992 r.y += imageRegion.y;
1993 }
1994
1995 if (myPosition->mWidth.ConvertsToLength()) {
1996 int32_t val = myPosition->mWidth.ToLength();
1997 r.width += val;
1998 } else if (useImageRegion && imageRegion.width > 0) {
1999 r.width += imageRegion.width;
2000 } else {
2001 needWidth = true;
2002 }
2003
2004 if (myPosition->mHeight.ConvertsToLength()) {
2005 int32_t val = myPosition->mHeight.ToLength();
2006 r.height += val;
2007 } else if (useImageRegion && imageRegion.height > 0)
2008 r.height += imageRegion.height;
2009 else
2010 needHeight = true;
2011
2012 if (image) {
2013 if (needWidth || needHeight) {
2014 // Get the natural image size.
2015
2016 if (needWidth) {
2017 // Get the size from the image.
2018 nscoord width;
2019 image->GetWidth(&width);
2020 r.width += nsPresContext::CSSPixelsToAppUnits(width);
2021 }
2022
2023 if (needHeight) {
2024 nscoord height;
2025 image->GetHeight(&height);
2026 r.height += nsPresContext::CSSPixelsToAppUnits(height);
2027 }
2028 }
2029 }
2030
2031 return r;
2032 }
2033
2034 // GetImageDestSize returns the destination size of the image.
2035 // The width and height do not include borders and padding.
2036 // The width and height have not been adjusted to fit in the row height
2037 // or cell width.
2038 // The width and height reflect the destination size specified in CSS,
2039 // or the image region specified in CSS, or the natural size of the
2040 // image.
2041 // If only the destination width has been specified in CSS, the height is
2042 // calculated to maintain the aspect ratio of the image.
2043 // If only the destination height has been specified in CSS, the width is
2044 // calculated to maintain the aspect ratio of the image.
GetImageDestSize(ComputedStyle * aComputedStyle,bool useImageRegion,imgIContainer * image)2045 nsSize nsTreeBodyFrame::GetImageDestSize(ComputedStyle* aComputedStyle,
2046 bool useImageRegion,
2047 imgIContainer* image) {
2048 nsSize size(0, 0);
2049
2050 // We need to get the width and height.
2051 bool needWidth = false;
2052 bool needHeight = false;
2053
2054 // Get the style position to see if the CSS has specified the
2055 // destination width/height.
2056 const nsStylePosition* myPosition = aComputedStyle->StylePosition();
2057
2058 if (myPosition->mWidth.ConvertsToLength()) {
2059 // CSS has specified the destination width.
2060 size.width = myPosition->mWidth.ToLength();
2061 } else {
2062 // We'll need to get the width of the image/region.
2063 needWidth = true;
2064 }
2065
2066 if (myPosition->mHeight.ConvertsToLength()) {
2067 // CSS has specified the destination height.
2068 size.height = myPosition->mHeight.ToLength();
2069 } else {
2070 // We'll need to get the height of the image/region.
2071 needHeight = true;
2072 }
2073
2074 if (needWidth || needHeight) {
2075 // We need to get the size of the image/region.
2076 nsSize imageSize(0, 0);
2077
2078 const nsStyleList* myList = aComputedStyle->StyleList();
2079 nsRect imageRegion = myList->GetImageRegion();
2080 if (useImageRegion && imageRegion.width > 0) {
2081 // CSS has specified an image region.
2082 // Use the width of the image region.
2083 imageSize.width = imageRegion.width;
2084 } else if (image) {
2085 nscoord width;
2086 image->GetWidth(&width);
2087 imageSize.width = nsPresContext::CSSPixelsToAppUnits(width);
2088 }
2089
2090 if (useImageRegion && imageRegion.height > 0) {
2091 // CSS has specified an image region.
2092 // Use the height of the image region.
2093 imageSize.height = imageRegion.height;
2094 } else if (image) {
2095 nscoord height;
2096 image->GetHeight(&height);
2097 imageSize.height = nsPresContext::CSSPixelsToAppUnits(height);
2098 }
2099
2100 if (needWidth) {
2101 if (!needHeight && imageSize.height != 0) {
2102 // The CSS specified the destination height, but not the destination
2103 // width. We need to calculate the width so that we maintain the
2104 // image's aspect ratio.
2105 size.width = imageSize.width * size.height / imageSize.height;
2106 } else {
2107 size.width = imageSize.width;
2108 }
2109 }
2110
2111 if (needHeight) {
2112 if (!needWidth && imageSize.width != 0) {
2113 // The CSS specified the destination width, but not the destination
2114 // height. We need to calculate the height so that we maintain the
2115 // image's aspect ratio.
2116 size.height = imageSize.height * size.width / imageSize.width;
2117 } else {
2118 size.height = imageSize.height;
2119 }
2120 }
2121 }
2122
2123 return size;
2124 }
2125
2126 // GetImageSourceRect returns the source rectangle of the image to be
2127 // displayed.
2128 // The width and height reflect the image region specified in CSS, or
2129 // the natural size of the image.
2130 // The width and height do not include borders and padding.
2131 // The width and height do not reflect the destination size specified
2132 // in CSS.
GetImageSourceRect(ComputedStyle * aComputedStyle,bool useImageRegion,imgIContainer * image)2133 nsRect nsTreeBodyFrame::GetImageSourceRect(ComputedStyle* aComputedStyle,
2134 bool useImageRegion,
2135 imgIContainer* image) {
2136 const nsStyleList* myList = aComputedStyle->StyleList();
2137 // CSS has specified an image region.
2138 if (useImageRegion && myList->mImageRegion.IsRect()) {
2139 return myList->GetImageRegion();
2140 }
2141
2142 if (!image) {
2143 return nsRect();
2144 }
2145
2146 nsRect r;
2147 // Use the actual image size.
2148 nscoord coord;
2149 if (NS_SUCCEEDED(image->GetWidth(&coord))) {
2150 r.width = nsPresContext::CSSPixelsToAppUnits(coord);
2151 }
2152 if (NS_SUCCEEDED(image->GetHeight(&coord))) {
2153 r.height = nsPresContext::CSSPixelsToAppUnits(coord);
2154 }
2155 return r;
2156 }
2157
GetRowHeight()2158 int32_t nsTreeBodyFrame::GetRowHeight() {
2159 // Look up the correct height. It is equal to the specified height
2160 // + the specified margins.
2161 mScratchArray.Clear();
2162 ComputedStyle* rowContext =
2163 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
2164 if (rowContext) {
2165 const nsStylePosition* myPosition = rowContext->StylePosition();
2166
2167 nscoord minHeight = 0;
2168 if (myPosition->mMinHeight.ConvertsToLength()) {
2169 minHeight = myPosition->mMinHeight.ToLength();
2170 }
2171
2172 nscoord height = 0;
2173 if (myPosition->mHeight.ConvertsToLength()) {
2174 height = myPosition->mHeight.ToLength();
2175 }
2176
2177 if (height < minHeight) height = minHeight;
2178
2179 if (height > 0) {
2180 height = nsPresContext::AppUnitsToIntCSSPixels(height);
2181 height += height % 2;
2182 height = nsPresContext::CSSPixelsToAppUnits(height);
2183
2184 // XXX Check box-sizing to determine if border/padding should augment the
2185 // height Inflate the height by our margins.
2186 nsRect rowRect(0, 0, 0, height);
2187 nsMargin rowMargin;
2188 rowContext->StyleMargin()->GetMargin(rowMargin);
2189 rowRect.Inflate(rowMargin);
2190 height = rowRect.height;
2191 return height;
2192 }
2193 }
2194
2195 return nsPresContext::CSSPixelsToAppUnits(18); // As good a default as any.
2196 }
2197
GetIndentation()2198 int32_t nsTreeBodyFrame::GetIndentation() {
2199 // Look up the correct indentation. It is equal to the specified indentation
2200 // width.
2201 mScratchArray.Clear();
2202 ComputedStyle* indentContext =
2203 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeIndentation());
2204 if (indentContext) {
2205 const nsStylePosition* myPosition = indentContext->StylePosition();
2206 if (myPosition->mWidth.ConvertsToLength()) {
2207 return myPosition->mWidth.ToLength();
2208 }
2209 }
2210
2211 return nsPresContext::CSSPixelsToAppUnits(16); // As good a default as any.
2212 }
2213
CalcInnerBox()2214 void nsTreeBodyFrame::CalcInnerBox() {
2215 mInnerBox.SetRect(0, 0, mRect.width, mRect.height);
2216 AdjustForBorderPadding(mComputedStyle, mInnerBox);
2217 }
2218
CalcHorzWidth(const ScrollParts & aParts)2219 nscoord nsTreeBodyFrame::CalcHorzWidth(const ScrollParts& aParts) {
2220 // Compute the adjustment to the last column. This varies depending on the
2221 // visibility of the columnpicker and the scrollbar.
2222 if (aParts.mColumnsFrame)
2223 mAdjustWidth = mRect.width - aParts.mColumnsFrame->GetRect().width;
2224 else
2225 mAdjustWidth = 0;
2226
2227 nscoord width = 0;
2228
2229 // We calculate this from the scrollable frame, so that it
2230 // properly covers all contingencies of what could be
2231 // scrollable (columns, body, etc...)
2232
2233 if (aParts.mColumnsScrollFrame) {
2234 width = aParts.mColumnsScrollFrame->GetScrollRange().width +
2235 aParts.mColumnsScrollFrame->GetScrollPortRect().width;
2236 }
2237
2238 // If no horz scrolling periphery is present, then just return our width
2239 if (width == 0) width = mRect.width;
2240
2241 return width;
2242 }
2243
GetCursor(const nsPoint & aPoint)2244 Maybe<nsIFrame::Cursor> nsTreeBodyFrame::GetCursor(const nsPoint& aPoint) {
2245 // Check the GetScriptHandlingObject so we don't end up running code when
2246 // the document is a zombie.
2247 bool dummy;
2248 if (mView && GetContent()->GetComposedDoc()->GetScriptHandlingObject(dummy)) {
2249 int32_t row;
2250 nsTreeColumn* col;
2251 nsCSSAnonBoxPseudoStaticAtom* child;
2252 GetCellAt(aPoint.x, aPoint.y, &row, &col, &child);
2253
2254 if (child) {
2255 // Our scratch array is already prefilled.
2256 RefPtr<ComputedStyle> childContext = GetPseudoComputedStyle(child);
2257 StyleCursorKind kind = childContext->StyleUI()->mCursor.keyword;
2258 if (kind == StyleCursorKind::Auto) {
2259 kind = StyleCursorKind::Default;
2260 }
2261 return Some(
2262 Cursor{kind, AllowCustomCursorImage::Yes, std::move(childContext)});
2263 }
2264 }
2265 return nsLeafBoxFrame::GetCursor(aPoint);
2266 }
2267
GetDropEffect(WidgetGUIEvent * aEvent)2268 static uint32_t GetDropEffect(WidgetGUIEvent* aEvent) {
2269 NS_ASSERTION(aEvent->mClass == eDragEventClass, "wrong event type");
2270 WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
2271 nsContentUtils::SetDataTransferInEvent(dragEvent);
2272
2273 uint32_t action = 0;
2274 if (dragEvent->mDataTransfer) {
2275 action = dragEvent->mDataTransfer->DropEffectInt();
2276 }
2277 return action;
2278 }
2279
HandleEvent(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)2280 nsresult nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext,
2281 WidgetGUIEvent* aEvent,
2282 nsEventStatus* aEventStatus) {
2283 if (aEvent->mMessage == eMouseOver || aEvent->mMessage == eMouseMove) {
2284 nsPoint pt =
2285 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this});
2286 int32_t xTwips = pt.x - mInnerBox.x;
2287 int32_t yTwips = pt.y - mInnerBox.y;
2288 int32_t newrow = GetRowAtInternal(xTwips, yTwips);
2289 if (mMouseOverRow != newrow) {
2290 // redraw the old and the new row
2291 if (mMouseOverRow != -1) InvalidateRow(mMouseOverRow);
2292 mMouseOverRow = newrow;
2293 if (mMouseOverRow != -1) InvalidateRow(mMouseOverRow);
2294 }
2295 } else if (aEvent->mMessage == eMouseOut) {
2296 if (mMouseOverRow != -1) {
2297 InvalidateRow(mMouseOverRow);
2298 mMouseOverRow = -1;
2299 }
2300 } else if (aEvent->mMessage == eDragEnter) {
2301 if (!mSlots) mSlots = new Slots();
2302
2303 // Cache several things we'll need throughout the course of our work. These
2304 // will all get released on a drag exit.
2305
2306 if (mSlots->mTimer) {
2307 mSlots->mTimer->Cancel();
2308 mSlots->mTimer = nullptr;
2309 }
2310
2311 // Cache the drag session.
2312 mSlots->mIsDragging = true;
2313 mSlots->mDropRow = -1;
2314 mSlots->mDropOrient = -1;
2315 mSlots->mDragAction = GetDropEffect(aEvent);
2316 } else if (aEvent->mMessage == eDragOver) {
2317 // The mouse is hovering over this tree. If we determine things are
2318 // different from the last time, invalidate the drop feedback at the old
2319 // position, query the view to see if the current location is droppable,
2320 // and then invalidate the drop feedback at the new location if it is.
2321 // The mouse may or may not have changed position from the last time
2322 // we were called, so optimize out a lot of the extra notifications by
2323 // checking if anything changed first. For drop feedback we use drop,
2324 // dropBefore and dropAfter property.
2325
2326 if (!mView || !mSlots) return NS_OK;
2327
2328 // Save last values, we will need them.
2329 int32_t lastDropRow = mSlots->mDropRow;
2330 int16_t lastDropOrient = mSlots->mDropOrient;
2331 #ifndef XP_MACOSX
2332 int16_t lastScrollLines = mSlots->mScrollLines;
2333 #endif
2334
2335 // Find out the current drag action
2336 uint32_t lastDragAction = mSlots->mDragAction;
2337 mSlots->mDragAction = GetDropEffect(aEvent);
2338
2339 // Compute the row mouse is over and the above/below/on state.
2340 // Below we'll use this to see if anything changed.
2341 // Also check if we want to auto-scroll.
2342 ComputeDropPosition(aEvent, &mSlots->mDropRow, &mSlots->mDropOrient,
2343 &mSlots->mScrollLines);
2344
2345 // While we're here, handle tracking of scrolling during a drag.
2346 if (mSlots->mScrollLines) {
2347 if (mSlots->mDropAllowed) {
2348 // Invalidate primary cell at old location.
2349 mSlots->mDropAllowed = false;
2350 InvalidateDropFeedback(lastDropRow, lastDropOrient);
2351 }
2352 #ifdef XP_MACOSX
2353 ScrollByLines(mSlots->mScrollLines);
2354 #else
2355 if (!lastScrollLines) {
2356 // Cancel any previously initialized timer.
2357 if (mSlots->mTimer) {
2358 mSlots->mTimer->Cancel();
2359 mSlots->mTimer = nullptr;
2360 }
2361
2362 // Set a timer to trigger the tree scrolling.
2363 CreateTimer(LookAndFeel::IntID::TreeLazyScrollDelay, LazyScrollCallback,
2364 nsITimer::TYPE_ONE_SHOT, getter_AddRefs(mSlots->mTimer),
2365 "nsTreeBodyFrame::LazyScrollCallback");
2366 }
2367 #endif
2368 // Bail out to prevent spring loaded timer and feedback line settings.
2369 return NS_OK;
2370 }
2371
2372 // If changed from last time, invalidate primary cell at the old location
2373 // and if allowed, invalidate primary cell at the new location. If nothing
2374 // changed, just bail.
2375 if (mSlots->mDropRow != lastDropRow ||
2376 mSlots->mDropOrient != lastDropOrient ||
2377 mSlots->mDragAction != lastDragAction) {
2378 // Invalidate row at the old location.
2379 if (mSlots->mDropAllowed) {
2380 mSlots->mDropAllowed = false;
2381 InvalidateDropFeedback(lastDropRow, lastDropOrient);
2382 }
2383
2384 if (mSlots->mTimer) {
2385 // Timer is active but for a different row than the current one, kill
2386 // it.
2387 mSlots->mTimer->Cancel();
2388 mSlots->mTimer = nullptr;
2389 }
2390
2391 if (mSlots->mDropRow >= 0) {
2392 if (!mSlots->mTimer && mSlots->mDropOrient == nsITreeView::DROP_ON) {
2393 // Either there wasn't a timer running or it was just killed above.
2394 // If over a folder, start up a timer to open the folder.
2395 bool isContainer = false;
2396 mView->IsContainer(mSlots->mDropRow, &isContainer);
2397 if (isContainer) {
2398 bool isOpen = false;
2399 mView->IsContainerOpen(mSlots->mDropRow, &isOpen);
2400 if (!isOpen) {
2401 // This node isn't expanded, set a timer to expand it.
2402 CreateTimer(LookAndFeel::IntID::TreeOpenDelay, OpenCallback,
2403 nsITimer::TYPE_ONE_SHOT,
2404 getter_AddRefs(mSlots->mTimer),
2405 "nsTreeBodyFrame::OpenCallback");
2406 }
2407 }
2408 }
2409
2410 // The dataTransfer was initialized by the call to GetDropEffect above.
2411 bool canDropAtNewLocation = false;
2412 mView->CanDrop(mSlots->mDropRow, mSlots->mDropOrient,
2413 aEvent->AsDragEvent()->mDataTransfer,
2414 &canDropAtNewLocation);
2415
2416 if (canDropAtNewLocation) {
2417 // Invalidate row at the new location.
2418 mSlots->mDropAllowed = canDropAtNewLocation;
2419 InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient);
2420 }
2421 }
2422 }
2423
2424 // Indicate that the drop is allowed by preventing the default behaviour.
2425 if (mSlots->mDropAllowed) *aEventStatus = nsEventStatus_eConsumeNoDefault;
2426 } else if (aEvent->mMessage == eDrop) {
2427 // this event was meant for another frame, so ignore it
2428 if (!mSlots) return NS_OK;
2429
2430 // Tell the view where the drop happened.
2431
2432 // Remove the drop folder and all its parents from the array.
2433 int32_t parentIndex;
2434 nsresult rv = mView->GetParentIndex(mSlots->mDropRow, &parentIndex);
2435 while (NS_SUCCEEDED(rv) && parentIndex >= 0) {
2436 mSlots->mArray.RemoveElement(parentIndex);
2437 rv = mView->GetParentIndex(parentIndex, &parentIndex);
2438 }
2439
2440 NS_ASSERTION(aEvent->mClass == eDragEventClass, "wrong event type");
2441 WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
2442 nsContentUtils::SetDataTransferInEvent(dragEvent);
2443
2444 mView->Drop(mSlots->mDropRow, mSlots->mDropOrient,
2445 dragEvent->mDataTransfer);
2446 mSlots->mDropRow = -1;
2447 mSlots->mDropOrient = -1;
2448 mSlots->mIsDragging = false;
2449 *aEventStatus =
2450 nsEventStatus_eConsumeNoDefault; // already handled the drop
2451 } else if (aEvent->mMessage == eDragExit) {
2452 // this event was meant for another frame, so ignore it
2453 if (!mSlots) return NS_OK;
2454
2455 // Clear out all our tracking vars.
2456
2457 if (mSlots->mDropAllowed) {
2458 mSlots->mDropAllowed = false;
2459 InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient);
2460 } else
2461 mSlots->mDropAllowed = false;
2462 mSlots->mIsDragging = false;
2463 mSlots->mScrollLines = 0;
2464 // If a drop is occuring, the exit event will fire just before the drop
2465 // event, so don't reset mDropRow or mDropOrient as these fields are used
2466 // by the drop event.
2467 if (mSlots->mTimer) {
2468 mSlots->mTimer->Cancel();
2469 mSlots->mTimer = nullptr;
2470 }
2471
2472 if (!mSlots->mArray.IsEmpty()) {
2473 // Close all spring loaded folders except the drop folder.
2474 CreateTimer(LookAndFeel::IntID::TreeCloseDelay, CloseCallback,
2475 nsITimer::TYPE_ONE_SHOT, getter_AddRefs(mSlots->mTimer),
2476 "nsTreeBodyFrame::CloseCallback");
2477 }
2478 }
2479
2480 return NS_OK;
2481 }
2482
2483 class nsDisplayTreeBody final : public nsPaintedDisplayItem {
2484 public:
nsDisplayTreeBody(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame)2485 nsDisplayTreeBody(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
2486 : nsPaintedDisplayItem(aBuilder, aFrame) {
2487 MOZ_COUNT_CTOR(nsDisplayTreeBody);
2488 }
MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTreeBody)2489 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTreeBody)
2490
2491 nsDisplayItemGeometry* AllocateGeometry(
2492 nsDisplayListBuilder* aBuilder) override {
2493 return new nsDisplayTreeBodyGeometry(this, aBuilder, IsWindowActive());
2494 }
2495
Destroy(nsDisplayListBuilder * aBuilder)2496 void Destroy(nsDisplayListBuilder* aBuilder) override {
2497 aBuilder->UnregisterThemeGeometry(this);
2498 nsPaintedDisplayItem::Destroy(aBuilder);
2499 }
2500
IsWindowActive() const2501 bool IsWindowActive() const {
2502 EventStates docState =
2503 mFrame->PresContext()->Document()->GetDocumentState();
2504 return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
2505 }
2506
ComputeInvalidationRegion(nsDisplayListBuilder * aBuilder,const nsDisplayItemGeometry * aGeometry,nsRegion * aInvalidRegion) const2507 void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
2508 const nsDisplayItemGeometry* aGeometry,
2509 nsRegion* aInvalidRegion) const override {
2510 auto geometry = static_cast<const nsDisplayTreeBodyGeometry*>(aGeometry);
2511
2512 if ((aBuilder->ShouldSyncDecodeImages() &&
2513 geometry->ShouldInvalidateToSyncDecodeImages()) ||
2514 IsWindowActive() != geometry->mWindowIsActive) {
2515 bool snap;
2516 aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
2517 }
2518
2519 nsPaintedDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry,
2520 aInvalidRegion);
2521 }
2522
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)2523 virtual void Paint(nsDisplayListBuilder* aBuilder,
2524 gfxContext* aCtx) override {
2525 MOZ_ASSERT(aBuilder);
2526 DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
2527 IsSubpixelAADisabled());
2528
2529 ImgDrawResult result = static_cast<nsTreeBodyFrame*>(mFrame)->PaintTreeBody(
2530 *aCtx, GetPaintRect(), ToReferenceFrame(), aBuilder);
2531
2532 nsDisplayTreeBodyGeometry::UpdateDrawResult(this, result);
2533 }
2534
2535 NS_DISPLAY_DECL_NAME("XULTreeBody", TYPE_XUL_TREE_BODY)
2536
GetComponentAlphaBounds(nsDisplayListBuilder * aBuilder) const2537 virtual nsRect GetComponentAlphaBounds(
2538 nsDisplayListBuilder* aBuilder) const override {
2539 bool snap;
2540 return GetBounds(aBuilder, &snap);
2541 }
2542 };
2543
2544 #ifdef XP_MACOSX
IsInSourceList(nsIFrame * aFrame)2545 static bool IsInSourceList(nsIFrame* aFrame) {
2546 for (nsIFrame* frame = aFrame; frame;
2547 frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) {
2548 if (frame->StyleDisplay()->EffectiveAppearance() ==
2549 StyleAppearance::MozMacSourceList) {
2550 return true;
2551 }
2552 }
2553 return false;
2554 }
2555 #endif
2556
2557 // Painting routines
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)2558 void nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
2559 const nsDisplayListSet& aLists) {
2560 // REVIEW: why did we paint if we were collapsed? that makes no sense!
2561 if (!IsVisibleForPainting()) return; // We're invisible. Don't paint.
2562
2563 // Handles painting our background, border, and outline.
2564 nsLeafBoxFrame::BuildDisplayList(aBuilder, aLists);
2565
2566 // Bail out now if there's no view or we can't run script because the
2567 // document is a zombie
2568 if (!mView || !GetContent()->GetComposedDoc()->GetWindow()) return;
2569
2570 nsDisplayItem* item = MakeDisplayItem<nsDisplayTreeBody>(aBuilder, this);
2571 if (!item) {
2572 return;
2573 }
2574 aLists.Content()->AppendToTop(item);
2575
2576 #ifdef XP_MACOSX
2577 XULTreeElement* tree = GetBaseElement();
2578 nsIFrame* treeFrame = tree ? tree->GetPrimaryFrame() : nullptr;
2579 nsCOMPtr<nsITreeSelection> selection;
2580 mView->GetSelection(getter_AddRefs(selection));
2581 nsITheme* theme = PresContext()->Theme();
2582 // On Mac, we support native theming of selected rows. On 10.10 and higher,
2583 // this means applying vibrancy which require us to register the theme
2584 // geometrics for the row. In order to make the vibrancy effect to work
2585 // properly, we also need an ancestor frame to be themed as a source list.
2586 if (selection && theme && IsInSourceList(treeFrame)) {
2587 // Loop through our onscreen rows. If the row is selected and a
2588 // -moz-appearance is provided, RegisterThemeGeometry might be necessary.
2589 const auto end = std::min(mRowCount, LastVisibleRow() + 1);
2590 for (auto i = FirstVisibleRow(); i < end; i++) {
2591 bool isSelected;
2592 selection->IsSelected(i, &isSelected);
2593 if (isSelected) {
2594 PrefillPropertyArray(i, nullptr);
2595 nsAutoString properties;
2596 mView->GetRowProperties(i, properties);
2597 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
2598 ComputedStyle* rowContext =
2599 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
2600 auto appearance = rowContext->StyleDisplay()->EffectiveAppearance();
2601 if (appearance != StyleAppearance::None) {
2602 if (theme->ThemeSupportsWidget(PresContext(), this, appearance)) {
2603 nsITheme::ThemeGeometryType type =
2604 theme->ThemeGeometryTypeForWidget(this, appearance);
2605 if (type != nsITheme::eThemeGeometryTypeUnknown) {
2606 nsRect rowRect(mInnerBox.x,
2607 mInnerBox.y + mRowHeight * (i - FirstVisibleRow()),
2608 mInnerBox.width, mRowHeight);
2609 aBuilder->RegisterThemeGeometry(
2610 type, item,
2611 LayoutDeviceIntRect::FromUnknownRect(
2612 (rowRect + aBuilder->ToReferenceFrame(this))
2613 .ToNearestPixels(
2614 PresContext()->AppUnitsPerDevPixel())));
2615 }
2616 }
2617 }
2618 }
2619 }
2620 }
2621 #endif
2622 }
2623
PaintTreeBody(gfxContext & aRenderingContext,const nsRect & aDirtyRect,nsPoint aPt,nsDisplayListBuilder * aBuilder)2624 ImgDrawResult nsTreeBodyFrame::PaintTreeBody(gfxContext& aRenderingContext,
2625 const nsRect& aDirtyRect,
2626 nsPoint aPt,
2627 nsDisplayListBuilder* aBuilder) {
2628 // Update our available height and our page count.
2629 CalcInnerBox();
2630
2631 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
2632
2633 aRenderingContext.Save();
2634 aRenderingContext.Clip(NSRectToSnappedRect(
2635 mInnerBox + aPt, PresContext()->AppUnitsPerDevPixel(), *drawTarget));
2636 int32_t oldPageCount = mPageLength;
2637 if (!mHasFixedRowCount)
2638 mPageLength =
2639 (mRowHeight > 0) ? (mInnerBox.height / mRowHeight) : mRowCount;
2640
2641 if (oldPageCount != mPageLength ||
2642 mHorzWidth != CalcHorzWidth(GetScrollParts())) {
2643 // Schedule a ResizeReflow that will update our info properly.
2644 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::Resize,
2645 NS_FRAME_IS_DIRTY);
2646 }
2647 #ifdef DEBUG
2648 int32_t rowCount = mRowCount;
2649 mView->GetRowCount(&rowCount);
2650 NS_WARNING_ASSERTION(mRowCount == rowCount, "row count changed unexpectedly");
2651 #endif
2652
2653 ImgDrawResult result = ImgDrawResult::SUCCESS;
2654
2655 // Loop through our columns and paint them (e.g., for sorting). This is only
2656 // relevant when painting backgrounds, since columns contain no content.
2657 // Content is contained in the rows.
2658 for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
2659 currCol = currCol->GetNext()) {
2660 nsRect colRect;
2661 nsresult rv =
2662 currCol->GetRect(this, mInnerBox.y, mInnerBox.height, &colRect);
2663 // Don't paint hidden columns.
2664 if (NS_FAILED(rv) || colRect.width == 0) continue;
2665
2666 if (OffsetForHorzScroll(colRect, false)) {
2667 nsRect dirtyRect;
2668 colRect += aPt;
2669 if (dirtyRect.IntersectRect(aDirtyRect, colRect)) {
2670 result &= PaintColumn(currCol, colRect, PresContext(),
2671 aRenderingContext, aDirtyRect);
2672 }
2673 }
2674 }
2675 // Loop through our on-screen rows.
2676 for (int32_t i = mTopRowIndex;
2677 i < mRowCount && i <= mTopRowIndex + mPageLength; i++) {
2678 nsRect rowRect(mInnerBox.x, mInnerBox.y + mRowHeight * (i - mTopRowIndex),
2679 mInnerBox.width, mRowHeight);
2680 nsRect dirtyRect;
2681 if (dirtyRect.IntersectRect(aDirtyRect, rowRect + aPt) &&
2682 rowRect.y < (mInnerBox.y + mInnerBox.height)) {
2683 result &= PaintRow(i, rowRect + aPt, PresContext(), aRenderingContext,
2684 aDirtyRect, aPt, aBuilder);
2685 }
2686 }
2687
2688 if (mSlots && mSlots->mDropAllowed &&
2689 (mSlots->mDropOrient == nsITreeView::DROP_BEFORE ||
2690 mSlots->mDropOrient == nsITreeView::DROP_AFTER)) {
2691 nscoord yPos = mInnerBox.y +
2692 mRowHeight * (mSlots->mDropRow - mTopRowIndex) -
2693 mRowHeight / 2;
2694 nsRect feedbackRect(mInnerBox.x, yPos, mInnerBox.width, mRowHeight);
2695 if (mSlots->mDropOrient == nsITreeView::DROP_AFTER)
2696 feedbackRect.y += mRowHeight;
2697
2698 nsRect dirtyRect;
2699 feedbackRect += aPt;
2700 if (dirtyRect.IntersectRect(aDirtyRect, feedbackRect)) {
2701 result &= PaintDropFeedback(feedbackRect, PresContext(),
2702 aRenderingContext, aDirtyRect, aPt);
2703 }
2704 }
2705 aRenderingContext.Restore();
2706
2707 return result;
2708 }
2709
PaintColumn(nsTreeColumn * aColumn,const nsRect & aColumnRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect)2710 ImgDrawResult nsTreeBodyFrame::PaintColumn(nsTreeColumn* aColumn,
2711 const nsRect& aColumnRect,
2712 nsPresContext* aPresContext,
2713 gfxContext& aRenderingContext,
2714 const nsRect& aDirtyRect) {
2715 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
2716
2717 // Now obtain the properties for our cell.
2718 PrefillPropertyArray(-1, aColumn);
2719 nsAutoString properties;
2720 mView->GetColumnProperties(aColumn, properties);
2721 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
2722
2723 // Resolve style for the column. It contains all the info we need to lay
2724 // ourselves out and to paint.
2725 ComputedStyle* colContext =
2726 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeColumn());
2727
2728 // Obtain the margins for the cell and then deflate our rect by that
2729 // amount. The cell is assumed to be contained within the deflated rect.
2730 nsRect colRect(aColumnRect);
2731 nsMargin colMargin;
2732 colContext->StyleMargin()->GetMargin(colMargin);
2733 colRect.Deflate(colMargin);
2734
2735 return PaintBackgroundLayer(colContext, aPresContext, aRenderingContext,
2736 colRect, aDirtyRect);
2737 }
2738
PaintRow(int32_t aRowIndex,const nsRect & aRowRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nsPoint aPt,nsDisplayListBuilder * aBuilder)2739 ImgDrawResult nsTreeBodyFrame::PaintRow(int32_t aRowIndex,
2740 const nsRect& aRowRect,
2741 nsPresContext* aPresContext,
2742 gfxContext& aRenderingContext,
2743 const nsRect& aDirtyRect, nsPoint aPt,
2744 nsDisplayListBuilder* aBuilder) {
2745 // We have been given a rect for our row. We treat this row like a full-blown
2746 // frame, meaning that it can have borders, margins, padding, and a
2747 // background.
2748
2749 // Without a view, we have no data. Check for this up front.
2750 if (!mView) {
2751 return ImgDrawResult::SUCCESS;
2752 }
2753
2754 nsresult rv;
2755
2756 // Now obtain the properties for our row.
2757 // XXX Automatically fill in the following props: open, closed, container,
2758 // leaf, selected, focused
2759 PrefillPropertyArray(aRowIndex, nullptr);
2760
2761 nsAutoString properties;
2762 mView->GetRowProperties(aRowIndex, properties);
2763 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
2764
2765 // Resolve style for the row. It contains all the info we need to lay
2766 // ourselves out and to paint.
2767 ComputedStyle* rowContext =
2768 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
2769
2770 // Obtain the margins for the row and then deflate our rect by that
2771 // amount. The row is assumed to be contained within the deflated rect.
2772 nsRect rowRect(aRowRect);
2773 nsMargin rowMargin;
2774 rowContext->StyleMargin()->GetMargin(rowMargin);
2775 rowRect.Deflate(rowMargin);
2776
2777 ImgDrawResult result = ImgDrawResult::SUCCESS;
2778
2779 // Paint our borders and background for our row rect.
2780 nsITheme* theme = nullptr;
2781 auto appearance = rowContext->StyleDisplay()->EffectiveAppearance();
2782 if (appearance != StyleAppearance::None) {
2783 theme = aPresContext->Theme();
2784 }
2785
2786 if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, appearance)) {
2787 nsRect dirty;
2788 dirty.IntersectRect(rowRect, aDirtyRect);
2789 theme->DrawWidgetBackground(&aRenderingContext, this, appearance, rowRect,
2790 dirty);
2791 } else {
2792 result &= PaintBackgroundLayer(rowContext, aPresContext, aRenderingContext,
2793 rowRect, aDirtyRect);
2794 }
2795
2796 // Adjust the rect for its border and padding.
2797 nsRect originalRowRect = rowRect;
2798 AdjustForBorderPadding(rowContext, rowRect);
2799
2800 bool isSeparator = false;
2801 mView->IsSeparator(aRowIndex, &isSeparator);
2802 if (isSeparator) {
2803 // The row is a separator.
2804
2805 nscoord primaryX = rowRect.x;
2806 nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn();
2807 if (primaryCol) {
2808 // Paint the primary cell.
2809 nsRect cellRect;
2810 rv = primaryCol->GetRect(this, rowRect.y, rowRect.height, &cellRect);
2811 if (NS_FAILED(rv)) {
2812 MOZ_ASSERT_UNREACHABLE("primary column is invalid");
2813 return result;
2814 }
2815
2816 if (OffsetForHorzScroll(cellRect, false)) {
2817 cellRect.x += aPt.x;
2818 nsRect dirtyRect;
2819 nsRect checkRect(cellRect.x, originalRowRect.y, cellRect.width,
2820 originalRowRect.height);
2821 if (dirtyRect.IntersectRect(aDirtyRect, checkRect)) {
2822 result &=
2823 PaintCell(aRowIndex, primaryCol, cellRect, aPresContext,
2824 aRenderingContext, aDirtyRect, primaryX, aPt, aBuilder);
2825 }
2826 }
2827
2828 // Paint the left side of the separator.
2829 nscoord currX;
2830 nsTreeColumn* previousCol = primaryCol->GetPrevious();
2831 if (previousCol) {
2832 nsRect prevColRect;
2833 rv = previousCol->GetRect(this, 0, 0, &prevColRect);
2834 if (NS_SUCCEEDED(rv)) {
2835 currX = (prevColRect.x - mHorzPosition) + prevColRect.width + aPt.x;
2836 } else {
2837 MOZ_ASSERT_UNREACHABLE(
2838 "The column before the primary column is "
2839 "invalid");
2840 currX = rowRect.x;
2841 }
2842 } else {
2843 currX = rowRect.x;
2844 }
2845
2846 int32_t level;
2847 mView->GetLevel(aRowIndex, &level);
2848 if (level == 0) currX += mIndentation;
2849
2850 if (currX > rowRect.x) {
2851 nsRect separatorRect(rowRect);
2852 separatorRect.width -= rowRect.x + rowRect.width - currX;
2853 result &= PaintSeparator(aRowIndex, separatorRect, aPresContext,
2854 aRenderingContext, aDirtyRect);
2855 }
2856 }
2857
2858 // Paint the right side (whole) separator.
2859 nsRect separatorRect(rowRect);
2860 if (primaryX > rowRect.x) {
2861 separatorRect.width -= primaryX - rowRect.x;
2862 separatorRect.x += primaryX - rowRect.x;
2863 }
2864 result &= PaintSeparator(aRowIndex, separatorRect, aPresContext,
2865 aRenderingContext, aDirtyRect);
2866 } else {
2867 // Now loop over our cells. Only paint a cell if it intersects with our
2868 // dirty rect.
2869 for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
2870 currCol = currCol->GetNext()) {
2871 nsRect cellRect;
2872 rv = currCol->GetRect(this, rowRect.y, rowRect.height, &cellRect);
2873 // Don't paint cells in hidden columns.
2874 if (NS_FAILED(rv) || cellRect.width == 0) continue;
2875
2876 if (OffsetForHorzScroll(cellRect, false)) {
2877 cellRect.x += aPt.x;
2878
2879 // for primary columns, use the row's vertical size so that the
2880 // lines get drawn properly
2881 nsRect checkRect = cellRect;
2882 if (currCol->IsPrimary())
2883 checkRect = nsRect(cellRect.x, originalRowRect.y, cellRect.width,
2884 originalRowRect.height);
2885
2886 nsRect dirtyRect;
2887 nscoord dummy;
2888 if (dirtyRect.IntersectRect(aDirtyRect, checkRect))
2889 result &=
2890 PaintCell(aRowIndex, currCol, cellRect, aPresContext,
2891 aRenderingContext, aDirtyRect, dummy, aPt, aBuilder);
2892 }
2893 }
2894 }
2895
2896 return result;
2897 }
2898
PaintSeparator(int32_t aRowIndex,const nsRect & aSeparatorRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect)2899 ImgDrawResult nsTreeBodyFrame::PaintSeparator(int32_t aRowIndex,
2900 const nsRect& aSeparatorRect,
2901 nsPresContext* aPresContext,
2902 gfxContext& aRenderingContext,
2903 const nsRect& aDirtyRect) {
2904 // Resolve style for the separator.
2905 ComputedStyle* separatorContext =
2906 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeSeparator());
2907 bool useTheme = false;
2908 nsITheme* theme = nullptr;
2909 StyleAppearance appearance =
2910 separatorContext->StyleDisplay()->EffectiveAppearance();
2911 if (appearance != StyleAppearance::None) {
2912 theme = aPresContext->Theme();
2913 if (theme->ThemeSupportsWidget(aPresContext, nullptr, appearance))
2914 useTheme = true;
2915 }
2916
2917 ImgDrawResult result = ImgDrawResult::SUCCESS;
2918
2919 // use -moz-appearance if provided.
2920 if (useTheme) {
2921 nsRect dirty;
2922 dirty.IntersectRect(aSeparatorRect, aDirtyRect);
2923 theme->DrawWidgetBackground(&aRenderingContext, this, appearance,
2924 aSeparatorRect, dirty);
2925 } else {
2926 const nsStylePosition* stylePosition = separatorContext->StylePosition();
2927
2928 // Obtain the height for the separator or use the default value.
2929 nscoord height;
2930 if (stylePosition->mHeight.ConvertsToLength()) {
2931 height = stylePosition->mHeight.ToLength();
2932 } else {
2933 // Use default height 2px.
2934 height = nsPresContext::CSSPixelsToAppUnits(2);
2935 }
2936
2937 // Obtain the margins for the separator and then deflate our rect by that
2938 // amount. The separator is assumed to be contained within the deflated
2939 // rect.
2940 nsRect separatorRect(aSeparatorRect.x, aSeparatorRect.y,
2941 aSeparatorRect.width, height);
2942 nsMargin separatorMargin;
2943 separatorContext->StyleMargin()->GetMargin(separatorMargin);
2944 separatorRect.Deflate(separatorMargin);
2945
2946 // Center the separator.
2947 separatorRect.y += (aSeparatorRect.height - height) / 2;
2948
2949 result &=
2950 PaintBackgroundLayer(separatorContext, aPresContext, aRenderingContext,
2951 separatorRect, aDirtyRect);
2952 }
2953
2954 return result;
2955 }
2956
PaintCell(int32_t aRowIndex,nsTreeColumn * aColumn,const nsRect & aCellRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nscoord & aCurrX,nsPoint aPt,nsDisplayListBuilder * aBuilder)2957 ImgDrawResult nsTreeBodyFrame::PaintCell(
2958 int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aCellRect,
2959 nsPresContext* aPresContext, gfxContext& aRenderingContext,
2960 const nsRect& aDirtyRect, nscoord& aCurrX, nsPoint aPt,
2961 nsDisplayListBuilder* aBuilder) {
2962 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
2963
2964 // Now obtain the properties for our cell.
2965 // XXX Automatically fill in the following props: open, closed, container,
2966 // leaf, selected, focused, and the col ID.
2967 PrefillPropertyArray(aRowIndex, aColumn);
2968 nsAutoString properties;
2969 mView->GetCellProperties(aRowIndex, aColumn, properties);
2970 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
2971
2972 // Resolve style for the cell. It contains all the info we need to lay
2973 // ourselves out and to paint.
2974 ComputedStyle* cellContext =
2975 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
2976
2977 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
2978
2979 // Obtain the margins for the cell and then deflate our rect by that
2980 // amount. The cell is assumed to be contained within the deflated rect.
2981 nsRect cellRect(aCellRect);
2982 nsMargin cellMargin;
2983 cellContext->StyleMargin()->GetMargin(cellMargin);
2984 cellRect.Deflate(cellMargin);
2985
2986 // Paint our borders and background for our row rect.
2987 ImgDrawResult result = PaintBackgroundLayer(
2988 cellContext, aPresContext, aRenderingContext, cellRect, aDirtyRect);
2989
2990 // Adjust the rect for its border and padding.
2991 AdjustForBorderPadding(cellContext, cellRect);
2992
2993 nscoord currX = cellRect.x;
2994 nscoord remainingWidth = cellRect.width;
2995
2996 // Now we paint the contents of the cells.
2997 // Directionality of the tree determines the order in which we paint.
2998 // StyleDirection::Ltr means paint from left to right.
2999 // StyleDirection::Rtl means paint from right to left.
3000
3001 if (aColumn->IsPrimary()) {
3002 // If we're the primary column, we need to indent and paint the twisty and
3003 // any connecting lines between siblings.
3004
3005 int32_t level;
3006 mView->GetLevel(aRowIndex, &level);
3007
3008 if (!isRTL) currX += mIndentation * level;
3009 remainingWidth -= mIndentation * level;
3010
3011 // Resolve the style to use for the connecting lines.
3012 ComputedStyle* lineContext =
3013 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeLine());
3014
3015 if (mIndentation && level &&
3016 lineContext->StyleVisibility()->IsVisibleOrCollapsed()) {
3017 // Paint the thread lines.
3018
3019 // Get the size of the twisty. We don't want to paint the twisty
3020 // before painting of connecting lines since it would paint lines over
3021 // the twisty. But we need to leave a place for it.
3022 ComputedStyle* twistyContext =
3023 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
3024
3025 nsRect imageSize;
3026 nsRect twistyRect(aCellRect);
3027 GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, aPresContext,
3028 twistyContext);
3029
3030 nsMargin twistyMargin;
3031 twistyContext->StyleMargin()->GetMargin(twistyMargin);
3032 twistyRect.Inflate(twistyMargin);
3033
3034 const nsStyleBorder* borderStyle = lineContext->StyleBorder();
3035 // Resolve currentcolor values against the treeline context
3036 nscolor color = borderStyle->mBorderLeftColor.CalcColor(*lineContext);
3037 ColorPattern colorPatt(ToDeviceColor(color));
3038
3039 StyleBorderStyle style = borderStyle->GetBorderStyle(eSideLeft);
3040 StrokeOptions strokeOptions;
3041 nsLayoutUtils::InitDashPattern(strokeOptions, style);
3042
3043 nscoord srcX = currX + twistyRect.width - mIndentation / 2;
3044 nscoord lineY = (aRowIndex - mTopRowIndex) * mRowHeight + aPt.y;
3045
3046 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
3047 nsPresContext* pc = PresContext();
3048
3049 // Don't paint off our cell.
3050 if (srcX <= cellRect.x + cellRect.width) {
3051 nscoord destX = currX + twistyRect.width;
3052 if (destX > cellRect.x + cellRect.width)
3053 destX = cellRect.x + cellRect.width;
3054 if (isRTL) {
3055 srcX = currX + remainingWidth - (srcX - cellRect.x);
3056 destX = currX + remainingWidth - (destX - cellRect.x);
3057 }
3058 Point p1(pc->AppUnitsToGfxUnits(srcX),
3059 pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2));
3060 Point p2(pc->AppUnitsToGfxUnits(destX),
3061 pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2));
3062 SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget,
3063 strokeOptions.mLineWidth);
3064 drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions);
3065 }
3066
3067 int32_t currentParent = aRowIndex;
3068 for (int32_t i = level; i > 0; i--) {
3069 if (srcX <= cellRect.x + cellRect.width) {
3070 // Paint full vertical line only if we have next sibling.
3071 bool hasNextSibling;
3072 mView->HasNextSibling(currentParent, aRowIndex, &hasNextSibling);
3073 if (hasNextSibling || i == level) {
3074 Point p1(pc->AppUnitsToGfxUnits(srcX),
3075 pc->AppUnitsToGfxUnits(lineY));
3076 Point p2;
3077 p2.x = pc->AppUnitsToGfxUnits(srcX);
3078
3079 if (hasNextSibling)
3080 p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight);
3081 else if (i == level)
3082 p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2);
3083
3084 SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget,
3085 strokeOptions.mLineWidth);
3086 drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions);
3087 }
3088 }
3089
3090 int32_t parent;
3091 if (NS_FAILED(mView->GetParentIndex(currentParent, &parent)) ||
3092 parent < 0)
3093 break;
3094 currentParent = parent;
3095 srcX -= mIndentation;
3096 }
3097 }
3098
3099 // Always leave space for the twisty.
3100 nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
3101 result &= PaintTwisty(aRowIndex, aColumn, twistyRect, aPresContext,
3102 aRenderingContext, aDirtyRect, remainingWidth, currX);
3103 }
3104
3105 // Now paint the icon for our cell.
3106 nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);
3107 nsRect dirtyRect;
3108 if (dirtyRect.IntersectRect(aDirtyRect, iconRect)) {
3109 result &= PaintImage(aRowIndex, aColumn, iconRect, aPresContext,
3110 aRenderingContext, aDirtyRect, remainingWidth, currX,
3111 aBuilder);
3112 }
3113
3114 // Now paint our element, but only if we aren't a cycler column.
3115 // XXX until we have the ability to load images, allow the view to
3116 // insert text into cycler columns...
3117 if (!aColumn->IsCycler()) {
3118 nsRect elementRect(currX, cellRect.y, remainingWidth, cellRect.height);
3119 nsRect dirtyRect;
3120 if (dirtyRect.IntersectRect(aDirtyRect, elementRect)) {
3121 switch (aColumn->GetType()) {
3122 case TreeColumn_Binding::TYPE_TEXT:
3123 result &= PaintText(aRowIndex, aColumn, elementRect, aPresContext,
3124 aRenderingContext, aDirtyRect, currX);
3125 break;
3126 case TreeColumn_Binding::TYPE_CHECKBOX:
3127 result &= PaintCheckbox(aRowIndex, aColumn, elementRect, aPresContext,
3128 aRenderingContext, aDirtyRect);
3129 break;
3130 }
3131 }
3132 }
3133
3134 aCurrX = currX;
3135
3136 return result;
3137 }
3138
PaintTwisty(int32_t aRowIndex,nsTreeColumn * aColumn,const nsRect & aTwistyRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nscoord & aRemainingWidth,nscoord & aCurrX)3139 ImgDrawResult nsTreeBodyFrame::PaintTwisty(
3140 int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aTwistyRect,
3141 nsPresContext* aPresContext, gfxContext& aRenderingContext,
3142 const nsRect& aDirtyRect, nscoord& aRemainingWidth, nscoord& aCurrX) {
3143 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
3144
3145 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
3146 nscoord rightEdge = aCurrX + aRemainingWidth;
3147 // Paint the twisty, but only if we are a non-empty container.
3148 bool shouldPaint = false;
3149 bool isContainer = false;
3150 mView->IsContainer(aRowIndex, &isContainer);
3151 if (isContainer) {
3152 bool isContainerEmpty = false;
3153 mView->IsContainerEmpty(aRowIndex, &isContainerEmpty);
3154 if (!isContainerEmpty) shouldPaint = true;
3155 }
3156
3157 // Resolve style for the twisty.
3158 ComputedStyle* twistyContext =
3159 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
3160
3161 // Obtain the margins for the twisty and then deflate our rect by that
3162 // amount. The twisty is assumed to be contained within the deflated rect.
3163 nsRect twistyRect(aTwistyRect);
3164 nsMargin twistyMargin;
3165 twistyContext->StyleMargin()->GetMargin(twistyMargin);
3166 twistyRect.Deflate(twistyMargin);
3167
3168 nsRect imageSize;
3169 nsITheme* theme = GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect,
3170 aPresContext, twistyContext);
3171
3172 // Subtract out the remaining width. This is done even when we don't actually
3173 // paint a twisty in this cell, so that cells in different rows still line up.
3174 nsRect copyRect(twistyRect);
3175 copyRect.Inflate(twistyMargin);
3176 aRemainingWidth -= copyRect.width;
3177 if (!isRTL) aCurrX += copyRect.width;
3178
3179 ImgDrawResult result = ImgDrawResult::SUCCESS;
3180
3181 if (shouldPaint) {
3182 // Paint our borders and background for our image rect.
3183 result &= PaintBackgroundLayer(twistyContext, aPresContext,
3184 aRenderingContext, twistyRect, aDirtyRect);
3185
3186 if (theme) {
3187 if (isRTL) twistyRect.x = rightEdge - twistyRect.width;
3188 // yeah, I know it says we're drawing a background, but a twisty is really
3189 // a fg object since it doesn't have anything that gecko would want to
3190 // draw over it. Besides, we have to prevent imagelib from drawing it.
3191 nsRect dirty;
3192 dirty.IntersectRect(twistyRect, aDirtyRect);
3193 theme->DrawWidgetBackground(
3194 &aRenderingContext, this,
3195 twistyContext->StyleDisplay()->EffectiveAppearance(), twistyRect,
3196 dirty);
3197 } else {
3198 // Time to paint the twisty.
3199 // Adjust the rect for its border and padding.
3200 nsMargin bp(0, 0, 0, 0);
3201 GetBorderPadding(twistyContext, bp);
3202 twistyRect.Deflate(bp);
3203 if (isRTL) twistyRect.x = rightEdge - twistyRect.width;
3204 imageSize.Deflate(bp);
3205
3206 // Get the image for drawing.
3207 nsCOMPtr<imgIContainer> image;
3208 bool useImageRegion = true;
3209 GetImage(aRowIndex, aColumn, true, twistyContext, useImageRegion,
3210 getter_AddRefs(image));
3211 if (image) {
3212 nsPoint anchorPoint = twistyRect.TopLeft();
3213
3214 // Center the image. XXX Obey vertical-align style prop?
3215 if (imageSize.height < twistyRect.height) {
3216 anchorPoint.y += (twistyRect.height - imageSize.height) / 2;
3217 }
3218
3219 // Apply context paint if applicable
3220 Maybe<SVGImageContext> svgContext;
3221 SVGImageContext::MaybeStoreContextPaint(svgContext, twistyContext,
3222 image);
3223
3224 // Paint the image.
3225 result &= nsLayoutUtils::DrawSingleUnscaledImage(
3226 aRenderingContext, aPresContext, image, SamplingFilter::POINT,
3227 anchorPoint, &aDirtyRect, svgContext, imgIContainer::FLAG_NONE,
3228 &imageSize);
3229 }
3230 }
3231 }
3232
3233 return result;
3234 }
3235
PaintImage(int32_t aRowIndex,nsTreeColumn * aColumn,const nsRect & aImageRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nscoord & aRemainingWidth,nscoord & aCurrX,nsDisplayListBuilder * aBuilder)3236 ImgDrawResult nsTreeBodyFrame::PaintImage(
3237 int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aImageRect,
3238 nsPresContext* aPresContext, gfxContext& aRenderingContext,
3239 const nsRect& aDirtyRect, nscoord& aRemainingWidth, nscoord& aCurrX,
3240 nsDisplayListBuilder* aBuilder) {
3241 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
3242
3243 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
3244 nscoord rightEdge = aCurrX + aRemainingWidth;
3245 // Resolve style for the image.
3246 ComputedStyle* imageContext =
3247 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeImage());
3248
3249 // Obtain opacity value for the image.
3250 float opacity = imageContext->StyleEffects()->mOpacity;
3251
3252 // Obtain the margins for the image and then deflate our rect by that
3253 // amount. The image is assumed to be contained within the deflated rect.
3254 nsRect imageRect(aImageRect);
3255 nsMargin imageMargin;
3256 imageContext->StyleMargin()->GetMargin(imageMargin);
3257 imageRect.Deflate(imageMargin);
3258
3259 // Get the image.
3260 bool useImageRegion = true;
3261 nsCOMPtr<imgIContainer> image;
3262 GetImage(aRowIndex, aColumn, false, imageContext, useImageRegion,
3263 getter_AddRefs(image));
3264
3265 // Get the image destination size.
3266 nsSize imageDestSize = GetImageDestSize(imageContext, useImageRegion, image);
3267 if (!imageDestSize.width || !imageDestSize.height) {
3268 return ImgDrawResult::SUCCESS;
3269 }
3270
3271 // Get the borders and padding.
3272 nsMargin bp(0, 0, 0, 0);
3273 GetBorderPadding(imageContext, bp);
3274
3275 // destRect will be passed as the aDestRect argument in the DrawImage method.
3276 // Start with the imageDestSize width and height.
3277 nsRect destRect(0, 0, imageDestSize.width, imageDestSize.height);
3278 // Inflate destRect for borders and padding so that we can compare/adjust
3279 // with respect to imageRect.
3280 destRect.Inflate(bp);
3281
3282 // The destRect width and height have not been adjusted to fit within the
3283 // cell width and height.
3284 // We must adjust the width even if image is null, because the width is used
3285 // to update the aRemainingWidth and aCurrX values.
3286 // Since the height isn't used unless the image is not null, we will adjust
3287 // the height inside the if (image) block below.
3288
3289 if (destRect.width > imageRect.width) {
3290 // The destRect is too wide to fit within the cell width.
3291 // Adjust destRect width to fit within the cell width.
3292 destRect.width = imageRect.width;
3293 } else {
3294 // The cell is wider than the destRect.
3295 // In a cycler column, the image is centered horizontally.
3296 if (!aColumn->IsCycler()) {
3297 // If this column is not a cycler, we won't center the image horizontally.
3298 // We adjust the imageRect width so that the image is placed at the start
3299 // of the cell.
3300 imageRect.width = destRect.width;
3301 }
3302 }
3303
3304 ImgDrawResult result = ImgDrawResult::SUCCESS;
3305
3306 if (image) {
3307 if (isRTL) imageRect.x = rightEdge - imageRect.width;
3308 // Paint our borders and background for our image rect
3309 result &= PaintBackgroundLayer(imageContext, aPresContext,
3310 aRenderingContext, imageRect, aDirtyRect);
3311
3312 // The destRect x and y have not been set yet. Let's do that now.
3313 // Initially, we use the imageRect x and y.
3314 destRect.x = imageRect.x;
3315 destRect.y = imageRect.y;
3316
3317 if (destRect.width < imageRect.width) {
3318 // The destRect width is smaller than the cell width.
3319 // Center the image horizontally in the cell.
3320 // Adjust the destRect x accordingly.
3321 destRect.x += (imageRect.width - destRect.width) / 2;
3322 }
3323
3324 // Now it's time to adjust the destRect height to fit within the cell
3325 // height.
3326 if (destRect.height > imageRect.height) {
3327 // The destRect height is larger than the cell height.
3328 // Adjust destRect height to fit within the cell height.
3329 destRect.height = imageRect.height;
3330 } else if (destRect.height < imageRect.height) {
3331 // The destRect height is smaller than the cell height.
3332 // Center the image vertically in the cell.
3333 // Adjust the destRect y accordingly.
3334 destRect.y += (imageRect.height - destRect.height) / 2;
3335 }
3336
3337 // It's almost time to paint the image.
3338 // Deflate destRect for the border and padding.
3339 destRect.Deflate(bp);
3340
3341 // Compute the area where our whole image would be mapped, to get the
3342 // desired subregion onto our actual destRect:
3343 nsRect wholeImageDest;
3344 CSSIntSize rawImageCSSIntSize;
3345 if (NS_SUCCEEDED(image->GetWidth(&rawImageCSSIntSize.width)) &&
3346 NS_SUCCEEDED(image->GetHeight(&rawImageCSSIntSize.height))) {
3347 // Get the image source rectangle - the rectangle containing the part of
3348 // the image that we are going to display. sourceRect will be passed as
3349 // the aSrcRect argument in the DrawImage method.
3350 nsRect sourceRect =
3351 GetImageSourceRect(imageContext, useImageRegion, image);
3352
3353 // Let's say that the image is 100 pixels tall and that the CSS has
3354 // specified that the destination height should be 50 pixels tall. Let's
3355 // say that the cell height is only 20 pixels. So, in those 20 visible
3356 // pixels, we want to see the top 20/50ths of the image. So, the
3357 // sourceRect.height should be 100 * 20 / 50, which is 40 pixels.
3358 // Essentially, we are scaling the image as dictated by the CSS
3359 // destination height and width, and we are then clipping the scaled
3360 // image by the cell width and height.
3361 nsSize rawImageSize(CSSPixel::ToAppUnits(rawImageCSSIntSize));
3362 wholeImageDest = nsLayoutUtils::GetWholeImageDestination(
3363 rawImageSize, sourceRect, nsRect(destRect.TopLeft(), imageDestSize));
3364 } else {
3365 // GetWidth/GetHeight failed, so we can't easily map a subregion of the
3366 // source image onto the destination area.
3367 // * If this happens with a RasterImage, it probably means the image is
3368 // in an error state, and we shouldn't draw anything. Hence, we leave
3369 // wholeImageDest as an empty rect (its initial state).
3370 // * If this happens with a VectorImage, it probably means the image has
3371 // no explicit width or height attribute -- but we can still proceed and
3372 // just treat the destination area as our whole SVG image area. Hence, we
3373 // set wholeImageDest to the full destRect.
3374 if (image->GetType() == imgIContainer::TYPE_VECTOR) {
3375 wholeImageDest = destRect;
3376 }
3377 }
3378
3379 if (opacity != 1.0f) {
3380 aRenderingContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
3381 opacity);
3382 }
3383
3384 uint32_t drawFlags = aBuilder && aBuilder->UseHighQualityScaling()
3385 ? imgIContainer::FLAG_HIGH_QUALITY_SCALING
3386 : imgIContainer::FLAG_NONE;
3387 result &= nsLayoutUtils::DrawImage(
3388 aRenderingContext, imageContext, aPresContext, image,
3389 nsLayoutUtils::GetSamplingFilterForFrame(this), wholeImageDest,
3390 destRect, destRect.TopLeft(), aDirtyRect, drawFlags);
3391
3392 if (opacity != 1.0f) {
3393 aRenderingContext.PopGroupAndBlend();
3394 }
3395 }
3396
3397 // Update the aRemainingWidth and aCurrX values.
3398 imageRect.Inflate(imageMargin);
3399 aRemainingWidth -= imageRect.width;
3400 if (!isRTL) {
3401 aCurrX += imageRect.width;
3402 }
3403
3404 return result;
3405 }
3406
PaintText(int32_t aRowIndex,nsTreeColumn * aColumn,const nsRect & aTextRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nscoord & aCurrX)3407 ImgDrawResult nsTreeBodyFrame::PaintText(
3408 int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aTextRect,
3409 nsPresContext* aPresContext, gfxContext& aRenderingContext,
3410 const nsRect& aDirtyRect, nscoord& aCurrX) {
3411 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
3412
3413 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
3414
3415 // Now obtain the text for our cell.
3416 nsAutoString text;
3417 mView->GetCellText(aRowIndex, aColumn, text);
3418
3419 // We're going to paint this text so we need to ensure bidi is enabled if
3420 // necessary
3421 CheckTextForBidi(text);
3422
3423 ImgDrawResult result = ImgDrawResult::SUCCESS;
3424
3425 if (text.Length() == 0) {
3426 // Don't paint an empty string. XXX What about background/borders? Still
3427 // paint?
3428 return result;
3429 }
3430
3431 int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
3432 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
3433
3434 // Resolve style for the text. It contains all the info we need to lay
3435 // ourselves out and to paint.
3436 ComputedStyle* textContext =
3437 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
3438
3439 // Obtain opacity value for the image.
3440 float opacity = textContext->StyleEffects()->mOpacity;
3441
3442 // Obtain the margins for the text and then deflate our rect by that
3443 // amount. The text is assumed to be contained within the deflated rect.
3444 nsRect textRect(aTextRect);
3445 nsMargin textMargin;
3446 textContext->StyleMargin()->GetMargin(textMargin);
3447 textRect.Deflate(textMargin);
3448
3449 // Adjust the rect for its border and padding.
3450 nsMargin bp(0, 0, 0, 0);
3451 GetBorderPadding(textContext, bp);
3452 textRect.Deflate(bp);
3453
3454 // Compute our text size.
3455 RefPtr<nsFontMetrics> fontMet =
3456 nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, PresContext());
3457
3458 nscoord height = fontMet->MaxHeight();
3459 nscoord baseline = fontMet->MaxAscent();
3460
3461 // Center the text. XXX Obey vertical-align style prop?
3462 if (height < textRect.height) {
3463 textRect.y += (textRect.height - height) / 2;
3464 textRect.height = height;
3465 }
3466
3467 // Set our font.
3468 AdjustForCellText(text, aRowIndex, aColumn, aRenderingContext, *fontMet,
3469 textRect);
3470 textRect.Inflate(bp);
3471
3472 // Subtract out the remaining width.
3473 if (!isRTL) aCurrX += textRect.width + textMargin.LeftRight();
3474
3475 result &= PaintBackgroundLayer(textContext, aPresContext, aRenderingContext,
3476 textRect, aDirtyRect);
3477
3478 // Time to paint our text.
3479 textRect.Deflate(bp);
3480
3481 // Set our color.
3482 ColorPattern color(ToDeviceColor(textContext->StyleText()->mColor));
3483
3484 // Draw decorations.
3485 StyleTextDecorationLine decorations =
3486 textContext->StyleTextReset()->mTextDecorationLine;
3487
3488 nscoord offset;
3489 nscoord size;
3490 if (decorations & (StyleTextDecorationLine::OVERLINE |
3491 StyleTextDecorationLine::UNDERLINE)) {
3492 fontMet->GetUnderline(offset, size);
3493 if (decorations & StyleTextDecorationLine::OVERLINE) {
3494 nsRect r(textRect.x, textRect.y, textRect.width, size);
3495 Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
3496 drawTarget->FillRect(devPxRect, color);
3497 }
3498 if (decorations & StyleTextDecorationLine::UNDERLINE) {
3499 nsRect r(textRect.x, textRect.y + baseline - offset, textRect.width,
3500 size);
3501 Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
3502 drawTarget->FillRect(devPxRect, color);
3503 }
3504 }
3505 if (decorations & StyleTextDecorationLine::LINE_THROUGH) {
3506 fontMet->GetStrikeout(offset, size);
3507 nsRect r(textRect.x, textRect.y + baseline - offset, textRect.width, size);
3508 Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
3509 drawTarget->FillRect(devPxRect, color);
3510 }
3511 ComputedStyle* cellContext =
3512 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
3513
3514 if (opacity != 1.0f) {
3515 aRenderingContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
3516 opacity);
3517 }
3518
3519 aRenderingContext.SetColor(
3520 sRGBColor::FromABGR(textContext->StyleText()->mColor.ToColor()));
3521 nsLayoutUtils::DrawString(
3522 this, *fontMet, &aRenderingContext, text.get(), text.Length(),
3523 textRect.TopLeft() + nsPoint(0, baseline), cellContext);
3524
3525 if (opacity != 1.0f) {
3526 aRenderingContext.PopGroupAndBlend();
3527 }
3528
3529 return result;
3530 }
3531
PaintCheckbox(int32_t aRowIndex,nsTreeColumn * aColumn,const nsRect & aCheckboxRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect)3532 ImgDrawResult nsTreeBodyFrame::PaintCheckbox(int32_t aRowIndex,
3533 nsTreeColumn* aColumn,
3534 const nsRect& aCheckboxRect,
3535 nsPresContext* aPresContext,
3536 gfxContext& aRenderingContext,
3537 const nsRect& aDirtyRect) {
3538 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
3539
3540 // Resolve style for the checkbox.
3541 ComputedStyle* checkboxContext =
3542 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCheckbox());
3543
3544 nscoord rightEdge = aCheckboxRect.XMost();
3545
3546 // Obtain the margins for the checkbox and then deflate our rect by that
3547 // amount. The checkbox is assumed to be contained within the deflated rect.
3548 nsRect checkboxRect(aCheckboxRect);
3549 nsMargin checkboxMargin;
3550 checkboxContext->StyleMargin()->GetMargin(checkboxMargin);
3551 checkboxRect.Deflate(checkboxMargin);
3552
3553 nsRect imageSize = GetImageSize(aRowIndex, aColumn, true, checkboxContext);
3554
3555 if (imageSize.height > checkboxRect.height) {
3556 imageSize.height = checkboxRect.height;
3557 }
3558 if (imageSize.width > checkboxRect.width) {
3559 imageSize.width = checkboxRect.width;
3560 }
3561
3562 if (StyleVisibility()->mDirection == StyleDirection::Rtl) {
3563 checkboxRect.x = rightEdge - checkboxRect.width;
3564 }
3565
3566 // Paint our borders and background for our image rect.
3567 ImgDrawResult result =
3568 PaintBackgroundLayer(checkboxContext, aPresContext, aRenderingContext,
3569 checkboxRect, aDirtyRect);
3570
3571 // Time to paint the checkbox.
3572 // Adjust the rect for its border and padding.
3573 nsMargin bp(0, 0, 0, 0);
3574 GetBorderPadding(checkboxContext, bp);
3575 checkboxRect.Deflate(bp);
3576
3577 // Get the image for drawing.
3578 nsCOMPtr<imgIContainer> image;
3579 bool useImageRegion = true;
3580 GetImage(aRowIndex, aColumn, true, checkboxContext, useImageRegion,
3581 getter_AddRefs(image));
3582 if (image) {
3583 nsPoint pt = checkboxRect.TopLeft();
3584
3585 if (imageSize.height < checkboxRect.height) {
3586 pt.y += (checkboxRect.height - imageSize.height) / 2;
3587 }
3588
3589 if (imageSize.width < checkboxRect.width) {
3590 pt.x += (checkboxRect.width - imageSize.width) / 2;
3591 }
3592
3593 // Apply context paint if applicable
3594 Maybe<SVGImageContext> svgContext;
3595 SVGImageContext::MaybeStoreContextPaint(svgContext, checkboxContext, image);
3596 // Paint the image.
3597 result &= nsLayoutUtils::DrawSingleUnscaledImage(
3598 aRenderingContext, aPresContext, image, SamplingFilter::POINT, pt,
3599 &aDirtyRect, svgContext, imgIContainer::FLAG_NONE, &imageSize);
3600 }
3601
3602 return result;
3603 }
3604
PaintDropFeedback(const nsRect & aDropFeedbackRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nsPoint aPt)3605 ImgDrawResult nsTreeBodyFrame::PaintDropFeedback(
3606 const nsRect& aDropFeedbackRect, nsPresContext* aPresContext,
3607 gfxContext& aRenderingContext, const nsRect& aDirtyRect, nsPoint aPt) {
3608 // Paint the drop feedback in between rows.
3609
3610 nscoord currX;
3611
3612 // Adjust for the primary cell.
3613 nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn();
3614
3615 if (primaryCol) {
3616 #ifdef DEBUG
3617 nsresult rv =
3618 #endif
3619 primaryCol->GetXInTwips(this, &currX);
3620 NS_ASSERTION(NS_SUCCEEDED(rv), "primary column is invalid?");
3621
3622 currX += aPt.x - mHorzPosition;
3623 } else {
3624 currX = aDropFeedbackRect.x;
3625 }
3626
3627 PrefillPropertyArray(mSlots->mDropRow, primaryCol);
3628
3629 // Resolve the style to use for the drop feedback.
3630 ComputedStyle* feedbackContext =
3631 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeDropFeedback());
3632
3633 ImgDrawResult result = ImgDrawResult::SUCCESS;
3634
3635 // Paint only if it is visible.
3636 if (feedbackContext->StyleVisibility()->IsVisibleOrCollapsed()) {
3637 int32_t level;
3638 mView->GetLevel(mSlots->mDropRow, &level);
3639
3640 // If our previous or next row has greater level use that for
3641 // correct visual indentation.
3642 if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) {
3643 if (mSlots->mDropRow > 0) {
3644 int32_t previousLevel;
3645 mView->GetLevel(mSlots->mDropRow - 1, &previousLevel);
3646 if (previousLevel > level) level = previousLevel;
3647 }
3648 } else {
3649 if (mSlots->mDropRow < mRowCount - 1) {
3650 int32_t nextLevel;
3651 mView->GetLevel(mSlots->mDropRow + 1, &nextLevel);
3652 if (nextLevel > level) level = nextLevel;
3653 }
3654 }
3655
3656 currX += mIndentation * level;
3657
3658 if (primaryCol) {
3659 ComputedStyle* twistyContext =
3660 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
3661 nsRect imageSize;
3662 nsRect twistyRect;
3663 GetTwistyRect(mSlots->mDropRow, primaryCol, imageSize, twistyRect,
3664 aPresContext, twistyContext);
3665 nsMargin twistyMargin;
3666 twistyContext->StyleMargin()->GetMargin(twistyMargin);
3667 twistyRect.Inflate(twistyMargin);
3668 currX += twistyRect.width;
3669 }
3670
3671 const nsStylePosition* stylePosition = feedbackContext->StylePosition();
3672
3673 // Obtain the width for the drop feedback or use default value.
3674 nscoord width;
3675 if (stylePosition->mWidth.ConvertsToLength()) {
3676 width = stylePosition->mWidth.ToLength();
3677 } else {
3678 // Use default width 50px.
3679 width = nsPresContext::CSSPixelsToAppUnits(50);
3680 }
3681
3682 // Obtain the height for the drop feedback or use default value.
3683 nscoord height;
3684 if (stylePosition->mHeight.ConvertsToLength()) {
3685 height = stylePosition->mHeight.ToLength();
3686 } else {
3687 // Use default height 2px.
3688 height = nsPresContext::CSSPixelsToAppUnits(2);
3689 }
3690
3691 // Obtain the margins for the drop feedback and then deflate our rect
3692 // by that amount.
3693 nsRect feedbackRect(currX, aDropFeedbackRect.y, width, height);
3694 nsMargin margin;
3695 feedbackContext->StyleMargin()->GetMargin(margin);
3696 feedbackRect.Deflate(margin);
3697
3698 feedbackRect.y += (aDropFeedbackRect.height - height) / 2;
3699
3700 // Finally paint the drop feedback.
3701 result &= PaintBackgroundLayer(feedbackContext, aPresContext,
3702 aRenderingContext, feedbackRect, aDirtyRect);
3703 }
3704
3705 return result;
3706 }
3707
PaintBackgroundLayer(ComputedStyle * aComputedStyle,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aRect,const nsRect & aDirtyRect)3708 ImgDrawResult nsTreeBodyFrame::PaintBackgroundLayer(
3709 ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
3710 gfxContext& aRenderingContext, const nsRect& aRect,
3711 const nsRect& aDirtyRect) {
3712 const nsStyleBorder* myBorder = aComputedStyle->StyleBorder();
3713 nsCSSRendering::PaintBGParams params =
3714 nsCSSRendering::PaintBGParams::ForAllLayers(
3715 *aPresContext, aDirtyRect, aRect, this,
3716 nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES);
3717 ImgDrawResult result = nsCSSRendering::PaintStyleImageLayerWithSC(
3718 params, aRenderingContext, aComputedStyle, *myBorder);
3719
3720 result &= nsCSSRendering::PaintBorderWithStyleBorder(
3721 aPresContext, aRenderingContext, this, aDirtyRect, aRect, *myBorder,
3722 mComputedStyle, PaintBorderFlags::SyncDecodeImages);
3723
3724 nsCSSRendering::PaintNonThemedOutline(aPresContext, aRenderingContext, this,
3725 aDirtyRect, aRect, aComputedStyle);
3726
3727 return result;
3728 }
3729
3730 // Scrolling
EnsureRowIsVisible(int32_t aRow)3731 nsresult nsTreeBodyFrame::EnsureRowIsVisible(int32_t aRow) {
3732 ScrollParts parts = GetScrollParts();
3733 nsresult rv = EnsureRowIsVisibleInternal(parts, aRow);
3734 NS_ENSURE_SUCCESS(rv, rv);
3735 UpdateScrollbars(parts);
3736 return rv;
3737 }
3738
EnsureRowIsVisibleInternal(const ScrollParts & aParts,int32_t aRow)3739 nsresult nsTreeBodyFrame::EnsureRowIsVisibleInternal(const ScrollParts& aParts,
3740 int32_t aRow) {
3741 if (!mView || !mPageLength) return NS_OK;
3742
3743 if (mTopRowIndex <= aRow && mTopRowIndex + mPageLength > aRow) return NS_OK;
3744
3745 if (aRow < mTopRowIndex)
3746 ScrollToRowInternal(aParts, aRow);
3747 else {
3748 // Bring it just on-screen.
3749 int32_t distance = aRow - (mTopRowIndex + mPageLength) + 1;
3750 ScrollToRowInternal(aParts, mTopRowIndex + distance);
3751 }
3752
3753 return NS_OK;
3754 }
3755
EnsureCellIsVisible(int32_t aRow,nsTreeColumn * aCol)3756 nsresult nsTreeBodyFrame::EnsureCellIsVisible(int32_t aRow,
3757 nsTreeColumn* aCol) {
3758 if (!aCol) return NS_ERROR_INVALID_ARG;
3759
3760 ScrollParts parts = GetScrollParts();
3761
3762 nscoord result = -1;
3763 nsresult rv;
3764
3765 nscoord columnPos;
3766 rv = aCol->GetXInTwips(this, &columnPos);
3767 if (NS_FAILED(rv)) return rv;
3768
3769 nscoord columnWidth;
3770 rv = aCol->GetWidthInTwips(this, &columnWidth);
3771 if (NS_FAILED(rv)) return rv;
3772
3773 // If the start of the column is before the
3774 // start of the horizontal view, then scroll
3775 if (columnPos < mHorzPosition) result = columnPos;
3776 // If the end of the column is past the end of
3777 // the horizontal view, then scroll
3778 else if ((columnPos + columnWidth) > (mHorzPosition + mInnerBox.width))
3779 result = ((columnPos + columnWidth) - (mHorzPosition + mInnerBox.width)) +
3780 mHorzPosition;
3781
3782 if (result != -1) {
3783 rv = ScrollHorzInternal(parts, result);
3784 if (NS_FAILED(rv)) return rv;
3785 }
3786
3787 rv = EnsureRowIsVisibleInternal(parts, aRow);
3788 NS_ENSURE_SUCCESS(rv, rv);
3789 UpdateScrollbars(parts);
3790 return rv;
3791 }
3792
ScrollToRow(int32_t aRow)3793 void nsTreeBodyFrame::ScrollToRow(int32_t aRow) {
3794 ScrollParts parts = GetScrollParts();
3795 ScrollToRowInternal(parts, aRow);
3796 UpdateScrollbars(parts);
3797 }
3798
ScrollToRowInternal(const ScrollParts & aParts,int32_t aRow)3799 nsresult nsTreeBodyFrame::ScrollToRowInternal(const ScrollParts& aParts,
3800 int32_t aRow) {
3801 ScrollInternal(aParts, aRow);
3802
3803 return NS_OK;
3804 }
3805
ScrollByLines(int32_t aNumLines)3806 void nsTreeBodyFrame::ScrollByLines(int32_t aNumLines) {
3807 if (!mView) {
3808 return;
3809 }
3810 int32_t newIndex = mTopRowIndex + aNumLines;
3811 ScrollToRow(newIndex);
3812 }
3813
ScrollByPages(int32_t aNumPages)3814 void nsTreeBodyFrame::ScrollByPages(int32_t aNumPages) {
3815 if (!mView) {
3816 return;
3817 }
3818 int32_t newIndex = mTopRowIndex + aNumPages * mPageLength;
3819 ScrollToRow(newIndex);
3820 }
3821
ScrollInternal(const ScrollParts & aParts,int32_t aRow)3822 nsresult nsTreeBodyFrame::ScrollInternal(const ScrollParts& aParts,
3823 int32_t aRow) {
3824 if (!mView) {
3825 return NS_OK;
3826 }
3827
3828 // Note that we may be "over scrolled" at this point; that is the
3829 // current mTopRowIndex may be larger than mRowCount - mPageLength.
3830 // This can happen when items are removed for example. (bug 1085050)
3831
3832 int32_t maxTopRowIndex = std::max(0, mRowCount - mPageLength);
3833 aRow = mozilla::clamped(aRow, 0, maxTopRowIndex);
3834 if (aRow == mTopRowIndex) {
3835 return NS_OK;
3836 }
3837 mTopRowIndex = aRow;
3838 Invalidate();
3839 PostScrollEvent();
3840 return NS_OK;
3841 }
3842
ScrollHorzInternal(const ScrollParts & aParts,int32_t aPosition)3843 nsresult nsTreeBodyFrame::ScrollHorzInternal(const ScrollParts& aParts,
3844 int32_t aPosition) {
3845 if (!mView || !aParts.mColumnsScrollFrame || !aParts.mHScrollbar)
3846 return NS_OK;
3847
3848 if (aPosition == mHorzPosition) return NS_OK;
3849
3850 if (aPosition < 0 || aPosition > mHorzWidth) return NS_OK;
3851
3852 nsRect bounds = aParts.mColumnsFrame->GetRect();
3853 if (aPosition > (mHorzWidth - bounds.width))
3854 aPosition = mHorzWidth - bounds.width;
3855
3856 mHorzPosition = aPosition;
3857
3858 Invalidate();
3859
3860 // Update the column scroll view
3861 AutoWeakFrame weakFrame(this);
3862 aParts.mColumnsScrollFrame->ScrollTo(nsPoint(mHorzPosition, 0),
3863 ScrollMode::Instant);
3864 if (!weakFrame.IsAlive()) {
3865 return NS_ERROR_FAILURE;
3866 }
3867 // And fire off an event about it all
3868 PostScrollEvent();
3869 return NS_OK;
3870 }
3871
ScrollByPage(nsScrollbarFrame * aScrollbar,int32_t aDirection,nsIScrollbarMediator::ScrollSnapMode aSnap)3872 void nsTreeBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar,
3873 int32_t aDirection,
3874 nsIScrollbarMediator::ScrollSnapMode aSnap) {
3875 // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
3876 MOZ_ASSERT(aScrollbar != nullptr);
3877 ScrollByPages(aDirection);
3878 }
3879
ScrollByWhole(nsScrollbarFrame * aScrollbar,int32_t aDirection,nsIScrollbarMediator::ScrollSnapMode aSnap)3880 void nsTreeBodyFrame::ScrollByWhole(
3881 nsScrollbarFrame* aScrollbar, int32_t aDirection,
3882 nsIScrollbarMediator::ScrollSnapMode aSnap) {
3883 // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
3884 MOZ_ASSERT(aScrollbar != nullptr);
3885 int32_t newIndex = aDirection < 0 ? 0 : mTopRowIndex;
3886 ScrollToRow(newIndex);
3887 }
3888
ScrollByLine(nsScrollbarFrame * aScrollbar,int32_t aDirection,nsIScrollbarMediator::ScrollSnapMode aSnap)3889 void nsTreeBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar,
3890 int32_t aDirection,
3891 nsIScrollbarMediator::ScrollSnapMode aSnap) {
3892 // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
3893 MOZ_ASSERT(aScrollbar != nullptr);
3894 ScrollByLines(aDirection);
3895 }
3896
ScrollByUnit(nsScrollbarFrame * aScrollbar,ScrollMode aMode,int32_t aDirection,ScrollUnit aUnit,ScrollSnapMode aSnap)3897 void nsTreeBodyFrame::ScrollByUnit(nsScrollbarFrame* aScrollbar,
3898 ScrollMode aMode, int32_t aDirection,
3899 ScrollUnit aUnit,
3900 ScrollSnapMode aSnap /* = DISABLE_SNAP */) {
3901 MOZ_ASSERT_UNREACHABLE("Can't get here, we pass false to MoveToNewPosition");
3902 }
3903
RepeatButtonScroll(nsScrollbarFrame * aScrollbar)3904 void nsTreeBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar) {
3905 ScrollParts parts = GetScrollParts();
3906 int32_t increment = aScrollbar->GetIncrement();
3907 int32_t direction = 0;
3908 if (increment < 0) {
3909 direction = -1;
3910 } else if (increment > 0) {
3911 direction = 1;
3912 }
3913 bool isHorizontal = aScrollbar->IsXULHorizontal();
3914
3915 AutoWeakFrame weakFrame(this);
3916 if (isHorizontal) {
3917 int32_t curpos = aScrollbar->MoveToNewPosition(
3918 nsScrollbarFrame::ImplementsScrollByUnit::No);
3919 if (weakFrame.IsAlive()) {
3920 ScrollHorzInternal(parts, curpos);
3921 }
3922 } else {
3923 ScrollToRowInternal(parts, mTopRowIndex + direction);
3924 }
3925
3926 if (weakFrame.IsAlive() && mScrollbarActivity) {
3927 mScrollbarActivity->ActivityOccurred();
3928 }
3929 if (weakFrame.IsAlive()) {
3930 UpdateScrollbars(parts);
3931 }
3932 }
3933
ThumbMoved(nsScrollbarFrame * aScrollbar,nscoord aOldPos,nscoord aNewPos)3934 void nsTreeBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar, nscoord aOldPos,
3935 nscoord aNewPos) {
3936 ScrollParts parts = GetScrollParts();
3937
3938 if (aOldPos == aNewPos) return;
3939
3940 AutoWeakFrame weakFrame(this);
3941
3942 // Vertical Scrollbar
3943 if (parts.mVScrollbar == aScrollbar) {
3944 nscoord rh = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
3945 nscoord newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
3946 nscoord newrow = (rh > 0) ? (newIndex / rh) : 0;
3947 ScrollInternal(parts, newrow);
3948 // Horizontal Scrollbar
3949 } else if (parts.mHScrollbar == aScrollbar) {
3950 int32_t newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
3951 ScrollHorzInternal(parts, newIndex);
3952 }
3953 if (weakFrame.IsAlive()) {
3954 UpdateScrollbars(parts);
3955 }
3956 }
3957
3958 // The style cache.
GetPseudoComputedStyle(nsCSSAnonBoxPseudoStaticAtom * aPseudoElement)3959 ComputedStyle* nsTreeBodyFrame::GetPseudoComputedStyle(
3960 nsCSSAnonBoxPseudoStaticAtom* aPseudoElement) {
3961 return mStyleCache.GetComputedStyle(PresContext(), mContent, mComputedStyle,
3962 aPseudoElement, mScratchArray);
3963 }
3964
GetBaseElement()3965 XULTreeElement* nsTreeBodyFrame::GetBaseElement() {
3966 if (!mTree) {
3967 nsIFrame* parent = GetParent();
3968 while (parent) {
3969 nsIContent* content = parent->GetContent();
3970 if (content && content->IsXULElement(nsGkAtoms::tree)) {
3971 mTree = XULTreeElement::FromNodeOrNull(content->AsElement());
3972 break;
3973 }
3974
3975 parent = parent->GetInFlowParent();
3976 }
3977 }
3978
3979 return mTree;
3980 }
3981
ClearStyleAndImageCaches()3982 nsresult nsTreeBodyFrame::ClearStyleAndImageCaches() {
3983 mStyleCache.Clear();
3984 CancelImageRequests();
3985 mImageCache.Clear();
3986 return NS_OK;
3987 }
3988
RemoveImageCacheEntry(int32_t aRowIndex,nsTreeColumn * aCol)3989 void nsTreeBodyFrame::RemoveImageCacheEntry(int32_t aRowIndex,
3990 nsTreeColumn* aCol) {
3991 nsAutoString imageSrc;
3992 if (NS_SUCCEEDED(mView->GetImageSrc(aRowIndex, aCol, imageSrc))) {
3993 nsTreeImageCacheEntry entry;
3994 if (mImageCache.Get(imageSrc, &entry)) {
3995 nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request,
3996 nullptr);
3997 entry.request->UnlockImage();
3998 entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
3999 mImageCache.Remove(imageSrc);
4000 }
4001 }
4002 }
4003
4004 /* virtual */
DidSetComputedStyle(ComputedStyle * aOldComputedStyle)4005 void nsTreeBodyFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
4006 nsLeafBoxFrame::DidSetComputedStyle(aOldComputedStyle);
4007
4008 // Clear the style cache; the pointers are no longer even valid
4009 mStyleCache.Clear();
4010 // XXX The following is hacky, but it's not incorrect,
4011 // and appears to fix a few bugs with style changes, like text zoom and
4012 // dpi changes
4013 mIndentation = GetIndentation();
4014 mRowHeight = GetRowHeight();
4015 mStringWidth = -1;
4016 }
4017
OffsetForHorzScroll(nsRect & rect,bool clip)4018 bool nsTreeBodyFrame::OffsetForHorzScroll(nsRect& rect, bool clip) {
4019 rect.x -= mHorzPosition;
4020
4021 // Scrolled out before
4022 if (rect.XMost() <= mInnerBox.x) return false;
4023
4024 // Scrolled out after
4025 if (rect.x > mInnerBox.XMost()) return false;
4026
4027 if (clip) {
4028 nscoord leftEdge = std::max(rect.x, mInnerBox.x);
4029 nscoord rightEdge = std::min(rect.XMost(), mInnerBox.XMost());
4030 rect.x = leftEdge;
4031 rect.width = rightEdge - leftEdge;
4032
4033 // Should have returned false above
4034 NS_ASSERTION(rect.width >= 0, "horz scroll code out of sync");
4035 }
4036
4037 return true;
4038 }
4039
CanAutoScroll(int32_t aRowIndex)4040 bool nsTreeBodyFrame::CanAutoScroll(int32_t aRowIndex) {
4041 // Check first for partially visible last row.
4042 if (aRowIndex == mRowCount - 1) {
4043 nscoord y = mInnerBox.y + (aRowIndex - mTopRowIndex) * mRowHeight;
4044 if (y < mInnerBox.height && y + mRowHeight > mInnerBox.height) return true;
4045 }
4046
4047 if (aRowIndex > 0 && aRowIndex < mRowCount - 1) return true;
4048
4049 return false;
4050 }
4051
4052 // Given a dom event, figure out which row in the tree the mouse is over,
4053 // if we should drop before/after/on that row or we should auto-scroll.
4054 // Doesn't query the content about if the drag is allowable, that's done
4055 // elsewhere.
4056 //
4057 // For containers, we break up the vertical space of the row as follows: if in
4058 // the topmost 25%, the drop is _before_ the row the mouse is over; if in the
4059 // last 25%, _after_; in the middle 50%, we consider it a drop _on_ the
4060 // container.
4061 //
4062 // For non-containers, if the mouse is in the top 50% of the row, the drop is
4063 // _before_ and the bottom 50% _after_
ComputeDropPosition(WidgetGUIEvent * aEvent,int32_t * aRow,int16_t * aOrient,int16_t * aScrollLines)4064 void nsTreeBodyFrame::ComputeDropPosition(WidgetGUIEvent* aEvent, int32_t* aRow,
4065 int16_t* aOrient,
4066 int16_t* aScrollLines) {
4067 *aOrient = -1;
4068 *aScrollLines = 0;
4069
4070 // Convert the event's point to our coordinates. We want it in
4071 // the coordinates of our inner box's coordinates.
4072 nsPoint pt =
4073 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this});
4074 int32_t xTwips = pt.x - mInnerBox.x;
4075 int32_t yTwips = pt.y - mInnerBox.y;
4076
4077 *aRow = GetRowAtInternal(xTwips, yTwips);
4078 if (*aRow >= 0) {
4079 // Compute the top/bottom of the row in question.
4080 int32_t yOffset = yTwips - mRowHeight * (*aRow - mTopRowIndex);
4081
4082 bool isContainer = false;
4083 mView->IsContainer(*aRow, &isContainer);
4084 if (isContainer) {
4085 // for a container, use a 25%/50%/25% breakdown
4086 if (yOffset < mRowHeight / 4)
4087 *aOrient = nsITreeView::DROP_BEFORE;
4088 else if (yOffset > mRowHeight - (mRowHeight / 4))
4089 *aOrient = nsITreeView::DROP_AFTER;
4090 else
4091 *aOrient = nsITreeView::DROP_ON;
4092 } else {
4093 // for a non-container use a 50%/50% breakdown
4094 if (yOffset < mRowHeight / 2)
4095 *aOrient = nsITreeView::DROP_BEFORE;
4096 else
4097 *aOrient = nsITreeView::DROP_AFTER;
4098 }
4099 }
4100
4101 if (CanAutoScroll(*aRow)) {
4102 // Get the max value from the look and feel service.
4103 int32_t scrollLinesMax =
4104 LookAndFeel::GetInt(LookAndFeel::IntID::TreeScrollLinesMax, 0);
4105 scrollLinesMax--;
4106 if (scrollLinesMax < 0) scrollLinesMax = 0;
4107
4108 // Determine if we're w/in a margin of the top/bottom of the tree during a
4109 // drag. This will ultimately cause us to scroll, but that's done elsewhere.
4110 nscoord height = (3 * mRowHeight) / 4;
4111 if (yTwips < height) {
4112 // scroll up
4113 *aScrollLines =
4114 NSToIntRound(-scrollLinesMax * (1 - (float)yTwips / height) - 1);
4115 } else if (yTwips > mRect.height - height) {
4116 // scroll down
4117 *aScrollLines = NSToIntRound(
4118 scrollLinesMax * (1 - (float)(mRect.height - yTwips) / height) + 1);
4119 }
4120 }
4121 } // ComputeDropPosition
4122
OpenCallback(nsITimer * aTimer,void * aClosure)4123 void nsTreeBodyFrame::OpenCallback(nsITimer* aTimer, void* aClosure) {
4124 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4125 if (self) {
4126 aTimer->Cancel();
4127 self->mSlots->mTimer = nullptr;
4128
4129 if (self->mSlots->mDropRow >= 0) {
4130 self->mSlots->mArray.AppendElement(self->mSlots->mDropRow);
4131 self->mView->ToggleOpenState(self->mSlots->mDropRow);
4132 }
4133 }
4134 }
4135
CloseCallback(nsITimer * aTimer,void * aClosure)4136 void nsTreeBodyFrame::CloseCallback(nsITimer* aTimer, void* aClosure) {
4137 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4138 if (self) {
4139 aTimer->Cancel();
4140 self->mSlots->mTimer = nullptr;
4141
4142 for (uint32_t i = self->mSlots->mArray.Length(); i--;) {
4143 if (self->mView) self->mView->ToggleOpenState(self->mSlots->mArray[i]);
4144 }
4145 self->mSlots->mArray.Clear();
4146 }
4147 }
4148
LazyScrollCallback(nsITimer * aTimer,void * aClosure)4149 void nsTreeBodyFrame::LazyScrollCallback(nsITimer* aTimer, void* aClosure) {
4150 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4151 if (self) {
4152 aTimer->Cancel();
4153 self->mSlots->mTimer = nullptr;
4154
4155 if (self->mView) {
4156 // Set a new timer to scroll the tree repeatedly.
4157 self->CreateTimer(LookAndFeel::IntID::TreeScrollDelay, ScrollCallback,
4158 nsITimer::TYPE_REPEATING_SLACK,
4159 getter_AddRefs(self->mSlots->mTimer),
4160 "nsTreeBodyFrame::ScrollCallback");
4161 self->ScrollByLines(self->mSlots->mScrollLines);
4162 // ScrollByLines may have deleted |self|.
4163 }
4164 }
4165 }
4166
ScrollCallback(nsITimer * aTimer,void * aClosure)4167 void nsTreeBodyFrame::ScrollCallback(nsITimer* aTimer, void* aClosure) {
4168 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4169 if (self) {
4170 // Don't scroll if we are already at the top or bottom of the view.
4171 if (self->mView && self->CanAutoScroll(self->mSlots->mDropRow)) {
4172 self->ScrollByLines(self->mSlots->mScrollLines);
4173 } else {
4174 aTimer->Cancel();
4175 self->mSlots->mTimer = nullptr;
4176 }
4177 }
4178 }
4179
4180 NS_IMETHODIMP
Run()4181 nsTreeBodyFrame::ScrollEvent::Run() {
4182 if (mInner) {
4183 mInner->FireScrollEvent();
4184 }
4185 return NS_OK;
4186 }
4187
FireScrollEvent()4188 void nsTreeBodyFrame::FireScrollEvent() {
4189 mScrollEvent.Forget();
4190 WidgetGUIEvent event(true, eScroll, nullptr);
4191 // scroll events fired at elements don't bubble
4192 event.mFlags.mBubbles = false;
4193 EventDispatcher::Dispatch(GetContent(), PresContext(), &event);
4194 }
4195
PostScrollEvent()4196 void nsTreeBodyFrame::PostScrollEvent() {
4197 if (mScrollEvent.IsPending()) return;
4198
4199 RefPtr<ScrollEvent> event = new ScrollEvent(this);
4200 nsresult rv =
4201 mContent->OwnerDoc()->Dispatch(TaskCategory::Other, do_AddRef(event));
4202 if (NS_FAILED(rv)) {
4203 NS_WARNING("failed to dispatch ScrollEvent");
4204 } else {
4205 mScrollEvent = std::move(event);
4206 }
4207 }
4208
ScrollbarActivityStarted() const4209 void nsTreeBodyFrame::ScrollbarActivityStarted() const {
4210 if (mScrollbarActivity) {
4211 mScrollbarActivity->ActivityStarted();
4212 }
4213 }
4214
ScrollbarActivityStopped() const4215 void nsTreeBodyFrame::ScrollbarActivityStopped() const {
4216 if (mScrollbarActivity) {
4217 mScrollbarActivity->ActivityStopped();
4218 }
4219 }
4220
DetachImageListeners()4221 void nsTreeBodyFrame::DetachImageListeners() { mCreatedListeners.Clear(); }
4222
RemoveTreeImageListener(nsTreeImageListener * aListener)4223 void nsTreeBodyFrame::RemoveTreeImageListener(nsTreeImageListener* aListener) {
4224 if (aListener) {
4225 mCreatedListeners.Remove(aListener);
4226 }
4227 }
4228
4229 #ifdef ACCESSIBILITY
InitCustomEvent(CustomEvent * aEvent,const nsAString & aType,nsIWritablePropertyBag2 * aDetail)4230 static void InitCustomEvent(CustomEvent* aEvent, const nsAString& aType,
4231 nsIWritablePropertyBag2* aDetail) {
4232 AutoJSAPI jsapi;
4233 if (!jsapi.Init(aEvent->GetParentObject())) {
4234 return;
4235 }
4236
4237 JSContext* cx = jsapi.cx();
4238 JS::Rooted<JS::Value> detail(cx);
4239 if (!ToJSValue(cx, aDetail, &detail)) {
4240 jsapi.ClearException();
4241 return;
4242 }
4243
4244 aEvent->InitCustomEvent(cx, aType, /* aCanBubble = */ true,
4245 /* aCancelable = */ false, detail);
4246 }
4247
FireRowCountChangedEvent(int32_t aIndex,int32_t aCount)4248 void nsTreeBodyFrame::FireRowCountChangedEvent(int32_t aIndex, int32_t aCount) {
4249 RefPtr<XULTreeElement> tree(GetBaseElement());
4250 if (!tree) return;
4251
4252 RefPtr<Document> doc = tree->OwnerDoc();
4253 MOZ_ASSERT(doc);
4254
4255 RefPtr<Event> event =
4256 doc->CreateEvent(u"customevent"_ns, CallerType::System, IgnoreErrors());
4257
4258 CustomEvent* treeEvent = event->AsCustomEvent();
4259 if (!treeEvent) {
4260 return;
4261 }
4262
4263 nsCOMPtr<nsIWritablePropertyBag2> propBag(
4264 do_CreateInstance("@mozilla.org/hash-property-bag;1"));
4265 if (!propBag) {
4266 return;
4267 }
4268
4269 // Set 'index' data - the row index rows are changed from.
4270 propBag->SetPropertyAsInt32(u"index"_ns, aIndex);
4271
4272 // Set 'count' data - the number of changed rows.
4273 propBag->SetPropertyAsInt32(u"count"_ns, aCount);
4274
4275 InitCustomEvent(treeEvent, u"TreeRowCountChanged"_ns, propBag);
4276
4277 event->SetTrusted(true);
4278
4279 RefPtr<AsyncEventDispatcher> asyncDispatcher =
4280 new AsyncEventDispatcher(tree, event);
4281 asyncDispatcher->PostDOMEvent();
4282 }
4283
FireInvalidateEvent(int32_t aStartRowIdx,int32_t aEndRowIdx,nsTreeColumn * aStartCol,nsTreeColumn * aEndCol)4284 void nsTreeBodyFrame::FireInvalidateEvent(int32_t aStartRowIdx,
4285 int32_t aEndRowIdx,
4286 nsTreeColumn* aStartCol,
4287 nsTreeColumn* aEndCol) {
4288 RefPtr<XULTreeElement> tree(GetBaseElement());
4289 if (!tree) return;
4290
4291 RefPtr<Document> doc = tree->OwnerDoc();
4292
4293 RefPtr<Event> event =
4294 doc->CreateEvent(u"customevent"_ns, CallerType::System, IgnoreErrors());
4295
4296 CustomEvent* treeEvent = event->AsCustomEvent();
4297 if (!treeEvent) {
4298 return;
4299 }
4300
4301 nsCOMPtr<nsIWritablePropertyBag2> propBag(
4302 do_CreateInstance("@mozilla.org/hash-property-bag;1"));
4303 if (!propBag) {
4304 return;
4305 }
4306
4307 if (aStartRowIdx != -1 && aEndRowIdx != -1) {
4308 // Set 'startrow' data - the start index of invalidated rows.
4309 propBag->SetPropertyAsInt32(u"startrow"_ns, aStartRowIdx);
4310
4311 // Set 'endrow' data - the end index of invalidated rows.
4312 propBag->SetPropertyAsInt32(u"endrow"_ns, aEndRowIdx);
4313 }
4314
4315 if (aStartCol && aEndCol) {
4316 // Set 'startcolumn' data - the start index of invalidated rows.
4317 int32_t startColIdx = aStartCol->GetIndex();
4318
4319 propBag->SetPropertyAsInt32(u"startcolumn"_ns, startColIdx);
4320
4321 // Set 'endcolumn' data - the start index of invalidated rows.
4322 int32_t endColIdx = aEndCol->GetIndex();
4323 propBag->SetPropertyAsInt32(u"endcolumn"_ns, endColIdx);
4324 }
4325
4326 InitCustomEvent(treeEvent, u"TreeInvalidated"_ns, propBag);
4327
4328 event->SetTrusted(true);
4329
4330 RefPtr<AsyncEventDispatcher> asyncDispatcher =
4331 new AsyncEventDispatcher(tree, event);
4332 asyncDispatcher->PostDOMEvent();
4333 }
4334 #endif
4335
4336 class nsOverflowChecker : public Runnable {
4337 public:
nsOverflowChecker(nsTreeBodyFrame * aFrame)4338 explicit nsOverflowChecker(nsTreeBodyFrame* aFrame)
4339 : mozilla::Runnable("nsOverflowChecker"), mFrame(aFrame) {}
Run()4340 NS_IMETHOD Run() override {
4341 if (mFrame.IsAlive()) {
4342 nsTreeBodyFrame* tree = static_cast<nsTreeBodyFrame*>(mFrame.GetFrame());
4343 nsTreeBodyFrame::ScrollParts parts = tree->GetScrollParts();
4344 tree->CheckOverflow(parts);
4345 }
4346 return NS_OK;
4347 }
4348
4349 private:
4350 WeakFrame mFrame;
4351 };
4352
FullScrollbarsUpdate(bool aNeedsFullInvalidation)4353 bool nsTreeBodyFrame::FullScrollbarsUpdate(bool aNeedsFullInvalidation) {
4354 ScrollParts parts = GetScrollParts();
4355 AutoWeakFrame weakFrame(this);
4356 AutoWeakFrame weakColumnsFrame(parts.mColumnsFrame);
4357 UpdateScrollbars(parts);
4358 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
4359 if (aNeedsFullInvalidation) {
4360 Invalidate();
4361 }
4362 InvalidateScrollbars(parts, weakColumnsFrame);
4363 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
4364
4365 // Overflow checking dispatches synchronous events, which can cause infinite
4366 // recursion during reflow. Do the first overflow check synchronously, but
4367 // force any nested checks to round-trip through the event loop. See bug
4368 // 905909.
4369 RefPtr<nsOverflowChecker> checker = new nsOverflowChecker(this);
4370 if (!mCheckingOverflow) {
4371 nsContentUtils::AddScriptRunner(checker);
4372 } else {
4373 mContent->OwnerDoc()->Dispatch(TaskCategory::Other, checker.forget());
4374 }
4375 return weakFrame.IsAlive();
4376 }
4377
OnImageIsAnimated(imgIRequest * aRequest)4378 void nsTreeBodyFrame::OnImageIsAnimated(imgIRequest* aRequest) {
4379 nsLayoutUtils::RegisterImageRequest(PresContext(), aRequest, nullptr);
4380 }
4381