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()->Cursor().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 namespace mozilla {
2484
2485 class nsDisplayTreeBody final : public nsPaintedDisplayItem {
2486 public:
nsDisplayTreeBody(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame)2487 nsDisplayTreeBody(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
2488 : nsPaintedDisplayItem(aBuilder, aFrame) {
2489 MOZ_COUNT_CTOR(nsDisplayTreeBody);
2490 }
MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTreeBody)2491 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTreeBody)
2492
2493 nsDisplayItemGeometry* AllocateGeometry(
2494 nsDisplayListBuilder* aBuilder) override {
2495 return new nsDisplayTreeBodyGeometry(this, aBuilder, IsWindowActive());
2496 }
2497
Destroy(nsDisplayListBuilder * aBuilder)2498 void Destroy(nsDisplayListBuilder* aBuilder) override {
2499 aBuilder->UnregisterThemeGeometry(this);
2500 nsPaintedDisplayItem::Destroy(aBuilder);
2501 }
2502
IsWindowActive() const2503 bool IsWindowActive() const {
2504 EventStates docState =
2505 mFrame->PresContext()->Document()->GetDocumentState();
2506 return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
2507 }
2508
ComputeInvalidationRegion(nsDisplayListBuilder * aBuilder,const nsDisplayItemGeometry * aGeometry,nsRegion * aInvalidRegion) const2509 void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
2510 const nsDisplayItemGeometry* aGeometry,
2511 nsRegion* aInvalidRegion) const override {
2512 auto geometry = static_cast<const nsDisplayTreeBodyGeometry*>(aGeometry);
2513
2514 if ((aBuilder->ShouldSyncDecodeImages() &&
2515 geometry->ShouldInvalidateToSyncDecodeImages()) ||
2516 IsWindowActive() != geometry->mWindowIsActive) {
2517 bool snap;
2518 aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
2519 }
2520
2521 nsPaintedDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry,
2522 aInvalidRegion);
2523 }
2524
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)2525 virtual void Paint(nsDisplayListBuilder* aBuilder,
2526 gfxContext* aCtx) override {
2527 MOZ_ASSERT(aBuilder);
2528 ImgDrawResult result = static_cast<nsTreeBodyFrame*>(mFrame)->PaintTreeBody(
2529 *aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame(), aBuilder);
2530
2531 nsDisplayTreeBodyGeometry::UpdateDrawResult(this, result);
2532 }
2533
2534 NS_DISPLAY_DECL_NAME("XULTreeBody", TYPE_XUL_TREE_BODY)
2535
GetComponentAlphaBounds(nsDisplayListBuilder * aBuilder) const2536 virtual nsRect GetComponentAlphaBounds(
2537 nsDisplayListBuilder* aBuilder) const override {
2538 bool snap;
2539 return GetBounds(aBuilder, &snap);
2540 }
2541 };
2542
2543 } // namespace mozilla
2544
2545 #ifdef XP_MACOSX
IsInSourceList(nsIFrame * aFrame)2546 static bool IsInSourceList(nsIFrame* aFrame) {
2547 for (nsIFrame* frame = aFrame; frame;
2548 frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) {
2549 if (frame->StyleDisplay()->EffectiveAppearance() ==
2550 StyleAppearance::MozMacSourceList) {
2551 return true;
2552 }
2553 }
2554 return false;
2555 }
2556 #endif
2557
2558 // Painting routines
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)2559 void nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
2560 const nsDisplayListSet& aLists) {
2561 // REVIEW: why did we paint if we were collapsed? that makes no sense!
2562 if (!IsVisibleForPainting()) return; // We're invisible. Don't paint.
2563
2564 // Handles painting our background, border, and outline.
2565 nsLeafBoxFrame::BuildDisplayList(aBuilder, aLists);
2566
2567 // Bail out now if there's no view or we can't run script because the
2568 // document is a zombie
2569 if (!mView || !GetContent()->GetComposedDoc()->GetWindow()) return;
2570
2571 nsDisplayItem* item = MakeDisplayItem<nsDisplayTreeBody>(aBuilder, this);
2572 if (!item) {
2573 return;
2574 }
2575 aLists.Content()->AppendToTop(item);
2576
2577 #ifdef XP_MACOSX
2578 XULTreeElement* tree = GetBaseElement();
2579 nsIFrame* treeFrame = tree ? tree->GetPrimaryFrame() : nullptr;
2580 nsCOMPtr<nsITreeSelection> selection;
2581 mView->GetSelection(getter_AddRefs(selection));
2582 nsITheme* theme = PresContext()->Theme();
2583 // On Mac, we support native theming of selected rows. On 10.10 and higher,
2584 // this means applying vibrancy which require us to register the theme
2585 // geometrics for the row. In order to make the vibrancy effect to work
2586 // properly, we also need an ancestor frame to be themed as a source list.
2587 if (selection && theme && IsInSourceList(treeFrame)) {
2588 // Loop through our onscreen rows. If the row is selected and a
2589 // -moz-appearance is provided, RegisterThemeGeometry might be necessary.
2590 const auto end = std::min(mRowCount, LastVisibleRow() + 1);
2591 for (auto i = FirstVisibleRow(); i < end; i++) {
2592 bool isSelected;
2593 selection->IsSelected(i, &isSelected);
2594 if (isSelected) {
2595 PrefillPropertyArray(i, nullptr);
2596 nsAutoString properties;
2597 mView->GetRowProperties(i, properties);
2598 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
2599 ComputedStyle* rowContext =
2600 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
2601 auto appearance = rowContext->StyleDisplay()->EffectiveAppearance();
2602 if (appearance != StyleAppearance::None) {
2603 if (theme->ThemeSupportsWidget(PresContext(), this, appearance)) {
2604 nsITheme::ThemeGeometryType type =
2605 theme->ThemeGeometryTypeForWidget(this, appearance);
2606 if (type != nsITheme::eThemeGeometryTypeUnknown) {
2607 nsRect rowRect(mInnerBox.x,
2608 mInnerBox.y + mRowHeight * (i - FirstVisibleRow()),
2609 mInnerBox.width, mRowHeight);
2610 aBuilder->RegisterThemeGeometry(
2611 type, item,
2612 LayoutDeviceIntRect::FromUnknownRect(
2613 (rowRect + aBuilder->ToReferenceFrame(this))
2614 .ToNearestPixels(
2615 PresContext()->AppUnitsPerDevPixel())));
2616 }
2617 }
2618 }
2619 }
2620 }
2621 }
2622 #endif
2623 }
2624
PaintTreeBody(gfxContext & aRenderingContext,const nsRect & aDirtyRect,nsPoint aPt,nsDisplayListBuilder * aBuilder)2625 ImgDrawResult nsTreeBodyFrame::PaintTreeBody(gfxContext& aRenderingContext,
2626 const nsRect& aDirtyRect,
2627 nsPoint aPt,
2628 nsDisplayListBuilder* aBuilder) {
2629 // Update our available height and our page count.
2630 CalcInnerBox();
2631
2632 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
2633
2634 aRenderingContext.Save();
2635 aRenderingContext.Clip(NSRectToSnappedRect(
2636 mInnerBox + aPt, PresContext()->AppUnitsPerDevPixel(), *drawTarget));
2637 int32_t oldPageCount = mPageLength;
2638 if (!mHasFixedRowCount)
2639 mPageLength =
2640 (mRowHeight > 0) ? (mInnerBox.height / mRowHeight) : mRowCount;
2641
2642 if (oldPageCount != mPageLength ||
2643 mHorzWidth != CalcHorzWidth(GetScrollParts())) {
2644 // Schedule a ResizeReflow that will update our info properly.
2645 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::Resize,
2646 NS_FRAME_IS_DIRTY);
2647 }
2648 #ifdef DEBUG
2649 int32_t rowCount = mRowCount;
2650 mView->GetRowCount(&rowCount);
2651 NS_WARNING_ASSERTION(mRowCount == rowCount, "row count changed unexpectedly");
2652 #endif
2653
2654 ImgDrawResult result = ImgDrawResult::SUCCESS;
2655
2656 // Loop through our columns and paint them (e.g., for sorting). This is only
2657 // relevant when painting backgrounds, since columns contain no content.
2658 // Content is contained in the rows.
2659 for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
2660 currCol = currCol->GetNext()) {
2661 nsRect colRect;
2662 nsresult rv =
2663 currCol->GetRect(this, mInnerBox.y, mInnerBox.height, &colRect);
2664 // Don't paint hidden columns.
2665 if (NS_FAILED(rv) || colRect.width == 0) continue;
2666
2667 if (OffsetForHorzScroll(colRect, false)) {
2668 nsRect dirtyRect;
2669 colRect += aPt;
2670 if (dirtyRect.IntersectRect(aDirtyRect, colRect)) {
2671 result &= PaintColumn(currCol, colRect, PresContext(),
2672 aRenderingContext, aDirtyRect);
2673 }
2674 }
2675 }
2676 // Loop through our on-screen rows.
2677 for (int32_t i = mTopRowIndex;
2678 i < mRowCount && i <= mTopRowIndex + mPageLength; i++) {
2679 nsRect rowRect(mInnerBox.x, mInnerBox.y + mRowHeight * (i - mTopRowIndex),
2680 mInnerBox.width, mRowHeight);
2681 nsRect dirtyRect;
2682 if (dirtyRect.IntersectRect(aDirtyRect, rowRect + aPt) &&
2683 rowRect.y < (mInnerBox.y + mInnerBox.height)) {
2684 result &= PaintRow(i, rowRect + aPt, PresContext(), aRenderingContext,
2685 aDirtyRect, aPt, aBuilder);
2686 }
2687 }
2688
2689 if (mSlots && mSlots->mDropAllowed &&
2690 (mSlots->mDropOrient == nsITreeView::DROP_BEFORE ||
2691 mSlots->mDropOrient == nsITreeView::DROP_AFTER)) {
2692 nscoord yPos = mInnerBox.y +
2693 mRowHeight * (mSlots->mDropRow - mTopRowIndex) -
2694 mRowHeight / 2;
2695 nsRect feedbackRect(mInnerBox.x, yPos, mInnerBox.width, mRowHeight);
2696 if (mSlots->mDropOrient == nsITreeView::DROP_AFTER)
2697 feedbackRect.y += mRowHeight;
2698
2699 nsRect dirtyRect;
2700 feedbackRect += aPt;
2701 if (dirtyRect.IntersectRect(aDirtyRect, feedbackRect)) {
2702 result &= PaintDropFeedback(feedbackRect, PresContext(),
2703 aRenderingContext, aDirtyRect, aPt);
2704 }
2705 }
2706 aRenderingContext.Restore();
2707
2708 return result;
2709 }
2710
PaintColumn(nsTreeColumn * aColumn,const nsRect & aColumnRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect)2711 ImgDrawResult nsTreeBodyFrame::PaintColumn(nsTreeColumn* aColumn,
2712 const nsRect& aColumnRect,
2713 nsPresContext* aPresContext,
2714 gfxContext& aRenderingContext,
2715 const nsRect& aDirtyRect) {
2716 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
2717
2718 // Now obtain the properties for our cell.
2719 PrefillPropertyArray(-1, aColumn);
2720 nsAutoString properties;
2721 mView->GetColumnProperties(aColumn, properties);
2722 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
2723
2724 // Resolve style for the column. It contains all the info we need to lay
2725 // ourselves out and to paint.
2726 ComputedStyle* colContext =
2727 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeColumn());
2728
2729 // Obtain the margins for the cell and then deflate our rect by that
2730 // amount. The cell is assumed to be contained within the deflated rect.
2731 nsRect colRect(aColumnRect);
2732 nsMargin colMargin;
2733 colContext->StyleMargin()->GetMargin(colMargin);
2734 colRect.Deflate(colMargin);
2735
2736 return PaintBackgroundLayer(colContext, aPresContext, aRenderingContext,
2737 colRect, aDirtyRect);
2738 }
2739
PaintRow(int32_t aRowIndex,const nsRect & aRowRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nsPoint aPt,nsDisplayListBuilder * aBuilder)2740 ImgDrawResult nsTreeBodyFrame::PaintRow(int32_t aRowIndex,
2741 const nsRect& aRowRect,
2742 nsPresContext* aPresContext,
2743 gfxContext& aRenderingContext,
2744 const nsRect& aDirtyRect, nsPoint aPt,
2745 nsDisplayListBuilder* aBuilder) {
2746 // We have been given a rect for our row. We treat this row like a full-blown
2747 // frame, meaning that it can have borders, margins, padding, and a
2748 // background.
2749
2750 // Without a view, we have no data. Check for this up front.
2751 if (!mView) {
2752 return ImgDrawResult::SUCCESS;
2753 }
2754
2755 nsresult rv;
2756
2757 // Now obtain the properties for our row.
2758 // XXX Automatically fill in the following props: open, closed, container,
2759 // leaf, selected, focused
2760 PrefillPropertyArray(aRowIndex, nullptr);
2761
2762 nsAutoString properties;
2763 mView->GetRowProperties(aRowIndex, properties);
2764 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
2765
2766 // Resolve style for the row. It contains all the info we need to lay
2767 // ourselves out and to paint.
2768 ComputedStyle* rowContext =
2769 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
2770
2771 // Obtain the margins for the row and then deflate our rect by that
2772 // amount. The row is assumed to be contained within the deflated rect.
2773 nsRect rowRect(aRowRect);
2774 nsMargin rowMargin;
2775 rowContext->StyleMargin()->GetMargin(rowMargin);
2776 rowRect.Deflate(rowMargin);
2777
2778 ImgDrawResult result = ImgDrawResult::SUCCESS;
2779
2780 // Paint our borders and background for our row rect.
2781 nsITheme* theme = nullptr;
2782 auto appearance = rowContext->StyleDisplay()->EffectiveAppearance();
2783 if (appearance != StyleAppearance::None) {
2784 theme = aPresContext->Theme();
2785 }
2786
2787 if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, appearance)) {
2788 nsRect dirty;
2789 dirty.IntersectRect(rowRect, aDirtyRect);
2790 theme->DrawWidgetBackground(&aRenderingContext, this, appearance, rowRect,
2791 dirty);
2792 } else {
2793 result &= PaintBackgroundLayer(rowContext, aPresContext, aRenderingContext,
2794 rowRect, aDirtyRect);
2795 }
2796
2797 // Adjust the rect for its border and padding.
2798 nsRect originalRowRect = rowRect;
2799 AdjustForBorderPadding(rowContext, rowRect);
2800
2801 bool isSeparator = false;
2802 mView->IsSeparator(aRowIndex, &isSeparator);
2803 if (isSeparator) {
2804 // The row is a separator.
2805
2806 nscoord primaryX = rowRect.x;
2807 nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn();
2808 if (primaryCol) {
2809 // Paint the primary cell.
2810 nsRect cellRect;
2811 rv = primaryCol->GetRect(this, rowRect.y, rowRect.height, &cellRect);
2812 if (NS_FAILED(rv)) {
2813 MOZ_ASSERT_UNREACHABLE("primary column is invalid");
2814 return result;
2815 }
2816
2817 if (OffsetForHorzScroll(cellRect, false)) {
2818 cellRect.x += aPt.x;
2819 nsRect dirtyRect;
2820 nsRect checkRect(cellRect.x, originalRowRect.y, cellRect.width,
2821 originalRowRect.height);
2822 if (dirtyRect.IntersectRect(aDirtyRect, checkRect)) {
2823 result &=
2824 PaintCell(aRowIndex, primaryCol, cellRect, aPresContext,
2825 aRenderingContext, aDirtyRect, primaryX, aPt, aBuilder);
2826 }
2827 }
2828
2829 // Paint the left side of the separator.
2830 nscoord currX;
2831 nsTreeColumn* previousCol = primaryCol->GetPrevious();
2832 if (previousCol) {
2833 nsRect prevColRect;
2834 rv = previousCol->GetRect(this, 0, 0, &prevColRect);
2835 if (NS_SUCCEEDED(rv)) {
2836 currX = (prevColRect.x - mHorzPosition) + prevColRect.width + aPt.x;
2837 } else {
2838 MOZ_ASSERT_UNREACHABLE(
2839 "The column before the primary column is "
2840 "invalid");
2841 currX = rowRect.x;
2842 }
2843 } else {
2844 currX = rowRect.x;
2845 }
2846
2847 int32_t level;
2848 mView->GetLevel(aRowIndex, &level);
2849 if (level == 0) currX += mIndentation;
2850
2851 if (currX > rowRect.x) {
2852 nsRect separatorRect(rowRect);
2853 separatorRect.width -= rowRect.x + rowRect.width - currX;
2854 result &= PaintSeparator(aRowIndex, separatorRect, aPresContext,
2855 aRenderingContext, aDirtyRect);
2856 }
2857 }
2858
2859 // Paint the right side (whole) separator.
2860 nsRect separatorRect(rowRect);
2861 if (primaryX > rowRect.x) {
2862 separatorRect.width -= primaryX - rowRect.x;
2863 separatorRect.x += primaryX - rowRect.x;
2864 }
2865 result &= PaintSeparator(aRowIndex, separatorRect, aPresContext,
2866 aRenderingContext, aDirtyRect);
2867 } else {
2868 // Now loop over our cells. Only paint a cell if it intersects with our
2869 // dirty rect.
2870 for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
2871 currCol = currCol->GetNext()) {
2872 nsRect cellRect;
2873 rv = currCol->GetRect(this, rowRect.y, rowRect.height, &cellRect);
2874 // Don't paint cells in hidden columns.
2875 if (NS_FAILED(rv) || cellRect.width == 0) continue;
2876
2877 if (OffsetForHorzScroll(cellRect, false)) {
2878 cellRect.x += aPt.x;
2879
2880 // for primary columns, use the row's vertical size so that the
2881 // lines get drawn properly
2882 nsRect checkRect = cellRect;
2883 if (currCol->IsPrimary())
2884 checkRect = nsRect(cellRect.x, originalRowRect.y, cellRect.width,
2885 originalRowRect.height);
2886
2887 nsRect dirtyRect;
2888 nscoord dummy;
2889 if (dirtyRect.IntersectRect(aDirtyRect, checkRect))
2890 result &=
2891 PaintCell(aRowIndex, currCol, cellRect, aPresContext,
2892 aRenderingContext, aDirtyRect, dummy, aPt, aBuilder);
2893 }
2894 }
2895 }
2896
2897 return result;
2898 }
2899
PaintSeparator(int32_t aRowIndex,const nsRect & aSeparatorRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect)2900 ImgDrawResult nsTreeBodyFrame::PaintSeparator(int32_t aRowIndex,
2901 const nsRect& aSeparatorRect,
2902 nsPresContext* aPresContext,
2903 gfxContext& aRenderingContext,
2904 const nsRect& aDirtyRect) {
2905 // Resolve style for the separator.
2906 ComputedStyle* separatorContext =
2907 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeSeparator());
2908 bool useTheme = false;
2909 nsITheme* theme = nullptr;
2910 StyleAppearance appearance =
2911 separatorContext->StyleDisplay()->EffectiveAppearance();
2912 if (appearance != StyleAppearance::None) {
2913 theme = aPresContext->Theme();
2914 if (theme->ThemeSupportsWidget(aPresContext, nullptr, appearance))
2915 useTheme = true;
2916 }
2917
2918 ImgDrawResult result = ImgDrawResult::SUCCESS;
2919
2920 // use -moz-appearance if provided.
2921 if (useTheme) {
2922 nsRect dirty;
2923 dirty.IntersectRect(aSeparatorRect, aDirtyRect);
2924 theme->DrawWidgetBackground(&aRenderingContext, this, appearance,
2925 aSeparatorRect, dirty);
2926 } else {
2927 const nsStylePosition* stylePosition = separatorContext->StylePosition();
2928
2929 // Obtain the height for the separator or use the default value.
2930 nscoord height;
2931 if (stylePosition->mHeight.ConvertsToLength()) {
2932 height = stylePosition->mHeight.ToLength();
2933 } else {
2934 // Use default height 2px.
2935 height = nsPresContext::CSSPixelsToAppUnits(2);
2936 }
2937
2938 // Obtain the margins for the separator and then deflate our rect by that
2939 // amount. The separator is assumed to be contained within the deflated
2940 // rect.
2941 nsRect separatorRect(aSeparatorRect.x, aSeparatorRect.y,
2942 aSeparatorRect.width, height);
2943 nsMargin separatorMargin;
2944 separatorContext->StyleMargin()->GetMargin(separatorMargin);
2945 separatorRect.Deflate(separatorMargin);
2946
2947 // Center the separator.
2948 separatorRect.y += (aSeparatorRect.height - height) / 2;
2949
2950 result &=
2951 PaintBackgroundLayer(separatorContext, aPresContext, aRenderingContext,
2952 separatorRect, aDirtyRect);
2953 }
2954
2955 return result;
2956 }
2957
PaintCell(int32_t aRowIndex,nsTreeColumn * aColumn,const nsRect & aCellRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nscoord & aCurrX,nsPoint aPt,nsDisplayListBuilder * aBuilder)2958 ImgDrawResult nsTreeBodyFrame::PaintCell(
2959 int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aCellRect,
2960 nsPresContext* aPresContext, gfxContext& aRenderingContext,
2961 const nsRect& aDirtyRect, nscoord& aCurrX, nsPoint aPt,
2962 nsDisplayListBuilder* aBuilder) {
2963 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
2964
2965 // Now obtain the properties for our cell.
2966 // XXX Automatically fill in the following props: open, closed, container,
2967 // leaf, selected, focused, and the col ID.
2968 PrefillPropertyArray(aRowIndex, aColumn);
2969 nsAutoString properties;
2970 mView->GetCellProperties(aRowIndex, aColumn, properties);
2971 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
2972
2973 // Resolve style for the cell. It contains all the info we need to lay
2974 // ourselves out and to paint.
2975 ComputedStyle* cellContext =
2976 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
2977
2978 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
2979
2980 // Obtain the margins for the cell and then deflate our rect by that
2981 // amount. The cell is assumed to be contained within the deflated rect.
2982 nsRect cellRect(aCellRect);
2983 nsMargin cellMargin;
2984 cellContext->StyleMargin()->GetMargin(cellMargin);
2985 cellRect.Deflate(cellMargin);
2986
2987 // Paint our borders and background for our row rect.
2988 ImgDrawResult result = PaintBackgroundLayer(
2989 cellContext, aPresContext, aRenderingContext, cellRect, aDirtyRect);
2990
2991 // Adjust the rect for its border and padding.
2992 AdjustForBorderPadding(cellContext, cellRect);
2993
2994 nscoord currX = cellRect.x;
2995 nscoord remainingWidth = cellRect.width;
2996
2997 // Now we paint the contents of the cells.
2998 // Directionality of the tree determines the order in which we paint.
2999 // StyleDirection::Ltr means paint from left to right.
3000 // StyleDirection::Rtl means paint from right to left.
3001
3002 if (aColumn->IsPrimary()) {
3003 // If we're the primary column, we need to indent and paint the twisty and
3004 // any connecting lines between siblings.
3005
3006 int32_t level;
3007 mView->GetLevel(aRowIndex, &level);
3008
3009 if (!isRTL) currX += mIndentation * level;
3010 remainingWidth -= mIndentation * level;
3011
3012 // Resolve the style to use for the connecting lines.
3013 ComputedStyle* lineContext =
3014 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeLine());
3015
3016 if (mIndentation && level &&
3017 lineContext->StyleVisibility()->IsVisibleOrCollapsed()) {
3018 // Paint the thread lines.
3019
3020 // Get the size of the twisty. We don't want to paint the twisty
3021 // before painting of connecting lines since it would paint lines over
3022 // the twisty. But we need to leave a place for it.
3023 ComputedStyle* twistyContext =
3024 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
3025
3026 nsRect imageSize;
3027 nsRect twistyRect(aCellRect);
3028 GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, aPresContext,
3029 twistyContext);
3030
3031 nsMargin twistyMargin;
3032 twistyContext->StyleMargin()->GetMargin(twistyMargin);
3033 twistyRect.Inflate(twistyMargin);
3034
3035 const nsStyleBorder* borderStyle = lineContext->StyleBorder();
3036 // Resolve currentcolor values against the treeline context
3037 nscolor color = borderStyle->mBorderLeftColor.CalcColor(*lineContext);
3038 ColorPattern colorPatt(ToDeviceColor(color));
3039
3040 StyleBorderStyle style = borderStyle->GetBorderStyle(eSideLeft);
3041 StrokeOptions strokeOptions;
3042 nsLayoutUtils::InitDashPattern(strokeOptions, style);
3043
3044 nscoord srcX = currX + twistyRect.width - mIndentation / 2;
3045 nscoord lineY = (aRowIndex - mTopRowIndex) * mRowHeight + aPt.y;
3046
3047 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
3048 nsPresContext* pc = PresContext();
3049
3050 // Don't paint off our cell.
3051 if (srcX <= cellRect.x + cellRect.width) {
3052 nscoord destX = currX + twistyRect.width;
3053 if (destX > cellRect.x + cellRect.width)
3054 destX = cellRect.x + cellRect.width;
3055 if (isRTL) {
3056 srcX = currX + remainingWidth - (srcX - cellRect.x);
3057 destX = currX + remainingWidth - (destX - cellRect.x);
3058 }
3059 Point p1(pc->AppUnitsToGfxUnits(srcX),
3060 pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2));
3061 Point p2(pc->AppUnitsToGfxUnits(destX),
3062 pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2));
3063 SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget,
3064 strokeOptions.mLineWidth);
3065 drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions);
3066 }
3067
3068 int32_t currentParent = aRowIndex;
3069 for (int32_t i = level; i > 0; i--) {
3070 if (srcX <= cellRect.x + cellRect.width) {
3071 // Paint full vertical line only if we have next sibling.
3072 bool hasNextSibling;
3073 mView->HasNextSibling(currentParent, aRowIndex, &hasNextSibling);
3074 if (hasNextSibling || i == level) {
3075 Point p1(pc->AppUnitsToGfxUnits(srcX),
3076 pc->AppUnitsToGfxUnits(lineY));
3077 Point p2;
3078 p2.x = pc->AppUnitsToGfxUnits(srcX);
3079
3080 if (hasNextSibling)
3081 p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight);
3082 else if (i == level)
3083 p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2);
3084
3085 SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget,
3086 strokeOptions.mLineWidth);
3087 drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions);
3088 }
3089 }
3090
3091 int32_t parent;
3092 if (NS_FAILED(mView->GetParentIndex(currentParent, &parent)) ||
3093 parent < 0)
3094 break;
3095 currentParent = parent;
3096 srcX -= mIndentation;
3097 }
3098 }
3099
3100 // Always leave space for the twisty.
3101 nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
3102 result &= PaintTwisty(aRowIndex, aColumn, twistyRect, aPresContext,
3103 aRenderingContext, aDirtyRect, remainingWidth, currX);
3104 }
3105
3106 // Now paint the icon for our cell.
3107 nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);
3108 nsRect dirtyRect;
3109 if (dirtyRect.IntersectRect(aDirtyRect, iconRect)) {
3110 result &= PaintImage(aRowIndex, aColumn, iconRect, aPresContext,
3111 aRenderingContext, aDirtyRect, remainingWidth, currX,
3112 aBuilder);
3113 }
3114
3115 // Now paint our element, but only if we aren't a cycler column.
3116 // XXX until we have the ability to load images, allow the view to
3117 // insert text into cycler columns...
3118 if (!aColumn->IsCycler()) {
3119 nsRect elementRect(currX, cellRect.y, remainingWidth, cellRect.height);
3120 nsRect dirtyRect;
3121 if (dirtyRect.IntersectRect(aDirtyRect, elementRect)) {
3122 switch (aColumn->GetType()) {
3123 case TreeColumn_Binding::TYPE_TEXT:
3124 result &= PaintText(aRowIndex, aColumn, elementRect, aPresContext,
3125 aRenderingContext, aDirtyRect, currX);
3126 break;
3127 case TreeColumn_Binding::TYPE_CHECKBOX:
3128 result &= PaintCheckbox(aRowIndex, aColumn, elementRect, aPresContext,
3129 aRenderingContext, aDirtyRect);
3130 break;
3131 }
3132 }
3133 }
3134
3135 aCurrX = currX;
3136
3137 return result;
3138 }
3139
PaintTwisty(int32_t aRowIndex,nsTreeColumn * aColumn,const nsRect & aTwistyRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nscoord & aRemainingWidth,nscoord & aCurrX)3140 ImgDrawResult nsTreeBodyFrame::PaintTwisty(
3141 int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aTwistyRect,
3142 nsPresContext* aPresContext, gfxContext& aRenderingContext,
3143 const nsRect& aDirtyRect, nscoord& aRemainingWidth, nscoord& aCurrX) {
3144 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
3145
3146 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
3147 nscoord rightEdge = aCurrX + aRemainingWidth;
3148 // Paint the twisty, but only if we are a non-empty container.
3149 bool shouldPaint = false;
3150 bool isContainer = false;
3151 mView->IsContainer(aRowIndex, &isContainer);
3152 if (isContainer) {
3153 bool isContainerEmpty = false;
3154 mView->IsContainerEmpty(aRowIndex, &isContainerEmpty);
3155 if (!isContainerEmpty) shouldPaint = true;
3156 }
3157
3158 // Resolve style for the twisty.
3159 ComputedStyle* twistyContext =
3160 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
3161
3162 // Obtain the margins for the twisty and then deflate our rect by that
3163 // amount. The twisty is assumed to be contained within the deflated rect.
3164 nsRect twistyRect(aTwistyRect);
3165 nsMargin twistyMargin;
3166 twistyContext->StyleMargin()->GetMargin(twistyMargin);
3167 twistyRect.Deflate(twistyMargin);
3168
3169 nsRect imageSize;
3170 nsITheme* theme = GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect,
3171 aPresContext, twistyContext);
3172
3173 // Subtract out the remaining width. This is done even when we don't actually
3174 // paint a twisty in this cell, so that cells in different rows still line up.
3175 nsRect copyRect(twistyRect);
3176 copyRect.Inflate(twistyMargin);
3177 aRemainingWidth -= copyRect.width;
3178 if (!isRTL) aCurrX += copyRect.width;
3179
3180 ImgDrawResult result = ImgDrawResult::SUCCESS;
3181
3182 if (shouldPaint) {
3183 // Paint our borders and background for our image rect.
3184 result &= PaintBackgroundLayer(twistyContext, aPresContext,
3185 aRenderingContext, twistyRect, aDirtyRect);
3186
3187 if (theme) {
3188 if (isRTL) twistyRect.x = rightEdge - twistyRect.width;
3189 // yeah, I know it says we're drawing a background, but a twisty is really
3190 // a fg object since it doesn't have anything that gecko would want to
3191 // draw over it. Besides, we have to prevent imagelib from drawing it.
3192 nsRect dirty;
3193 dirty.IntersectRect(twistyRect, aDirtyRect);
3194 theme->DrawWidgetBackground(
3195 &aRenderingContext, this,
3196 twistyContext->StyleDisplay()->EffectiveAppearance(), twistyRect,
3197 dirty);
3198 } else {
3199 // Time to paint the twisty.
3200 // Adjust the rect for its border and padding.
3201 nsMargin bp(0, 0, 0, 0);
3202 GetBorderPadding(twistyContext, bp);
3203 twistyRect.Deflate(bp);
3204 if (isRTL) twistyRect.x = rightEdge - twistyRect.width;
3205 imageSize.Deflate(bp);
3206
3207 // Get the image for drawing.
3208 nsCOMPtr<imgIContainer> image;
3209 bool useImageRegion = true;
3210 GetImage(aRowIndex, aColumn, true, twistyContext, useImageRegion,
3211 getter_AddRefs(image));
3212 if (image) {
3213 nsPoint anchorPoint = twistyRect.TopLeft();
3214
3215 // Center the image. XXX Obey vertical-align style prop?
3216 if (imageSize.height < twistyRect.height) {
3217 anchorPoint.y += (twistyRect.height - imageSize.height) / 2;
3218 }
3219
3220 // Apply context paint if applicable
3221 Maybe<SVGImageContext> svgContext;
3222 SVGImageContext::MaybeStoreContextPaint(svgContext, twistyContext,
3223 image);
3224
3225 // Paint the image.
3226 result &= nsLayoutUtils::DrawSingleUnscaledImage(
3227 aRenderingContext, aPresContext, image, SamplingFilter::POINT,
3228 anchorPoint, &aDirtyRect, svgContext, imgIContainer::FLAG_NONE,
3229 &imageSize);
3230 }
3231 }
3232 }
3233
3234 return result;
3235 }
3236
PaintImage(int32_t aRowIndex,nsTreeColumn * aColumn,const nsRect & aImageRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nscoord & aRemainingWidth,nscoord & aCurrX,nsDisplayListBuilder * aBuilder)3237 ImgDrawResult nsTreeBodyFrame::PaintImage(
3238 int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aImageRect,
3239 nsPresContext* aPresContext, gfxContext& aRenderingContext,
3240 const nsRect& aDirtyRect, nscoord& aRemainingWidth, nscoord& aCurrX,
3241 nsDisplayListBuilder* aBuilder) {
3242 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
3243
3244 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
3245 nscoord rightEdge = aCurrX + aRemainingWidth;
3246 // Resolve style for the image.
3247 ComputedStyle* imageContext =
3248 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeImage());
3249
3250 // Obtain opacity value for the image.
3251 float opacity = imageContext->StyleEffects()->mOpacity;
3252
3253 // Obtain the margins for the image and then deflate our rect by that
3254 // amount. The image is assumed to be contained within the deflated rect.
3255 nsRect imageRect(aImageRect);
3256 nsMargin imageMargin;
3257 imageContext->StyleMargin()->GetMargin(imageMargin);
3258 imageRect.Deflate(imageMargin);
3259
3260 // Get the image.
3261 bool useImageRegion = true;
3262 nsCOMPtr<imgIContainer> image;
3263 GetImage(aRowIndex, aColumn, false, imageContext, useImageRegion,
3264 getter_AddRefs(image));
3265
3266 // Get the image destination size.
3267 nsSize imageDestSize = GetImageDestSize(imageContext, useImageRegion, image);
3268 if (!imageDestSize.width || !imageDestSize.height) {
3269 return ImgDrawResult::SUCCESS;
3270 }
3271
3272 // Get the borders and padding.
3273 nsMargin bp(0, 0, 0, 0);
3274 GetBorderPadding(imageContext, bp);
3275
3276 // destRect will be passed as the aDestRect argument in the DrawImage method.
3277 // Start with the imageDestSize width and height.
3278 nsRect destRect(0, 0, imageDestSize.width, imageDestSize.height);
3279 // Inflate destRect for borders and padding so that we can compare/adjust
3280 // with respect to imageRect.
3281 destRect.Inflate(bp);
3282
3283 // The destRect width and height have not been adjusted to fit within the
3284 // cell width and height.
3285 // We must adjust the width even if image is null, because the width is used
3286 // to update the aRemainingWidth and aCurrX values.
3287 // Since the height isn't used unless the image is not null, we will adjust
3288 // the height inside the if (image) block below.
3289
3290 if (destRect.width > imageRect.width) {
3291 // The destRect is too wide to fit within the cell width.
3292 // Adjust destRect width to fit within the cell width.
3293 destRect.width = imageRect.width;
3294 } else {
3295 // The cell is wider than the destRect.
3296 // In a cycler column, the image is centered horizontally.
3297 if (!aColumn->IsCycler()) {
3298 // If this column is not a cycler, we won't center the image horizontally.
3299 // We adjust the imageRect width so that the image is placed at the start
3300 // of the cell.
3301 imageRect.width = destRect.width;
3302 }
3303 }
3304
3305 ImgDrawResult result = ImgDrawResult::SUCCESS;
3306
3307 if (image) {
3308 if (isRTL) imageRect.x = rightEdge - imageRect.width;
3309 // Paint our borders and background for our image rect
3310 result &= PaintBackgroundLayer(imageContext, aPresContext,
3311 aRenderingContext, imageRect, aDirtyRect);
3312
3313 // The destRect x and y have not been set yet. Let's do that now.
3314 // Initially, we use the imageRect x and y.
3315 destRect.x = imageRect.x;
3316 destRect.y = imageRect.y;
3317
3318 if (destRect.width < imageRect.width) {
3319 // The destRect width is smaller than the cell width.
3320 // Center the image horizontally in the cell.
3321 // Adjust the destRect x accordingly.
3322 destRect.x += (imageRect.width - destRect.width) / 2;
3323 }
3324
3325 // Now it's time to adjust the destRect height to fit within the cell
3326 // height.
3327 if (destRect.height > imageRect.height) {
3328 // The destRect height is larger than the cell height.
3329 // Adjust destRect height to fit within the cell height.
3330 destRect.height = imageRect.height;
3331 } else if (destRect.height < imageRect.height) {
3332 // The destRect height is smaller than the cell height.
3333 // Center the image vertically in the cell.
3334 // Adjust the destRect y accordingly.
3335 destRect.y += (imageRect.height - destRect.height) / 2;
3336 }
3337
3338 // It's almost time to paint the image.
3339 // Deflate destRect for the border and padding.
3340 destRect.Deflate(bp);
3341
3342 // Compute the area where our whole image would be mapped, to get the
3343 // desired subregion onto our actual destRect:
3344 nsRect wholeImageDest;
3345 CSSIntSize rawImageCSSIntSize;
3346 if (NS_SUCCEEDED(image->GetWidth(&rawImageCSSIntSize.width)) &&
3347 NS_SUCCEEDED(image->GetHeight(&rawImageCSSIntSize.height))) {
3348 // Get the image source rectangle - the rectangle containing the part of
3349 // the image that we are going to display. sourceRect will be passed as
3350 // the aSrcRect argument in the DrawImage method.
3351 nsRect sourceRect =
3352 GetImageSourceRect(imageContext, useImageRegion, image);
3353
3354 // Let's say that the image is 100 pixels tall and that the CSS has
3355 // specified that the destination height should be 50 pixels tall. Let's
3356 // say that the cell height is only 20 pixels. So, in those 20 visible
3357 // pixels, we want to see the top 20/50ths of the image. So, the
3358 // sourceRect.height should be 100 * 20 / 50, which is 40 pixels.
3359 // Essentially, we are scaling the image as dictated by the CSS
3360 // destination height and width, and we are then clipping the scaled
3361 // image by the cell width and height.
3362 nsSize rawImageSize(CSSPixel::ToAppUnits(rawImageCSSIntSize));
3363 wholeImageDest = nsLayoutUtils::GetWholeImageDestination(
3364 rawImageSize, sourceRect, nsRect(destRect.TopLeft(), imageDestSize));
3365 } else {
3366 // GetWidth/GetHeight failed, so we can't easily map a subregion of the
3367 // source image onto the destination area.
3368 // * If this happens with a RasterImage, it probably means the image is
3369 // in an error state, and we shouldn't draw anything. Hence, we leave
3370 // wholeImageDest as an empty rect (its initial state).
3371 // * If this happens with a VectorImage, it probably means the image has
3372 // no explicit width or height attribute -- but we can still proceed and
3373 // just treat the destination area as our whole SVG image area. Hence, we
3374 // set wholeImageDest to the full destRect.
3375 if (image->GetType() == imgIContainer::TYPE_VECTOR) {
3376 wholeImageDest = destRect;
3377 }
3378 }
3379
3380 if (opacity != 1.0f) {
3381 aRenderingContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
3382 opacity);
3383 }
3384
3385 uint32_t drawFlags = aBuilder && aBuilder->UseHighQualityScaling()
3386 ? imgIContainer::FLAG_HIGH_QUALITY_SCALING
3387 : imgIContainer::FLAG_NONE;
3388 result &= nsLayoutUtils::DrawImage(
3389 aRenderingContext, imageContext, aPresContext, image,
3390 nsLayoutUtils::GetSamplingFilterForFrame(this), wholeImageDest,
3391 destRect, destRect.TopLeft(), aDirtyRect, drawFlags);
3392
3393 if (opacity != 1.0f) {
3394 aRenderingContext.PopGroupAndBlend();
3395 }
3396 }
3397
3398 // Update the aRemainingWidth and aCurrX values.
3399 imageRect.Inflate(imageMargin);
3400 aRemainingWidth -= imageRect.width;
3401 if (!isRTL) {
3402 aCurrX += imageRect.width;
3403 }
3404
3405 return result;
3406 }
3407
PaintText(int32_t aRowIndex,nsTreeColumn * aColumn,const nsRect & aTextRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nscoord & aCurrX)3408 ImgDrawResult nsTreeBodyFrame::PaintText(
3409 int32_t aRowIndex, nsTreeColumn* aColumn, const nsRect& aTextRect,
3410 nsPresContext* aPresContext, gfxContext& aRenderingContext,
3411 const nsRect& aDirtyRect, nscoord& aCurrX) {
3412 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
3413
3414 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
3415
3416 // Now obtain the text for our cell.
3417 nsAutoString text;
3418 mView->GetCellText(aRowIndex, aColumn, text);
3419
3420 // We're going to paint this text so we need to ensure bidi is enabled if
3421 // necessary
3422 CheckTextForBidi(text);
3423
3424 ImgDrawResult result = ImgDrawResult::SUCCESS;
3425
3426 if (text.Length() == 0) {
3427 // Don't paint an empty string. XXX What about background/borders? Still
3428 // paint?
3429 return result;
3430 }
3431
3432 int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
3433 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
3434
3435 // Resolve style for the text. It contains all the info we need to lay
3436 // ourselves out and to paint.
3437 ComputedStyle* textContext =
3438 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
3439
3440 // Obtain opacity value for the image.
3441 float opacity = textContext->StyleEffects()->mOpacity;
3442
3443 // Obtain the margins for the text and then deflate our rect by that
3444 // amount. The text is assumed to be contained within the deflated rect.
3445 nsRect textRect(aTextRect);
3446 nsMargin textMargin;
3447 textContext->StyleMargin()->GetMargin(textMargin);
3448 textRect.Deflate(textMargin);
3449
3450 // Adjust the rect for its border and padding.
3451 nsMargin bp(0, 0, 0, 0);
3452 GetBorderPadding(textContext, bp);
3453 textRect.Deflate(bp);
3454
3455 // Compute our text size.
3456 RefPtr<nsFontMetrics> fontMet =
3457 nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, PresContext());
3458
3459 nscoord height = fontMet->MaxHeight();
3460 nscoord baseline = fontMet->MaxAscent();
3461
3462 // Center the text. XXX Obey vertical-align style prop?
3463 if (height < textRect.height) {
3464 textRect.y += (textRect.height - height) / 2;
3465 textRect.height = height;
3466 }
3467
3468 // Set our font.
3469 AdjustForCellText(text, aRowIndex, aColumn, aRenderingContext, *fontMet,
3470 textRect);
3471 textRect.Inflate(bp);
3472
3473 // Subtract out the remaining width.
3474 if (!isRTL) aCurrX += textRect.width + textMargin.LeftRight();
3475
3476 result &= PaintBackgroundLayer(textContext, aPresContext, aRenderingContext,
3477 textRect, aDirtyRect);
3478
3479 // Time to paint our text.
3480 textRect.Deflate(bp);
3481
3482 // Set our color.
3483 ColorPattern color(ToDeviceColor(textContext->StyleText()->mColor));
3484
3485 // Draw decorations.
3486 StyleTextDecorationLine decorations =
3487 textContext->StyleTextReset()->mTextDecorationLine;
3488
3489 nscoord offset;
3490 nscoord size;
3491 if (decorations & (StyleTextDecorationLine::OVERLINE |
3492 StyleTextDecorationLine::UNDERLINE)) {
3493 fontMet->GetUnderline(offset, size);
3494 if (decorations & StyleTextDecorationLine::OVERLINE) {
3495 nsRect r(textRect.x, textRect.y, textRect.width, size);
3496 Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
3497 drawTarget->FillRect(devPxRect, color);
3498 }
3499 if (decorations & StyleTextDecorationLine::UNDERLINE) {
3500 nsRect r(textRect.x, textRect.y + baseline - offset, textRect.width,
3501 size);
3502 Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
3503 drawTarget->FillRect(devPxRect, color);
3504 }
3505 }
3506 if (decorations & StyleTextDecorationLine::LINE_THROUGH) {
3507 fontMet->GetStrikeout(offset, size);
3508 nsRect r(textRect.x, textRect.y + baseline - offset, textRect.width, size);
3509 Rect devPxRect = NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget);
3510 drawTarget->FillRect(devPxRect, color);
3511 }
3512 ComputedStyle* cellContext =
3513 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
3514
3515 if (opacity != 1.0f) {
3516 aRenderingContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
3517 opacity);
3518 }
3519
3520 aRenderingContext.SetColor(
3521 sRGBColor::FromABGR(textContext->StyleText()->mColor.ToColor()));
3522 nsLayoutUtils::DrawString(
3523 this, *fontMet, &aRenderingContext, text.get(), text.Length(),
3524 textRect.TopLeft() + nsPoint(0, baseline), cellContext);
3525
3526 if (opacity != 1.0f) {
3527 aRenderingContext.PopGroupAndBlend();
3528 }
3529
3530 return result;
3531 }
3532
PaintCheckbox(int32_t aRowIndex,nsTreeColumn * aColumn,const nsRect & aCheckboxRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect)3533 ImgDrawResult nsTreeBodyFrame::PaintCheckbox(int32_t aRowIndex,
3534 nsTreeColumn* aColumn,
3535 const nsRect& aCheckboxRect,
3536 nsPresContext* aPresContext,
3537 gfxContext& aRenderingContext,
3538 const nsRect& aDirtyRect) {
3539 MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");
3540
3541 // Resolve style for the checkbox.
3542 ComputedStyle* checkboxContext =
3543 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCheckbox());
3544
3545 nscoord rightEdge = aCheckboxRect.XMost();
3546
3547 // Obtain the margins for the checkbox and then deflate our rect by that
3548 // amount. The checkbox is assumed to be contained within the deflated rect.
3549 nsRect checkboxRect(aCheckboxRect);
3550 nsMargin checkboxMargin;
3551 checkboxContext->StyleMargin()->GetMargin(checkboxMargin);
3552 checkboxRect.Deflate(checkboxMargin);
3553
3554 nsRect imageSize = GetImageSize(aRowIndex, aColumn, true, checkboxContext);
3555
3556 if (imageSize.height > checkboxRect.height) {
3557 imageSize.height = checkboxRect.height;
3558 }
3559 if (imageSize.width > checkboxRect.width) {
3560 imageSize.width = checkboxRect.width;
3561 }
3562
3563 if (StyleVisibility()->mDirection == StyleDirection::Rtl) {
3564 checkboxRect.x = rightEdge - checkboxRect.width;
3565 }
3566
3567 // Paint our borders and background for our image rect.
3568 ImgDrawResult result =
3569 PaintBackgroundLayer(checkboxContext, aPresContext, aRenderingContext,
3570 checkboxRect, aDirtyRect);
3571
3572 // Time to paint the checkbox.
3573 // Adjust the rect for its border and padding.
3574 nsMargin bp(0, 0, 0, 0);
3575 GetBorderPadding(checkboxContext, bp);
3576 checkboxRect.Deflate(bp);
3577
3578 // Get the image for drawing.
3579 nsCOMPtr<imgIContainer> image;
3580 bool useImageRegion = true;
3581 GetImage(aRowIndex, aColumn, true, checkboxContext, useImageRegion,
3582 getter_AddRefs(image));
3583 if (image) {
3584 nsPoint pt = checkboxRect.TopLeft();
3585
3586 if (imageSize.height < checkboxRect.height) {
3587 pt.y += (checkboxRect.height - imageSize.height) / 2;
3588 }
3589
3590 if (imageSize.width < checkboxRect.width) {
3591 pt.x += (checkboxRect.width - imageSize.width) / 2;
3592 }
3593
3594 // Apply context paint if applicable
3595 Maybe<SVGImageContext> svgContext;
3596 SVGImageContext::MaybeStoreContextPaint(svgContext, checkboxContext, image);
3597 // Paint the image.
3598 result &= nsLayoutUtils::DrawSingleUnscaledImage(
3599 aRenderingContext, aPresContext, image, SamplingFilter::POINT, pt,
3600 &aDirtyRect, svgContext, imgIContainer::FLAG_NONE, &imageSize);
3601 }
3602
3603 return result;
3604 }
3605
PaintDropFeedback(const nsRect & aDropFeedbackRect,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,nsPoint aPt)3606 ImgDrawResult nsTreeBodyFrame::PaintDropFeedback(
3607 const nsRect& aDropFeedbackRect, nsPresContext* aPresContext,
3608 gfxContext& aRenderingContext, const nsRect& aDirtyRect, nsPoint aPt) {
3609 // Paint the drop feedback in between rows.
3610
3611 nscoord currX;
3612
3613 // Adjust for the primary cell.
3614 nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn();
3615
3616 if (primaryCol) {
3617 #ifdef DEBUG
3618 nsresult rv =
3619 #endif
3620 primaryCol->GetXInTwips(this, &currX);
3621 NS_ASSERTION(NS_SUCCEEDED(rv), "primary column is invalid?");
3622
3623 currX += aPt.x - mHorzPosition;
3624 } else {
3625 currX = aDropFeedbackRect.x;
3626 }
3627
3628 PrefillPropertyArray(mSlots->mDropRow, primaryCol);
3629
3630 // Resolve the style to use for the drop feedback.
3631 ComputedStyle* feedbackContext =
3632 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeDropFeedback());
3633
3634 ImgDrawResult result = ImgDrawResult::SUCCESS;
3635
3636 // Paint only if it is visible.
3637 if (feedbackContext->StyleVisibility()->IsVisibleOrCollapsed()) {
3638 int32_t level;
3639 mView->GetLevel(mSlots->mDropRow, &level);
3640
3641 // If our previous or next row has greater level use that for
3642 // correct visual indentation.
3643 if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) {
3644 if (mSlots->mDropRow > 0) {
3645 int32_t previousLevel;
3646 mView->GetLevel(mSlots->mDropRow - 1, &previousLevel);
3647 if (previousLevel > level) level = previousLevel;
3648 }
3649 } else {
3650 if (mSlots->mDropRow < mRowCount - 1) {
3651 int32_t nextLevel;
3652 mView->GetLevel(mSlots->mDropRow + 1, &nextLevel);
3653 if (nextLevel > level) level = nextLevel;
3654 }
3655 }
3656
3657 currX += mIndentation * level;
3658
3659 if (primaryCol) {
3660 ComputedStyle* twistyContext =
3661 GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
3662 nsRect imageSize;
3663 nsRect twistyRect;
3664 GetTwistyRect(mSlots->mDropRow, primaryCol, imageSize, twistyRect,
3665 aPresContext, twistyContext);
3666 nsMargin twistyMargin;
3667 twistyContext->StyleMargin()->GetMargin(twistyMargin);
3668 twistyRect.Inflate(twistyMargin);
3669 currX += twistyRect.width;
3670 }
3671
3672 const nsStylePosition* stylePosition = feedbackContext->StylePosition();
3673
3674 // Obtain the width for the drop feedback or use default value.
3675 nscoord width;
3676 if (stylePosition->mWidth.ConvertsToLength()) {
3677 width = stylePosition->mWidth.ToLength();
3678 } else {
3679 // Use default width 50px.
3680 width = nsPresContext::CSSPixelsToAppUnits(50);
3681 }
3682
3683 // Obtain the height for the drop feedback or use default value.
3684 nscoord height;
3685 if (stylePosition->mHeight.ConvertsToLength()) {
3686 height = stylePosition->mHeight.ToLength();
3687 } else {
3688 // Use default height 2px.
3689 height = nsPresContext::CSSPixelsToAppUnits(2);
3690 }
3691
3692 // Obtain the margins for the drop feedback and then deflate our rect
3693 // by that amount.
3694 nsRect feedbackRect(currX, aDropFeedbackRect.y, width, height);
3695 nsMargin margin;
3696 feedbackContext->StyleMargin()->GetMargin(margin);
3697 feedbackRect.Deflate(margin);
3698
3699 feedbackRect.y += (aDropFeedbackRect.height - height) / 2;
3700
3701 // Finally paint the drop feedback.
3702 result &= PaintBackgroundLayer(feedbackContext, aPresContext,
3703 aRenderingContext, feedbackRect, aDirtyRect);
3704 }
3705
3706 return result;
3707 }
3708
PaintBackgroundLayer(ComputedStyle * aComputedStyle,nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aRect,const nsRect & aDirtyRect)3709 ImgDrawResult nsTreeBodyFrame::PaintBackgroundLayer(
3710 ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
3711 gfxContext& aRenderingContext, const nsRect& aRect,
3712 const nsRect& aDirtyRect) {
3713 const nsStyleBorder* myBorder = aComputedStyle->StyleBorder();
3714 nsCSSRendering::PaintBGParams params =
3715 nsCSSRendering::PaintBGParams::ForAllLayers(
3716 *aPresContext, aDirtyRect, aRect, this,
3717 nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES);
3718 ImgDrawResult result = nsCSSRendering::PaintStyleImageLayerWithSC(
3719 params, aRenderingContext, aComputedStyle, *myBorder);
3720
3721 result &= nsCSSRendering::PaintBorderWithStyleBorder(
3722 aPresContext, aRenderingContext, this, aDirtyRect, aRect, *myBorder,
3723 mComputedStyle, PaintBorderFlags::SyncDecodeImages);
3724
3725 nsCSSRendering::PaintNonThemedOutline(aPresContext, aRenderingContext, this,
3726 aDirtyRect, aRect, aComputedStyle);
3727
3728 return result;
3729 }
3730
3731 // Scrolling
EnsureRowIsVisible(int32_t aRow)3732 nsresult nsTreeBodyFrame::EnsureRowIsVisible(int32_t aRow) {
3733 ScrollParts parts = GetScrollParts();
3734 nsresult rv = EnsureRowIsVisibleInternal(parts, aRow);
3735 NS_ENSURE_SUCCESS(rv, rv);
3736 UpdateScrollbars(parts);
3737 return rv;
3738 }
3739
EnsureRowIsVisibleInternal(const ScrollParts & aParts,int32_t aRow)3740 nsresult nsTreeBodyFrame::EnsureRowIsVisibleInternal(const ScrollParts& aParts,
3741 int32_t aRow) {
3742 if (!mView || !mPageLength) return NS_OK;
3743
3744 if (mTopRowIndex <= aRow && mTopRowIndex + mPageLength > aRow) return NS_OK;
3745
3746 if (aRow < mTopRowIndex)
3747 ScrollToRowInternal(aParts, aRow);
3748 else {
3749 // Bring it just on-screen.
3750 int32_t distance = aRow - (mTopRowIndex + mPageLength) + 1;
3751 ScrollToRowInternal(aParts, mTopRowIndex + distance);
3752 }
3753
3754 return NS_OK;
3755 }
3756
EnsureCellIsVisible(int32_t aRow,nsTreeColumn * aCol)3757 nsresult nsTreeBodyFrame::EnsureCellIsVisible(int32_t aRow,
3758 nsTreeColumn* aCol) {
3759 if (!aCol) return NS_ERROR_INVALID_ARG;
3760
3761 ScrollParts parts = GetScrollParts();
3762
3763 nscoord result = -1;
3764 nsresult rv;
3765
3766 nscoord columnPos;
3767 rv = aCol->GetXInTwips(this, &columnPos);
3768 if (NS_FAILED(rv)) return rv;
3769
3770 nscoord columnWidth;
3771 rv = aCol->GetWidthInTwips(this, &columnWidth);
3772 if (NS_FAILED(rv)) return rv;
3773
3774 // If the start of the column is before the
3775 // start of the horizontal view, then scroll
3776 if (columnPos < mHorzPosition) result = columnPos;
3777 // If the end of the column is past the end of
3778 // the horizontal view, then scroll
3779 else if ((columnPos + columnWidth) > (mHorzPosition + mInnerBox.width))
3780 result = ((columnPos + columnWidth) - (mHorzPosition + mInnerBox.width)) +
3781 mHorzPosition;
3782
3783 if (result != -1) {
3784 rv = ScrollHorzInternal(parts, result);
3785 if (NS_FAILED(rv)) return rv;
3786 }
3787
3788 rv = EnsureRowIsVisibleInternal(parts, aRow);
3789 NS_ENSURE_SUCCESS(rv, rv);
3790 UpdateScrollbars(parts);
3791 return rv;
3792 }
3793
ScrollToRow(int32_t aRow)3794 void nsTreeBodyFrame::ScrollToRow(int32_t aRow) {
3795 ScrollParts parts = GetScrollParts();
3796 ScrollToRowInternal(parts, aRow);
3797 UpdateScrollbars(parts);
3798 }
3799
ScrollToRowInternal(const ScrollParts & aParts,int32_t aRow)3800 nsresult nsTreeBodyFrame::ScrollToRowInternal(const ScrollParts& aParts,
3801 int32_t aRow) {
3802 ScrollInternal(aParts, aRow);
3803
3804 return NS_OK;
3805 }
3806
ScrollByLines(int32_t aNumLines)3807 void nsTreeBodyFrame::ScrollByLines(int32_t aNumLines) {
3808 if (!mView) {
3809 return;
3810 }
3811 int32_t newIndex = mTopRowIndex + aNumLines;
3812 ScrollToRow(newIndex);
3813 }
3814
ScrollByPages(int32_t aNumPages)3815 void nsTreeBodyFrame::ScrollByPages(int32_t aNumPages) {
3816 if (!mView) {
3817 return;
3818 }
3819 int32_t newIndex = mTopRowIndex + aNumPages * mPageLength;
3820 ScrollToRow(newIndex);
3821 }
3822
ScrollInternal(const ScrollParts & aParts,int32_t aRow)3823 nsresult nsTreeBodyFrame::ScrollInternal(const ScrollParts& aParts,
3824 int32_t aRow) {
3825 if (!mView) {
3826 return NS_OK;
3827 }
3828
3829 // Note that we may be "over scrolled" at this point; that is the
3830 // current mTopRowIndex may be larger than mRowCount - mPageLength.
3831 // This can happen when items are removed for example. (bug 1085050)
3832
3833 int32_t maxTopRowIndex = std::max(0, mRowCount - mPageLength);
3834 aRow = mozilla::clamped(aRow, 0, maxTopRowIndex);
3835 if (aRow == mTopRowIndex) {
3836 return NS_OK;
3837 }
3838 mTopRowIndex = aRow;
3839 Invalidate();
3840 PostScrollEvent();
3841 return NS_OK;
3842 }
3843
ScrollHorzInternal(const ScrollParts & aParts,int32_t aPosition)3844 nsresult nsTreeBodyFrame::ScrollHorzInternal(const ScrollParts& aParts,
3845 int32_t aPosition) {
3846 if (!mView || !aParts.mColumnsScrollFrame || !aParts.mHScrollbar)
3847 return NS_OK;
3848
3849 if (aPosition == mHorzPosition) return NS_OK;
3850
3851 if (aPosition < 0 || aPosition > mHorzWidth) return NS_OK;
3852
3853 nsRect bounds = aParts.mColumnsFrame->GetRect();
3854 if (aPosition > (mHorzWidth - bounds.width))
3855 aPosition = mHorzWidth - bounds.width;
3856
3857 mHorzPosition = aPosition;
3858
3859 Invalidate();
3860
3861 // Update the column scroll view
3862 AutoWeakFrame weakFrame(this);
3863 aParts.mColumnsScrollFrame->ScrollTo(nsPoint(mHorzPosition, 0),
3864 ScrollMode::Instant);
3865 if (!weakFrame.IsAlive()) {
3866 return NS_ERROR_FAILURE;
3867 }
3868 // And fire off an event about it all
3869 PostScrollEvent();
3870 return NS_OK;
3871 }
3872
ScrollByPage(nsScrollbarFrame * aScrollbar,int32_t aDirection,nsIScrollbarMediator::ScrollSnapMode aSnap)3873 void nsTreeBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar,
3874 int32_t aDirection,
3875 nsIScrollbarMediator::ScrollSnapMode aSnap) {
3876 // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
3877 MOZ_ASSERT(aScrollbar != nullptr);
3878 ScrollByPages(aDirection);
3879 }
3880
ScrollByWhole(nsScrollbarFrame * aScrollbar,int32_t aDirection,nsIScrollbarMediator::ScrollSnapMode aSnap)3881 void nsTreeBodyFrame::ScrollByWhole(
3882 nsScrollbarFrame* aScrollbar, int32_t aDirection,
3883 nsIScrollbarMediator::ScrollSnapMode aSnap) {
3884 // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
3885 MOZ_ASSERT(aScrollbar != nullptr);
3886 int32_t newIndex = aDirection < 0 ? 0 : mTopRowIndex;
3887 ScrollToRow(newIndex);
3888 }
3889
ScrollByLine(nsScrollbarFrame * aScrollbar,int32_t aDirection,nsIScrollbarMediator::ScrollSnapMode aSnap)3890 void nsTreeBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar,
3891 int32_t aDirection,
3892 nsIScrollbarMediator::ScrollSnapMode aSnap) {
3893 // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
3894 MOZ_ASSERT(aScrollbar != nullptr);
3895 ScrollByLines(aDirection);
3896 }
3897
ScrollByUnit(nsScrollbarFrame * aScrollbar,ScrollMode aMode,int32_t aDirection,ScrollUnit aUnit,ScrollSnapMode aSnap)3898 void nsTreeBodyFrame::ScrollByUnit(nsScrollbarFrame* aScrollbar,
3899 ScrollMode aMode, int32_t aDirection,
3900 ScrollUnit aUnit,
3901 ScrollSnapMode aSnap /* = DISABLE_SNAP */) {
3902 MOZ_ASSERT_UNREACHABLE("Can't get here, we pass false to MoveToNewPosition");
3903 }
3904
RepeatButtonScroll(nsScrollbarFrame * aScrollbar)3905 void nsTreeBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar) {
3906 ScrollParts parts = GetScrollParts();
3907 int32_t increment = aScrollbar->GetIncrement();
3908 int32_t direction = 0;
3909 if (increment < 0) {
3910 direction = -1;
3911 } else if (increment > 0) {
3912 direction = 1;
3913 }
3914 bool isHorizontal = aScrollbar->IsXULHorizontal();
3915
3916 AutoWeakFrame weakFrame(this);
3917 if (isHorizontal) {
3918 int32_t curpos = aScrollbar->MoveToNewPosition(
3919 nsScrollbarFrame::ImplementsScrollByUnit::No);
3920 if (weakFrame.IsAlive()) {
3921 ScrollHorzInternal(parts, curpos);
3922 }
3923 } else {
3924 ScrollToRowInternal(parts, mTopRowIndex + direction);
3925 }
3926
3927 if (weakFrame.IsAlive() && mScrollbarActivity) {
3928 mScrollbarActivity->ActivityOccurred();
3929 }
3930 if (weakFrame.IsAlive()) {
3931 UpdateScrollbars(parts);
3932 }
3933 }
3934
ThumbMoved(nsScrollbarFrame * aScrollbar,nscoord aOldPos,nscoord aNewPos)3935 void nsTreeBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar, nscoord aOldPos,
3936 nscoord aNewPos) {
3937 ScrollParts parts = GetScrollParts();
3938
3939 if (aOldPos == aNewPos) return;
3940
3941 AutoWeakFrame weakFrame(this);
3942
3943 // Vertical Scrollbar
3944 if (parts.mVScrollbar == aScrollbar) {
3945 nscoord rh = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
3946 nscoord newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
3947 nscoord newrow = (rh > 0) ? (newIndex / rh) : 0;
3948 ScrollInternal(parts, newrow);
3949 // Horizontal Scrollbar
3950 } else if (parts.mHScrollbar == aScrollbar) {
3951 int32_t newIndex = nsPresContext::AppUnitsToIntCSSPixels(aNewPos);
3952 ScrollHorzInternal(parts, newIndex);
3953 }
3954 if (weakFrame.IsAlive()) {
3955 UpdateScrollbars(parts);
3956 }
3957 }
3958
3959 // The style cache.
GetPseudoComputedStyle(nsCSSAnonBoxPseudoStaticAtom * aPseudoElement)3960 ComputedStyle* nsTreeBodyFrame::GetPseudoComputedStyle(
3961 nsCSSAnonBoxPseudoStaticAtom* aPseudoElement) {
3962 return mStyleCache.GetComputedStyle(PresContext(), mContent, mComputedStyle,
3963 aPseudoElement, mScratchArray);
3964 }
3965
GetBaseElement()3966 XULTreeElement* nsTreeBodyFrame::GetBaseElement() {
3967 if (!mTree) {
3968 nsIFrame* parent = GetParent();
3969 while (parent) {
3970 nsIContent* content = parent->GetContent();
3971 if (content && content->IsXULElement(nsGkAtoms::tree)) {
3972 mTree = XULTreeElement::FromNodeOrNull(content->AsElement());
3973 break;
3974 }
3975
3976 parent = parent->GetInFlowParent();
3977 }
3978 }
3979
3980 return mTree;
3981 }
3982
ClearStyleAndImageCaches()3983 nsresult nsTreeBodyFrame::ClearStyleAndImageCaches() {
3984 mStyleCache.Clear();
3985 CancelImageRequests();
3986 mImageCache.Clear();
3987 return NS_OK;
3988 }
3989
RemoveImageCacheEntry(int32_t aRowIndex,nsTreeColumn * aCol)3990 void nsTreeBodyFrame::RemoveImageCacheEntry(int32_t aRowIndex,
3991 nsTreeColumn* aCol) {
3992 nsAutoString imageSrc;
3993 if (NS_SUCCEEDED(mView->GetImageSrc(aRowIndex, aCol, imageSrc))) {
3994 nsTreeImageCacheEntry entry;
3995 if (mImageCache.Get(imageSrc, &entry)) {
3996 nsLayoutUtils::DeregisterImageRequest(PresContext(), entry.request,
3997 nullptr);
3998 entry.request->UnlockImage();
3999 entry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
4000 mImageCache.Remove(imageSrc);
4001 }
4002 }
4003 }
4004
4005 /* virtual */
DidSetComputedStyle(ComputedStyle * aOldComputedStyle)4006 void nsTreeBodyFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
4007 nsLeafBoxFrame::DidSetComputedStyle(aOldComputedStyle);
4008
4009 // Clear the style cache; the pointers are no longer even valid
4010 mStyleCache.Clear();
4011 // XXX The following is hacky, but it's not incorrect,
4012 // and appears to fix a few bugs with style changes, like text zoom and
4013 // dpi changes
4014 mIndentation = GetIndentation();
4015 mRowHeight = GetRowHeight();
4016 mStringWidth = -1;
4017 }
4018
OffsetForHorzScroll(nsRect & rect,bool clip)4019 bool nsTreeBodyFrame::OffsetForHorzScroll(nsRect& rect, bool clip) {
4020 rect.x -= mHorzPosition;
4021
4022 // Scrolled out before
4023 if (rect.XMost() <= mInnerBox.x) return false;
4024
4025 // Scrolled out after
4026 if (rect.x > mInnerBox.XMost()) return false;
4027
4028 if (clip) {
4029 nscoord leftEdge = std::max(rect.x, mInnerBox.x);
4030 nscoord rightEdge = std::min(rect.XMost(), mInnerBox.XMost());
4031 rect.x = leftEdge;
4032 rect.width = rightEdge - leftEdge;
4033
4034 // Should have returned false above
4035 NS_ASSERTION(rect.width >= 0, "horz scroll code out of sync");
4036 }
4037
4038 return true;
4039 }
4040
CanAutoScroll(int32_t aRowIndex)4041 bool nsTreeBodyFrame::CanAutoScroll(int32_t aRowIndex) {
4042 // Check first for partially visible last row.
4043 if (aRowIndex == mRowCount - 1) {
4044 nscoord y = mInnerBox.y + (aRowIndex - mTopRowIndex) * mRowHeight;
4045 if (y < mInnerBox.height && y + mRowHeight > mInnerBox.height) return true;
4046 }
4047
4048 if (aRowIndex > 0 && aRowIndex < mRowCount - 1) return true;
4049
4050 return false;
4051 }
4052
4053 // Given a dom event, figure out which row in the tree the mouse is over,
4054 // if we should drop before/after/on that row or we should auto-scroll.
4055 // Doesn't query the content about if the drag is allowable, that's done
4056 // elsewhere.
4057 //
4058 // For containers, we break up the vertical space of the row as follows: if in
4059 // the topmost 25%, the drop is _before_ the row the mouse is over; if in the
4060 // last 25%, _after_; in the middle 50%, we consider it a drop _on_ the
4061 // container.
4062 //
4063 // For non-containers, if the mouse is in the top 50% of the row, the drop is
4064 // _before_ and the bottom 50% _after_
ComputeDropPosition(WidgetGUIEvent * aEvent,int32_t * aRow,int16_t * aOrient,int16_t * aScrollLines)4065 void nsTreeBodyFrame::ComputeDropPosition(WidgetGUIEvent* aEvent, int32_t* aRow,
4066 int16_t* aOrient,
4067 int16_t* aScrollLines) {
4068 *aOrient = -1;
4069 *aScrollLines = 0;
4070
4071 // Convert the event's point to our coordinates. We want it in
4072 // the coordinates of our inner box's coordinates.
4073 nsPoint pt =
4074 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this});
4075 int32_t xTwips = pt.x - mInnerBox.x;
4076 int32_t yTwips = pt.y - mInnerBox.y;
4077
4078 *aRow = GetRowAtInternal(xTwips, yTwips);
4079 if (*aRow >= 0) {
4080 // Compute the top/bottom of the row in question.
4081 int32_t yOffset = yTwips - mRowHeight * (*aRow - mTopRowIndex);
4082
4083 bool isContainer = false;
4084 mView->IsContainer(*aRow, &isContainer);
4085 if (isContainer) {
4086 // for a container, use a 25%/50%/25% breakdown
4087 if (yOffset < mRowHeight / 4)
4088 *aOrient = nsITreeView::DROP_BEFORE;
4089 else if (yOffset > mRowHeight - (mRowHeight / 4))
4090 *aOrient = nsITreeView::DROP_AFTER;
4091 else
4092 *aOrient = nsITreeView::DROP_ON;
4093 } else {
4094 // for a non-container use a 50%/50% breakdown
4095 if (yOffset < mRowHeight / 2)
4096 *aOrient = nsITreeView::DROP_BEFORE;
4097 else
4098 *aOrient = nsITreeView::DROP_AFTER;
4099 }
4100 }
4101
4102 if (CanAutoScroll(*aRow)) {
4103 // Get the max value from the look and feel service.
4104 int32_t scrollLinesMax =
4105 LookAndFeel::GetInt(LookAndFeel::IntID::TreeScrollLinesMax, 0);
4106 scrollLinesMax--;
4107 if (scrollLinesMax < 0) scrollLinesMax = 0;
4108
4109 // Determine if we're w/in a margin of the top/bottom of the tree during a
4110 // drag. This will ultimately cause us to scroll, but that's done elsewhere.
4111 nscoord height = (3 * mRowHeight) / 4;
4112 if (yTwips < height) {
4113 // scroll up
4114 *aScrollLines =
4115 NSToIntRound(-scrollLinesMax * (1 - (float)yTwips / height) - 1);
4116 } else if (yTwips > mRect.height - height) {
4117 // scroll down
4118 *aScrollLines = NSToIntRound(
4119 scrollLinesMax * (1 - (float)(mRect.height - yTwips) / height) + 1);
4120 }
4121 }
4122 } // ComputeDropPosition
4123
OpenCallback(nsITimer * aTimer,void * aClosure)4124 void nsTreeBodyFrame::OpenCallback(nsITimer* aTimer, void* aClosure) {
4125 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4126 if (self) {
4127 aTimer->Cancel();
4128 self->mSlots->mTimer = nullptr;
4129
4130 if (self->mSlots->mDropRow >= 0) {
4131 self->mSlots->mArray.AppendElement(self->mSlots->mDropRow);
4132 self->mView->ToggleOpenState(self->mSlots->mDropRow);
4133 }
4134 }
4135 }
4136
CloseCallback(nsITimer * aTimer,void * aClosure)4137 void nsTreeBodyFrame::CloseCallback(nsITimer* aTimer, void* aClosure) {
4138 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4139 if (self) {
4140 aTimer->Cancel();
4141 self->mSlots->mTimer = nullptr;
4142
4143 for (uint32_t i = self->mSlots->mArray.Length(); i--;) {
4144 if (self->mView) self->mView->ToggleOpenState(self->mSlots->mArray[i]);
4145 }
4146 self->mSlots->mArray.Clear();
4147 }
4148 }
4149
LazyScrollCallback(nsITimer * aTimer,void * aClosure)4150 void nsTreeBodyFrame::LazyScrollCallback(nsITimer* aTimer, void* aClosure) {
4151 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4152 if (self) {
4153 aTimer->Cancel();
4154 self->mSlots->mTimer = nullptr;
4155
4156 if (self->mView) {
4157 // Set a new timer to scroll the tree repeatedly.
4158 self->CreateTimer(LookAndFeel::IntID::TreeScrollDelay, ScrollCallback,
4159 nsITimer::TYPE_REPEATING_SLACK,
4160 getter_AddRefs(self->mSlots->mTimer),
4161 "nsTreeBodyFrame::ScrollCallback");
4162 self->ScrollByLines(self->mSlots->mScrollLines);
4163 // ScrollByLines may have deleted |self|.
4164 }
4165 }
4166 }
4167
ScrollCallback(nsITimer * aTimer,void * aClosure)4168 void nsTreeBodyFrame::ScrollCallback(nsITimer* aTimer, void* aClosure) {
4169 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4170 if (self) {
4171 // Don't scroll if we are already at the top or bottom of the view.
4172 if (self->mView && self->CanAutoScroll(self->mSlots->mDropRow)) {
4173 self->ScrollByLines(self->mSlots->mScrollLines);
4174 } else {
4175 aTimer->Cancel();
4176 self->mSlots->mTimer = nullptr;
4177 }
4178 }
4179 }
4180
4181 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
Run()4182 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsTreeBodyFrame::ScrollEvent::Run() {
4183 if (mInner) {
4184 mInner->FireScrollEvent();
4185 }
4186 return NS_OK;
4187 }
4188
FireScrollEvent()4189 void nsTreeBodyFrame::FireScrollEvent() {
4190 mScrollEvent.Forget();
4191 WidgetGUIEvent event(true, eScroll, nullptr);
4192 // scroll events fired at elements don't bubble
4193 event.mFlags.mBubbles = false;
4194 RefPtr<nsIContent> content = GetContent();
4195 RefPtr<nsPresContext> presContext = PresContext();
4196 EventDispatcher::Dispatch(content, presContext, &event);
4197 }
4198
PostScrollEvent()4199 void nsTreeBodyFrame::PostScrollEvent() {
4200 if (mScrollEvent.IsPending()) return;
4201
4202 RefPtr<ScrollEvent> event = new ScrollEvent(this);
4203 nsresult rv =
4204 mContent->OwnerDoc()->Dispatch(TaskCategory::Other, do_AddRef(event));
4205 if (NS_FAILED(rv)) {
4206 NS_WARNING("failed to dispatch ScrollEvent");
4207 } else {
4208 mScrollEvent = std::move(event);
4209 }
4210 }
4211
ScrollbarActivityStarted() const4212 void nsTreeBodyFrame::ScrollbarActivityStarted() const {
4213 if (mScrollbarActivity) {
4214 mScrollbarActivity->ActivityStarted();
4215 }
4216 }
4217
ScrollbarActivityStopped() const4218 void nsTreeBodyFrame::ScrollbarActivityStopped() const {
4219 if (mScrollbarActivity) {
4220 mScrollbarActivity->ActivityStopped();
4221 }
4222 }
4223
DetachImageListeners()4224 void nsTreeBodyFrame::DetachImageListeners() { mCreatedListeners.Clear(); }
4225
RemoveTreeImageListener(nsTreeImageListener * aListener)4226 void nsTreeBodyFrame::RemoveTreeImageListener(nsTreeImageListener* aListener) {
4227 if (aListener) {
4228 mCreatedListeners.Remove(aListener);
4229 }
4230 }
4231
4232 #ifdef ACCESSIBILITY
InitCustomEvent(CustomEvent * aEvent,const nsAString & aType,nsIWritablePropertyBag2 * aDetail)4233 static void InitCustomEvent(CustomEvent* aEvent, const nsAString& aType,
4234 nsIWritablePropertyBag2* aDetail) {
4235 AutoJSAPI jsapi;
4236 if (!jsapi.Init(aEvent->GetParentObject())) {
4237 return;
4238 }
4239
4240 JSContext* cx = jsapi.cx();
4241 JS::Rooted<JS::Value> detail(cx);
4242 if (!ToJSValue(cx, aDetail, &detail)) {
4243 jsapi.ClearException();
4244 return;
4245 }
4246
4247 aEvent->InitCustomEvent(cx, aType, /* aCanBubble = */ true,
4248 /* aCancelable = */ false, detail);
4249 }
4250
FireRowCountChangedEvent(int32_t aIndex,int32_t aCount)4251 void nsTreeBodyFrame::FireRowCountChangedEvent(int32_t aIndex, int32_t aCount) {
4252 RefPtr<XULTreeElement> tree(GetBaseElement());
4253 if (!tree) return;
4254
4255 RefPtr<Document> doc = tree->OwnerDoc();
4256 MOZ_ASSERT(doc);
4257
4258 RefPtr<Event> event =
4259 doc->CreateEvent(u"customevent"_ns, CallerType::System, IgnoreErrors());
4260
4261 CustomEvent* treeEvent = event->AsCustomEvent();
4262 if (!treeEvent) {
4263 return;
4264 }
4265
4266 nsCOMPtr<nsIWritablePropertyBag2> propBag(
4267 do_CreateInstance("@mozilla.org/hash-property-bag;1"));
4268 if (!propBag) {
4269 return;
4270 }
4271
4272 // Set 'index' data - the row index rows are changed from.
4273 propBag->SetPropertyAsInt32(u"index"_ns, aIndex);
4274
4275 // Set 'count' data - the number of changed rows.
4276 propBag->SetPropertyAsInt32(u"count"_ns, aCount);
4277
4278 InitCustomEvent(treeEvent, u"TreeRowCountChanged"_ns, propBag);
4279
4280 event->SetTrusted(true);
4281
4282 RefPtr<AsyncEventDispatcher> asyncDispatcher =
4283 new AsyncEventDispatcher(tree, event);
4284 asyncDispatcher->PostDOMEvent();
4285 }
4286
FireInvalidateEvent(int32_t aStartRowIdx,int32_t aEndRowIdx,nsTreeColumn * aStartCol,nsTreeColumn * aEndCol)4287 void nsTreeBodyFrame::FireInvalidateEvent(int32_t aStartRowIdx,
4288 int32_t aEndRowIdx,
4289 nsTreeColumn* aStartCol,
4290 nsTreeColumn* aEndCol) {
4291 RefPtr<XULTreeElement> tree(GetBaseElement());
4292 if (!tree) return;
4293
4294 RefPtr<Document> doc = tree->OwnerDoc();
4295
4296 RefPtr<Event> event =
4297 doc->CreateEvent(u"customevent"_ns, CallerType::System, IgnoreErrors());
4298
4299 CustomEvent* treeEvent = event->AsCustomEvent();
4300 if (!treeEvent) {
4301 return;
4302 }
4303
4304 nsCOMPtr<nsIWritablePropertyBag2> propBag(
4305 do_CreateInstance("@mozilla.org/hash-property-bag;1"));
4306 if (!propBag) {
4307 return;
4308 }
4309
4310 if (aStartRowIdx != -1 && aEndRowIdx != -1) {
4311 // Set 'startrow' data - the start index of invalidated rows.
4312 propBag->SetPropertyAsInt32(u"startrow"_ns, aStartRowIdx);
4313
4314 // Set 'endrow' data - the end index of invalidated rows.
4315 propBag->SetPropertyAsInt32(u"endrow"_ns, aEndRowIdx);
4316 }
4317
4318 if (aStartCol && aEndCol) {
4319 // Set 'startcolumn' data - the start index of invalidated rows.
4320 int32_t startColIdx = aStartCol->GetIndex();
4321
4322 propBag->SetPropertyAsInt32(u"startcolumn"_ns, startColIdx);
4323
4324 // Set 'endcolumn' data - the start index of invalidated rows.
4325 int32_t endColIdx = aEndCol->GetIndex();
4326 propBag->SetPropertyAsInt32(u"endcolumn"_ns, endColIdx);
4327 }
4328
4329 InitCustomEvent(treeEvent, u"TreeInvalidated"_ns, propBag);
4330
4331 event->SetTrusted(true);
4332
4333 RefPtr<AsyncEventDispatcher> asyncDispatcher =
4334 new AsyncEventDispatcher(tree, event);
4335 asyncDispatcher->PostDOMEvent();
4336 }
4337 #endif
4338
4339 class nsOverflowChecker : public Runnable {
4340 public:
nsOverflowChecker(nsTreeBodyFrame * aFrame)4341 explicit nsOverflowChecker(nsTreeBodyFrame* aFrame)
4342 : mozilla::Runnable("nsOverflowChecker"), mFrame(aFrame) {}
Run()4343 NS_IMETHOD Run() override {
4344 if (mFrame.IsAlive()) {
4345 nsTreeBodyFrame* tree = static_cast<nsTreeBodyFrame*>(mFrame.GetFrame());
4346 nsTreeBodyFrame::ScrollParts parts = tree->GetScrollParts();
4347 tree->CheckOverflow(parts);
4348 }
4349 return NS_OK;
4350 }
4351
4352 private:
4353 WeakFrame mFrame;
4354 };
4355
FullScrollbarsUpdate(bool aNeedsFullInvalidation)4356 bool nsTreeBodyFrame::FullScrollbarsUpdate(bool aNeedsFullInvalidation) {
4357 ScrollParts parts = GetScrollParts();
4358 AutoWeakFrame weakFrame(this);
4359 AutoWeakFrame weakColumnsFrame(parts.mColumnsFrame);
4360 UpdateScrollbars(parts);
4361 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
4362 if (aNeedsFullInvalidation) {
4363 Invalidate();
4364 }
4365 InvalidateScrollbars(parts, weakColumnsFrame);
4366 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
4367
4368 // Overflow checking dispatches synchronous events, which can cause infinite
4369 // recursion during reflow. Do the first overflow check synchronously, but
4370 // force any nested checks to round-trip through the event loop. See bug
4371 // 905909.
4372 RefPtr<nsOverflowChecker> checker = new nsOverflowChecker(this);
4373 if (!mCheckingOverflow) {
4374 nsContentUtils::AddScriptRunner(checker);
4375 } else {
4376 mContent->OwnerDoc()->Dispatch(TaskCategory::Other, checker.forget());
4377 }
4378 return weakFrame.IsAlive();
4379 }
4380
OnImageIsAnimated(imgIRequest * aRequest)4381 void nsTreeBodyFrame::OnImageIsAnimated(imgIRequest* aRequest) {
4382 nsLayoutUtils::RegisterImageRequest(PresContext(), aRequest, nullptr);
4383 }
4384