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