nsIFrame.cpp (480299B)
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 /* base class of all rendering objects */ 8 9 #include "nsIFrame.h" 10 11 #include <stdarg.h> 12 13 #include <algorithm> 14 15 #include "AnchorPositioningUtils.h" 16 #include "LayoutLogging.h" 17 #include "RubyUtils.h" 18 #include "TextOverflow.h" 19 #include "gfx2DGlue.h" 20 #include "gfxUtils.h" 21 #include "mozilla/AbsoluteContainingBlock.h" 22 #include "mozilla/Attributes.h" 23 #include "mozilla/CaretAssociationHint.h" 24 #include "mozilla/ComputedStyle.h" 25 #include "mozilla/DebugOnly.h" 26 #include "mozilla/DisplayPortUtils.h" 27 #include "mozilla/EventForwards.h" 28 #include "mozilla/FocusModel.h" 29 #include "mozilla/IntegerRange.h" 30 #include "mozilla/Logging.h" 31 #include "mozilla/Maybe.h" 32 #include "mozilla/PresShell.h" 33 #include "mozilla/PresShellInlines.h" 34 #include "mozilla/RestyleManager.h" 35 #include "mozilla/ResultExtensions.h" 36 #include "mozilla/SVGIntegrationUtils.h" 37 #include "mozilla/SVGMaskFrame.h" 38 #include "mozilla/SVGObserverUtils.h" 39 #include "mozilla/SVGTextFrame.h" 40 #include "mozilla/SVGUtils.h" 41 #include "mozilla/ScrollContainerFrame.h" 42 #include "mozilla/SelectionMovementUtils.h" 43 #include "mozilla/ServoStyleConsts.h" 44 #include "mozilla/Sprintf.h" 45 #include "mozilla/StaticAnalysisFunctions.h" 46 #include "mozilla/StaticPrefs_dom.h" 47 #include "mozilla/StaticPrefs_layout.h" 48 #include "mozilla/StaticPrefs_print.h" 49 #include "mozilla/StaticPrefs_ui.h" 50 #include "mozilla/TextControlElement.h" 51 #include "mozilla/ToString.h" 52 #include "mozilla/Try.h" 53 #include "mozilla/ViewportFrame.h" 54 #include "mozilla/ViewportUtils.h" 55 #include "mozilla/WritingModes.h" 56 #include "mozilla/dom/AncestorIterator.h" 57 #include "mozilla/dom/CSSAnimation.h" 58 #include "mozilla/dom/CSSTransition.h" 59 #include "mozilla/dom/ContentVisibilityAutoStateChangeEvent.h" 60 #include "mozilla/dom/DocumentInlines.h" 61 #include "mozilla/dom/ElementInlines.h" 62 #include "mozilla/dom/HTMLDetailsElement.h" 63 #include "mozilla/dom/Selection.h" 64 #include "mozilla/gfx/2D.h" 65 #include "mozilla/gfx/PathHelpers.h" 66 #include "mozilla/intl/BidiEmbeddingLevel.h" 67 #include "nsAnimationManager.h" 68 #include "nsAtom.h" 69 #include "nsBidiPresUtils.h" 70 #include "nsCOMPtr.h" 71 #include "nsCSSAnonBoxes.h" 72 #include "nsCSSFrameConstructor.h" 73 #include "nsCSSProps.h" 74 #include "nsCSSPseudoElements.h" 75 #include "nsCSSRendering.h" 76 #include "nsCanvasFrame.h" 77 #include "nsContentUtils.h" 78 #include "nsFieldSetFrame.h" 79 #include "nsFlexContainerFrame.h" 80 #include "nsFocusManager.h" 81 #include "nsFrameList.h" 82 #include "nsFrameSelection.h" 83 #include "nsFrameState.h" 84 #include "nsFrameTraversal.h" 85 #include "nsGkAtoms.h" 86 #include "nsGridContainerFrame.h" 87 #include "nsIBaseWindow.h" 88 #include "nsIContent.h" 89 #include "nsIContentInlines.h" 90 #include "nsIPercentBSizeObserver.h" 91 #include "nsImageFrame.h" 92 #include "nsInlineFrame.h" 93 #include "nsLayoutUtils.h" 94 #include "nsMenuPopupFrame.h" 95 #include "nsNameSpaceManager.h" 96 #include "nsPlaceholderFrame.h" 97 #include "nsPresContext.h" 98 #include "nsPresContextInlines.h" 99 #include "nsRange.h" 100 #include "nsReadableUtils.h" 101 #include "nsString.h" 102 #include "nsStyleConsts.h" 103 #include "nsStyleStructInlines.h" 104 #include "nsTableWrapperFrame.h" 105 #include "nsTextControlFrame.h" 106 #include "nsXULElement.h" 107 108 // For triple-click pref 109 #include "RetainedDisplayListBuilder.h" 110 #include "ScrollSnap.h" 111 #include "StickyScrollContainer.h" 112 #include "gfxContext.h" 113 #include "imgIRequest.h" 114 #include "nsBlockFrame.h" 115 #include "nsChangeHint.h" 116 #include "nsContainerFrame.h" 117 #include "nsDisplayList.h" 118 #include "nsError.h" 119 #include "nsFontInflationData.h" 120 #include "nsIFrameInlines.h" 121 #include "nsRegion.h" 122 #include "nsStyleChangeList.h" 123 #include "nsSubDocumentFrame.h" 124 #include "nsViewportInfo.h" 125 #include "nsWindowSizes.h" 126 127 #ifdef ACCESSIBILITY 128 # include "nsAccessibilityService.h" 129 #endif 130 131 #include "ActiveLayerTracker.h" 132 #include "mozilla/AsyncEventDispatcher.h" 133 #include "mozilla/CSSClipPathInstance.h" 134 #include "mozilla/EffectCompositor.h" 135 #include "mozilla/EffectSet.h" 136 #include "mozilla/EventListenerManager.h" 137 #include "mozilla/EventStateManager.h" 138 #include "mozilla/LookAndFeel.h" 139 #include "mozilla/MouseEvents.h" 140 #include "mozilla/Preferences.h" 141 #include "mozilla/ServoStyleSet.h" 142 #include "mozilla/ServoStyleSetInlines.h" 143 #include "mozilla/css/ImageLoader.h" 144 #include "mozilla/dom/HTMLBodyElement.h" 145 #include "mozilla/dom/SVGPathData.h" 146 #include "mozilla/dom/TouchEvent.h" 147 #include "mozilla/gfx/Tools.h" 148 #include "mozilla/layers/WebRenderUserData.h" 149 #include "mozilla/layout/ScrollAnchorContainer.h" 150 #include "nsITheme.h" 151 #include "nsPrintfCString.h" 152 153 using namespace mozilla; 154 using namespace mozilla::css; 155 using namespace mozilla::dom; 156 using namespace mozilla::gfx; 157 using namespace mozilla::layers; 158 using namespace mozilla::layout; 159 using nsStyleTransformMatrix::TransformReferenceBox; 160 161 nsIFrame* nsILineIterator::LineInfo::GetLastFrameOnLine() const { 162 if (!mNumFramesOnLine) { 163 return nullptr; // empty line, not illegal 164 } 165 MOZ_ASSERT(mFirstFrameOnLine); 166 nsIFrame* maybeLastFrame = mFirstFrameOnLine; 167 for ([[maybe_unused]] int32_t i : IntegerRange(mNumFramesOnLine - 1)) { 168 maybeLastFrame = maybeLastFrame->GetNextSibling(); 169 if (NS_WARN_IF(!maybeLastFrame)) { 170 return nullptr; 171 } 172 } 173 return maybeLastFrame; 174 } 175 176 #ifdef HAVE_64BIT_BUILD 177 static_assert(sizeof(nsIFrame) == 120, "nsIFrame should remain small"); 178 #else 179 static_assert(sizeof(void*) == 4, "Odd build config?"); 180 // FIXME(emilio): Investigate why win32 and android-arm32 have bigger sizes (80) 181 // than Linux32 (76). 182 static_assert(sizeof(nsIFrame) <= 80, "nsIFrame should remain small"); 183 #endif 184 185 const mozilla::LayoutFrameType nsIFrame::sLayoutFrameTypes[kFrameClassCount] = { 186 #define FRAME_ID(class_, type_, ...) mozilla::LayoutFrameType::type_, 187 #define ABSTRACT_FRAME_ID(...) 188 #include "mozilla/FrameIdList.h" 189 #undef FRAME_ID 190 #undef ABSTRACT_FRAME_ID 191 }; 192 193 const nsIFrame::ClassFlags nsIFrame::sLayoutFrameClassFlags[kFrameClassCount] = 194 { 195 #define FRAME_ID(class_, type_, flags_, ...) flags_, 196 #define ABSTRACT_FRAME_ID(...) 197 #include "mozilla/FrameIdList.h" 198 #undef FRAME_ID 199 #undef ABSTRACT_FRAME_ID 200 }; 201 202 std::ostream& operator<<(std::ostream& aStream, const nsDirection& aDirection) { 203 return aStream << (aDirection == eDirNext ? "eDirNext" : "eDirPrevious"); 204 } 205 206 struct nsContentAndOffset { 207 nsIContent* mContent = nullptr; 208 int32_t mOffset = 0; 209 }; 210 211 #include "nsILineIterator.h" 212 #include "prenv.h" 213 214 FrameDestroyContext::~FrameDestroyContext() { 215 for (auto& content : mozilla::Reversed(mAnonymousContent)) { 216 mPresShell->NativeAnonymousContentWillBeRemoved(content); 217 content->UnbindFromTree(); 218 } 219 } 220 221 // Formerly the nsIFrameDebug interface 222 223 std::ostream& operator<<(std::ostream& aStream, const nsReflowStatus& aStatus) { 224 char complete = 'Y'; 225 if (aStatus.IsIncomplete()) { 226 complete = 'N'; 227 } else if (aStatus.IsOverflowIncomplete()) { 228 complete = 'O'; 229 } 230 231 char brk = 'N'; 232 if (aStatus.IsInlineBreakBefore()) { 233 brk = 'B'; 234 } else if (aStatus.IsInlineBreakAfter()) { 235 brk = 'A'; 236 } 237 238 aStream << "[" 239 << "Complete=" << complete << "," 240 << "NIF=" << (aStatus.NextInFlowNeedsReflow() ? 'Y' : 'N') << "," 241 << "Break=" << brk << "," 242 << "FirstLetter=" << (aStatus.FirstLetterComplete() ? 'Y' : 'N') 243 << "]"; 244 return aStream; 245 } 246 247 #ifdef DEBUG 248 249 /** 250 * Note: the log module is created during library initialization which 251 * means that you cannot perform logging before then. 252 */ 253 mozilla::LazyLogModule nsIFrame::sFrameLogModule("frame"); 254 255 #endif 256 257 NS_DECLARE_FRAME_PROPERTY_DELETABLE(AbsoluteContainingBlockProperty, 258 AbsoluteContainingBlock) 259 260 bool nsIFrame::HasAbsolutelyPositionedChildren() const { 261 const auto* absCB = GetAbsoluteContainingBlock(); 262 return absCB && absCB->HasAbsoluteFrames(); 263 } 264 265 AbsoluteContainingBlock* nsIFrame::GetAbsoluteContainingBlock() const { 266 if (!IsAbsoluteContainer()) { 267 return nullptr; 268 } 269 AbsoluteContainingBlock* absCB = 270 GetProperty(AbsoluteContainingBlockProperty()); 271 NS_ASSERTION(absCB, 272 "The frame is marked as an abspos container but doesn't have " 273 "the property"); 274 return absCB; 275 } 276 277 void nsIFrame::MarkAsAbsoluteContainingBlock() { 278 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)); 279 NS_ASSERTION(!GetProperty(AbsoluteContainingBlockProperty()), 280 "Already has an abs-pos containing block property?"); 281 NS_ASSERTION(!HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN), 282 "Already has NS_FRAME_HAS_ABSPOS_CHILDREN state bit?"); 283 AddStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN); 284 SetProperty(AbsoluteContainingBlockProperty(), 285 new AbsoluteContainingBlock(GetAbsoluteListID())); 286 } 287 288 void nsIFrame::MarkAsNotAbsoluteContainingBlock() { 289 NS_ASSERTION(!HasAbsolutelyPositionedChildren(), "Think of the children!"); 290 NS_ASSERTION(GetProperty(AbsoluteContainingBlockProperty()), 291 "Should have an abs-pos containing block property"); 292 NS_ASSERTION(HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN), 293 "Should have NS_FRAME_HAS_ABSPOS_CHILDREN state bit"); 294 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)); 295 RemoveStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN); 296 RemoveProperty(AbsoluteContainingBlockProperty()); 297 } 298 299 bool nsIFrame::CheckAndClearPaintedState() { 300 bool result = HasAnyStateBits(NS_FRAME_PAINTED_THEBES); 301 RemoveStateBits(NS_FRAME_PAINTED_THEBES); 302 303 for (const auto& childList : ChildLists()) { 304 for (nsIFrame* child : childList.mList) { 305 if (child->CheckAndClearPaintedState()) { 306 result = true; 307 } 308 } 309 } 310 return result; 311 } 312 313 nsIFrame* nsIFrame::FindLineContainer() const { 314 MOZ_ASSERT(IsLineParticipant()); 315 nsIFrame* parent = GetParent(); 316 while (parent && 317 (parent->IsLineParticipant() || parent->CanContinueTextRun())) { 318 parent = parent->GetParent(); 319 } 320 return parent; 321 } 322 323 bool nsIFrame::CheckAndClearDisplayListState() { 324 bool result = BuiltDisplayList(); 325 SetBuiltDisplayList(false); 326 327 for (const auto& childList : ChildLists()) { 328 for (nsIFrame* child : childList.mList) { 329 if (child->CheckAndClearDisplayListState()) { 330 result = true; 331 } 332 } 333 } 334 return result; 335 } 336 337 bool nsIFrame::IsVisibleConsideringAncestors(uint32_t aFlags) const { 338 if (!StyleVisibility()->IsVisible() || 339 HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 340 return false; 341 } 342 343 if (PresShell()->IsUnderHiddenEmbedderElement()) { 344 return false; 345 } 346 347 const nsIFrame* frame = this; 348 while (frame) { 349 // Checking mMozSubtreeHiddenOnlyVisually is relatively slow because it 350 // involves loading more memory. It's only allowed in chrome sheets so let's 351 // only support it in the parent process so we can mostly optimize this out 352 // in content processes. 353 if (XRE_IsParentProcess()) { 354 if (const nsMenuPopupFrame* popup = do_QueryFrame(frame); 355 popup && !popup->IsOpen()) { 356 return false; 357 } 358 if (frame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) { 359 return false; 360 } 361 } 362 363 // This method is used to determine if a frame is focusable, because it's 364 // called by nsIFrame::IsFocusable. `content-visibility: auto` should not 365 // force this frame to be unfocusable, so we only take into account 366 // `content-visibility: hidden` here. 367 if (this != frame && 368 frame->HidesContent(IncludeContentVisibility::Hidden)) { 369 return false; 370 } 371 372 if (nsIFrame* parent = frame->GetParent()) { 373 frame = parent; 374 } else { 375 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame); 376 if (!parent) { 377 break; 378 } 379 380 if ((aFlags & nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) == 0 && 381 parent->PresContext()->IsChrome() && 382 !frame->PresContext()->IsChrome()) { 383 break; 384 } 385 386 frame = parent; 387 } 388 } 389 390 return true; 391 } 392 393 void nsIFrame::FindCloserFrameForSelection( 394 const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) { 395 if (nsLayoutUtils::PointIsCloserToRect(aPoint, mRect, 396 aCurrentBestFrame->mXDistance, 397 aCurrentBestFrame->mYDistance)) { 398 aCurrentBestFrame->mFrame = this; 399 } 400 } 401 402 void nsIFrame::ElementStateChanged(mozilla::dom::ElementState aStates) {} 403 404 void WeakFrame::Clear(mozilla::PresShell* aPresShell) { 405 if (aPresShell) { 406 aPresShell->RemoveWeakFrame(this); 407 } 408 mFrame = nullptr; 409 } 410 411 AutoWeakFrame::AutoWeakFrame(const WeakFrame& aOther) 412 : mPrev(nullptr), mFrame(nullptr) { 413 Init(aOther.GetFrame()); 414 } 415 416 void AutoWeakFrame::Clear(mozilla::PresShell* aPresShell) { 417 if (aPresShell) { 418 aPresShell->RemoveAutoWeakFrame(this); 419 } 420 mFrame = nullptr; 421 mPrev = nullptr; 422 } 423 424 AutoWeakFrame::~AutoWeakFrame() { 425 Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr); 426 } 427 428 void AutoWeakFrame::Init(nsIFrame* aFrame) { 429 Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr); 430 mFrame = aFrame; 431 if (mFrame) { 432 mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell(); 433 NS_WARNING_ASSERTION(presShell, "Null PresShell in AutoWeakFrame!"); 434 if (presShell) { 435 presShell->AddAutoWeakFrame(this); 436 } else { 437 mFrame = nullptr; 438 } 439 } 440 } 441 442 void WeakFrame::Init(nsIFrame* aFrame) { 443 Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr); 444 mFrame = aFrame; 445 if (mFrame) { 446 mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell(); 447 MOZ_ASSERT(presShell, "Null PresShell in WeakFrame!"); 448 if (presShell) { 449 presShell->AddWeakFrame(this); 450 } else { 451 mFrame = nullptr; 452 } 453 } 454 } 455 456 nsIFrame* NS_NewEmptyFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 457 return new (aPresShell) nsIFrame(aStyle, aPresShell->GetPresContext()); 458 } 459 460 nsIFrame::~nsIFrame() { 461 MOZ_COUNT_DTOR(nsIFrame); 462 463 MOZ_ASSERT(GetVisibility() != Visibility::ApproximatelyVisible, 464 "Visible nsFrame is being destroyed"); 465 } 466 467 NS_IMPL_FRAMEARENA_HELPERS(nsIFrame) 468 469 // Dummy operator delete. Will never be called, but must be defined 470 // to satisfy some C++ ABIs. 471 void nsIFrame::operator delete(void*, size_t) { 472 MOZ_CRASH("nsIFrame::operator delete should never be called"); 473 } 474 475 NS_QUERYFRAME_HEAD(nsIFrame) 476 NS_QUERYFRAME_ENTRY(nsIFrame) 477 NS_QUERYFRAME_TAIL_INHERITANCE_ROOT 478 479 ///////////////////////////////////////////////////////////////////////////// 480 // nsIFrame 481 482 static bool IsFontSizeInflationContainer(nsIFrame* aFrame, 483 const nsStyleDisplay* aStyleDisplay) { 484 /* 485 * Font size inflation is built around the idea that we're inflating 486 * the fonts for a pan-and-zoom UI so that when the user scales up a 487 * block or other container to fill the width of the device, the fonts 488 * will be readable. To do this, we need to pick what counts as a 489 * container. 490 * 491 * From a code perspective, the only hard requirement is that frames 492 * that are line participants (nsIFrame::IsLineParticipant) are never 493 * containers, since line layout assumes that the inflation is consistent 494 * within a line. 495 * 496 * This is not an imposition, since we obviously want a bunch of text 497 * (possibly with inline elements) flowing within a block to count the 498 * block (or higher) as its container. 499 * 500 * We also want form controls, including the text in the anonymous 501 * content inside of them, to match each other and the text next to 502 * them, so they and their anonymous content should also not be a 503 * container. 504 * 505 * However, because we can't reliably compute sizes across XUL during 506 * reflow, any XUL frame with a XUL parent is always a container. 507 * 508 * There are contexts where it would be nice if some blocks didn't 509 * count as a container, so that, for example, an indented quotation 510 * didn't end up with a smaller font size. However, it's hard to 511 * distinguish these situations where we really do want the indented 512 * thing to count as a container, so we don't try, and blocks are 513 * always containers. 514 */ 515 516 // The root frame should always be an inflation container. 517 if (!aFrame->GetParent()) { 518 return true; 519 } 520 521 nsIContent* content = aFrame->GetContent(); 522 if (content && content->IsInNativeAnonymousSubtree()) { 523 // Native anonymous content shouldn't be a font inflation root, 524 // except for the custom content container. 525 return content == 526 aFrame->PresContext()->Document()->GetCustomContentContainer(); 527 } 528 529 LayoutFrameType frameType = aFrame->Type(); 530 bool isInline = 531 aFrame->GetDisplay().IsInlineFlow() || RubyUtils::IsRubyBox(frameType) || 532 (aStyleDisplay->IsFloatingStyle() && 533 frameType == LayoutFrameType::Letter) || 534 // Given multiple frames for the same node, only the 535 // outer one should be considered a container. 536 // (Important, e.g., for nsSelectsAreaFrame.) 537 (aFrame->GetParent()->GetContent() == content) || 538 (content && 539 // Form controls shouldn't become inflation containers. 540 (content->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup, 541 nsGkAtoms::select, nsGkAtoms::input, 542 nsGkAtoms::button, nsGkAtoms::textarea))); 543 NS_ASSERTION(!aFrame->IsLineParticipant() || isInline || 544 // br frames and mathml frames report being line 545 // participants even when their position or display is 546 // set 547 aFrame->IsBrFrame() || aFrame->IsMathMLFrame(), 548 "line participants must not be containers"); 549 return !isInline; 550 } 551 552 static void MaybeScheduleReflowSVGNonDisplayText(nsIFrame* aFrame) { 553 if (!aFrame->IsInSVGTextSubtree()) { 554 return; 555 } 556 557 // We need to ensure that any non-display SVGTextFrames get reflowed when a 558 // child text frame gets new style. Thus we need to schedule a reflow in 559 // |DidSetComputedStyle|. We also need to call it from |DestroyFrom|, 560 // because otherwise we won't get notified when style changes to 561 // "display:none". 562 SVGTextFrame* svgTextFrame = static_cast<SVGTextFrame*>( 563 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText)); 564 nsIFrame* anonBlock = svgTextFrame->PrincipalChildList().FirstChild(); 565 566 // Note that we must check NS_FRAME_FIRST_REFLOW on our SVGTextFrame's 567 // anonymous block frame rather than our aFrame, since NS_FRAME_FIRST_REFLOW 568 // may be set on us if we're a new frame that has been inserted after the 569 // document's first reflow. (In which case this DidSetComputedStyle call may 570 // be happening under frame construction under a Reflow() call.) 571 if (!anonBlock || anonBlock->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 572 return; 573 } 574 575 if (!svgTextFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || 576 svgTextFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)) { 577 return; 578 } 579 580 svgTextFrame->ScheduleReflowSVGNonDisplayText( 581 IntrinsicDirty::FrameAncestorsAndDescendants); 582 } 583 584 bool nsIFrame::IsReplaced() const { 585 if (HasAnyClassFlag(ClassFlags::Replaced)) { 586 return true; 587 } 588 if (!Style()->IsAnonBox() && mContent->IsHTMLElement(nsGkAtoms::button)) { 589 // Button always behaves as a replaced element. 590 return true; 591 } 592 return false; 593 } 594 595 bool nsIFrame::ShouldPropagateRepaintsToRoot() const { 596 if (!IsPrimaryFrame()) { 597 // special case for table frames because style images are associated to the 598 // table frame, but the table wrapper frame is the primary frame 599 if (IsTableFrame()) { 600 MOZ_ASSERT(GetParent() && GetParent()->IsTableWrapperFrame()); 601 return GetParent()->ShouldPropagateRepaintsToRoot(); 602 } 603 604 return false; 605 } 606 nsIContent* content = GetContent(); 607 Document* document = content->OwnerDoc(); 608 return content == document->GetRootElement() || 609 content == document->GetBodyElement(); 610 } 611 612 bool nsIFrame::IsRenderedLegend() const { 613 if (auto* parent = GetParent(); parent && parent->IsFieldSetFrame()) { 614 return static_cast<nsFieldSetFrame*>(parent)->GetLegend() == this; 615 } 616 return false; 617 } 618 619 void nsIFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 620 nsIFrame* aPrevInFlow) { 621 MOZ_ASSERT(nsQueryFrame::FrameIID(mClass) == GetFrameId()); 622 MOZ_ASSERT(!mContent, "Double-initing a frame?"); 623 624 mContent = aContent; 625 mParent = aParent; 626 MOZ_ASSERT(!mParent || PresShell() == mParent->PresShell()); 627 628 if (aPrevInFlow) { 629 mWritingMode = aPrevInFlow->GetWritingMode(); 630 631 // Copy some state bits from prev-in-flow (the bits that should apply 632 // throughout a continuation chain). The bits are sorted according to their 633 // order in nsFrameStateBits.h. 634 635 // clang-format off 636 AddStateBits(aPrevInFlow->GetStateBits() & 637 (NS_FRAME_GENERATED_CONTENT | 638 NS_FRAME_OUT_OF_FLOW | 639 NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN | 640 NS_FRAME_PART_OF_IBSPLIT | 641 NS_FRAME_MAY_BE_TRANSFORMED | 642 NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)); 643 // clang-format on 644 645 // Copy other bits in nsIFrame from prev-in-flow. 646 mHasColumnSpanSiblings = aPrevInFlow->HasColumnSpanSiblings(); 647 648 // If our prev-in-flow is an absolute containing block, we must be one, too. 649 if (aPrevInFlow->IsAbsoluteContainer()) { 650 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN), 651 "We should've carried this bit from our prev-in-flow!"); 652 MarkAsAbsoluteContainingBlock(); 653 } 654 } else { 655 PresContext()->ConstructedFrame(); 656 } 657 658 if (GetParent()) { 659 if (MOZ_UNLIKELY(mContent == PresContext()->Document()->GetRootElement() && 660 mContent == GetParent()->GetContent())) { 661 // Our content is the root element and we have the same content as our 662 // parent. That is, we are the internal anonymous frame of the root 663 // element. Copy the used mWritingMode from our parent because 664 // mDocElementContainingBlock gets its mWritingMode from <body>. 665 mWritingMode = GetParent()->GetWritingMode(); 666 } 667 668 // Copy some state bits from our parent (the bits that should apply 669 // recursively throughout a subtree). The bits are sorted according to their 670 // order in nsFrameStateBits.h. 671 672 // clang-format off 673 AddStateBits(GetParent()->GetStateBits() & 674 (NS_FRAME_GENERATED_CONTENT | 675 NS_FRAME_IS_SVG_TEXT | 676 NS_FRAME_IN_POPUP | 677 NS_FRAME_IS_NONDISPLAY)); 678 // clang-format on 679 680 if (HasAnyStateBits(NS_FRAME_IN_POPUP) && TrackingVisibility()) { 681 // Assume all frames in popups are visible. 682 IncApproximateVisibleCount(); 683 } 684 } 685 if (aPrevInFlow) { 686 mMayHaveOpacityAnimation = aPrevInFlow->MayHaveOpacityAnimation(); 687 mMayHaveTransformAnimation = aPrevInFlow->MayHaveTransformAnimation(); 688 } else if (mContent) { 689 // It's fine to fetch the EffectSet for the style frame here because in the 690 // following code we take care of the case where animations may target 691 // a different frame. 692 EffectSet* effectSet = EffectSet::GetForStyleFrame(this); 693 if (effectSet) { 694 mMayHaveOpacityAnimation = effectSet->MayHaveOpacityAnimation(); 695 696 if (effectSet->MayHaveTransformAnimation()) { 697 // If we are the inner table frame for display:table content, then 698 // transform animations should go on our parent frame (the table wrapper 699 // frame). 700 // 701 // We do this when initializing the child frame (table inner frame), 702 // because when initializng the table wrapper frame, we don't yet have 703 // access to its children so we can't tell if we have transform 704 // animations or not. 705 if (SupportsCSSTransforms()) { 706 mMayHaveTransformAnimation = true; 707 AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); 708 } else if (aParent && nsLayoutUtils::GetStyleFrame(aParent) == this) { 709 MOZ_ASSERT( 710 aParent->SupportsCSSTransforms(), 711 "Style frames that don't support transforms should have parents" 712 " that do"); 713 aParent->mMayHaveTransformAnimation = true; 714 aParent->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); 715 } 716 } 717 } 718 } 719 720 const nsStyleDisplay* disp = StyleDisplay(); 721 if (disp->HasTransform(this)) { 722 // If 'transform' dynamically changes, RestyleManager takes care of 723 // updating this bit. 724 AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); 725 } 726 727 if (nsLayoutUtils::FontSizeInflationEnabled(PresContext()) || 728 !GetParent() 729 #ifdef DEBUG 730 // We have assertions that check inflation invariants even when 731 // font size inflation is not enabled. 732 || true 733 #endif 734 ) { 735 if (IsFontSizeInflationContainer(this, disp)) { 736 AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER); 737 if (!GetParent() || 738 // I'd use NS_FRAME_OUT_OF_FLOW, but it's not set yet. 739 disp->IsFloating(this) || disp->IsAbsolutelyPositioned(this) || 740 GetParent()->IsFlexContainerFrame() || 741 GetParent()->IsGridContainerFrame()) { 742 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); 743 } 744 } 745 NS_ASSERTION( 746 GetParent() || HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER), 747 "root frame should always be a container"); 748 } 749 750 if (TrackingVisibility() && PresShell()->AssumeAllFramesVisible()) { 751 IncApproximateVisibleCount(); 752 } 753 754 DidSetComputedStyle(nullptr); 755 756 // For a newly created frame, we need to update this frame's visibility state. 757 // Usually we update the state when the frame is restyled and has a 758 // VisibilityChange change hint but we don't generate any change hints for 759 // newly created frames. 760 // Note: We don't need to do this for placeholders since placeholders have 761 // different styles so that the styles don't have visibility:hidden even if 762 // the parent has visibility:hidden style. We also don't need to update the 763 // state when creating continuations because its visibility is the same as its 764 // prev-in-flow, and the animation code cares only primary frames. 765 if (!IsPlaceholderFrame() && !aPrevInFlow) { 766 UpdateVisibleDescendantsState(); 767 } 768 769 if (!aPrevInFlow && HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 770 // We aren't going to get a reflow, so nothing else will call 771 // InvalidateRenderingObservers, we have to do it here. 772 SVGObserverUtils::InvalidateRenderingObservers(this); 773 } 774 } 775 776 void nsIFrame::InitPrimaryFrame() { 777 MOZ_ASSERT(IsPrimaryFrame()); 778 HandlePrimaryFrameStyleChange(nullptr); 779 } 780 781 void nsIFrame::HandlePrimaryFrameStyleChange(ComputedStyle* aOldStyle) { 782 const nsStyleDisplay* disp = StyleDisplay(); 783 const nsStyleDisplay* oldDisp = 784 aOldStyle ? aOldStyle->StyleDisplay() : nullptr; 785 786 const bool wasQueryContainer = oldDisp && oldDisp->IsQueryContainer(); 787 const bool isQueryContainer = disp->IsQueryContainer(); 788 if (wasQueryContainer != isQueryContainer) { 789 auto* pc = PresContext(); 790 if (isQueryContainer) { 791 pc->RegisterContainerQueryFrame(this); 792 } else { 793 pc->UnregisterContainerQueryFrame(this); 794 } 795 } 796 797 const bool wasReferringToAnchor = aOldStyle && 798 oldDisp->IsAbsolutelyPositionedStyle() && 799 aOldStyle->HasAnchorPosReference(); 800 const bool isReferringToAnchor = HasAnchorPosReference(); 801 if (wasReferringToAnchor && !isReferringToAnchor) { 802 PresShell()->RemoveAnchorPosPositioned(this); 803 RemoveProperty(NormalPositionProperty()); 804 } else if (!wasReferringToAnchor && isReferringToAnchor) { 805 PresShell()->AddAnchorPosPositioned(this); 806 } 807 808 bool handleAnchorPosAnchorNameChange = 809 oldDisp ? oldDisp->mAnchorName != disp->mAnchorName 810 : disp->HasAnchorName(); 811 if (handleAnchorPosAnchorNameChange && 812 !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 813 // TODO: Add invalidation. 814 // TODO: Only remove/add the necessary names below. 815 if (oldDisp && oldDisp->HasAnchorName()) { 816 for (const auto& name : oldDisp->mAnchorName.AsSpan()) { 817 PresShell()->RemoveAnchorPosAnchor(name.AsAtom(), this); 818 } 819 } 820 for (const auto& name : disp->mAnchorName.AsSpan()) { 821 PresShell()->AddAnchorPosAnchor(name.AsAtom(), this); 822 } 823 } 824 825 // According to the Anchor Positioning spec, 826 // https://drafts.csswg.org/css-anchor-position-1/#last-successful-position-option: 827 // 828 // If el has a last successful position option remove its last successful 829 // position option if any of the following are true: 830 // 1. Its computed position value has changed, its containing block has 831 // changed, or it no longer generates a box. 832 // 2. Its computed value for any longhand of position-try has changed. 833 // 3. Its computed value for any @position-try property has changed. 834 // 4. Any of the @position-try rules referenced by it have been added, 835 // removed, or mutated. 836 // 837 // Case 1 will cause a reframe, so does not need to be handled here. 838 // Cases 2 and 3 are handled here. 839 // TODO: case 4, see bug 1987960. 840 if (aOldStyle && HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && 841 HasProperty(LastSuccessfulPositionFallback())) { 842 const auto* pos = StylePosition(); 843 const auto* oldPos = aOldStyle->StylePosition(); 844 if (pos->mPositionTryFallbacks != oldPos->mPositionTryFallbacks || 845 pos->mPositionTryOrder != oldPos->mPositionTryOrder || 846 pos->mOffset != oldPos->mOffset || 847 pos->mAlignSelf != oldPos->mAlignSelf || 848 pos->mJustifySelf != oldPos->mJustifySelf || 849 pos->mPositionAnchor != oldPos->mPositionAnchor || 850 pos->mPositionArea != oldPos->mPositionArea || 851 pos->mMinWidth != oldPos->mMinWidth || 852 pos->mMinHeight != oldPos->mMinHeight || 853 pos->mMaxWidth != oldPos->mMaxWidth || 854 pos->mMaxHeight != oldPos->mMaxHeight || 855 pos->mWidth != oldPos->mWidth || pos->mHeight != oldPos->mHeight || 856 StyleMargin()->mMargin != aOldStyle->StyleMargin()->mMargin) { 857 RemoveProperty(LastSuccessfulPositionFallback()); 858 } 859 } 860 861 const auto cv = disp->ContentVisibility(*this); 862 if (!oldDisp || oldDisp->ContentVisibility(*this) != cv) { 863 if (cv == StyleContentVisibility::Auto) { 864 PresShell()->RegisterContentVisibilityAutoFrame(this); 865 } else { 866 if (auto* element = Element::FromNodeOrNull(GetContent())) { 867 element->ClearContentRelevancy(); 868 } 869 PresShell()->UnregisterContentVisibilityAutoFrame(this); 870 } 871 PresContext()->SetNeedsToUpdateHiddenByContentVisibilityForAnimations(); 872 } 873 874 HandleLastRememberedSize(); 875 } 876 877 void nsIFrame::Destroy(DestroyContext& aContext) { 878 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), 879 "destroy called on frame while scripts not blocked"); 880 NS_ASSERTION(!GetNextSibling() && !GetPrevSibling(), 881 "Frames should be removed before destruction."); 882 MOZ_ASSERT(!HasAbsolutelyPositionedChildren()); 883 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT), 884 "NS_FRAME_PART_OF_IBSPLIT set on non-nsContainerFrame?"); 885 886 MaybeScheduleReflowSVGNonDisplayText(this); 887 888 SVGObserverUtils::InvalidateDirectRenderingObservers( 889 this, SVGObserverUtils::INVALIDATE_DESTROY); 890 891 const auto* disp = StyleDisplay(); 892 if (disp->mPosition == StylePositionProperty::Sticky) { 893 if (auto* ssc = StickyScrollContainer::GetOrCreateForFrame(this)) { 894 ssc->RemoveFrame(this); 895 } 896 } 897 898 if (HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 899 if (nsPlaceholderFrame* placeholder = GetPlaceholderFrame()) { 900 placeholder->SetOutOfFlowFrame(nullptr); 901 } 902 } 903 904 nsPresContext* pc = PresContext(); 905 mozilla::PresShell* ps = pc->GetPresShell(); 906 if (IsPrimaryFrame()) { 907 if (disp->IsQueryContainer()) { 908 pc->UnregisterContainerQueryFrame(this); 909 } 910 if (disp->ContentVisibility(*this) == StyleContentVisibility::Auto) { 911 ps->UnregisterContentVisibilityAutoFrame(this); 912 } 913 // This needs to happen before we clear our Properties() table. 914 ActiveLayerTracker::TransferActivityToContent(this, mContent); 915 } 916 917 ScrollAnchorContainer* anchor = nullptr; 918 if (IsScrollAnchor(&anchor)) { 919 anchor->InvalidateAnchor(); 920 } 921 922 if (HasCSSAnimations() || HasCSSTransitions() || 923 // It's fine to look up the style frame here since if we're destroying the 924 // frames for display:table content we should be destroying both wrapper 925 // and inner frame. 926 EffectSet::GetForStyleFrame(this)) { 927 // If no new frame for this element is created by the end of the 928 // restyling process, stop animations and transitions for this frame 929 RestyleManager::AnimationsWithDestroyedFrame* adf = 930 pc->RestyleManager()->GetAnimationsWithDestroyedFrame(); 931 // AnimationsWithDestroyedFrame only lives during the restyling process. 932 if (adf) { 933 adf->Put(mContent, mComputedStyle); 934 } 935 } 936 937 if (HasAnchorPosName()) { 938 for (const auto& name : disp->mAnchorName.AsSpan()) { 939 PresShell()->RemoveAnchorPosAnchor(name.AsAtom(), this); 940 } 941 } 942 943 if (HasAnchorPosReference()) { 944 ps->RemoveAnchorPosPositioned(this); 945 } 946 947 // Disable visibility tracking. Note that we have to do this before we clear 948 // frame properties and lose track of whether we were previously visible. 949 // XXX(seth): It'd be ideal to assert that we're already marked nonvisible 950 // here, but it's unfortunately tricky to guarantee in the face of things like 951 // frame reconstruction induced by style changes. 952 DisableVisibilityTracking(); 953 954 // Ensure that we're not in the approximately visible list anymore. 955 ps->RemoveFrameFromApproximatelyVisibleList(this); 956 957 ps->NotifyDestroyingFrame(this); 958 959 if (HasAnyStateBits(NS_FRAME_EXTERNAL_REFERENCE)) { 960 ps->ClearFrameRefs(this); 961 } 962 963 // Make sure that our deleted frame can't be returned from GetPrimaryFrame() 964 if (IsPrimaryFrame()) { 965 mContent->SetPrimaryFrame(nullptr); 966 967 // Pass the root of a generated content subtree (e.g. ::after/::before) to 968 // aPostDestroyData to unbind it after frame destruction is done. 969 if (HasAnyStateBits(NS_FRAME_GENERATED_CONTENT) && 970 mContent->IsRootOfNativeAnonymousSubtree()) { 971 aContext.AddAnonymousContent(mContent.forget()); 972 } 973 } 974 975 // Remove all properties attached to the frame, to ensure any property 976 // destructors that need the frame pointer are handled properly. 977 RemoveAllProperties(); 978 979 // Must retrieve the object ID before calling destructors, so the 980 // vtable is still valid. 981 // 982 // Note to future tweakers: having the method that returns the 983 // object size call the destructor will not avoid an indirect call; 984 // the compiler cannot devirtualize the call to the destructor even 985 // if it's from a method defined in the same class. 986 987 nsQueryFrame::FrameIID id = GetFrameId(); 988 this->~nsIFrame(); 989 990 #ifdef DEBUG 991 { 992 nsIFrame* rootFrame = ps->GetRootFrame(); 993 MOZ_ASSERT(rootFrame); 994 if (this != rootFrame) { 995 auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(rootFrame); 996 auto* data = builder ? builder->Data() : nullptr; 997 998 const bool inData = 999 data && (data->IsModified(this) || data->HasProps(this)); 1000 1001 if (inData) { 1002 DL_LOG(LogLevel::Warning, "Frame %p found in retained data", this); 1003 } 1004 1005 MOZ_ASSERT(!inData, "Deleted frame in retained data!"); 1006 } 1007 } 1008 #endif 1009 1010 // Now that we're totally cleaned out, we need to add ourselves to 1011 // the presshell's recycler. 1012 ps->FreeFrame(id, this); 1013 } 1014 1015 std::pair<int32_t, int32_t> nsIFrame::GetOffsets() const { 1016 return std::make_pair(0, 0); 1017 } 1018 1019 static void CompareLayers( 1020 const nsStyleImageLayers* aFirstLayers, 1021 const nsStyleImageLayers* aSecondLayers, 1022 const std::function<void(imgRequestProxy* aReq)>& aCallback) { 1023 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, (*aFirstLayers)) { 1024 const auto& image = aFirstLayers->mLayers[i].mImage; 1025 if (!image.IsImageRequestType() || !image.IsResolved()) { 1026 continue; 1027 } 1028 1029 // aCallback is called when the style image in aFirstLayers is thought to 1030 // be different with the corresponded one in aSecondLayers 1031 if (!aSecondLayers || i >= aSecondLayers->mImageCount || 1032 (!aSecondLayers->mLayers[i].mImage.IsResolved() || 1033 image.GetImageRequest() != 1034 aSecondLayers->mLayers[i].mImage.GetImageRequest())) { 1035 if (imgRequestProxy* req = image.GetImageRequest()) { 1036 aCallback(req); 1037 } 1038 } 1039 } 1040 } 1041 1042 static void AddAndRemoveImageAssociations( 1043 ImageLoader& aImageLoader, nsIFrame* aFrame, 1044 const nsStyleImageLayers* aOldLayers, 1045 const nsStyleImageLayers* aNewLayers) { 1046 // If the old context had a background-image image, or mask-image image, 1047 // and new context does not have the same image, clear the image load 1048 // notifier (which keeps the image loading, if it still is) for the frame. 1049 // We want to do this conservatively because some frames paint their 1050 // backgrounds from some other frame's style data, and we don't want 1051 // to clear those notifiers unless we have to. (They'll be reset 1052 // when we paint, although we could miss a notification in that 1053 // interval.) 1054 if (aOldLayers && aFrame->HasImageRequest()) { 1055 CompareLayers(aOldLayers, aNewLayers, [&](imgRequestProxy* aReq) { 1056 aImageLoader.DisassociateRequestFromFrame(aReq, aFrame); 1057 }); 1058 } 1059 1060 CompareLayers(aNewLayers, aOldLayers, [&](imgRequestProxy* aReq) { 1061 aImageLoader.AssociateRequestToFrame(aReq, aFrame); 1062 }); 1063 } 1064 1065 void nsIFrame::AddDisplayItem(nsDisplayItem* aItem) { 1066 MOZ_DIAGNOSTIC_ASSERT(!mDisplayItems.Contains(aItem)); 1067 mDisplayItems.AppendElement(aItem); 1068 #ifdef ACCESSIBILITY 1069 if (nsAccessibilityService* accService = GetAccService()) { 1070 accService->NotifyOfPossibleBoundsChange(PresShell(), mContent); 1071 } 1072 #endif 1073 } 1074 1075 bool nsIFrame::RemoveDisplayItem(nsDisplayItem* aItem) { 1076 return mDisplayItems.RemoveElement(aItem); 1077 } 1078 1079 bool nsIFrame::HasDisplayItems() { return !mDisplayItems.IsEmpty(); } 1080 1081 bool nsIFrame::HasDisplayItem(nsDisplayItem* aItem) { 1082 return mDisplayItems.Contains(aItem); 1083 } 1084 1085 bool nsIFrame::HasDisplayItem(uint32_t aKey) { 1086 for (nsDisplayItem* i : mDisplayItems) { 1087 if (i->GetPerFrameKey() == aKey) { 1088 return true; 1089 } 1090 } 1091 return false; 1092 } 1093 1094 template <typename Condition> 1095 static void DiscardDisplayItems(nsIFrame* aFrame, Condition aCondition) { 1096 for (nsDisplayItem* i : aFrame->DisplayItems()) { 1097 // Only discard items that are invalidated by this frame, as we're only 1098 // guaranteed to rebuild those items. Table background items are created by 1099 // the relevant table part, but have the cell frame as the primary frame, 1100 // and we don't want to remove them if this is the cell. 1101 if (aCondition(i) && i->FrameForInvalidation() == aFrame) { 1102 i->SetCantBeReused(); 1103 } 1104 } 1105 } 1106 1107 static void DiscardOldItems(nsIFrame* aFrame) { 1108 DiscardDisplayItems(aFrame, 1109 [](nsDisplayItem* aItem) { return aItem->IsOldItem(); }); 1110 } 1111 1112 void nsIFrame::RemoveDisplayItemDataForDeletion() { 1113 // Destroying a WebRenderUserDataTable can cause destruction of other objects 1114 // which can remove frame properties in their destructor. If we delete a frame 1115 // property it runs the destructor of the stored object in the middle of 1116 // updating the frame property table, so if the destruction of that object 1117 // causes another update to the frame property table it would leave the frame 1118 // property table in an inconsistent state. So we remove it from the table and 1119 // then destroy it. (bug 1530657) 1120 WebRenderUserDataTable* userDataTable = 1121 TakeProperty(WebRenderUserDataProperty::Key()); 1122 if (userDataTable) { 1123 for (const auto& data : userDataTable->Values()) { 1124 data->RemoveFromTable(); 1125 } 1126 delete userDataTable; 1127 } 1128 1129 auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(this); 1130 if (!builder) { 1131 MOZ_ASSERT(DisplayItems().IsEmpty()); 1132 MOZ_ASSERT(!IsFrameModified()); 1133 return; 1134 } 1135 1136 for (nsDisplayItem* i : DisplayItems()) { 1137 if (i->GetDependentFrame() == this && !i->HasDeletedFrame()) { 1138 i->Frame()->MarkNeedsDisplayItemRebuild(); 1139 } 1140 i->RemoveFrame(this); 1141 } 1142 1143 DisplayItems().Clear(); 1144 1145 nsAutoString name; 1146 #ifdef DEBUG_FRAME_DUMP 1147 if (DL_LOG_TEST(LogLevel::Debug)) { 1148 GetFrameName(name); 1149 } 1150 #endif 1151 DL_LOGV("Removing display item data for frame %p (%s)", this, 1152 NS_ConvertUTF16toUTF8(name).get()); 1153 1154 auto* data = builder->Data(); 1155 if (MayHaveWillChangeBudget()) { 1156 // Keep the frame in list, so it can be removed from the will-change budget. 1157 data->Flags(this) = RetainedDisplayListData::FrameFlag::HadWillChange; 1158 } else { 1159 data->Remove(this); 1160 } 1161 } 1162 1163 void nsIFrame::MarkNeedsDisplayItemRebuild() { 1164 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled() || IsFrameModified() || 1165 HasAnyStateBits(NS_FRAME_IN_POPUP)) { 1166 // Skip frames that are already marked modified. 1167 return; 1168 } 1169 1170 if (Type() == LayoutFrameType::Placeholder) { 1171 nsIFrame* oof = static_cast<nsPlaceholderFrame*>(this)->GetOutOfFlowFrame(); 1172 if (oof) { 1173 oof->MarkNeedsDisplayItemRebuild(); 1174 } 1175 // Do not mark placeholder frames modified. 1176 return; 1177 } 1178 1179 #ifdef ACCESSIBILITY 1180 if (nsAccessibilityService* accService = GetAccService()) { 1181 accService->NotifyOfPossibleBoundsChange(PresShell(), mContent); 1182 } 1183 #endif 1184 1185 nsIFrame* rootFrame = PresShell()->GetRootFrame(); 1186 1187 if (rootFrame->IsFrameModified()) { 1188 // The whole frame tree is modified. 1189 return; 1190 } 1191 1192 auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(this); 1193 if (!builder) { 1194 MOZ_ASSERT(DisplayItems().IsEmpty()); 1195 return; 1196 } 1197 1198 RetainedDisplayListData* data = builder->Data(); 1199 MOZ_ASSERT(data); 1200 1201 if (data->AtModifiedFrameLimit()) { 1202 // This marks the whole frame tree modified. 1203 // See |RetainedDisplayListBuilder::ShouldBuildPartial()|. 1204 data->AddModifiedFrame(rootFrame); 1205 return; 1206 } 1207 1208 nsAutoString name; 1209 #ifdef DEBUG_FRAME_DUMP 1210 if (DL_LOG_TEST(LogLevel::Debug)) { 1211 GetFrameName(name); 1212 } 1213 #endif 1214 1215 DL_LOGV("RDL - Rebuilding display items for frame %p (%s)", this, 1216 NS_ConvertUTF16toUTF8(name).get()); 1217 1218 data->AddModifiedFrame(this); 1219 1220 MOZ_ASSERT( 1221 PresContext()->LayoutPhaseCount(nsLayoutPhase::DisplayListBuilding) == 0); 1222 1223 // Hopefully this is cheap, but we could use a frame state bit to note 1224 // the presence of dependencies to speed it up. 1225 for (nsDisplayItem* i : DisplayItems()) { 1226 if (i->HasDeletedFrame() || i->Frame() == this) { 1227 // Ignore the items with deleted frames, and the items with |this| as 1228 // the primary frame. 1229 continue; 1230 } 1231 1232 if (i->GetDependentFrame() == this) { 1233 // For items with |this| as a dependent frame, mark the primary frame 1234 // for rebuild. 1235 i->Frame()->MarkNeedsDisplayItemRebuild(); 1236 } 1237 } 1238 } 1239 1240 // Subclass hook for style post processing 1241 /* virtual */ 1242 void nsIFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { 1243 #ifdef ACCESSIBILITY 1244 // Don't notify for reconstructed frames here, since the frame is still being 1245 // constructed at this point and so LocalAccessible::GetFrame() will return 1246 // null. Style changes for reconstructed frames are handled in 1247 // DocAccessible::PruneOrInsertSubtree. 1248 if (aOldComputedStyle) { 1249 if (nsAccessibilityService* accService = GetAccService()) { 1250 accService->NotifyOfComputedStyleChange(PresShell(), mContent); 1251 } 1252 } 1253 #endif 1254 1255 MaybeScheduleReflowSVGNonDisplayText(this); 1256 1257 Document* doc = PresContext()->Document(); 1258 ImageLoader& loader = doc->EnsureStyleImageLoader(); 1259 // Continuing text frame doesn't initialize its continuation pointer before 1260 // reaching here for the first time, so we have to exclude text frames. This 1261 // doesn't affect correctness because text can't match selectors. 1262 // 1263 // FIXME(emilio): We should consider fixing that. 1264 // 1265 // TODO(emilio): Can we avoid doing some / all of the image stuff when 1266 // isNonTextFirstContinuation is false? We should consider doing this just for 1267 // primary frames and pseudos, but the first-line reparenting code makes it 1268 // all bad, should get around to bug 1465474 eventually :( 1269 const bool isNonText = !IsTextFrame(); 1270 if (isNonText) { 1271 mComputedStyle->StartImageLoads(*doc, aOldComputedStyle); 1272 } 1273 1274 const bool isRootElementStyle = Style()->IsRootElementStyle(); 1275 if (isRootElementStyle) { 1276 PresShell()->SetNeedsWindowPropertiesSync(); 1277 } 1278 1279 const nsStyleImageLayers* oldLayers = 1280 aOldComputedStyle ? &aOldComputedStyle->StyleBackground()->mImage 1281 : nullptr; 1282 const nsStyleImageLayers* newLayers = &StyleBackground()->mImage; 1283 AddAndRemoveImageAssociations(loader, this, oldLayers, newLayers); 1284 1285 oldLayers = 1286 aOldComputedStyle ? &aOldComputedStyle->StyleSVGReset()->mMask : nullptr; 1287 newLayers = &StyleSVGReset()->mMask; 1288 AddAndRemoveImageAssociations(loader, this, oldLayers, newLayers); 1289 1290 const nsStyleDisplay* disp = StyleDisplay(); 1291 bool handleStickyChange = false; 1292 if (aOldComputedStyle) { 1293 // Detect style changes that should trigger a scroll anchor adjustment 1294 // suppression. 1295 // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers 1296 bool needScrollAnchorSuppression = false; 1297 1298 const nsStyleMargin* oldMargin = aOldComputedStyle->StyleMargin(); 1299 if (!oldMargin->MarginEquals(*StyleMargin())) { 1300 needScrollAnchorSuppression = true; 1301 } 1302 1303 const nsStylePadding* oldPadding = aOldComputedStyle->StylePadding(); 1304 if (oldPadding->mPadding != StylePadding()->mPadding) { 1305 SetHasPaddingChange(true); 1306 needScrollAnchorSuppression = true; 1307 } 1308 1309 const nsStyleDisplay* oldDisp = aOldComputedStyle->StyleDisplay(); 1310 if (oldDisp->mOverflowAnchor != disp->mOverflowAnchor) { 1311 if (auto* container = ScrollAnchorContainer::FindFor(this)) { 1312 container->InvalidateAnchor(); 1313 } 1314 if (ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(this)) { 1315 scrollContainerFrame->Anchor()->InvalidateAnchor(); 1316 } 1317 } 1318 1319 if (mInScrollAnchorChain) { 1320 const nsStylePosition* pos = StylePosition(); 1321 const nsStylePosition* oldPos = aOldComputedStyle->StylePosition(); 1322 if (!needScrollAnchorSuppression && 1323 (oldPos->mOffset != pos->mOffset || oldPos->mWidth != pos->mWidth || 1324 oldPos->mMinWidth != pos->mMinWidth || 1325 oldPos->mMaxWidth != pos->mMaxWidth || 1326 oldPos->mHeight != pos->mHeight || 1327 oldPos->mMinHeight != pos->mMinHeight || 1328 oldPos->mMaxHeight != pos->mMaxHeight || 1329 oldDisp->mPosition != disp->mPosition || 1330 oldDisp->mTransform != disp->mTransform)) { 1331 needScrollAnchorSuppression = true; 1332 } 1333 1334 if (needScrollAnchorSuppression && 1335 StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) { 1336 ScrollAnchorContainer::FindFor(this)->SuppressAdjustments(); 1337 } 1338 } 1339 1340 if (disp->mPosition != oldDisp->mPosition) { 1341 if (!disp->IsRelativelyOrStickyPositionedStyle() && 1342 oldDisp->IsRelativelyOrStickyPositionedStyle()) { 1343 RemoveProperty(NormalPositionProperty()); 1344 } 1345 1346 handleStickyChange = disp->mPosition == StylePositionProperty::Sticky || 1347 oldDisp->mPosition == StylePositionProperty::Sticky; 1348 } 1349 if (disp->mScrollSnapAlign != oldDisp->mScrollSnapAlign) { 1350 ScrollSnapUtils::PostPendingResnapFor(this); 1351 } 1352 if (isRootElementStyle && 1353 disp->mScrollSnapType != oldDisp->mScrollSnapType) { 1354 if (ScrollContainerFrame* sf = 1355 PresShell()->GetRootScrollContainerFrame()) { 1356 sf->PostPendingResnap(); 1357 } 1358 } 1359 if (StyleUIReset()->mMozSubtreeHiddenOnlyVisually && 1360 !aOldComputedStyle->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) { 1361 PresShell::ClearMouseCapture(this); 1362 } 1363 } else { // !aOldComputedStyle 1364 handleStickyChange = disp->mPosition == StylePositionProperty::Sticky; 1365 } 1366 1367 if (handleStickyChange && !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) && 1368 !GetPrevInFlow()) { 1369 // Note that we only add first continuations, but we really only 1370 // want to add first continuation-or-ib-split-siblings. But since we don't 1371 // yet know if we're a later part of a block-in-inline split, we'll just 1372 // add later members of a block-in-inline split here, and then 1373 // StickyScrollContainer will remove them later. 1374 if (auto* ssc = StickyScrollContainer::GetOrCreateForFrame(this)) { 1375 if (disp->mPosition == StylePositionProperty::Sticky) { 1376 ssc->AddFrame(this); 1377 } else { 1378 ssc->RemoveFrame(this); 1379 } 1380 } 1381 } 1382 1383 imgIRequest* oldBorderImage = 1384 aOldComputedStyle 1385 ? aOldComputedStyle->StyleBorder()->GetBorderImageRequest() 1386 : nullptr; 1387 imgIRequest* newBorderImage = StyleBorder()->GetBorderImageRequest(); 1388 // FIXME (Bug 759996): The following is no longer true. 1389 // For border-images, we can't be as conservative (we need to set the 1390 // new loaders if there has been any change) since the CalcDifference 1391 // call depended on the result of GetComputedBorder() and that result 1392 // depends on whether the image has loaded, start the image load now 1393 // so that we'll get notified when it completes loading and can do a 1394 // restyle. Otherwise, the image might finish loading from the 1395 // network before we start listening to its notifications, and then 1396 // we'll never know that it's finished loading. Likewise, we want to 1397 // do this for freshly-created frames to prevent a similar race if the 1398 // image loads between reflow (which can depend on whether the image 1399 // is loaded) and paint. We also don't really care about any callers who try 1400 // to paint borders with a different style, because they won't have the 1401 // correct size for the border either. 1402 if (oldBorderImage != newBorderImage) { 1403 // stop and restart the image loading/notification 1404 if (oldBorderImage && HasImageRequest()) { 1405 loader.DisassociateRequestFromFrame(oldBorderImage, this); 1406 } 1407 if (newBorderImage) { 1408 loader.AssociateRequestToFrame(newBorderImage, this); 1409 } 1410 } 1411 1412 auto GetShapeImageRequest = [](const ComputedStyle* aStyle) -> imgIRequest* { 1413 if (!aStyle) { 1414 return nullptr; 1415 } 1416 auto& shape = aStyle->StyleDisplay()->mShapeOutside; 1417 if (!shape.IsImage()) { 1418 return nullptr; 1419 } 1420 return shape.AsImage().GetImageRequest(); 1421 }; 1422 1423 imgIRequest* oldShapeImage = GetShapeImageRequest(aOldComputedStyle); 1424 imgIRequest* newShapeImage = GetShapeImageRequest(Style()); 1425 if (oldShapeImage != newShapeImage) { 1426 if (oldShapeImage && HasImageRequest()) { 1427 loader.DisassociateRequestFromFrame(oldShapeImage, this); 1428 } 1429 if (newShapeImage) { 1430 loader.AssociateRequestToFrame( 1431 newShapeImage, this, 1432 ImageLoader::Flags:: 1433 RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking); 1434 } 1435 } 1436 1437 // SVGObserverUtils::GetEffectProperties() asserts that we only invoke it with 1438 // the first continuation so we need to check that in advance. 1439 const bool isNonTextFirstContinuation = isNonText && !GetPrevContinuation(); 1440 if (isNonTextFirstContinuation) { 1441 // Kick off loading of external SVG resources referenced from properties if 1442 // any. This currently includes filter, clip-path, and mask. 1443 SVGObserverUtils::InitiateResourceDocLoads(this); 1444 } 1445 1446 // If the page contains markup that overrides text direction, and 1447 // does not contain any characters that would activate the Unicode 1448 // bidi algorithm, we need to call |SetBidiEnabled| on the pres 1449 // context before reflow starts. See bug 115921. 1450 if (StyleVisibility()->mDirection == StyleDirection::Rtl) { 1451 PresContext()->SetBidiEnabled(); 1452 } 1453 1454 // The following part is for caching offset-path:path(). We cache the 1455 // flatten gfx path, so we don't have to rebuild and re-flattern it at 1456 // each cycle if we have animations on offset-* with a fixed offset-path. 1457 const StyleOffsetPath* oldPath = 1458 aOldComputedStyle ? &aOldComputedStyle->StyleDisplay()->mOffsetPath 1459 : nullptr; 1460 const StyleOffsetPath& newPath = StyleDisplay()->mOffsetPath; 1461 if (!oldPath || *oldPath != newPath) { 1462 // FIXME: Bug 1837042. Cache all basic shapes. 1463 if (newPath.IsPath()) { 1464 RefPtr<gfx::PathBuilder> builder = MotionPathUtils::GetPathBuilder(); 1465 RefPtr<gfx::Path> path = 1466 MotionPathUtils::BuildSVGPath(newPath.AsSVGPathData(), builder); 1467 if (path) { 1468 // The newPath could be path('') (i.e. empty path), so its gfx path 1469 // could be nullptr, and so we only set property for a non-empty path. 1470 SetProperty(nsIFrame::OffsetPathCache(), path.forget().take()); 1471 } else { 1472 // May have an old cached path, so we have to delete it. 1473 RemoveProperty(nsIFrame::OffsetPathCache()); 1474 } 1475 } else if (oldPath) { 1476 RemoveProperty(nsIFrame::OffsetPathCache()); 1477 } 1478 } 1479 1480 if (IsPrimaryFrame()) { 1481 MOZ_ASSERT(aOldComputedStyle); 1482 HandlePrimaryFrameStyleChange(aOldComputedStyle); 1483 } 1484 1485 RemoveStateBits(NS_FRAME_SIMPLE_DISPLAYLIST); 1486 1487 mMayHaveRoundedCorners = true; 1488 } 1489 1490 void nsIFrame::HandleLastRememberedSize() { 1491 MOZ_ASSERT(IsPrimaryFrame()); 1492 auto* element = Element::FromNodeOrNull(mContent); 1493 if (!element) { 1494 return; 1495 } 1496 const WritingMode wm = GetWritingMode(); 1497 const nsStylePosition* stylePos = StylePosition(); 1498 bool canRememberBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto(); 1499 bool canRememberISize = stylePos->ContainIntrinsicISize(wm).HasAuto(); 1500 if (!canRememberBSize) { 1501 element->RemoveLastRememberedBSize(); 1502 } 1503 if (!canRememberISize) { 1504 element->RemoveLastRememberedISize(); 1505 } 1506 if ((canRememberBSize || canRememberISize) && !HidesContent()) { 1507 bool isNonReplacedInline = IsLineParticipant() && !IsReplaced(); 1508 if (!isNonReplacedInline) { 1509 PresContext()->Document()->ObserveForLastRememberedSize(*element); 1510 return; 1511 } 1512 } 1513 PresContext()->Document()->UnobserveForLastRememberedSize(*element); 1514 } 1515 1516 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 1517 void nsIFrame::AssertNewStyleIsSane(ComputedStyle& aNewStyle) { 1518 MOZ_DIAGNOSTIC_ASSERT( 1519 aNewStyle.GetPseudoType() == mComputedStyle->GetPseudoType() || 1520 // ::first-line continuations are weird, this should probably be fixed via 1521 // bug 1465474. 1522 (mComputedStyle->GetPseudoType() == PseudoStyleType::firstLine && 1523 aNewStyle.GetPseudoType() == PseudoStyleType::mozLineFrame) || 1524 // ::first-letter continuations are broken, in particular floating ones, 1525 // see bug 1490281. The construction code tries to fix this up after the 1526 // fact, then restyling undoes it... 1527 (mComputedStyle->GetPseudoType() == PseudoStyleType::mozText && 1528 aNewStyle.GetPseudoType() == PseudoStyleType::firstLetterContinuation) || 1529 (mComputedStyle->GetPseudoType() == 1530 PseudoStyleType::firstLetterContinuation && 1531 aNewStyle.GetPseudoType() == PseudoStyleType::mozText)); 1532 } 1533 #endif 1534 1535 /* virtual */ 1536 nsMargin nsIFrame::GetUsedMargin() const { 1537 nsMargin margin; 1538 if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) || 1539 IsInSVGTextSubtree()) { 1540 return margin; 1541 } 1542 1543 if (nsMargin* m = GetProperty(UsedMarginProperty())) { 1544 margin = *m; 1545 } else if (!StyleMargin()->GetMargin(margin)) { 1546 // If we get here, our caller probably shouldn't be calling us... 1547 NS_ERROR( 1548 "Returning bogus 0-sized margin, because this margin " 1549 "depends on layout & isn't cached!"); 1550 } 1551 return margin; 1552 } 1553 1554 /* virtual */ 1555 nsMargin nsIFrame::GetUsedBorder() const { 1556 if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) || 1557 IsInSVGTextSubtree()) { 1558 return {}; 1559 } 1560 1561 const nsStyleDisplay* disp = StyleDisplay(); 1562 if (IsThemed(disp)) { 1563 // Theme methods don't use const-ness. 1564 auto* mutable_this = const_cast<nsIFrame*>(this); 1565 nsPresContext* pc = PresContext(); 1566 LayoutDeviceIntMargin widgetBorder = pc->Theme()->GetWidgetBorder( 1567 pc->DeviceContext(), mutable_this, disp->EffectiveAppearance()); 1568 return LayoutDevicePixel::ToAppUnits(widgetBorder, 1569 pc->AppUnitsPerDevPixel()); 1570 } 1571 1572 return StyleBorder()->GetComputedBorder(); 1573 } 1574 1575 /* virtual */ 1576 nsMargin nsIFrame::GetUsedPadding() const { 1577 nsMargin padding; 1578 if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) || 1579 IsInSVGTextSubtree()) { 1580 return padding; 1581 } 1582 1583 const nsStyleDisplay* disp = StyleDisplay(); 1584 if (IsThemed(disp)) { 1585 // Theme methods don't use const-ness. 1586 nsIFrame* mutable_this = const_cast<nsIFrame*>(this); 1587 nsPresContext* pc = PresContext(); 1588 LayoutDeviceIntMargin widgetPadding; 1589 if (pc->Theme()->GetWidgetPadding(pc->DeviceContext(), mutable_this, 1590 disp->EffectiveAppearance(), 1591 &widgetPadding)) { 1592 return LayoutDevicePixel::ToAppUnits(widgetPadding, 1593 pc->AppUnitsPerDevPixel()); 1594 } 1595 } 1596 1597 if (nsMargin* p = GetProperty(UsedPaddingProperty())) { 1598 padding = *p; 1599 } else if (!StylePadding()->GetPadding(padding)) { 1600 // If we get here, our caller probably shouldn't be calling us... 1601 NS_ERROR( 1602 "Returning bogus 0-sized padding, because this padding " 1603 "depends on layout & isn't cached!"); 1604 } 1605 return padding; 1606 } 1607 1608 nsIFrame::Sides nsIFrame::GetSkipSides() const { 1609 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == 1610 StyleBoxDecorationBreak::Clone) && 1611 !HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { 1612 return Sides(); 1613 } 1614 1615 // Convert the logical skip sides to physical sides using the frame's 1616 // writing mode 1617 WritingMode writingMode = GetWritingMode(); 1618 LogicalSides logicalSkip = GetLogicalSkipSides(); 1619 Sides skip; 1620 1621 if (logicalSkip.BStart()) { 1622 if (writingMode.IsVertical()) { 1623 skip |= writingMode.IsVerticalLR() ? SideBits::eLeft : SideBits::eRight; 1624 } else { 1625 skip |= SideBits::eTop; 1626 } 1627 } 1628 1629 if (logicalSkip.BEnd()) { 1630 if (writingMode.IsVertical()) { 1631 skip |= writingMode.IsVerticalLR() ? SideBits::eRight : SideBits::eLeft; 1632 } else { 1633 skip |= SideBits::eBottom; 1634 } 1635 } 1636 1637 if (logicalSkip.IStart()) { 1638 if (writingMode.IsVertical()) { 1639 skip |= SideBits::eTop; 1640 } else { 1641 skip |= writingMode.IsBidiLTR() ? SideBits::eLeft : SideBits::eRight; 1642 } 1643 } 1644 1645 if (logicalSkip.IEnd()) { 1646 if (writingMode.IsVertical()) { 1647 skip |= SideBits::eBottom; 1648 } else { 1649 skip |= writingMode.IsBidiLTR() ? SideBits::eRight : SideBits::eLeft; 1650 } 1651 } 1652 return skip; 1653 } 1654 1655 nsRect nsIFrame::GetPaddingRectRelativeToSelf() const { 1656 nsMargin border = GetUsedBorder().ApplySkipSides(GetSkipSides()); 1657 nsRect r(0, 0, mRect.width, mRect.height); 1658 r.Deflate(border); 1659 return r; 1660 } 1661 1662 nsRect nsIFrame::GetPaddingRect() const { 1663 return GetPaddingRectRelativeToSelf() + GetPosition(); 1664 } 1665 1666 WritingMode nsIFrame::WritingModeForLine(WritingMode aSelfWM, 1667 nsIFrame* aSubFrame) const { 1668 MOZ_ASSERT(aSelfWM == GetWritingMode()); 1669 WritingMode writingMode = aSelfWM; 1670 1671 if (StyleTextReset()->mUnicodeBidi == StyleUnicodeBidi::Plaintext) { 1672 mozilla::intl::BidiEmbeddingLevel frameLevel = 1673 nsBidiPresUtils::GetFrameBaseLevel(aSubFrame); 1674 writingMode.SetDirectionFromBidiLevel(frameLevel); 1675 } 1676 1677 return writingMode; 1678 } 1679 1680 nsRect nsIFrame::GetMarginRect() const { 1681 return GetMarginRectRelativeToSelf() + GetPosition(); 1682 } 1683 1684 nsRect nsIFrame::GetMarginRectRelativeToSelf() const { 1685 nsMargin m = GetUsedMargin().ApplySkipSides(GetSkipSides()); 1686 nsRect r(0, 0, mRect.width, mRect.height); 1687 r.Inflate(m); 1688 return r; 1689 } 1690 1691 bool nsIFrame::IsTransformed() const { 1692 if (!HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) { 1693 MOZ_ASSERT(!IsCSSTransformed()); 1694 MOZ_ASSERT(!GetParentSVGTransforms()); 1695 return false; 1696 } 1697 return IsCSSTransformed() || GetParentSVGTransforms(); 1698 } 1699 1700 bool nsIFrame::IsCSSTransformed() const { 1701 return HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) && 1702 (StyleDisplay()->HasTransform(this) || HasAnimationOfTransform()); 1703 } 1704 1705 bool nsIFrame::HasAnimationOfTransform() const { 1706 if (!MayHaveTransformAnimation()) { 1707 MOZ_ASSERT(!IsPrimaryFrame() || !SupportsCSSTransforms() || 1708 !nsLayoutUtils::HasAnimationOfTransformAndMotionPath(this)); 1709 return false; 1710 } 1711 return IsPrimaryFrame() && SupportsCSSTransforms() && 1712 nsLayoutUtils::HasAnimationOfTransformAndMotionPath(this); 1713 } 1714 1715 bool nsIFrame::ChildrenHavePerspective( 1716 const nsStyleDisplay* aStyleDisplay) const { 1717 MOZ_ASSERT(aStyleDisplay == StyleDisplay()); 1718 return aStyleDisplay->HasPerspective(this); 1719 } 1720 1721 bool nsIFrame::HasAnimationOfOpacity(EffectSet* aEffectSet) const { 1722 return ((nsLayoutUtils::IsPrimaryStyleFrame(this) || 1723 nsLayoutUtils::FirstContinuationOrIBSplitSibling(this) 1724 ->IsPrimaryFrame()) && 1725 nsLayoutUtils::HasAnimationOfPropertySet( 1726 this, nsCSSPropertyIDSet::OpacityProperties(), aEffectSet)); 1727 } 1728 1729 bool nsIFrame::HasOpacityInternal(float aThreshold, 1730 const nsStyleDisplay* aStyleDisplay, 1731 const nsStyleEffects* aStyleEffects, 1732 EffectSet* aEffectSet) const { 1733 MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument"); 1734 if (aStyleEffects->mOpacity < aThreshold || 1735 aStyleDisplay->mWillChange.bits & StyleWillChangeBits::OPACITY) { 1736 return true; 1737 } 1738 1739 if (!mMayHaveOpacityAnimation) { 1740 return false; 1741 } 1742 1743 return HasAnimationOfOpacity(aEffectSet); 1744 } 1745 1746 bool nsIFrame::DoGetParentSVGTransforms(gfx::Matrix*) const { return false; } 1747 1748 bool nsIFrame::Extend3DContext(const nsStyleDisplay* aStyleDisplay, 1749 const nsStyleEffects* aStyleEffects, 1750 mozilla::EffectSet* aEffectSetForOpacity) const { 1751 if (!HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) { 1752 return false; 1753 } 1754 const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay); 1755 if (disp->mTransformStyle != StyleTransformStyle::Preserve3d || 1756 !SupportsCSSTransforms()) { 1757 return false; 1758 } 1759 1760 // If we're all scroll frame, then all descendants will be clipped, so we 1761 // can't preserve 3d. 1762 if (IsScrollContainerFrame()) { 1763 return false; 1764 } 1765 1766 const nsStyleEffects* effects = StyleEffectsWithOptionalParam(aStyleEffects); 1767 if (HasOpacity(disp, effects, aEffectSetForOpacity)) { 1768 return false; 1769 } 1770 1771 return ShouldApplyOverflowClipping(disp).isEmpty() && 1772 !GetClipPropClipRect(disp, effects, GetSize()) && 1773 !SVGIntegrationUtils::UsingEffectsForFrame(this) && 1774 !effects->HasMixBlendMode() && 1775 !ForcesStackingContextForViewTransition() && 1776 disp->mIsolation != StyleIsolation::Isolate; 1777 } 1778 1779 bool nsIFrame::Combines3DTransformWithAncestors() const { 1780 // Check these first as they are faster then both calls below and are we are 1781 // likely to hit the early return (backface hidden is uncommon and 1782 // GetReferenceFrame is a hot caller of this which only calls this if 1783 // IsCSSTransformed is false). 1784 if (!IsCSSTransformed() && !BackfaceIsHidden()) { 1785 return false; 1786 } 1787 nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame(); 1788 return parent && parent->Extend3DContext(); 1789 } 1790 1791 bool nsIFrame::In3DContextAndBackfaceIsHidden() const { 1792 // While both tests fail most of the time, test BackfaceIsHidden() 1793 // first since it's likely to fail faster. 1794 return BackfaceIsHidden() && Combines3DTransformWithAncestors(); 1795 } 1796 1797 bool nsIFrame::HasPerspective() const { 1798 if (!IsCSSTransformed()) { 1799 return false; 1800 } 1801 nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame(); 1802 if (!parent) { 1803 return false; 1804 } 1805 return parent->ChildrenHavePerspective(); 1806 } 1807 1808 nsRect nsIFrame::GetContentRectRelativeToSelf() const { 1809 nsMargin bp = GetUsedBorderAndPadding().ApplySkipSides(GetSkipSides()); 1810 nsRect r(0, 0, mRect.width, mRect.height); 1811 r.Deflate(bp); 1812 return r; 1813 } 1814 1815 nsRect nsIFrame::GetContentRect() const { 1816 return GetContentRectRelativeToSelf() + GetPosition(); 1817 } 1818 1819 bool nsIFrame::ComputeBorderRadii(const BorderRadius& aBorderRadius, 1820 const nsSize& aFrameSize, 1821 const nsSize& aBorderArea, Sides aSkipSides, 1822 nsRectCornerRadii& aRadii) { 1823 // Percentages are relative to whichever side they're on. 1824 for (const auto i : mozilla::AllPhysicalHalfCorners()) { 1825 const LengthPercentage& c = aBorderRadius.Get(i); 1826 nscoord axis = HalfCornerIsX(i) ? aFrameSize.width : aFrameSize.height; 1827 aRadii[i] = std::max(0, c.Resolve(axis)); 1828 } 1829 1830 if (aSkipSides.Intersects(SideBits::eTop | SideBits::eLeft)) { 1831 aRadii.TopLeft() = {}; 1832 } 1833 if (aSkipSides.Intersects(SideBits::eTop | SideBits::eRight)) { 1834 aRadii.TopRight() = {}; 1835 } 1836 if (aSkipSides.Intersects(SideBits::eBottom | SideBits::eLeft)) { 1837 aRadii.BottomLeft() = {}; 1838 } 1839 if (aSkipSides.Intersects(SideBits::eBottom | SideBits::eRight)) { 1840 aRadii.BottomRight() = {}; 1841 } 1842 1843 // css3-background specifies this algorithm for reducing 1844 // corner radii when they are too big. 1845 bool haveRadius = false; 1846 double ratio = 1.0f; 1847 for (const auto side : mozilla::AllPhysicalSides()) { 1848 auto hc1 = SideToHalfCorner(side, false, true); 1849 auto hc2 = SideToHalfCorner(side, true, true); 1850 nscoord length = 1851 SideIsVertical(side) ? aBorderArea.height : aBorderArea.width; 1852 nscoord sum = aRadii[hc1] + aRadii[hc2]; 1853 if (sum) { 1854 haveRadius = true; 1855 // avoid floating point division in the normal case 1856 if (length < sum) { 1857 ratio = std::min(ratio, double(length) / sum); 1858 } 1859 } 1860 } 1861 if (ratio < 1.0) { 1862 for (const auto corner : mozilla::AllPhysicalHalfCorners()) { 1863 aRadii[corner] *= ratio; 1864 } 1865 } 1866 1867 return haveRadius; 1868 } 1869 1870 static inline bool RadiiAreDefinitelyZero(const BorderRadius& aBorderRadius) { 1871 for (const auto corner : mozilla::AllPhysicalHalfCorners()) { 1872 if (!aBorderRadius.Get(corner).IsDefinitelyZero()) { 1873 return false; 1874 } 1875 } 1876 return true; 1877 } 1878 1879 /* virtual */ 1880 bool nsIFrame::GetBorderRadii(const nsSize& aFrameSize, 1881 const nsSize& aBorderArea, Sides aSkipSides, 1882 nsRectCornerRadii& aRadii) const { 1883 if (!mMayHaveRoundedCorners) { 1884 return false; 1885 } 1886 1887 if (IsThemed()) { 1888 // When we're themed, the native theme code draws the border and 1889 // background, and therefore it doesn't make sense to tell other 1890 // code that's interested in border-radius that we have any radii. 1891 // 1892 // In an ideal world, we might have a way for the them to tell us an 1893 // border radius, but since we don't, we're better off assuming 1894 // zero. 1895 return false; 1896 } 1897 1898 const auto& radii = StyleBorder()->mBorderRadius; 1899 const bool hasRadii = 1900 ComputeBorderRadii(radii, aFrameSize, aBorderArea, aSkipSides, aRadii); 1901 if (!hasRadii) { 1902 // TODO(emilio): Maybe we can just remove this bit and do the 1903 // IsDefinitelyZero check unconditionally. That should still avoid most of 1904 // the work, though maybe not the cache miss of going through the style and 1905 // the border struct. 1906 const_cast<nsIFrame*>(this)->mMayHaveRoundedCorners = 1907 !RadiiAreDefinitelyZero(radii); 1908 } 1909 return hasRadii; 1910 } 1911 1912 bool nsIFrame::GetBorderRadii(nsRectCornerRadii& aRadii) const { 1913 nsSize sz = GetSize(); 1914 return GetBorderRadii(sz, sz, GetSkipSides(), aRadii); 1915 } 1916 1917 bool nsIFrame::GetMarginBoxBorderRadii(nsRectCornerRadii& aRadii) const { 1918 if (!GetBorderRadii(aRadii)) { 1919 return false; 1920 } 1921 aRadii.AdjustOutwards(GetUsedMargin()); 1922 return true; 1923 } 1924 1925 bool nsIFrame::GetPaddingBoxBorderRadii(nsRectCornerRadii& aRadii) const { 1926 if (!GetBorderRadii(aRadii)) { 1927 return false; 1928 } 1929 aRadii.AdjustInwards(GetUsedBorder()); 1930 return true; 1931 } 1932 1933 bool nsIFrame::GetContentBoxBorderRadii(nsRectCornerRadii& aRadii) const { 1934 if (!GetBorderRadii(aRadii)) { 1935 return false; 1936 } 1937 aRadii.AdjustInwards(GetUsedBorderAndPadding()); 1938 return true; 1939 } 1940 1941 bool nsIFrame::GetShapeBoxBorderRadii(nsRectCornerRadii& aRadii) const { 1942 using Tag = StyleShapeOutside::Tag; 1943 auto& shapeOutside = StyleDisplay()->mShapeOutside; 1944 auto box = StyleShapeBox::MarginBox; 1945 switch (shapeOutside.tag) { 1946 case Tag::Image: 1947 case Tag::None: 1948 return false; 1949 case Tag::Box: 1950 box = shapeOutside.AsBox(); 1951 break; 1952 case Tag::Shape: 1953 box = shapeOutside.AsShape()._1; 1954 break; 1955 } 1956 1957 switch (box) { 1958 case StyleShapeBox::ContentBox: 1959 return GetContentBoxBorderRadii(aRadii); 1960 case StyleShapeBox::PaddingBox: 1961 return GetPaddingBoxBorderRadii(aRadii); 1962 case StyleShapeBox::BorderBox: 1963 return GetBorderRadii(aRadii); 1964 case StyleShapeBox::MarginBox: 1965 return GetMarginBoxBorderRadii(aRadii); 1966 default: 1967 MOZ_ASSERT_UNREACHABLE("Unexpected box value"); 1968 return false; 1969 } 1970 } 1971 1972 nscoord nsIFrame::OneEmInAppUnits() const { 1973 return StyleFont() 1974 ->mFont.size.ScaledBy(nsLayoutUtils::FontSizeInflationFor(this)) 1975 .ToAppUnits(); 1976 } 1977 1978 RubyMetrics nsIFrame::RubyMetrics(float aRubyMetricsFactor) const { 1979 RefPtr<nsFontMetrics> fm = 1980 nsLayoutUtils::GetInflatedFontMetricsForFrame(this); 1981 return mozilla::RubyMetrics{ 1982 nscoord(NS_round(fm->TrimmedAscent() * aRubyMetricsFactor)), 1983 nscoord(NS_round(fm->TrimmedDescent() * aRubyMetricsFactor))}; 1984 } 1985 1986 ComputedStyle* nsIFrame::GetAdditionalComputedStyle(int32_t aIndex) const { 1987 MOZ_ASSERT(aIndex >= 0, "invalid index number"); 1988 return nullptr; 1989 } 1990 1991 void nsIFrame::SetAdditionalComputedStyle(int32_t aIndex, 1992 ComputedStyle* aComputedStyle) { 1993 MOZ_ASSERT(aIndex >= 0, "invalid index number"); 1994 } 1995 1996 nscoord nsIFrame::SynthesizeFallbackBaseline( 1997 WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { 1998 const auto margin = GetLogicalUsedMargin(aWM); 1999 NS_ASSERTION(!IsSubtreeDirty(), "frame must not be dirty"); 2000 if (aWM.IsCentralBaseline()) { 2001 return (BSize(aWM) + GetLogicalUsedMargin(aWM).BEnd(aWM)) / 2; 2002 } 2003 // Baseline for inverted line content is the top (block-start) margin edge, 2004 // as the frame is in effect "flipped" for alignment purposes. 2005 if (aWM.IsLineInverted()) { 2006 const auto marginStart = margin.BStart(aWM); 2007 return aBaselineGroup == BaselineSharingGroup::First 2008 ? -marginStart 2009 : BSize(aWM) + marginStart; 2010 } 2011 // Otherwise, the bottom margin edge, per CSS2.1's definition of the 2012 // 'baseline' value of 'vertical-align'. 2013 const auto marginEnd = margin.BEnd(aWM); 2014 return aBaselineGroup == BaselineSharingGroup::First ? BSize(aWM) + marginEnd 2015 : -marginEnd; 2016 } 2017 2018 nscoord nsIFrame::GetLogicalBaseline(WritingMode aWM) const { 2019 return GetLogicalBaseline(aWM, GetDefaultBaselineSharingGroup(), 2020 BaselineExportContext::LineLayout); 2021 } 2022 2023 nscoord nsIFrame::GetLogicalBaseline( 2024 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 2025 BaselineExportContext aExportContext) const { 2026 const auto result = 2027 GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext) 2028 .valueOrFrom([this, aWM, aBaselineGroup]() { 2029 return SynthesizeFallbackBaseline(aWM, aBaselineGroup); 2030 }); 2031 if (aBaselineGroup == BaselineSharingGroup::Last) { 2032 return BSize(aWM) - result; 2033 } 2034 return result; 2035 } 2036 2037 const nsFrameList& nsIFrame::GetChildList(ChildListID aListID) const { 2038 if (IsAbsoluteContainer()) { 2039 if (aListID == GetAbsoluteListID()) { 2040 return GetAbsoluteContainingBlock()->GetChildList(); 2041 } else if (aListID == FrameChildListID::PushedAbsolute) { 2042 return GetAbsoluteContainingBlock()->GetPushedChildList(); 2043 } 2044 } 2045 return nsFrameList::EmptyList(); 2046 } 2047 2048 void nsIFrame::GetChildLists(nsTArray<ChildList>* aLists) const { 2049 if (const auto* absCB = GetAbsoluteContainingBlock()) { 2050 const nsFrameList& absoluteList = absCB->GetChildList(); 2051 absoluteList.AppendIfNonempty(aLists, GetAbsoluteListID()); 2052 const nsFrameList& pushedAbsoluteList = absCB->GetPushedChildList(); 2053 pushedAbsoluteList.AppendIfNonempty(aLists, 2054 FrameChildListID::PushedAbsolute); 2055 } 2056 } 2057 2058 AutoTArray<nsIFrame::ChildList, 4> nsIFrame::CrossDocChildLists() { 2059 AutoTArray<ChildList, 4> childLists; 2060 nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(this); 2061 if (subdocumentFrame) { 2062 // Descend into the subdocument 2063 nsIFrame* root = subdocumentFrame->GetSubdocumentRootFrame(); 2064 if (root) { 2065 childLists.EmplaceBack( 2066 nsFrameList(root, nsLayoutUtils::GetLastSibling(root)), 2067 FrameChildListID::Principal); 2068 } 2069 } 2070 2071 GetChildLists(&childLists); 2072 return childLists; 2073 } 2074 2075 nsIFrame::CaretBlockAxisMetrics nsIFrame::GetCaretBlockAxisMetrics( 2076 mozilla::WritingMode aWM, const nsFontMetrics& aFM) const { 2077 // Note(dshin): Ultimately, this does something highly similar (But still 2078 // different) to `nsLayoutUtils::GetFirstLinePosition`. 2079 const auto baseline = GetCaretBaseline(); 2080 nscoord ascent = 0, descent = 0; 2081 ascent = aFM.MaxAscent(); 2082 descent = aFM.MaxDescent(); 2083 const nscoord height = ascent + descent; 2084 if (aWM.IsVertical() && aWM.IsLineInverted()) { 2085 return CaretBlockAxisMetrics{.mOffset = baseline - descent, 2086 .mExtent = height}; 2087 } 2088 return CaretBlockAxisMetrics{.mOffset = baseline - ascent, .mExtent = height}; 2089 } 2090 2091 nscoord nsIFrame::GetFontMetricsDerivedCaretBaseline(nscoord aBSize) const { 2092 float inflation = nsLayoutUtils::FontSizeInflationFor(this); 2093 RefPtr<nsFontMetrics> fm = 2094 nsLayoutUtils::GetFontMetricsForFrame(this, inflation); 2095 const WritingMode wm = GetWritingMode(); 2096 nscoord lineHeight = ReflowInput::CalcLineHeight( 2097 *Style(), PresContext(), GetContent(), aBSize, inflation); 2098 return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight, 2099 wm.IsLineInverted()) + 2100 GetLogicalUsedBorderAndPadding(wm).BStart(wm); 2101 } 2102 2103 const nsAtom* nsIFrame::ComputePageValue(const nsAtom* aAutoValue) const { 2104 const nsAtom* value = aAutoValue ? aAutoValue : nsGkAtoms::_empty; 2105 const nsIFrame* frame = this; 2106 // Find what CSS page name value this frame's subtree has, if any. 2107 // Starting with this frame, check if a page name other than auto is present, 2108 // and record it if so. Then, if the current frame is a container frame, find 2109 // the first non-placeholder child and repeat. 2110 // This will find the most deeply nested first in-flow child of this frame's 2111 // subtree, and return its page name (with auto resolved if applicable, and 2112 // subtrees with no page-names returning the empty atom rather than null). 2113 do { 2114 if (const nsAtom* maybePageName = frame->GetStylePageName()) { 2115 value = maybePageName; 2116 } 2117 // Get the next frame to read from. 2118 const nsIFrame* firstNonPlaceholderFrame = nullptr; 2119 // If this is a container frame, inspect its in-flow children. 2120 if (const nsContainerFrame* containerFrame = do_QueryFrame(frame)) { 2121 for (const nsIFrame* childFrame : containerFrame->PrincipalChildList()) { 2122 if (!childFrame->IsPlaceholderFrame()) { 2123 firstNonPlaceholderFrame = childFrame; 2124 break; 2125 } 2126 } 2127 } 2128 frame = firstNonPlaceholderFrame; 2129 } while (frame); 2130 return value; 2131 } 2132 2133 Visibility nsIFrame::GetVisibility() const { 2134 if (!HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) { 2135 return Visibility::Untracked; 2136 } 2137 2138 bool isSet = false; 2139 uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet); 2140 2141 MOZ_ASSERT(isSet, 2142 "Should have a VisibilityStateProperty value " 2143 "if NS_FRAME_VISIBILITY_IS_TRACKED is set"); 2144 2145 return visibleCount > 0 ? Visibility::ApproximatelyVisible 2146 : Visibility::ApproximatelyNonVisible; 2147 } 2148 2149 void nsIFrame::UpdateVisibilitySynchronously() { 2150 mozilla::PresShell* presShell = PresShell(); 2151 if (!presShell) { 2152 return; 2153 } 2154 2155 if (presShell->AssumeAllFramesVisible()) { 2156 presShell->EnsureFrameInApproximatelyVisibleList(this); 2157 return; 2158 } 2159 2160 bool visible = StyleVisibility()->IsVisible(); 2161 nsIFrame* f = GetParent(); 2162 nsRect rect = GetRectRelativeToSelf(); 2163 nsIFrame* rectFrame = this; 2164 while (f && visible) { 2165 if (ScrollContainerFrame* sf = do_QueryFrame(f)) { 2166 nsRect transformedRect = 2167 nsLayoutUtils::TransformFrameRectToAncestor(rectFrame, rect, f); 2168 if (!sf->IsRectNearlyVisible(transformedRect)) { 2169 visible = false; 2170 break; 2171 } 2172 2173 // In this code we're trying to synchronously update *approximate* 2174 // visibility. (In the future we may update precise visibility here as 2175 // well, which is why the method name does not contain 'approximate'.) The 2176 // IsRectNearlyVisible() check above tells us that the rect we're checking 2177 // is approximately visible within the scrollframe, but we still need to 2178 // ensure that, even if it was scrolled into view, it'd be visible when we 2179 // consider the rest of the document. To do that, we move transformedRect 2180 // to be contained in the scrollport as best we can (it might not fit) to 2181 // pretend that it was scrolled into view. 2182 rect = transformedRect.MoveInsideAndClamp(sf->GetScrollPortRect()); 2183 rectFrame = f; 2184 } 2185 nsIFrame* parent = f->GetParent(); 2186 if (!parent) { 2187 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(f); 2188 if (parent && parent->PresContext()->IsChrome()) { 2189 break; 2190 } 2191 } 2192 f = parent; 2193 } 2194 2195 if (visible) { 2196 presShell->EnsureFrameInApproximatelyVisibleList(this); 2197 } else { 2198 presShell->RemoveFrameFromApproximatelyVisibleList(this); 2199 } 2200 } 2201 2202 void nsIFrame::EnableVisibilityTracking() { 2203 if (HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) { 2204 return; // Nothing to do. 2205 } 2206 2207 MOZ_ASSERT(!HasProperty(VisibilityStateProperty()), 2208 "Shouldn't have a VisibilityStateProperty value " 2209 "if NS_FRAME_VISIBILITY_IS_TRACKED is not set"); 2210 2211 // Add the state bit so we know to track visibility for this frame, and 2212 // initialize the frame property. 2213 AddStateBits(NS_FRAME_VISIBILITY_IS_TRACKED); 2214 SetProperty(VisibilityStateProperty(), 0); 2215 2216 mozilla::PresShell* presShell = PresShell(); 2217 if (!presShell) { 2218 return; 2219 } 2220 2221 // Schedule a visibility update. This method will virtually always be called 2222 // when layout has changed anyway, so it's very unlikely that any additional 2223 // visibility updates will be triggered by this, but this way we guarantee 2224 // that if this frame is currently visible we'll eventually find out. 2225 presShell->ScheduleApproximateFrameVisibilityUpdateSoon(); 2226 } 2227 2228 void nsIFrame::DisableVisibilityTracking() { 2229 if (!HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) { 2230 return; // Nothing to do. 2231 } 2232 2233 bool isSet = false; 2234 uint32_t visibleCount = TakeProperty(VisibilityStateProperty(), &isSet); 2235 2236 MOZ_ASSERT(isSet, 2237 "Should have a VisibilityStateProperty value " 2238 "if NS_FRAME_VISIBILITY_IS_TRACKED is set"); 2239 2240 RemoveStateBits(NS_FRAME_VISIBILITY_IS_TRACKED); 2241 2242 if (visibleCount == 0) { 2243 return; // We were nonvisible. 2244 } 2245 2246 // We were visible, so send an OnVisibilityChange() notification. 2247 OnVisibilityChange(Visibility::ApproximatelyNonVisible); 2248 } 2249 2250 void nsIFrame::DecApproximateVisibleCount( 2251 const Maybe<OnNonvisible>& aNonvisibleAction 2252 /* = Nothing() */) { 2253 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)); 2254 2255 bool isSet = false; 2256 uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet); 2257 2258 MOZ_ASSERT(isSet, 2259 "Should have a VisibilityStateProperty value " 2260 "if NS_FRAME_VISIBILITY_IS_TRACKED is set"); 2261 MOZ_ASSERT(visibleCount > 0, 2262 "Frame is already nonvisible and we're " 2263 "decrementing its visible count?"); 2264 2265 visibleCount--; 2266 SetProperty(VisibilityStateProperty(), visibleCount); 2267 if (visibleCount > 0) { 2268 return; 2269 } 2270 2271 // We just became nonvisible, so send an OnVisibilityChange() notification. 2272 OnVisibilityChange(Visibility::ApproximatelyNonVisible, aNonvisibleAction); 2273 } 2274 2275 void nsIFrame::IncApproximateVisibleCount() { 2276 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)); 2277 2278 bool isSet = false; 2279 uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet); 2280 2281 MOZ_ASSERT(isSet, 2282 "Should have a VisibilityStateProperty value " 2283 "if NS_FRAME_VISIBILITY_IS_TRACKED is set"); 2284 2285 visibleCount++; 2286 SetProperty(VisibilityStateProperty(), visibleCount); 2287 if (visibleCount > 1) { 2288 return; 2289 } 2290 2291 // We just became visible, so send an OnVisibilityChange() notification. 2292 OnVisibilityChange(Visibility::ApproximatelyVisible); 2293 } 2294 2295 void nsIFrame::OnVisibilityChange(Visibility aNewVisibility, 2296 const Maybe<OnNonvisible>& aNonvisibleAction 2297 /* = Nothing() */) { 2298 // XXX(seth): In bug 1218990 we'll implement visibility tracking for CSS 2299 // images here. 2300 } 2301 2302 static nsIFrame* GetActiveSelectionFrame(nsPresContext* aPresContext, 2303 nsIFrame* aFrame) { 2304 nsIContent* capturingContent = PresShell::GetCapturingContent(); 2305 if (capturingContent) { 2306 nsIFrame* activeFrame = aPresContext->GetPrimaryFrameFor(capturingContent); 2307 return activeFrame ? activeFrame : aFrame; 2308 } 2309 2310 return aFrame; 2311 } 2312 2313 bool nsIFrame::ShouldHandleSelectionMovementEvents() { 2314 if (GetDisplaySelection() == nsISelectionController::SELECTION_OFF) { 2315 return false; 2316 } 2317 if (!IsSelectable()) { 2318 // Check whether style allows selection. 2319 return false; 2320 } 2321 if (IsScrollbarFrame() || IsHTMLCanvasFrame()) { 2322 // Scrollbars and canvas don't move selection with the mouse. 2323 return false; 2324 } 2325 return true; 2326 } 2327 2328 static Element* FindElementAncestorForMozSelection(nsIContent* aContent) { 2329 NS_ENSURE_TRUE(aContent, nullptr); 2330 while (aContent && aContent->IsInNativeAnonymousSubtree()) { 2331 aContent = aContent->GetClosestNativeAnonymousSubtreeRootParentOrHost(); 2332 } 2333 NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?"); 2334 return aContent ? aContent->GetAsElementOrParentElement() : nullptr; 2335 } 2336 2337 already_AddRefed<ComputedStyle> nsIFrame::ComputeSelectionStyle( 2338 int16_t aSelectionStatus) const { 2339 // Just bail out if not a selection-status that ::selection applies to. 2340 if (aSelectionStatus != nsISelectionController::SELECTION_ON && 2341 aSelectionStatus != nsISelectionController::SELECTION_DISABLED) { 2342 return nullptr; 2343 } 2344 Element* element = FindElementAncestorForMozSelection(GetContent()); 2345 if (!element) { 2346 return nullptr; 2347 } 2348 RefPtr<ComputedStyle> pseudoStyle = 2349 PresContext()->StyleSet()->ProbePseudoElementStyle( 2350 *element, PseudoStyleType::selection, nullptr, Style()); 2351 if (!pseudoStyle) { 2352 return nullptr; 2353 } 2354 // When in high-contrast mode, the style system ends up ignoring the color 2355 // declarations, which means that the ::selection style becomes the inherited 2356 // color, and default background. That's no good. 2357 // When force-color-adjust is set to none allow using the color styles, 2358 // as they will not be replaced. 2359 if (PresContext()->ForcingColors() && 2360 pseudoStyle->StyleText()->mForcedColorAdjust != 2361 StyleForcedColorAdjust::None) { 2362 return nullptr; 2363 } 2364 return do_AddRef(pseudoStyle); 2365 } 2366 2367 already_AddRefed<ComputedStyle> nsIFrame::ComputeHighlightSelectionStyle( 2368 nsAtom* aHighlightName) { 2369 Element* element = FindElementAncestorForMozSelection(GetContent()); 2370 if (!element) { 2371 return nullptr; 2372 } 2373 return PresContext()->StyleSet()->ProbePseudoElementStyle( 2374 *element, PseudoStyleType::highlight, aHighlightName, Style()); 2375 } 2376 2377 already_AddRefed<ComputedStyle> nsIFrame::ComputeTargetTextStyle() const { 2378 const Element* element = FindElementAncestorForMozSelection(GetContent()); 2379 if (!element) { 2380 return nullptr; 2381 } 2382 RefPtr pseudoStyle = PresContext()->StyleSet()->ProbePseudoElementStyle( 2383 *element, PseudoStyleType::targetText, nullptr, Style()); 2384 if (!pseudoStyle) { 2385 return nullptr; 2386 } 2387 if (PresContext()->ForcingColors() && 2388 pseudoStyle->StyleText()->mForcedColorAdjust != 2389 StyleForcedColorAdjust::None) { 2390 return nullptr; 2391 } 2392 return pseudoStyle.forget(); 2393 } 2394 2395 nsTextControlFrame* nsIFrame::GetContainingTextControlFrame() const { 2396 const nsIFrame* cur = this; 2397 do { 2398 if (const nsTextControlFrame* tc = do_QueryFrame(cur)) { 2399 return const_cast<nsTextControlFrame*>(tc); 2400 } 2401 auto* content = cur->GetContent(); 2402 if (!content || !content->IsInNativeAnonymousSubtree()) { 2403 // All content inside text controls is anonymous. 2404 return nullptr; 2405 } 2406 cur = cur->GetParent(); 2407 } while (cur); 2408 return nullptr; 2409 } 2410 2411 bool nsIFrame::CanBeDynamicReflowRoot() const { 2412 const auto& display = *StyleDisplay(); 2413 if (IsLineParticipant() || display.mDisplay.IsRuby() || 2414 display.IsInnerTableStyle() || 2415 display.DisplayInside() == StyleDisplayInside::Table) { 2416 // We have a display type where 'width' and 'height' don't actually set the 2417 // width or height (i.e., the size depends on content). 2418 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_DYNAMIC_REFLOW_ROOT), 2419 "should not have dynamic reflow root bit"); 2420 return false; 2421 } 2422 2423 // In general, frames that have contain:layout+size can be reflow roots. 2424 // (One exception: table-wrapper frames don't work well as reflow roots, 2425 // because their inner-table ReflowInput init path tries to reuse & deref 2426 // the wrapper's containing block's reflow input, which may be null if we 2427 // initiate reflow from the table-wrapper itself.) 2428 // 2429 // Changes to `contain` force frame reconstructions, so we used to use 2430 // NS_FRAME_REFLOW_ROOT, this bit could be set for the whole lifetime of 2431 // this frame. But after the support of `content-visibility: auto` which 2432 // is with contain layout + size when it's not relevant to user, and only 2433 // with contain layout when it is relevant. The frame does not reconstruct 2434 // when the relevancy changes. So we use NS_FRAME_DYNAMIC_REFLOW_ROOT instead. 2435 // 2436 // We place it above the pref check on purpose, to make sure it works for 2437 // containment even with the pref disabled. 2438 if (display.IsContainLayout() && GetContainSizeAxes().IsBoth()) { 2439 return true; 2440 } 2441 2442 if (!StaticPrefs::layout_dynamic_reflow_roots_enabled()) { 2443 return false; 2444 } 2445 2446 // We can't serve as a dynamic reflow root if our used 'width' and 'height' 2447 // might be influenced by content. 2448 // 2449 // FIXME: For display:block, we should probably optimize inline-size: auto. 2450 // FIXME: Other flex and grid cases? 2451 const auto& pos = *StylePosition(); 2452 const auto anchorResolutionParams = AnchorPosResolutionParams::From(this); 2453 const auto width = pos.GetWidth(anchorResolutionParams); 2454 const auto height = pos.GetHeight(anchorResolutionParams); 2455 if (!width->IsLengthPercentage() || width->HasPercent() || 2456 !height->IsLengthPercentage() || height->HasPercent() || 2457 IsIntrinsicKeyword(*pos.GetMinWidth(anchorResolutionParams)) || 2458 IsIntrinsicKeyword(*pos.GetMaxWidth(anchorResolutionParams)) || 2459 IsIntrinsicKeyword(*pos.GetMinHeight(anchorResolutionParams)) || 2460 IsIntrinsicKeyword(*pos.GetMaxHeight(anchorResolutionParams)) || 2461 ((pos.GetMinWidth(anchorResolutionParams)->IsAuto() || 2462 pos.GetMinHeight(anchorResolutionParams)->IsAuto()) && 2463 IsFlexOrGridItem())) { 2464 return false; 2465 } 2466 2467 // If our flex-basis is 'auto', it'll defer to 'width' (or 'height') which 2468 // we've already checked. Otherwise, it preempts them, so we need to 2469 // perform the same "could-this-value-be-influenced-by-content" checks that 2470 // we performed for 'width' and 'height' above. 2471 if (IsFlexItem()) { 2472 const auto& flexBasis = pos.mFlexBasis; 2473 if (!flexBasis.IsAuto()) { 2474 if (!flexBasis.IsSize() || !flexBasis.AsSize().IsLengthPercentage() || 2475 flexBasis.AsSize().HasPercent()) { 2476 return false; 2477 } 2478 } 2479 } 2480 2481 if (!IsFixedPosContainingBlock()) { 2482 // We can't treat this frame as a reflow root, since dynamic changes 2483 // to absolutely-positioned frames inside of it require that we 2484 // reflow the placeholder before we reflow the absolutely positioned 2485 // frame. 2486 // FIXME: Alternatively, we could sort the reflow roots in 2487 // PresShell::ProcessReflowCommands by depth in the tree, from 2488 // deepest to least deep. However, for performance (FIXME) we 2489 // should really be sorting them in the opposite order! 2490 return false; 2491 } 2492 2493 // If we participate in a container's block reflow context, or margins 2494 // can collapse through us, we can't be a dynamic reflow root. 2495 // (NS_BLOCK_BFC is block specific bit, check first as an optimization, it's 2496 // okay because we also check that it is a block frame.) 2497 if (!HasAnyStateBits(NS_BLOCK_BFC) && IsBlockFrameOrSubclass()) { 2498 return false; 2499 } 2500 2501 // Subgrids are never reflow roots, but 'contain:layout/paint' prevents 2502 // creating a subgrid in the first place. 2503 if (pos.mGridTemplateColumns.IsSubgrid() || 2504 pos.mGridTemplateRows.IsSubgrid()) { 2505 // NOTE: we could check that 'display' of our parent's primary frame is 2506 // '[inline-]grid' here but that's probably not worth it in practice. 2507 if (!display.IsContainLayout() && !display.IsContainPaint()) { 2508 return false; 2509 } 2510 } 2511 2512 // If we are split, we can't be a dynamic reflow root. Our reflow status may 2513 // change after reflow, and our parent is responsible to create or delete our 2514 // next-in-flow. 2515 if (GetPrevContinuation() || GetNextContinuation()) { 2516 return false; 2517 } 2518 2519 return true; 2520 } 2521 2522 /******************************************************** 2523 * Refreshes each content's frame 2524 *********************************************************/ 2525 2526 void nsIFrame::DisplayOutlineUnconditional(nsDisplayListBuilder* aBuilder, 2527 const nsDisplayListSet& aLists) { 2528 // Per https://drafts.csswg.org/css-tables-3/#global-style-overrides: 2529 // "All css properties of table-column and table-column-group boxes are 2530 // ignored, except when explicitly specified by this specification." 2531 // CSS outlines fall into this category, so we skip them on these boxes. 2532 MOZ_ASSERT(!IsTableColGroupFrame() && !IsTableColFrame()); 2533 const auto& outline = *StyleOutline(); 2534 2535 if (!outline.ShouldPaintOutline()) { 2536 return; 2537 } 2538 2539 // Outlines are painted by the table wrapper frame. 2540 if (IsTableFrame()) { 2541 return; 2542 } 2543 2544 if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT) && 2545 ScrollableOverflowRect().IsEmpty()) { 2546 // Skip parts of IB-splits with an empty overflow rect, see bug 434301. 2547 // We may still want to fix some of the overflow area calculations over in 2548 // that bug. 2549 return; 2550 } 2551 2552 // We don't display outline-style: auto on themed frames that have their own 2553 // focus indicators. 2554 if (outline.mOutlineStyle.IsAuto()) { 2555 auto* disp = StyleDisplay(); 2556 if (IsThemed(disp) && PresContext()->Theme()->ThemeDrawsFocusForWidget( 2557 this, disp->EffectiveAppearance())) { 2558 return; 2559 } 2560 } 2561 2562 aLists.Outlines()->AppendNewToTop<nsDisplayOutline>(aBuilder, this); 2563 } 2564 2565 void nsIFrame::DisplayOutline(nsDisplayListBuilder* aBuilder, 2566 const nsDisplayListSet& aLists) { 2567 if (!IsVisibleForPainting()) { 2568 return; 2569 } 2570 2571 DisplayOutlineUnconditional(aBuilder, aLists); 2572 } 2573 2574 void nsIFrame::DisplayInsetBoxShadowUnconditional( 2575 nsDisplayListBuilder* aBuilder, nsDisplayList* aList) { 2576 // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted 2577 // just because we're visible? Or should it depend on the cell visibility 2578 // when we're not the whole table? 2579 const auto* effects = StyleEffects(); 2580 if (effects->HasBoxShadowWithInset(true)) { 2581 aList->AppendNewToTop<nsDisplayBoxShadowInner>(aBuilder, this); 2582 } 2583 } 2584 2585 void nsIFrame::DisplayInsetBoxShadow(nsDisplayListBuilder* aBuilder, 2586 nsDisplayList* aList) { 2587 if (!IsVisibleForPainting()) { 2588 return; 2589 } 2590 2591 DisplayInsetBoxShadowUnconditional(aBuilder, aList); 2592 } 2593 2594 void nsIFrame::DisplayOutsetBoxShadowUnconditional( 2595 nsDisplayListBuilder* aBuilder, nsDisplayList* aList) { 2596 // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted 2597 // just because we're visible? Or should it depend on the cell visibility 2598 // when we're not the whole table? 2599 const auto* effects = StyleEffects(); 2600 if (effects->HasBoxShadowWithInset(false)) { 2601 aList->AppendNewToTop<nsDisplayBoxShadowOuter>(aBuilder, this); 2602 } 2603 } 2604 2605 void nsIFrame::DisplayOutsetBoxShadow(nsDisplayListBuilder* aBuilder, 2606 nsDisplayList* aList) { 2607 if (!IsVisibleForPainting()) { 2608 return; 2609 } 2610 2611 DisplayOutsetBoxShadowUnconditional(aBuilder, aList); 2612 } 2613 2614 void nsIFrame::DisplayCaret(nsDisplayListBuilder* aBuilder, 2615 nsDisplayList* aList) { 2616 if (!IsVisibleForPainting()) { 2617 return; 2618 } 2619 2620 aList->AppendNewToTop<nsDisplayCaret>(aBuilder, this); 2621 } 2622 2623 nscolor nsIFrame::GetCaretColorAt(int32_t aOffset) { 2624 return nsLayoutUtils::GetTextColor(this, &nsStyleUI::mCaretColor); 2625 } 2626 2627 auto nsIFrame::ComputeShouldPaintBackground() const -> ShouldPaintBackground { 2628 nsPresContext* pc = PresContext(); 2629 ShouldPaintBackground settings{pc->GetBackgroundColorDraw(), 2630 pc->GetBackgroundImageDraw()}; 2631 if (settings.mColor && settings.mImage) { 2632 return settings; 2633 } 2634 2635 if (StyleVisibility()->mPrintColorAdjust == StylePrintColorAdjust::Exact) { 2636 return {true, true}; 2637 } 2638 2639 return settings; 2640 } 2641 2642 bool nsIFrame::DisplayBackgroundUnconditional(nsDisplayListBuilder* aBuilder, 2643 const nsDisplayListSet& aLists) { 2644 if (aBuilder->IsForEventDelivery() && !aBuilder->HitTestIsForVisibility()) { 2645 // For hit-testing, we generally just need a light-weight data structure 2646 // like nsDisplayEventReceiver. But if the hit-testing is for visibility, 2647 // then we need to know the opaque region in order to determine whether to 2648 // stop or not. 2649 aLists.BorderBackground()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, 2650 this); 2651 return false; 2652 } 2653 2654 const AppendedBackgroundType result = 2655 nsDisplayBackgroundImage::AppendBackgroundItemsToTop( 2656 aBuilder, this, 2657 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this), 2658 aLists.BorderBackground()); 2659 2660 if (result == AppendedBackgroundType::None) { 2661 aBuilder->BuildCompositorHitTestInfoIfNeeded(this, 2662 aLists.BorderBackground()); 2663 } 2664 2665 return result == AppendedBackgroundType::ThemedBackground; 2666 } 2667 2668 void nsIFrame::DisplayBorderBackgroundOutline(nsDisplayListBuilder* aBuilder, 2669 const nsDisplayListSet& aLists) { 2670 // The visibility check belongs here since child elements have the 2671 // opportunity to override the visibility property and display even if 2672 // their parent is hidden. 2673 if (!IsVisibleForPainting()) { 2674 return; 2675 } 2676 2677 DisplayOutsetBoxShadowUnconditional(aBuilder, aLists.BorderBackground()); 2678 2679 bool bgIsThemed = DisplayBackgroundUnconditional(aBuilder, aLists); 2680 DisplayInsetBoxShadowUnconditional(aBuilder, aLists.BorderBackground()); 2681 2682 // If there's a themed background, we should not create a border item. 2683 // It won't be rendered. 2684 // Don't paint borders for tables here, since they paint them in a different 2685 // order. 2686 if (!bgIsThemed && StyleBorder()->HasBorder() && !IsTableFrame()) { 2687 aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder, this); 2688 } 2689 2690 DisplayOutlineUnconditional(aBuilder, aLists); 2691 } 2692 2693 inline static bool IsSVGContentWithCSSClip(const nsIFrame* aFrame) { 2694 // The CSS spec says that the 'clip' property only applies to absolutely 2695 // positioned elements, whereas the SVG spec says that it applies to SVG 2696 // elements regardless of the value of the 'position' property. Here we obey 2697 // the CSS spec for outer-<svg> (since that's what we generally do), but 2698 // obey the SVG spec for other SVG elements to which 'clip' applies. 2699 return aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) && 2700 aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::svg, 2701 nsGkAtoms::foreignObject); 2702 } 2703 2704 Maybe<nsRect> nsIFrame::GetClipPropClipRect(const nsStyleDisplay* aDisp, 2705 const nsStyleEffects* aEffects, 2706 const nsSize& aSize) const { 2707 if (aEffects->mClip.IsAuto() || 2708 !(aDisp->IsAbsolutelyPositioned(this) || IsSVGContentWithCSSClip(this))) { 2709 return Nothing(); 2710 } 2711 2712 auto& clipRect = aEffects->mClip.AsRect(); 2713 nsRect rect = clipRect.ToLayoutRect(); 2714 if (MOZ_LIKELY(StyleBorder()->mBoxDecorationBreak == 2715 StyleBoxDecorationBreak::Slice)) { 2716 // The clip applies to the joined boxes so it's relative the first 2717 // continuation. 2718 nscoord y = 0; 2719 for (nsIFrame* f = GetPrevContinuation(); f; f = f->GetPrevContinuation()) { 2720 y += f->GetRect().height; 2721 } 2722 rect.MoveBy(nsPoint(0, -y)); 2723 } 2724 2725 if (clipRect.right.IsAuto()) { 2726 rect.width = aSize.width - rect.x; 2727 } 2728 if (clipRect.bottom.IsAuto()) { 2729 rect.height = aSize.height - rect.y; 2730 } 2731 return Some(rect); 2732 } 2733 2734 // https://drafts.csswg.org/css-view-transitions-1/#named-and-transitioning 2735 // 2736 // Note https://github.com/w3c/csswg-drafts/issues/11772, however, for the root 2737 // style check. 2738 bool nsIFrame::ForcesStackingContextForViewTransition() const { 2739 auto* style = Style(); 2740 return !style->IsRootElementStyle() && 2741 (style->StyleUIReset()->HasViewTransitionName() || 2742 HasAnyStateBits(NS_FRAME_CAPTURED_IN_VIEW_TRANSITION) || 2743 style->StyleDisplay()->mWillChange.bits & 2744 mozilla::StyleWillChangeBits::VIEW_TRANSITION_NAME); 2745 } 2746 2747 /** 2748 * If the CSS 'overflow' property applies to this frame, and is not 2749 * handled by constructing a dedicated nsHTML/XULScrollFrame, set up clipping 2750 * for that overflow in aBuilder->ClipState() to clip all containing-block 2751 * descendants. 2752 */ 2753 static void ApplyOverflowClipping( 2754 nsDisplayListBuilder* aBuilder, const nsIFrame* aFrame, 2755 PhysicalAxes aClipAxes, 2756 DisplayListClipState::AutoClipMultiple& aClipState) { 2757 nsRect clipRect; 2758 nsRectCornerRadii radii; 2759 bool haveRadii = 2760 aFrame->ComputeOverflowClipRectRelativeToSelf(aClipAxes, clipRect, radii); 2761 aClipState.ClipContainingBlockDescendantsExtra( 2762 clipRect + aBuilder->ToReferenceFrame(aFrame), 2763 haveRadii ? &radii : nullptr); 2764 } 2765 2766 static Sides ToSkipSides(PhysicalAxes aClipAxes) { 2767 SideBits result{}; 2768 if (!aClipAxes.contains(PhysicalAxis::Vertical)) { 2769 result |= SideBits::eTop; 2770 result |= SideBits::eBottom; 2771 } 2772 if (!aClipAxes.contains(PhysicalAxis::Horizontal)) { 2773 result |= SideBits::eLeft; 2774 result |= SideBits::eRight; 2775 } 2776 return Sides(result); 2777 } 2778 2779 bool nsIFrame::ComputeOverflowClipRectRelativeToSelf( 2780 const PhysicalAxes aClipAxes, nsRect& aOutRect, 2781 nsRectCornerRadii& aOutRadii) const { 2782 // Only 'clip' is handled here (and 'hidden' for table frames, and any 2783 // non-'visible' value for blocks in a paginated context). 2784 // We allow 'clip' to apply to any kind of frame. This is required by 2785 // comboboxes which make their display text (an inline frame) have clipping. 2786 MOZ_ASSERT(!aClipAxes.isEmpty()); 2787 MOZ_ASSERT(ShouldApplyOverflowClipping(StyleDisplay()) == aClipAxes); 2788 auto boxMargin = OverflowClipMargin(aClipAxes, /* aAllowNegative = */ true); 2789 boxMargin.ApplySkipSides(GetSkipSides() | ToSkipSides(aClipAxes)); 2790 2791 aOutRect = nsRect(nsPoint(), GetSize()); 2792 aOutRect.Inflate(boxMargin); 2793 if (MOZ_UNLIKELY(!aClipAxes.contains(PhysicalAxis::Horizontal))) { 2794 // NOTE(mats) We shouldn't be clipping at all in this dimension really, 2795 // but clipping in just one axis isn't supported by our GFX APIs so we 2796 // clip to our visual overflow rect instead. 2797 nsRect o = InkOverflowRectRelativeToSelf(); 2798 aOutRect.x = o.x; 2799 aOutRect.width = o.width; 2800 } 2801 if (MOZ_UNLIKELY(!aClipAxes.contains(PhysicalAxis::Vertical))) { 2802 // See the note above. 2803 nsRect o = InkOverflowRectRelativeToSelf(); 2804 aOutRect.y = o.y; 2805 aOutRect.height = o.height; 2806 } 2807 if (!GetBorderRadii(aOutRadii)) { 2808 return false; 2809 } 2810 aOutRadii.AdjustOutwards(boxMargin); 2811 return true; 2812 } 2813 2814 nsMargin nsIFrame::OverflowClipMargin(PhysicalAxes aClipAxes, 2815 bool aAllowNegative) const { 2816 nsMargin result; 2817 if (aClipAxes.isEmpty()) { 2818 return result; 2819 } 2820 const auto& margin = StyleMargin()->mOverflowClipMargin; 2821 if (!aAllowNegative && margin.offset.IsZero()) { 2822 return result; 2823 } 2824 switch (margin.visual_box) { 2825 case StyleOverflowClipMarginBox::BorderBox: 2826 break; 2827 case StyleOverflowClipMarginBox::PaddingBox: 2828 result = -GetUsedBorder(); 2829 break; 2830 case StyleOverflowClipMarginBox::ContentBox: 2831 result = -GetUsedBorderAndPadding(); 2832 break; 2833 } 2834 if (!margin.offset.IsZero()) { 2835 nscoord marginAu = margin.offset.ToAppUnits(); 2836 result += nsMargin(marginAu, marginAu, marginAu, marginAu); 2837 } 2838 if (!aAllowNegative) { 2839 result.EnsureAtLeast(nsMargin()); 2840 } 2841 return result; 2842 } 2843 2844 /** 2845 * Returns whether a display item that gets created with the builder's current 2846 * state will have a scrolled clip, i.e. a clip that is scrolled by a scroll 2847 * frame which does not move the item itself. 2848 */ 2849 static bool BuilderHasScrolledClip(nsDisplayListBuilder* aBuilder) { 2850 const DisplayItemClipChain* currentClip = 2851 aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder); 2852 if (!currentClip) { 2853 return false; 2854 } 2855 2856 const ActiveScrolledRoot* currentClipASR = currentClip->mASR; 2857 const ActiveScrolledRoot* currentASR = aBuilder->CurrentActiveScrolledRoot(); 2858 return ActiveScrolledRoot::PickDescendant(currentClipASR, currentASR) != 2859 currentASR; 2860 } 2861 2862 class AutoTrackStackingContextBits { 2863 nsDisplayListBuilder& mBuilder; 2864 StackingContextBits mBitsToSet; 2865 2866 public: 2867 explicit AutoTrackStackingContextBits(nsDisplayListBuilder& aBuilder) 2868 : mBuilder(aBuilder), mBitsToSet(aBuilder.GetStackingContextBits()) {} 2869 2870 ~AutoTrackStackingContextBits() { 2871 mBuilder.SetStackingContextBits(mBitsToSet); 2872 } 2873 2874 void AddToParent(StackingContextBits aBits) { mBitsToSet |= aBits; } 2875 }; 2876 2877 static bool IsFrameOrAncestorApzAware(nsIFrame* aFrame) { 2878 nsIContent* node = aFrame->GetContent(); 2879 if (!node) { 2880 return false; 2881 } 2882 2883 do { 2884 if (node->IsNodeApzAware()) { 2885 return true; 2886 } 2887 nsIContent* shadowRoot = node->GetShadowRoot(); 2888 if (shadowRoot && shadowRoot->IsNodeApzAware()) { 2889 return true; 2890 } 2891 2892 // Even if the node owning aFrame doesn't have apz-aware event listeners 2893 // itself, its shadow root or display: contents ancestors (which have no 2894 // frames) might, so we need to account for them too. 2895 } while ((node = node->GetFlattenedTreeParent()) && node->IsElement() && 2896 node->AsElement()->IsDisplayContents()); 2897 2898 return false; 2899 } 2900 2901 static void CheckForApzAwareEventHandlers(nsDisplayListBuilder* aBuilder, 2902 nsIFrame* aFrame) { 2903 if (aBuilder->GetAncestorHasApzAwareEventHandler()) { 2904 return; 2905 } 2906 2907 if (IsFrameOrAncestorApzAware(aFrame)) { 2908 aBuilder->SetAncestorHasApzAwareEventHandler(true); 2909 } 2910 } 2911 2912 static void UpdateCurrentHitTestInfo(nsDisplayListBuilder* aBuilder, 2913 nsIFrame* aFrame) { 2914 if (!aBuilder->BuildCompositorHitTestInfo()) { 2915 // Compositor hit test info is not used. 2916 return; 2917 } 2918 2919 CheckForApzAwareEventHandlers(aBuilder, aFrame); 2920 2921 const CompositorHitTestInfo info = 2922 aFrame->GetCompositorHitTestInfoWithoutPointerEvents(aBuilder); 2923 aBuilder->SetInheritedCompositorHitTestInfo(info); 2924 } 2925 2926 /** 2927 * True if aDescendant participates the context aAncestor participating. 2928 */ 2929 static bool FrameParticipatesIn3DContext(nsIFrame* aAncestor, 2930 nsIFrame* aDescendant) { 2931 MOZ_ASSERT(aAncestor != aDescendant); 2932 MOZ_ASSERT(aAncestor->GetContent() != aDescendant->GetContent()); 2933 MOZ_ASSERT(aAncestor->Extend3DContext()); 2934 2935 nsIFrame* ancestor = aAncestor->FirstContinuation(); 2936 MOZ_ASSERT(ancestor->IsPrimaryFrame()); 2937 2938 nsIFrame* frame; 2939 for (frame = aDescendant->GetClosestFlattenedTreeAncestorPrimaryFrame(); 2940 frame && ancestor != frame; 2941 frame = frame->GetClosestFlattenedTreeAncestorPrimaryFrame()) { 2942 if (!frame->Extend3DContext()) { 2943 return false; 2944 } 2945 } 2946 2947 MOZ_ASSERT(frame == ancestor); 2948 return true; 2949 } 2950 2951 static bool ItemParticipatesIn3DContext(nsIFrame* aAncestor, 2952 nsDisplayItem* aItem) { 2953 auto type = aItem->GetType(); 2954 const bool isContainer = type == DisplayItemType::TYPE_WRAP_LIST || 2955 type == DisplayItemType::TYPE_CONTAINER; 2956 2957 if (isContainer && aItem->GetChildren()->Length() == 1) { 2958 // If the wraplist has only one child item, use the type of that item. 2959 type = aItem->GetChildren()->GetBottom()->GetType(); 2960 } 2961 2962 if (type != DisplayItemType::TYPE_TRANSFORM && 2963 type != DisplayItemType::TYPE_PERSPECTIVE) { 2964 return false; 2965 } 2966 nsIFrame* transformFrame = aItem->Frame(); 2967 if (aAncestor->GetContent() == transformFrame->GetContent()) { 2968 return true; 2969 } 2970 return FrameParticipatesIn3DContext(aAncestor, transformFrame); 2971 } 2972 2973 static void WrapSeparatorTransform(nsDisplayListBuilder* aBuilder, 2974 nsIFrame* aFrame, 2975 nsDisplayList* aNonParticipants, 2976 nsDisplayList* aParticipants, int aIndex, 2977 nsDisplayItem** aSeparator) { 2978 if (aNonParticipants->IsEmpty()) { 2979 return; 2980 } 2981 2982 nsDisplayTransform* item = MakeDisplayItemWithIndex<nsDisplayTransform>( 2983 aBuilder, aFrame, aIndex, aNonParticipants, aBuilder->GetVisibleRect()); 2984 2985 if (*aSeparator == nullptr && item) { 2986 *aSeparator = item; 2987 } 2988 2989 aParticipants->AppendToTop(item); 2990 } 2991 2992 // Try to compute a clip rect to bound the contents of the mask item 2993 // that will be built for |aMaskedFrame|. If we're not able to compute 2994 // one, return an empty Maybe. 2995 // The returned clip rect, if there is one, is relative to |aMaskedFrame|. 2996 static Maybe<nsRect> ComputeClipForMaskItem( 2997 nsDisplayListBuilder* aBuilder, nsIFrame* aMaskedFrame, 2998 const SVGUtils::MaskUsage& aMaskUsage) { 2999 const nsStyleSVGReset* svgReset = aMaskedFrame->StyleSVGReset(); 3000 3001 nsPoint offsetToUserSpace = 3002 nsLayoutUtils::ComputeOffsetToUserSpace(aBuilder, aMaskedFrame); 3003 int32_t devPixelRatio = aMaskedFrame->PresContext()->AppUnitsPerDevPixel(); 3004 gfxPoint devPixelOffsetToUserSpace = 3005 nsLayoutUtils::PointToGfxPoint(offsetToUserSpace, devPixelRatio); 3006 CSSToLayoutDeviceScale cssToDevScale = 3007 aMaskedFrame->PresContext()->CSSToDevPixelScale(); 3008 3009 nsPoint toReferenceFrame; 3010 aBuilder->FindReferenceFrameFor(aMaskedFrame, &toReferenceFrame); 3011 3012 Maybe<gfxRect> combinedClip; 3013 if (aMaskUsage.ShouldApplyBasicShapeOrPath()) { 3014 Maybe<Rect> result = 3015 CSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip( 3016 aMaskedFrame, svgReset->mClipPath); 3017 if (result) { 3018 combinedClip = Some(ThebesRect(*result)); 3019 } 3020 } else if (aMaskUsage.ShouldApplyClipPath()) { 3021 gfxRect result = SVGUtils::GetBBox( 3022 aMaskedFrame, 3023 SVGUtils::eBBoxIncludeClipped | SVGUtils::eBBoxIncludeFill | 3024 SVGUtils::eBBoxIncludeMarkers | SVGUtils::eBBoxIncludeStroke | 3025 SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath); 3026 combinedClip = Some( 3027 ThebesRect((CSSRect::FromUnknownRect(ToRect(result)) * cssToDevScale) 3028 .ToUnknownRect())); 3029 } else { 3030 // The code for this case is adapted from ComputeMaskGeometry(). 3031 3032 nsRect borderArea(toReferenceFrame, aMaskedFrame->GetSize()); 3033 borderArea -= offsetToUserSpace; 3034 3035 // Use an infinite dirty rect to pass into nsCSSRendering:: 3036 // GetImageLayerClip() because we don't have an actual dirty rect to 3037 // pass in. This is fine because the only time GetImageLayerClip() will 3038 // not intersect the incoming dirty rect with something is in the "NoClip" 3039 // case, and we handle that specially. 3040 nsRect dirtyRect(nscoord_MIN / 2, nscoord_MIN / 2, nscoord_MAX, 3041 nscoord_MAX); 3042 3043 nsIFrame* firstFrame = 3044 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aMaskedFrame); 3045 nsTArray<SVGMaskFrame*> maskFrames; 3046 // XXX check return value? 3047 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames); 3048 3049 for (uint32_t i = 0; i < maskFrames.Length(); ++i) { 3050 gfxRect clipArea; 3051 if (maskFrames[i]) { 3052 clipArea = maskFrames[i]->GetMaskArea(aMaskedFrame); 3053 clipArea = ThebesRect( 3054 (CSSRect::FromUnknownRect(ToRect(clipArea)) * cssToDevScale) 3055 .ToUnknownRect()); 3056 } else { 3057 const auto& layer = svgReset->mMask.mLayers[i]; 3058 if (layer.mClip == StyleGeometryBox::NoClip) { 3059 return Nothing(); 3060 } 3061 3062 nsCSSRendering::ImageLayerClipState clipState; 3063 nsCSSRendering::GetImageLayerClip( 3064 layer, aMaskedFrame, *aMaskedFrame->StyleBorder(), borderArea, 3065 dirtyRect, false /* aWillPaintBorder */, devPixelRatio, &clipState); 3066 clipArea = clipState.mDirtyRectInDevPx; 3067 } 3068 combinedClip = UnionMaybeRects(combinedClip, Some(clipArea)); 3069 } 3070 } 3071 if (combinedClip) { 3072 // Convert to user space. 3073 *combinedClip += devPixelOffsetToUserSpace; 3074 3075 // Round the clip out. In FrameLayerBuilder we round clips to nearest 3076 // pixels, and if we have a really thin clip here, that can cause the 3077 // clip to become empty if we didn't round out here. 3078 // The rounding happens in coordinates that are relative to the reference 3079 // frame, which matches what FrameLayerBuilder does. 3080 combinedClip->RoundOut(); 3081 3082 // Convert to app units. 3083 nsRect result = 3084 nsLayoutUtils::RoundGfxRectToAppRect(*combinedClip, devPixelRatio); 3085 3086 // The resulting clip is relative to the reference frame, but the caller 3087 // expects it to be relative to the masked frame, so adjust it. 3088 result -= toReferenceFrame; 3089 return Some(result); 3090 } 3091 return Nothing(); 3092 } 3093 3094 struct AutoCheckBuilder { 3095 explicit AutoCheckBuilder(nsDisplayListBuilder* aBuilder) 3096 : mBuilder(aBuilder) { 3097 aBuilder->Check(); 3098 } 3099 3100 ~AutoCheckBuilder() { mBuilder->Check(); } 3101 3102 nsDisplayListBuilder* mBuilder; 3103 }; 3104 3105 /** 3106 * Tries to reuse a top-level stacking context item from the previous paint. 3107 * Returns true if an item was reused, otherwise false. 3108 */ 3109 bool TryToReuseStackingContextItem(nsDisplayListBuilder* aBuilder, 3110 nsDisplayList* aList, nsIFrame* aFrame) { 3111 if (!aBuilder->IsForPainting() || !aBuilder->IsPartialUpdate() || 3112 aBuilder->InInvalidSubtree()) { 3113 return false; 3114 } 3115 3116 if (aFrame->IsFrameModified() || aFrame->HasModifiedDescendants()) { 3117 return false; 3118 } 3119 3120 auto& items = aFrame->DisplayItems(); 3121 auto* res = std::find_if( 3122 items.begin(), items.end(), 3123 [](nsDisplayItem* aItem) { return aItem->IsPreProcessed(); }); 3124 3125 if (res == items.end()) { 3126 return false; 3127 } 3128 3129 nsDisplayItem* container = *res; 3130 MOZ_ASSERT(container->Frame() == aFrame); 3131 DL_LOGD("RDL - Found SC item %p (%s) (frame: %p)", container, 3132 container->Name(), container->Frame()); 3133 3134 aList->AppendToTop(container); 3135 aBuilder->ReuseDisplayItem(container); 3136 return true; 3137 } 3138 3139 void nsIFrame::BuildDisplayListForStackingContext( 3140 nsDisplayListBuilder* aBuilder, nsDisplayList* aList, 3141 bool* aCreatedContainerItem) { 3142 #ifdef DEBUG 3143 DL_LOGV("BuildDisplayListForStackingContext (%p) <", this); 3144 ScopeExit e( 3145 [this]() { DL_LOGV("> BuildDisplayListForStackingContext (%p)", this); }); 3146 #endif 3147 3148 AutoCheckBuilder check(aBuilder); 3149 3150 if (aBuilder->IsReusingStackingContextItems() && 3151 TryToReuseStackingContextItem(aBuilder, aList, this)) { 3152 if (aCreatedContainerItem) { 3153 *aCreatedContainerItem = true; 3154 } 3155 return; 3156 } 3157 3158 if (HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)) { 3159 return; 3160 } 3161 3162 const auto& style = *Style(); 3163 const nsStyleDisplay* disp = style.StyleDisplay(); 3164 const nsStyleEffects* effects = style.StyleEffects(); 3165 EffectSet* effectSetForOpacity = 3166 EffectSet::GetForFrame(this, nsCSSPropertyIDSet::OpacityProperties()); 3167 // We can stop right away if this is a zero-opacity stacking context and 3168 // we're painting, and we're not animating opacity. 3169 bool needHitTestInfo = aBuilder->BuildCompositorHitTestInfo() && 3170 Style()->PointerEvents() != StylePointerEvents::None; 3171 bool opacityItemForEventsOnly = false; 3172 if (effects->IsTransparent() && aBuilder->IsForPainting() && 3173 !(disp->mWillChange.bits & StyleWillChangeBits::OPACITY) && 3174 !nsLayoutUtils::HasAnimationOfPropertySet( 3175 this, nsCSSPropertyIDSet::OpacityProperties(), effectSetForOpacity)) { 3176 if (needHitTestInfo) { 3177 opacityItemForEventsOnly = true; 3178 } else { 3179 return; 3180 } 3181 } 3182 3183 // Root gets handled in 3184 // ScrollContainerFrame::MaybeCreateTopLayerAndWrapRootItems. 3185 const bool capturedByViewTransition = 3186 HasAnyStateBits(NS_FRAME_CAPTURED_IN_VIEW_TRANSITION) && 3187 !style.IsRootElementStyle(); 3188 3189 // Do not need to build the display list of the captured frames for event 3190 // delivery (i.e. to determine if this frame is under the mouse position). 3191 // 3192 // Per spec, hit-testing is skipped because the element’s DOM location does 3193 // not correspond to where its contents are rendered, so we could just skip 3194 // the building of the display list for these frames, and then these APIs, 3195 // e.g. elementFromPoint(), will skip these frames as well. 3196 // https://drafts.csswg.org/css-view-transitions-1/#view-transition-stacking-layer 3197 if (capturedByViewTransition && aBuilder->IsForEventDelivery()) { 3198 return; 3199 } 3200 3201 if (aBuilder->IsForPainting() && disp->mWillChange.bits) { 3202 aBuilder->AddToWillChangeBudget(this, GetSize()); 3203 } 3204 3205 // For preserves3d, use the dirty rect already installed on the 3206 // builder, since aDirtyRect maybe distorted for transforms along 3207 // the chain. 3208 nsRect visibleRect = aBuilder->GetVisibleRect(); 3209 nsRect dirtyRect = aBuilder->GetDirtyRect(); 3210 3211 // We build an opacity item if it's not going to be drawn by SVG content. 3212 // We could in principle skip creating an nsDisplayOpacity item if 3213 // nsDisplayOpacity::NeedsActiveLayer returns false and usingSVGEffects is 3214 // true (the nsDisplayFilter/nsDisplayMasksAndClipPaths could handle the 3215 // opacity). Since SVG has perf issues where we sometimes spend a lot of 3216 // time creating display list items that might be helpful. We'd need to 3217 // restore our mechanism to do that (changed in bug 1482403), and we'd 3218 // need to invalidate the frame if the value that would be return from 3219 // NeedsActiveLayer was to change, which we don't currently do. 3220 const bool useOpacity = 3221 HasVisualOpacity(disp, effects, effectSetForOpacity) && 3222 !SVGUtils::CanOptimizeOpacity(this); 3223 3224 const bool isTransformed = IsTransformed(); 3225 const bool hasPerspective = isTransformed && HasPerspective(); 3226 const bool extend3DContext = 3227 Extend3DContext(disp, effects, effectSetForOpacity); 3228 const bool combines3DTransformWithAncestors = 3229 (extend3DContext || isTransformed) && Combines3DTransformWithAncestors(); 3230 3231 UniquePtr<nsDisplayListBuilder::AutoPreserves3DContext> 3232 autoPreserves3DContext; 3233 if (extend3DContext && !combines3DTransformWithAncestors) { 3234 // Start a new preserves3d context to keep informations on 3235 // nsDisplayListBuilder. 3236 autoPreserves3DContext = 3237 MakeUnique<nsDisplayListBuilder::AutoPreserves3DContext>(aBuilder); 3238 // Save dirty rect on the builder to avoid being distorted for 3239 // multiple transforms along the chain. 3240 aBuilder->SavePreserves3DRect(); 3241 3242 // We rebuild everything within preserve-3d and don't try 3243 // to retain, so override the dirty rect now. 3244 if (aBuilder->IsRetainingDisplayList()) { 3245 dirtyRect = visibleRect; 3246 aBuilder->SetDisablePartialUpdates(true); 3247 } 3248 } 3249 3250 AutoTrackStackingContextBits stackingContextTracker(*aBuilder); 3251 aBuilder->ClearStackingContextBits(); 3252 3253 nsRect visibleRectOutsideTransform = visibleRect; 3254 nsDisplayTransform::PrerenderInfo prerenderInfo; 3255 bool inTransform = aBuilder->IsInTransform(); 3256 if (isTransformed) { 3257 prerenderInfo = nsDisplayTransform::ShouldPrerenderTransformedContent( 3258 aBuilder, this, &visibleRect); 3259 3260 switch (prerenderInfo.mDecision) { 3261 case nsDisplayTransform::PrerenderDecision::Full: 3262 case nsDisplayTransform::PrerenderDecision::Partial: 3263 dirtyRect = visibleRect; 3264 break; 3265 case nsDisplayTransform::PrerenderDecision::No: { 3266 // If we didn't prerender an animated frame in a preserve-3d context, 3267 // then we want disable async animations for the rest of the preserve-3d 3268 // (especially ancestors). 3269 if ((extend3DContext || combines3DTransformWithAncestors) && 3270 prerenderInfo.mHasAnimations) { 3271 aBuilder->SavePreserves3DAllowAsyncAnimation(false); 3272 } 3273 3274 const nsRect overflow = InkOverflowRectRelativeToSelf(); 3275 if (overflow.IsEmpty() && !extend3DContext) { 3276 return; 3277 } 3278 3279 // If we're in preserve-3d then grab the dirty rect that was given to 3280 // the root and transform using the combined transform. 3281 if (combines3DTransformWithAncestors) { 3282 visibleRect = dirtyRect = aBuilder->GetPreserves3DRect(); 3283 } 3284 3285 const float appPerDev = PresContext()->AppUnitsPerDevPixel(); 3286 uint32_t flags = nsDisplayTransform::kTransformRectFlags & 3287 ~nsDisplayTransform::OFFSET_BY_ORIGIN; 3288 if (!hasPerspective) { 3289 flags &= ~nsDisplayTransform::INCLUDE_PERSPECTIVE; 3290 } 3291 if (!combines3DTransformWithAncestors) { 3292 flags &= ~nsDisplayTransform::INCLUDE_PRESERVE3D_ANCESTORS; 3293 } 3294 auto transform = nsDisplayTransform::GetResultingTransformMatrix( 3295 this, nsPoint(), appPerDev, flags); 3296 nsRect untransformedDirtyRect; 3297 if (nsDisplayTransform::UntransformRect(dirtyRect, overflow, transform, 3298 appPerDev, 3299 &untransformedDirtyRect)) { 3300 dirtyRect = untransformedDirtyRect; 3301 nsDisplayTransform::UntransformRect(visibleRect, overflow, transform, 3302 appPerDev, &visibleRect); 3303 } else { 3304 // This should only happen if the transform is singular, in which case 3305 // nothing is visible anyway 3306 dirtyRect.SetEmpty(); 3307 visibleRect.SetEmpty(); 3308 } 3309 } 3310 } 3311 inTransform = true; 3312 } else if (IsFixedPosContainingBlock()) { 3313 // Restict the building area to the overflow rect for these frames, since 3314 // RetainedDisplayListBuilder uses it to know if the size of the stacking 3315 // context changed. 3316 visibleRect.IntersectRect(visibleRect, InkOverflowRect()); 3317 dirtyRect.IntersectRect(dirtyRect, InkOverflowRect()); 3318 } 3319 3320 bool hasOverrideDirtyRect = false; 3321 // If we're doing a partial build, we're not invalid and we're capable 3322 // of having an override building rect (stacking context and fixed pos 3323 // containing block), then we should assume we have one. 3324 // Either we have an explicit one, or nothing in our subtree changed and 3325 // we have an implicit empty rect. 3326 // 3327 // These conditions should match |CanStoreDisplayListBuildingRect()| in 3328 // RetainedDisplayListBuilder.cpp 3329 if (!aBuilder->IsReusingStackingContextItems() && 3330 aBuilder->IsPartialUpdate() && !aBuilder->InInvalidSubtree() && 3331 !IsFrameModified() && IsFixedPosContainingBlock() && 3332 !GetPrevContinuation() && !GetNextContinuation()) { 3333 dirtyRect = nsRect(); 3334 if (HasOverrideDirtyRegion()) { 3335 nsDisplayListBuilder::DisplayListBuildingData* data = 3336 GetProperty(nsDisplayListBuilder::DisplayListBuildingRect()); 3337 if (data) { 3338 dirtyRect = data->mDirtyRect.Intersect(visibleRect); 3339 hasOverrideDirtyRect = true; 3340 } 3341 } 3342 } 3343 3344 const bool usingFilter = effects->HasFilters() && !style.IsRootElementStyle(); 3345 const SVGUtils::MaskUsage maskUsage = 3346 SVGUtils::DetermineMaskUsage(this, false); 3347 const bool usingMask = maskUsage.UsingMaskOrClipPath(); 3348 const bool usingSVGEffects = usingFilter || usingMask; 3349 3350 const nsRect visibleRectOutsideSVGEffects = visibleRect; 3351 nsDisplayList hoistedScrollInfoItemsStorage(aBuilder); 3352 if (usingSVGEffects) { 3353 dirtyRect = 3354 SVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, dirtyRect); 3355 visibleRect = 3356 SVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, visibleRect); 3357 aBuilder->EnterSVGEffectsContents(this, &hoistedScrollInfoItemsStorage); 3358 } 3359 3360 const bool useStickyPosition = 3361 disp->mPosition == StylePositionProperty::Sticky; 3362 bool shouldFlattenStickyItem = true; 3363 3364 const bool useFixedPosition = 3365 disp->mPosition == StylePositionProperty::Fixed && 3366 aBuilder->IsPaintingToWindow() && !IsMenuPopupFrame() && 3367 (DisplayPortUtils::IsFixedPosFrameInDisplayPort(this) || 3368 BuilderHasScrolledClip(aBuilder)); 3369 3370 if (capturedByViewTransition) { 3371 // Captured view transition elements must have their frames built regardless 3372 // of onscreen visibility so they can be snapshotted, since the snapshot can 3373 // itself be in view. We set visibleRect and dirtyRect to ensure the frame 3374 // and its descendants are painted. 3375 visibleRect = InkOverflowRectRelativeToSelf(); 3376 dirtyRect = InkOverflowRectRelativeToSelf(); 3377 } 3378 3379 nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList( 3380 aBuilder, this, visibleRect, dirtyRect, isTransformed); 3381 3382 UpdateCurrentHitTestInfo(aBuilder, this); 3383 3384 // Depending on the effects that are applied to this frame, we can create 3385 // multiple container display items and wrap them around our contents. 3386 // This enum lists all the potential container display items, in the order 3387 // outside to inside. 3388 enum class ContainerItemType : uint8_t { 3389 None = 0, 3390 FixedPosition, 3391 OwnLayerForTransformWithRoundedClip, 3392 Perspective, 3393 Transform, 3394 Filter, 3395 ViewTransitionCapture, 3396 }; 3397 3398 // NOTE(emilio): The order of these RAII objects is quite subtle. 3399 nsDisplayListBuilder::AutoEnterViewTransitionCapture 3400 inViewTransitionCaptureSetter(aBuilder, capturedByViewTransition); 3401 RefPtr<const ActiveScrolledRoot> stickyASR = nullptr; 3402 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder); 3403 if (aBuilder->IsInViewTransitionCapture()) { 3404 // View transition contents shouldn't scroll along our ASR. They get 3405 // "pulled out" of the rendering (or when they don't, you can't scroll 3406 // anyways). 3407 asrSetter.SetCurrentActiveScrolledRoot(nullptr); 3408 } 3409 if (useStickyPosition) { 3410 StickyScrollContainer* stickyScrollContainer = 3411 StickyScrollContainer::GetOrCreateForFrame(this); 3412 if (stickyScrollContainer) { 3413 if (aBuilder->IsPaintingToWindow() && 3414 !aBuilder->IsInViewTransitionCapture() && 3415 stickyScrollContainer->ScrollContainer() 3416 ->IsMaybeAsynchronouslyScrolled()) { 3417 shouldFlattenStickyItem = false; 3418 } 3419 stickyScrollContainer->SetShouldFlatten(shouldFlattenStickyItem); 3420 } 3421 3422 if (shouldFlattenStickyItem) { 3423 stickyASR = aBuilder->CurrentActiveScrolledRoot(); 3424 } else { 3425 stickyASR = aBuilder->GetOrCreateActiveScrolledRootForSticky( 3426 aBuilder->CurrentActiveScrolledRoot(), this); 3427 asrSetter.SetCurrentActiveScrolledRoot(stickyASR); 3428 } 3429 } 3430 3431 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder); 3432 3433 auto cssClip = GetClipPropClipRect(disp, effects, GetSize()); 3434 auto ApplyClipProp = [&](DisplayListClipState::AutoSaveRestore& aClipState) { 3435 if (!cssClip) { 3436 return; 3437 } 3438 nsPoint offset = aBuilder->GetCurrentFrameOffsetToReferenceFrame(); 3439 aBuilder->IntersectDirtyRect(*cssClip); 3440 aBuilder->IntersectVisibleRect(*cssClip); 3441 aClipState.ClipContentDescendants(*cssClip + offset); 3442 }; 3443 3444 // The CSS clip property is effectively inside the transform, but outside the 3445 // filters. So if we're not transformed we can apply it just here for 3446 // simplicity, instead of on each of the places that handle clipCapturedBy. 3447 DisplayListClipState::AutoSaveRestore untransformedCssClip(aBuilder); 3448 if (!isTransformed) { 3449 ApplyClipProp(untransformedCssClip); 3450 } 3451 3452 // If there is a current clip, then depending on the container items we 3453 // create, different things can happen to it. Some container items simply 3454 // propagate the clip to their children and aren't clipped themselves. 3455 // But other container items, especially those that establish a different 3456 // geometry for their contents (e.g. transforms), capture the clip on 3457 // themselves and unset the clip for their contents. If we create more than 3458 // one of those container items, the clip will be captured on the outermost 3459 // one and the inner container items will be unclipped. 3460 ContainerItemType clipCapturedBy = ContainerItemType::None; 3461 if (capturedByViewTransition) { 3462 clipCapturedBy = isTransformed ? ContainerItemType::Transform 3463 : ContainerItemType::ViewTransitionCapture; 3464 } else if (useFixedPosition) { 3465 clipCapturedBy = ContainerItemType::FixedPosition; 3466 } else if (isTransformed) { 3467 const DisplayItemClipChain* currentClip = 3468 aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder); 3469 if ((hasPerspective || extend3DContext) && 3470 (currentClip && currentClip->HasRoundedCorners())) { 3471 // If we're creating an nsDisplayTransform item that is going to combine 3472 // its transform with its children (preserve-3d or perspective), then we 3473 // can't have an intermediate surface. Mask layers force an intermediate 3474 // surface, so if we're going to need both then create a separate 3475 // wrapping layer for the mask. 3476 clipCapturedBy = ContainerItemType::OwnLayerForTransformWithRoundedClip; 3477 } else if (hasPerspective) { 3478 clipCapturedBy = ContainerItemType::Perspective; 3479 } else { 3480 clipCapturedBy = ContainerItemType::Transform; 3481 } 3482 } else if (usingFilter) { 3483 clipCapturedBy = ContainerItemType::Filter; 3484 } 3485 3486 DisplayListClipState::AutoSaveRestore clipState(aBuilder); 3487 if (clipCapturedBy != ContainerItemType::None) { 3488 clipState.Clear(); 3489 } 3490 3491 DisplayListClipState::AutoSaveRestore transformedCssClip(aBuilder); 3492 if (isTransformed) { 3493 // FIXME(emilio, bug 1525159): In the case we have a both a transform _and_ 3494 // filters, this clips the input to the filters as well, which is not 3495 // correct (clipping by the `clip` property is supposed to happen after 3496 // applying the filter effects, per [1]. 3497 // 3498 // This is not a regression though, since we used to do that anyway before 3499 // bug 1514384, and even without the transform we get it wrong. 3500 // 3501 // [1]: https://drafts.fxtf.org/css-masking/#placement 3502 ApplyClipProp(transformedCssClip); 3503 } 3504 3505 uint32_t numActiveScrollframesEncounteredBefore = 3506 aBuilder->GetNumActiveScrollframesEncountered(); 3507 3508 nsDisplayListCollection set(aBuilder); 3509 Maybe<nsRect> clipForMask; 3510 3511 { 3512 DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder); 3513 nsDisplayListBuilder::AutoInTransformSetter inTransformSetter(aBuilder, 3514 inTransform); 3515 nsDisplayListBuilder::AutoEnterFilter filterASRSetter(aBuilder, 3516 usingFilter); 3517 nsDisplayListBuilder::AutoInEventsOnly inEventsSetter( 3518 aBuilder, opacityItemForEventsOnly); 3519 3520 DisplayListClipState::AutoSaveRestore stickyItemNestedClipState(aBuilder); 3521 if (useStickyPosition && !shouldFlattenStickyItem) { 3522 stickyItemNestedClipState.MaybeRemoveDisplayportClip(); 3523 } 3524 3525 // If we have a mask, compute a clip to bound the masked content. 3526 // This is necessary in case the content moves with an ancestor 3527 // ASR of the mask. 3528 // Don't do this if we also have a filter, because then the clip 3529 // would be applied before the filter, violating 3530 // https://www.w3.org/TR/filter-effects-1/#placement. 3531 // Filters are a containing block for fixed and absolute descendants, 3532 // so the masked content cannot move with an ancestor ASR. 3533 if (usingMask && !usingFilter) { 3534 clipForMask = ComputeClipForMaskItem(aBuilder, this, maskUsage); 3535 if (clipForMask) { 3536 aBuilder->IntersectDirtyRect(*clipForMask); 3537 aBuilder->IntersectVisibleRect(*clipForMask); 3538 nestedClipState.ClipContentDescendants( 3539 *clipForMask + aBuilder->GetCurrentFrameOffsetToReferenceFrame()); 3540 } 3541 } 3542 3543 // extend3DContext also guarantees that applyAbsPosClipping and 3544 // usingSVGEffects are false We only modify the preserve-3d rect if we are 3545 // the top of a preserve-3d heirarchy 3546 if (extend3DContext) { 3547 // Mark these first so MarkAbsoluteFramesForDisplayList knows if we are 3548 // going to be forced to descend into frames. 3549 aBuilder->MarkPreserve3DFramesForDisplayList(this); 3550 } 3551 3552 aBuilder->AdjustWindowDraggingRegion(this); 3553 3554 MarkAbsoluteFramesForDisplayList(aBuilder); 3555 aBuilder->Check(); 3556 BuildDisplayList(aBuilder, set); 3557 SetBuiltDisplayList(true); 3558 aBuilder->Check(); 3559 aBuilder->DisplayCaret(this, set.Outlines()); 3560 3561 // Blend modes are a real pain for retained display lists. We build a blend 3562 // container item if the built list contains any blend mode items within 3563 // the current stacking context. This can change without an invalidation 3564 // to the stacking context frame, or the blend mode frame (e.g. by moving 3565 // an intermediate frame). 3566 // When we gain/remove a blend container item, we need to mark this frame 3567 // as invalid and have the full display list for merging to track 3568 // the change correctly. 3569 // It seems really hard to track this in advance, as the bookkeeping 3570 // required to note which stacking contexts have blend descendants 3571 // is complex and likely to be buggy. 3572 // Instead we're doing the sad thing, detecting it afterwards, and just 3573 // repeating display list building if it changed. 3574 // We have to repeat building for the entire display list (or at least 3575 // the outer stacking context), since we need to mark this frame as invalid 3576 // to remove any existing content that isn't wrapped in the blend container, 3577 // and then we need to build content infront/behind the blend container 3578 // to get correct positioning during merging. 3579 if (aBuilder->ContainsBlendMode() && aBuilder->IsRetainingDisplayList()) { 3580 if (aBuilder->IsPartialUpdate()) { 3581 aBuilder->SetPartialBuildFailed(true); 3582 } else { 3583 aBuilder->SetDisablePartialUpdates(true); 3584 } 3585 } 3586 } 3587 3588 if (aBuilder->IsBackgroundOnly()) { 3589 set.BlockBorderBackgrounds()->DeleteAll(aBuilder); 3590 set.Floats()->DeleteAll(aBuilder); 3591 set.Content()->DeleteAll(aBuilder); 3592 set.PositionedDescendants()->DeleteAll(aBuilder); 3593 set.Outlines()->DeleteAll(aBuilder); 3594 } 3595 3596 if (hasOverrideDirtyRect && 3597 StaticPrefs::layout_display_list_show_rebuild_area()) { 3598 nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>( 3599 aBuilder, this, 3600 dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(), 3601 NS_RGBA(255, 0, 0, 64), false); 3602 if (color) { 3603 color->SetOverrideZIndex(INT32_MAX); 3604 set.PositionedDescendants()->AppendToTop(color); 3605 } 3606 } 3607 3608 nsIContent* content = GetContent(); 3609 if (!content) { 3610 content = PresContext()->Document()->GetRootElement(); 3611 } 3612 3613 nsDisplayList resultList(aBuilder); 3614 set.SerializeWithCorrectZOrder(&resultList, content); 3615 3616 // Get the ASR to use for the container items that we create here. 3617 const ActiveScrolledRoot* containerItemASR = contASRTracker.GetContainerASR(); 3618 3619 bool createdContainer = false; 3620 const StackingContextBits localIsolationReasons = [&] { 3621 auto reasons = StackingContextBits::None; 3622 if (!GetParent()) { 3623 // We don't need to isolate the root frame. 3624 return reasons; 3625 } 3626 // Elements with a view-transition name also form a backdrop-root. Same for 3627 // masks / clip-path. 3628 // See https://www.w3.org/TR/css-view-transitions-1/#named-and-transitioning 3629 // and https://github.com/w3c/csswg-drafts/issues/11772 3630 const bool hasViewTransitionName = 3631 style.StyleUIReset()->HasViewTransitionName() && 3632 !style.IsRootElementStyle(); 3633 if ((disp->mWillChange.bits & StyleWillChangeBits::BACKDROP_ROOT) || 3634 hasViewTransitionName || usingMask) { 3635 reasons |= StackingContextBits::ContainsBackdropFilter; 3636 } 3637 if (!combines3DTransformWithAncestors) { 3638 reasons |= StackingContextBits::MayContainNonIsolated3DTransform; 3639 } 3640 return reasons; 3641 }(); 3642 3643 StackingContextBits currentIsolationReasons = 3644 localIsolationReasons & aBuilder->GetStackingContextBits(); 3645 bool isolated = false; 3646 auto MarkAsIsolated = [&] { 3647 isolated = true; 3648 currentIsolationReasons = StackingContextBits::None; 3649 }; 3650 auto ShouldForceIsolation = [&] { 3651 if (localIsolationReasons == StackingContextBits::None) { 3652 return false; 3653 } 3654 bool force = currentIsolationReasons != StackingContextBits::None; 3655 MarkAsIsolated(); 3656 return force; 3657 }; 3658 3659 // If adding both a nsDisplayBlendContainer and a nsDisplayBlendMode to the 3660 // same list, the nsDisplayBlendContainer should be added first. This only 3661 // happens when the element creating this stacking context has mix-blend-mode 3662 // and also contains a child which has mix-blend-mode. 3663 // The nsDisplayBlendContainer must be added to the list first, so it does not 3664 // isolate the containing element blending as well. 3665 if (aBuilder->ContainsBlendMode()) { 3666 resultList.AppendToTop(nsDisplayBlendContainer::CreateForMixBlendMode( 3667 aBuilder, this, &resultList, containerItemASR, 3668 nsDisplayItem::ContainerASRType::AncestorOfContained)); 3669 createdContainer = true; 3670 MarkAsIsolated(); 3671 } 3672 3673 // NOTE: When changing this condition make sure to tweak ScrollContainerFrame 3674 // as well. 3675 const bool usingBackdropFilter = effects->HasBackdropFilters() && 3676 IsVisibleForPainting() && 3677 !style.IsRootElementStyle(); 3678 if (usingBackdropFilter) { 3679 stackingContextTracker.AddToParent( 3680 StackingContextBits::ContainsBackdropFilter); 3681 nsRect backdropRect = 3682 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this); 3683 resultList.AppendNewToTop<nsDisplayBackdropFilters>( 3684 aBuilder, this, &resultList, backdropRect, this); 3685 createdContainer = true; 3686 MarkAsIsolated(); 3687 } 3688 3689 // If there are any SVG effects, wrap the list up in an SVG effects item 3690 // (which also handles CSS group opacity). Note that we create an SVG effects 3691 // item even if resultList is empty, since a filter can produce graphical 3692 // output even if the element being filtered wouldn't otherwise do so. 3693 if (usingSVGEffects) { 3694 MOZ_ASSERT(usingFilter || usingMask, 3695 "Beside filter & mask/clip-path, what else effect do we have?"); 3696 3697 if (clipCapturedBy == ContainerItemType::Filter) { 3698 clipState.Restore(); 3699 } 3700 // Revert to the post-filter dirty rect. 3701 aBuilder->SetVisibleRect(visibleRectOutsideSVGEffects); 3702 3703 // Skip all filter effects while generating glyph mask. 3704 if (usingFilter && !aBuilder->IsForGenerateGlyphMask()) { 3705 /* List now emptied, so add the new list to the top. */ 3706 resultList.AppendNewToTop<nsDisplayFilters>(aBuilder, this, &resultList, 3707 this, usingBackdropFilter); 3708 createdContainer = true; 3709 } 3710 3711 if (usingMask) { 3712 // The mask should move with aBuilder->CurrentActiveScrolledRoot(), so 3713 // that's the ASR we prefer to use for the mask item. However, we can 3714 // only do this if the mask if clipped with respect to that ASR, because 3715 // an item always needs to have finite bounds with respect to its ASR. 3716 // If we weren't able to compute a clip for the mask, we fall back to 3717 // using containerItemASR, which is the lowest common ancestor clip of 3718 // the mask's contents. That's not entirely correct, but it satisfies 3719 // the base requirement of the ASR system (that items have finite bounds 3720 // wrt. their ASR). 3721 const ActiveScrolledRoot* maskASR = 3722 clipForMask.isSome() ? aBuilder->CurrentActiveScrolledRoot() 3723 : containerItemASR; 3724 /* List now emptied, so add the new list to the top. */ 3725 resultList.AppendNewToTop<nsDisplayMasksAndClipPaths>( 3726 aBuilder, this, &resultList, maskASR, 3727 clipForMask.isSome() 3728 ? nsDisplayItem::ContainerASRType::Constant 3729 : nsDisplayItem::ContainerASRType::AncestorOfContained, 3730 usingBackdropFilter, ShouldForceIsolation()); 3731 createdContainer = true; 3732 } 3733 3734 // TODO(miko): We could probably create a wraplist here and avoid creating 3735 // it later in |BuildDisplayListForChild()|. 3736 createdContainer = false; 3737 3738 // Also add the hoisted scroll info items. We need those for APZ scrolling 3739 // because nsDisplayMasksAndClipPaths items can't build active layers. 3740 aBuilder->ExitSVGEffectsContents(); 3741 resultList.AppendToTop(&hoistedScrollInfoItemsStorage); 3742 } 3743 3744 // If the list is non-empty and there is CSS group opacity without SVG 3745 // effects, wrap it up in an opacity item. 3746 if (useOpacity) { 3747 const bool needsActiveOpacityLayer = 3748 nsDisplayOpacity::NeedsActiveLayer(aBuilder, this); 3749 resultList.AppendNewToTop<nsDisplayOpacity>( 3750 aBuilder, this, &resultList, containerItemASR, 3751 nsDisplayItem::ContainerASRType::AncestorOfContained, 3752 opacityItemForEventsOnly, needsActiveOpacityLayer, usingBackdropFilter, 3753 ShouldForceIsolation()); 3754 createdContainer = true; 3755 } 3756 3757 // We build nsDisplayViewTransitionCapture here, and then use 3758 // nsDisplayTransform to wrap it, to make sure we create the correct transform 3759 // for the captured element and its descendants. This is necessary to make 3760 // sure our captured element doesn't become blurry when using scale() 3761 // transform. 3762 // 3763 // So the display list looks like this: 3764 // nsDisplayTransform // For the captured element if it is transformed 3765 // VTCapture // For the captured element 3766 // ... 3767 // Other display items // For the descendants of the captured element 3768 // ... 3769 // ... 3770 // 3771 // We intentionally use nsDisplayTransform to wrap the VTCapture (so it's 3772 // different from opacity display item) because nsDisplayTransform may push a 3773 // reference frame which creates a new coordinate system in WR. So it's just 3774 // like a separator between the VTCapture the outside, if it is transformed. 3775 if (capturedByViewTransition) { 3776 resultList.AppendNewToTop<nsDisplayViewTransitionCapture>( 3777 aBuilder, this, &resultList, nullptr, false); 3778 createdContainer = true; 3779 MarkAsIsolated(); 3780 // We don't want the capture to be clipped, so we do this _after_ building 3781 // the wrapping item. 3782 if (clipCapturedBy == ContainerItemType::ViewTransitionCapture) { 3783 clipState.Restore(); 3784 } 3785 } 3786 3787 // If we're going to apply a transformation and don't have preserve-3d set, 3788 // wrap everything in an nsDisplayTransform. If there's nothing in the list, 3789 // don't add anything. 3790 // 3791 // For the preserve-3d case we want to individually wrap every child in the 3792 // list with a separate nsDisplayTransform instead. When the child is already 3793 // an nsDisplayTransform, we can skip this step, as the computed transform 3794 // will already include our own. 3795 // 3796 // We also traverse into sublists created by nsDisplayWrapList, so that we 3797 // find all the correct children. 3798 // 3799 // We still need to create nsDisplayTransform to wrap the VT capture element 3800 // to make sure WR renders it properly if it is transformed as well. 3801 if (isTransformed) { 3802 if (extend3DContext) { 3803 // Install dummy nsDisplayTransform as a leaf containing 3804 // descendants not participating this 3D rendering context. 3805 nsDisplayList nonparticipants(aBuilder); 3806 nsDisplayList participants(aBuilder); 3807 int index = 1; 3808 3809 nsDisplayItem* separator = nullptr; 3810 3811 // TODO: This can be simplified: |participants| is just |resultList|. 3812 for (nsDisplayItem* item : resultList.TakeItems()) { 3813 if (ItemParticipatesIn3DContext(this, item) && 3814 !item->GetClip().HasClip()) { 3815 // The frame of this item participates the same 3D context. 3816 WrapSeparatorTransform(aBuilder, this, &nonparticipants, 3817 &participants, index++, &separator); 3818 3819 participants.AppendToTop(item); 3820 } else { 3821 // The frame of the item doesn't participate the current 3822 // context, or has no transform. 3823 // 3824 // For items participating but not transformed, they are add 3825 // to nonparticipants to get a separator layer for handling 3826 // clips, if there is, on an intermediate surface. 3827 // \see ContainerLayer::DefaultComputeEffectiveTransforms(). 3828 nonparticipants.AppendToTop(item); 3829 } 3830 } 3831 WrapSeparatorTransform(aBuilder, this, &nonparticipants, &participants, 3832 index++, &separator); 3833 3834 if (separator) { 3835 createdContainer = true; 3836 MarkAsIsolated(); 3837 } 3838 3839 resultList.AppendToTop(&participants); 3840 } 3841 3842 transformedCssClip.Restore(); 3843 if (clipCapturedBy == ContainerItemType::Transform) { 3844 // Restore clip state now so nsDisplayTransform is clipped properly. 3845 clipState.Restore(); 3846 } 3847 // Revert to the dirtyrect coming in from the parent, without our transform 3848 // taken into account. 3849 aBuilder->SetVisibleRect(visibleRectOutsideTransform); 3850 3851 if (this != aBuilder->RootReferenceFrame()) { 3852 // Revert to the outer reference frame and offset because all display 3853 // items we create from now on are outside the transform. 3854 nsPoint toOuterReferenceFrame; 3855 const nsIFrame* outerReferenceFrame = 3856 aBuilder->FindReferenceFrameFor(GetParent(), &toOuterReferenceFrame); 3857 toOuterReferenceFrame += GetPosition(); 3858 3859 buildingDisplayList.SetReferenceFrameAndCurrentOffset( 3860 outerReferenceFrame, toOuterReferenceFrame); 3861 } 3862 3863 // We would like to block async animations for ancestors of ones not 3864 // prerendered in the preserve-3d tree. Now that we've finished processing 3865 // all descendants, update allowAsyncAnimation to take their prerender 3866 // state into account 3867 // FIXME: We don't block async animations for previous siblings because 3868 // their prerender decisions have been made. We may have to figure out a 3869 // better way to rollback their prerender decisions. 3870 // Alternatively we could not block animations for later siblings, and only 3871 // block them for ancestors of a blocked one. 3872 if ((extend3DContext || combines3DTransformWithAncestors) && 3873 prerenderInfo.CanUseAsyncAnimations() && 3874 !aBuilder->GetPreserves3DAllowAsyncAnimation()) { 3875 // aBuilder->GetPreserves3DAllowAsyncAnimation() means the inner or 3876 // previous silbing frames are allowed/disallowed for async animations. 3877 prerenderInfo.mDecision = nsDisplayTransform::PrerenderDecision::No; 3878 } 3879 3880 nsDisplayTransform* transformItem = MakeDisplayItem<nsDisplayTransform>( 3881 aBuilder, this, &resultList, visibleRect, prerenderInfo.mDecision, 3882 usingBackdropFilter, ShouldForceIsolation()); 3883 if (transformItem) { 3884 resultList.AppendToTop(transformItem); 3885 createdContainer = true; 3886 3887 if (numActiveScrollframesEncounteredBefore != 3888 aBuilder->GetNumActiveScrollframesEncountered()) { 3889 transformItem->SetContainsASRs(true); 3890 } 3891 3892 if (hasPerspective) { 3893 transformItem->MarkWithAssociatedPerspective(); 3894 3895 if (clipCapturedBy == ContainerItemType::Perspective) { 3896 clipState.Restore(); 3897 } 3898 resultList.AppendNewToTop<nsDisplayPerspective>(aBuilder, this, 3899 &resultList); 3900 createdContainer = true; 3901 } 3902 3903 // TODO(emilio): Ideally should also isolate when the transform is 3904 // potentially animated (prerenderInfo.mHasAnimations), but that causes a 3905 // lot of fuzz on Windows due to text antialiasing. 3906 const bool hasMaybe3dTransform = 3907 hasPerspective || !transformItem->GetTransform().Is2D(); 3908 if (hasMaybe3dTransform) { 3909 stackingContextTracker.AddToParent( 3910 StackingContextBits::MayContainNonIsolated3DTransform); 3911 } 3912 } 3913 if (clipCapturedBy == 3914 ContainerItemType::OwnLayerForTransformWithRoundedClip) { 3915 clipState.Restore(); 3916 resultList.AppendNewToTopWithIndex<nsDisplayOwnLayer>( 3917 aBuilder, this, 3918 /* aIndex = */ nsDisplayOwnLayer::OwnLayerForTransformWithRoundedClip, 3919 &resultList, aBuilder->CurrentActiveScrolledRoot(), 3920 nsDisplayItem::ContainerASRType::Constant, 3921 nsDisplayOwnLayerFlags::None, ScrollbarData{}, 3922 /* aForceActive = */ false, false); 3923 createdContainer = true; 3924 } 3925 } 3926 3927 // If we have sticky positioning, wrap it in a sticky position item. 3928 if (useFixedPosition && !capturedByViewTransition) { 3929 if (clipCapturedBy == ContainerItemType::FixedPosition) { 3930 clipState.Restore(); 3931 } 3932 // The ASR for the fixed item should be the ASR of our containing block, 3933 // which has been set as the builder's current ASR, unless this frame is 3934 // invisible and we hadn't saved display item data for it. In that case, 3935 // we need to take the containerItemASR since we might have fixed children. 3936 // For WebRender, we want to the know what |containerItemASR| is for the 3937 // case where the fixed-pos item is not a "real" fixed-pos item (e.g. it's 3938 // nested inside a scrolling transform), so we stash that on the display 3939 // item as well. 3940 const ActiveScrolledRoot* fixedASR = ActiveScrolledRoot::PickAncestor( 3941 containerItemASR, aBuilder->CurrentActiveScrolledRoot()); 3942 const ActiveScrolledRoot* scrollTargetASR = 3943 containerItemASR ? containerItemASR->GetNearestScrollASR() : nullptr; 3944 resultList.AppendNewToTop<nsDisplayFixedPosition>( 3945 aBuilder, this, &resultList, fixedASR, 3946 nsDisplayItem::ContainerASRType::AncestorOfContained, scrollTargetASR, 3947 ShouldForceIsolation()); 3948 createdContainer = true; 3949 } else if (useStickyPosition && !capturedByViewTransition) { 3950 // For position:sticky, the clip needs to be applied both to the sticky 3951 // container item and to the contents. The container item needs the clip 3952 // because a scrolled clip needs to move independently from the sticky 3953 // contents, and the contents need the clip so that they have finite 3954 // clipped bounds with respect to the container item's ASR. The latter is 3955 // a little tricky in the case where the sticky item has both fixed and 3956 // non-fixed descendants, because that means that the sticky container 3957 // item's ASR is the ASR of the fixed descendant. 3958 // For WebRender display list building, though, we still want to know the 3959 // the ASR that the sticky container item would normally have, so we stash 3960 // that on the display item as the "container ASR" (i.e. the normal ASR of 3961 // the container item, excluding the special behaviour induced by fixed 3962 // descendants). 3963 DisplayListClipState::AutoSaveRestore stickyItemClipState(aBuilder); 3964 stickyItemClipState.MaybeRemoveDisplayportClip(); 3965 const ActiveScrolledRoot* stickyItemASR = ActiveScrolledRoot::PickAncestor( 3966 containerItemASR, aBuilder->CurrentActiveScrolledRoot()); 3967 3968 auto* stickyItem = MakeDisplayItem<nsDisplayStickyPosition>( 3969 aBuilder, this, &resultList, stickyItemASR, 3970 nsDisplayItem::ContainerASRType::AncestorOfContained, 3971 aBuilder->CurrentActiveScrolledRoot()); 3972 3973 stickyItem->SetShouldFlatten(shouldFlattenStickyItem); 3974 3975 resultList.AppendToTop(stickyItem); 3976 createdContainer = true; 3977 3978 // If the sticky element is inside a filter, annotate the scroll frame that 3979 // scrolls the filter as having out-of-flow content inside a filter (this 3980 // inhibits paint skipping). 3981 if (aBuilder->GetFilterASR() && aBuilder->GetFilterASR() == stickyItemASR) { 3982 aBuilder->GetFilterASR() 3983 ->GetNearestScrollASR() 3984 ->ScrollFrame() 3985 ->SetHasOutOfFlowContentInsideFilter(); 3986 } 3987 } 3988 3989 // If there's blending, wrap up the list in a blend-mode item. Note that 3990 // opacity can be applied before blending as the blend color is not affected 3991 // by foreground opacity (only background alpha). 3992 if (effects->mMixBlendMode != StyleBlend::Normal) { 3993 stackingContextTracker.AddToParent( 3994 StackingContextBits::ContainsMixBlendMode); 3995 resultList.AppendNewToTop<nsDisplayBlendMode>( 3996 aBuilder, this, &resultList, effects->mMixBlendMode, containerItemASR, 3997 nsDisplayItem::ContainerASRType::AncestorOfContained, false); 3998 createdContainer = true; 3999 MarkAsIsolated(); 4000 } 4001 4002 if (!isolated && localIsolationReasons != StackingContextBits::None) { 4003 resultList.AppendToTop(nsDisplayBlendContainer::CreateForIsolation( 4004 aBuilder, this, &resultList, containerItemASR, 4005 nsDisplayItem::ContainerASRType::AncestorOfContained, 4006 ShouldForceIsolation())); 4007 createdContainer = true; 4008 } 4009 4010 if (!isolated && aBuilder->MayContainNonIsolated3DTransform()) { 4011 stackingContextTracker.AddToParent( 4012 StackingContextBits::MayContainNonIsolated3DTransform); 4013 } 4014 4015 if (aBuilder->IsReusingStackingContextItems()) { 4016 if (resultList.IsEmpty()) { 4017 return; 4018 } 4019 4020 nsDisplayItem* container = resultList.GetBottom(); 4021 if (resultList.Length() > 1 || container->Frame() != this) { 4022 container = MakeDisplayItem<nsDisplayContainer>( 4023 aBuilder, this, containerItemASR, 4024 nsDisplayItem::ContainerASRType::AncestorOfContained, &resultList); 4025 } else { 4026 MOZ_ASSERT(resultList.Length() == 1); 4027 resultList.Clear(); 4028 } 4029 4030 // Mark the outermost display item as reusable. These display items and 4031 // their chidren can be reused during the next paint if no ancestor or 4032 // descendant frames have been modified. 4033 if (!container->IsReusedItem()) { 4034 container->SetReusable(); 4035 } 4036 aList->AppendToTop(container); 4037 createdContainer = true; 4038 } else { 4039 aList->AppendToTop(&resultList); 4040 } 4041 4042 if (aCreatedContainerItem) { 4043 *aCreatedContainerItem = createdContainer; 4044 } 4045 } 4046 4047 static nsDisplayItem* WrapInWrapList(nsDisplayListBuilder* aBuilder, 4048 nsIFrame* aFrame, nsDisplayList* aList, 4049 const ActiveScrolledRoot* aContainerASR, 4050 bool aBuiltContainerItem = false) { 4051 nsDisplayItem* item = aList->GetBottom(); 4052 if (!item) { 4053 return nullptr; 4054 } 4055 4056 // We need a wrap list if there are multiple items, or if the single 4057 // item has a different frame. This can change in a partial build depending 4058 // on which items we build, so we need to ensure that we don't transition 4059 // to/from a wrap list without invalidating correctly. 4060 bool needsWrapList = 4061 aList->Length() > 1 || item->Frame() != aFrame || item->GetChildren(); 4062 4063 // If we have an explicit container item (that can't change without an 4064 // invalidation) or we're doing a full build and don't need a wrap list, then 4065 // we can skip adding one. 4066 if (aBuiltContainerItem || (!aBuilder->IsPartialUpdate() && !needsWrapList)) { 4067 MOZ_ASSERT(aList->Length() == 1); 4068 aList->Clear(); 4069 return item; 4070 } 4071 4072 // If we're doing a partial build and we didn't need a wrap list 4073 // previously then we can try to work from there. 4074 if (aBuilder->IsPartialUpdate() && 4075 !aFrame->HasDisplayItem(uint32_t(DisplayItemType::TYPE_CONTAINER))) { 4076 // If we now need a wrap list, we must previously have had no display items 4077 // or a single one belonging to this frame. Mark the item itself as 4078 // discarded so that RetainedDisplayListBuilder uses the ones we just built. 4079 // We don't want to mark the frame as modified as that would invalidate 4080 // positioned descendants that might be outside of this list, and might not 4081 // have been rebuilt this time. 4082 if (needsWrapList) { 4083 DiscardOldItems(aFrame); 4084 } else { 4085 MOZ_ASSERT(aList->Length() == 1); 4086 aList->Clear(); 4087 return item; 4088 } 4089 } 4090 4091 // The last case we could try to handle is when we previously had a wrap list, 4092 // but no longer need it. Unfortunately we can't differentiate this case from 4093 // a partial build where other children exist but we just didn't build them 4094 // this time. 4095 // TODO:RetainedDisplayListBuilder's merge phase has the full list and 4096 // could strip them out. 4097 4098 return MakeDisplayItem<nsDisplayContainer>( 4099 aBuilder, aFrame, aContainerASR, 4100 nsDisplayItem::ContainerASRType::AncestorOfContained, aList); 4101 } 4102 4103 /** 4104 * Check if a frame should be visited for building display list. 4105 */ 4106 static bool DescendIntoChild(nsDisplayListBuilder* aBuilder, 4107 const nsIFrame* aChild, const nsRect& aVisible, 4108 const nsRect& aDirty) { 4109 if (aChild->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { 4110 return true; 4111 } 4112 4113 // If the child is a scrollframe that we want to ignore, then we need 4114 // to descend into it because its scrolled child may intersect the dirty 4115 // area even if the scrollframe itself doesn't. 4116 if (aChild == aBuilder->GetIgnoreScrollFrame()) { 4117 return true; 4118 } 4119 4120 // There are cases where the "ignore scroll frame" on the builder is not set 4121 // correctly, and so we additionally want to catch cases where the child is 4122 // a root scrollframe and we are ignoring scrolling on the viewport. 4123 if (aChild == aBuilder->GetPresShellIgnoreScrollFrame()) { 4124 return true; 4125 } 4126 4127 nsRect overflow = aChild->InkOverflowRect(); 4128 4129 // On mobile, there may be a dynamic toolbar. The root content document's 4130 // root scroll frame's ink overflow rect does not include the toolbar 4131 // height, but if the toolbar is hidden, we still want to be able to target 4132 // content underneath the toolbar, so expand the overflow rect here to 4133 // allow display list building to descend into the scroll frame. 4134 if (aBuilder->IsForEventDelivery() && 4135 aChild == aChild->PresShell()->GetRootScrollContainerFrame() && 4136 aChild->PresContext()->IsRootContentDocumentCrossProcess() && 4137 aChild->PresContext()->HasDynamicToolbar()) { 4138 overflow.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar( 4139 aChild->PresContext(), overflow.Size())); 4140 } 4141 4142 if (aDirty.Intersects(overflow)) { 4143 return true; 4144 } 4145 4146 if (aChild->ForceDescendIntoIfVisible() && aVisible.Intersects(overflow)) { 4147 return true; 4148 } 4149 4150 if (aChild->IsTablePart()) { 4151 // Relative positioning and transforms can cause table parts to move, but we 4152 // will still paint the backgrounds for their ancestor parts under them at 4153 // their 'normal' position. That means that we must consider the overflow 4154 // rects at both positions. 4155 4156 // We convert the overflow rect into the nsTableFrame's coordinate 4157 // space, applying the normal position offset at each step. Then we 4158 // compare that against the builder's cached dirty rect in table 4159 // coordinate space. 4160 const nsIFrame* f = aChild; 4161 nsRect normalPositionOverflowRelativeToTable = overflow; 4162 4163 while (f->IsTablePart()) { 4164 normalPositionOverflowRelativeToTable += f->GetNormalPosition(); 4165 f = f->GetParent(); 4166 } 4167 4168 nsDisplayTableBackgroundSet* tableBGs = aBuilder->GetTableBackgroundSet(); 4169 if (tableBGs && tableBGs->GetDirtyRect().Intersects( 4170 normalPositionOverflowRelativeToTable)) { 4171 return true; 4172 } 4173 } 4174 4175 return false; 4176 } 4177 4178 void nsIFrame::BuildDisplayListForSimpleChild(nsDisplayListBuilder* aBuilder, 4179 nsIFrame* aChild, 4180 const nsDisplayListSet& aLists) { 4181 // This is the shortcut for frames been handled along the common 4182 // path, the most common one of THE COMMON CASE mentioned later. 4183 MOZ_ASSERT(aChild->Type() != LayoutFrameType::Placeholder); 4184 MOZ_ASSERT(!aBuilder->GetSelectedFramesOnly() && 4185 !aBuilder->GetIncludeAllOutOfFlows(), 4186 "It should be held for painting to window"); 4187 MOZ_ASSERT(aChild->HasAnyStateBits(NS_FRAME_SIMPLE_DISPLAYLIST)); 4188 4189 const nsPoint offset = aChild->GetOffsetTo(this); 4190 const nsRect visible = aBuilder->GetVisibleRect() - offset; 4191 const nsRect dirty = aBuilder->GetDirtyRect() - offset; 4192 4193 if (!DescendIntoChild(aBuilder, aChild, visible, dirty)) { 4194 DL_LOGV("Skipped frame %p", aChild); 4195 return; 4196 } 4197 4198 // Child cannot be transformed since it is not a stacking context. 4199 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( 4200 aBuilder, aChild, visible, dirty, false); 4201 4202 UpdateCurrentHitTestInfo(aBuilder, aChild); 4203 4204 aChild->MarkAbsoluteFramesForDisplayList(aBuilder); 4205 aBuilder->AdjustWindowDraggingRegion(aChild); 4206 aBuilder->Check(); 4207 aChild->BuildDisplayList(aBuilder, aLists); 4208 aChild->SetBuiltDisplayList(true); 4209 aBuilder->Check(); 4210 aBuilder->DisplayCaret(aChild, aLists.Outlines()); 4211 } 4212 4213 static bool ShouldSkipFrame(nsDisplayListBuilder* aBuilder, 4214 const nsIFrame* aFrame) { 4215 // If painting is restricted to just the background of the top level frame, 4216 // then we have nothing to do here. 4217 if (aBuilder->IsBackgroundOnly()) { 4218 return true; 4219 } 4220 if (aBuilder->IsForGenerateGlyphMask()) { 4221 if ((aFrame->IsLeaf() && !aFrame->IsTextFrame()) || 4222 aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 4223 // Only in-flow text frames are painted for background-clip: text mask 4224 // generation. 4225 return true; 4226 } 4227 } 4228 if (aBuilder->GetSelectedFramesOnly() && aFrame->IsLeaf() && 4229 !aFrame->IsSelected()) { 4230 return true; 4231 } 4232 static const nsFrameState skipFlags = NS_FRAME_TOO_DEEP_IN_FRAME_TREE | 4233 NS_FRAME_IS_NONDISPLAY | 4234 NS_FRAME_POSITION_VISIBILITY_HIDDEN; 4235 if (aFrame->HasAnyStateBits(skipFlags)) { 4236 return true; 4237 } 4238 // Checking mMozSubtreeHiddenOnlyVisually is relatively slow because it 4239 // involves loading more memory. It's only allowed in chrome sheets so let's 4240 // only support it in the parent process so we can mostly optimize this out in 4241 // content processes. 4242 return XRE_IsParentProcess() && 4243 aFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually; 4244 } 4245 4246 void nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder, 4247 nsIFrame* aChild, 4248 const nsDisplayListSet& aLists, 4249 DisplayChildFlags aFlags) { 4250 AutoCheckBuilder check(aBuilder); 4251 MOZ_ASSERT(!HidesContent(), "Caller should check"); 4252 #ifdef DEBUG 4253 DL_LOGV("BuildDisplayListForChild (%p) <", aChild); 4254 ScopeExit e( 4255 [aChild]() { DL_LOGV("> BuildDisplayListForChild (%p)", aChild); }); 4256 #endif 4257 4258 nsIFrame* child = aChild; 4259 auto* placeholder = child->IsPlaceholderFrame() 4260 ? static_cast<nsPlaceholderFrame*>(child) 4261 : nullptr; 4262 nsIFrame* childOrOutOfFlow = 4263 placeholder ? placeholder->GetOutOfFlowFrame() : child; 4264 if (ShouldSkipFrame(aBuilder, childOrOutOfFlow)) { 4265 return; 4266 } 4267 4268 // If we're generating a display list for printing, include Link items for 4269 // frames that correspond to HTML link elements so that we can have active 4270 // links in saved PDF output. Note that the state of "within a link" is 4271 // set on the display-list builder, such that all descendants of the link 4272 // element will generate display-list links. 4273 // TODO: we should be able to optimize this so as to avoid creating links 4274 // for the same destination that entirely overlap each other, which adds 4275 // nothing useful to the final PDF. 4276 Maybe<nsDisplayListBuilder::Linkifier> linkifier; 4277 if (StaticPrefs::print_save_as_pdf_links_enabled() && 4278 aBuilder->IsForPrinting()) { 4279 linkifier.emplace(aBuilder, childOrOutOfFlow, aLists.Content()); 4280 linkifier->MaybeAppendLink(aBuilder, childOrOutOfFlow); 4281 } 4282 4283 nsIFrame* parent = childOrOutOfFlow->GetParent(); 4284 const auto* parentDisplay = parent->StyleDisplay(); 4285 const auto overflowClipAxes = 4286 parent->ShouldApplyOverflowClipping(parentDisplay); 4287 4288 const bool isPaintingToWindow = aBuilder->IsPaintingToWindow(); 4289 const bool doingShortcut = 4290 isPaintingToWindow && 4291 child->HasAnyStateBits(NS_FRAME_SIMPLE_DISPLAYLIST) && 4292 // Animations may change the stacking context state. 4293 // ShouldApplyOverflowClipping is affected by the parent style, which does 4294 // not invalidate the NS_FRAME_SIMPLE_DISPLAYLIST bit. 4295 !(!overflowClipAxes.isEmpty() || child->MayHaveTransformAnimation() || 4296 child->MayHaveOpacityAnimation()); 4297 4298 if (aBuilder->IsForPainting()) { 4299 aBuilder->ClearWillChangeBudgetStatus(child); 4300 } 4301 4302 if (StaticPrefs::layout_css_scroll_anchoring_highlight()) { 4303 if (child->FirstContinuation()->IsScrollAnchor()) { 4304 nsRect bounds = child->GetContentRectRelativeToSelf() + 4305 aBuilder->ToReferenceFrame(child); 4306 nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>( 4307 aBuilder, child, bounds, NS_RGBA(255, 0, 255, 64)); 4308 if (color) { 4309 color->SetOverrideZIndex(INT32_MAX); 4310 aLists.PositionedDescendants()->AppendToTop(color); 4311 } 4312 } 4313 } 4314 4315 if (doingShortcut) { 4316 BuildDisplayListForSimpleChild(aBuilder, child, aLists); 4317 return; 4318 } 4319 4320 // dirty rect in child-relative coordinates 4321 NS_ASSERTION(aBuilder->GetCurrentFrame() == this, "Wrong coord space!"); 4322 const nsPoint offset = child->GetOffsetTo(this); 4323 nsRect visible = aBuilder->GetVisibleRect() - offset; 4324 nsRect dirty = aBuilder->GetDirtyRect() - offset; 4325 4326 nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData = nullptr; 4327 if (placeholder) { 4328 if (placeholder->HasAnyStateBits(PLACEHOLDER_FOR_TOPLAYER)) { 4329 // If the out-of-flow frame is in the top layer, the viewport frame 4330 // will paint it. Skip it here. Note that, only out-of-flow frames 4331 // with this property should be skipped, because non-HTML elements 4332 // may stop their children from being out-of-flow. Those frames 4333 // should still be handled in the normal in-flow path. 4334 return; 4335 } 4336 4337 child = childOrOutOfFlow; 4338 if (aBuilder->IsForPainting()) { 4339 aBuilder->ClearWillChangeBudgetStatus(child); 4340 } 4341 4342 // If 'child' is a pushed out-of-flow then it's owned by a block that's not 4343 // an ancestor of the placeholder, and it will be painted by that block and 4344 // should not be painted through the placeholder. Also recheck 4345 // NS_FRAME_TOO_DEEP_IN_FRAME_TREE and NS_FRAME_IS_NONDISPLAY. 4346 static const nsFrameState skipFlags = 4347 (NS_FRAME_IS_PUSHED_OUT_OF_FLOW | NS_FRAME_TOO_DEEP_IN_FRAME_TREE | 4348 NS_FRAME_IS_NONDISPLAY); 4349 if (child->HasAnyStateBits(skipFlags) || nsLayoutUtils::IsPopup(child)) { 4350 return; 4351 } 4352 4353 MOZ_ASSERT(child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); 4354 savedOutOfFlowData = nsDisplayListBuilder::GetOutOfFlowData(child); 4355 4356 if (aBuilder->GetIncludeAllOutOfFlows()) { 4357 visible = child->InkOverflowRect(); 4358 dirty = child->InkOverflowRect(); 4359 } else if (savedOutOfFlowData) { 4360 visible = 4361 savedOutOfFlowData->GetVisibleRectForFrame(aBuilder, child, &dirty); 4362 } else { 4363 // The out-of-flow frame did not intersect the dirty area. We may still 4364 // need to traverse into it, since it may contain placeholders we need 4365 // to enter to reach other out-of-flow frames that are visible. 4366 visible.SetEmpty(); 4367 dirty.SetEmpty(); 4368 } 4369 } 4370 4371 NS_ASSERTION(!child->IsPlaceholderFrame(), 4372 "Should have dealt with placeholders already"); 4373 4374 if (!DescendIntoChild(aBuilder, child, visible, dirty)) { 4375 DL_LOGV("Skipped frame %p", child); 4376 return; 4377 } 4378 4379 const bool isSVG = child->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); 4380 4381 // This flag is raised if the control flow strays off the common path. 4382 // The common path is the most common one of THE COMMON CASE mentioned later. 4383 bool awayFromCommonPath = !isPaintingToWindow; 4384 4385 // true if this is a real or pseudo stacking context 4386 bool pseudoStackingContext = 4387 aFlags.contains(DisplayChildFlag::ForcePseudoStackingContext); 4388 4389 if (!pseudoStackingContext && !isSVG && 4390 aFlags.contains(DisplayChildFlag::Inline) && 4391 !child->IsLineParticipant()) { 4392 // child is a non-inline frame in an inline context, i.e., 4393 // it acts like inline-block or inline-table. Therefore it is a 4394 // pseudo-stacking-context. 4395 pseudoStackingContext = true; 4396 } 4397 4398 // Since we're now sure that we're adding this frame to the display list 4399 // (which means we're painting it, modulo occlusion), mark it as visible 4400 // within the displayport. 4401 if (isPaintingToWindow && child->TrackingVisibility() && 4402 child->IsVisibleForPainting()) { 4403 child->PresShell()->EnsureFrameInApproximatelyVisibleList(child); 4404 awayFromCommonPath = true; 4405 } 4406 4407 // Child is composited if it's transformed, partially transparent, or has 4408 // SVG effects or a blend mode.. 4409 const nsStyleDisplay* disp = child->StyleDisplay(); 4410 const nsStyleEffects* effects = child->StyleEffects(); 4411 4412 const bool isPositioned = disp->IsPositionedStyle(); 4413 const bool isStackingContext = 4414 aFlags.contains(DisplayChildFlag::ForceStackingContext) || 4415 child->IsStackingContext(disp, effects); 4416 4417 if (pseudoStackingContext || isStackingContext || isPositioned || 4418 placeholder || (!isSVG && disp->IsFloating(child)) || 4419 (isSVG && effects->mClip.IsRect() && IsSVGContentWithCSSClip(child))) { 4420 pseudoStackingContext = true; 4421 awayFromCommonPath = true; 4422 } 4423 4424 NS_ASSERTION(!isStackingContext || pseudoStackingContext, 4425 "Stacking contexts must also be pseudo-stacking-contexts"); 4426 4427 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( 4428 aBuilder, child, visible, dirty); 4429 4430 UpdateCurrentHitTestInfo(aBuilder, child); 4431 4432 DisplayListClipState::AutoClipMultiple clipState(aBuilder); 4433 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder); 4434 4435 if (savedOutOfFlowData) { 4436 aBuilder->SetBuildingInvisibleItems(false); 4437 4438 nsIFrame* scrollsWithAnchor = nullptr; 4439 if (aBuilder->IsPaintingToWindow() && 4440 // If we are in view transition capture we get a null asr no matter 4441 // what, so don't bother checking for async scrolling with a CSS anchor 4442 // pos anchor. 4443 !aBuilder->IsInViewTransitionCapture() && 4444 child->IsAbsolutelyPositioned(disp) && 4445 // If there is an active view transition in this document it is tricky 4446 // to determine what will be an active scroll frame outside of that 4447 // frame's BuildDisplayList, so don't bother to async scroll with an 4448 // anchor in that case. Bug 2001861 tracks removing this check. 4449 !PresContext()->Document()->GetActiveViewTransition()) { 4450 scrollsWithAnchor = AnchorPositioningUtils::GetAnchorThatFrameScrollsWith( 4451 child, aBuilder); 4452 4453 if (scrollsWithAnchor && aBuilder->IsRetainingDisplayList()) { 4454 if (aBuilder->IsPartialUpdate()) { 4455 aBuilder->SetPartialBuildFailed(true); 4456 } else { 4457 aBuilder->SetDisablePartialUpdates(true); 4458 } 4459 } 4460 } 4461 4462 const ActiveScrolledRoot* asr = 4463 savedOutOfFlowData->mContainingBlockActiveScrolledRoot; 4464 4465 #ifdef DEBUG 4466 if (aBuilder->IsPaintingToWindow()) { 4467 // Assert that the asr is as expected. 4468 if (savedOutOfFlowData->mContainingBlockInViewTransitionCapture) { 4469 MOZ_ASSERT(asr == nullptr); 4470 MOZ_ASSERT(aBuilder->IsInViewTransitionCapture()); 4471 } else if ((asr ? FrameAndASRKind{asr->mFrame, asr->mKind} 4472 : FrameAndASRKind::default_value()) != 4473 DisplayPortUtils::GetASRAncestorFrame( 4474 {child->GetParent(), ActiveScrolledRoot::ASRKind::Scroll}, 4475 aBuilder)) { 4476 // A weird case for native anonymous content in the custom content 4477 // container when the root is captured by a view transition. This 4478 // content is built outside of the view transition capture but the 4479 // containing block (the canvas frame) was built inside the capture, so 4480 // savedOutOfFlowData is saved as if we are inside the capture while we 4481 // are outside it (bug 2002160). 4482 MOZ_ASSERT(asr == nullptr); 4483 MOZ_ASSERT(PresContext()->Document()->GetActiveViewTransition()); 4484 MOZ_ASSERT( 4485 child->GetParent()->GetContent()->IsInNativeAnonymousSubtree()); 4486 bool inTopLayer = false; 4487 nsIFrame* curr = child->GetParent(); 4488 while (curr) { 4489 if (curr->StyleDisplay()->mTopLayer == StyleTopLayer::Auto) { 4490 inTopLayer = true; 4491 break; 4492 } 4493 curr = curr->GetParent(); 4494 } 4495 MOZ_ASSERT(inTopLayer); 4496 } 4497 } 4498 #endif 4499 4500 if (scrollsWithAnchor) { 4501 asr = DisplayPortUtils::ActivateDisplayportOnASRAncestors( 4502 scrollsWithAnchor, child->GetParent(), asr, aBuilder); 4503 4504 // TODO should we set the scroll parent id too? 4505 // https://github.com/w3c/csswg-drafts/issues/12042 4506 } 4507 4508 if (aBuilder->IsInViewTransitionCapture()) { 4509 if (!savedOutOfFlowData->mContainingBlockInViewTransitionCapture) { 4510 clipState.Clear(); 4511 } else { 4512 clipState.SetClipChainForContainingBlockDescendants( 4513 savedOutOfFlowData->mContainingBlockClipChain); 4514 } 4515 asrSetter.SetCurrentActiveScrolledRoot(nullptr); 4516 } else { 4517 clipState.SetClipChainForContainingBlockDescendants( 4518 savedOutOfFlowData->mContainingBlockClipChain); 4519 asrSetter.SetCurrentActiveScrolledRoot(asr); 4520 asrSetter.SetCurrentScrollParentId(savedOutOfFlowData->mScrollParentId); 4521 } 4522 MOZ_ASSERT(awayFromCommonPath, 4523 "It is impossible when savedOutOfFlowData is true"); 4524 } else if (HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) && 4525 placeholder) { 4526 NS_ASSERTION(visible.IsEmpty(), "should have empty visible rect"); 4527 // Every item we build from now until we descent into an out of flow that 4528 // does have saved out of flow data should be invisible. This state gets 4529 // restored when AutoBuildingDisplayList gets out of scope. 4530 aBuilder->SetBuildingInvisibleItems(true); 4531 4532 // If we have nested out-of-flow frames and the outer one isn't visible 4533 // then we won't have stored clip data for it. We can just clear the clip 4534 // instead since we know we won't render anything, and the inner out-of-flow 4535 // frame will setup the correct clip for itself. 4536 clipState.SetClipChainForContainingBlockDescendants(nullptr); 4537 } 4538 4539 // Setup clipping for the parent's overflow:clip, 4540 // or overflow:hidden on elements that don't support scrolling (and therefore 4541 // don't create nsHTML/XULScrollFrame). This clipping needs to not clip 4542 // anything directly rendered by the parent, only the rendering of its 4543 // children. 4544 // Don't use overflowClip to restrict the dirty rect, since some of the 4545 // descendants may not be clipped by it. Even if we end up with unnecessary 4546 // display items, they'll be pruned during ComputeVisibility. 4547 // 4548 // FIXME(emilio): Why can't we handle this more similarly to `clip` (on the 4549 // parent, rather than on the children)? Would ClipContentDescendants do what 4550 // we want? 4551 if (!overflowClipAxes.isEmpty()) { 4552 ApplyOverflowClipping(aBuilder, parent, overflowClipAxes, clipState); 4553 awayFromCommonPath = true; 4554 } 4555 4556 nsDisplayList list(aBuilder); 4557 nsDisplayList extraPositionedDescendants(aBuilder); 4558 const ActiveScrolledRoot* wrapListASR; 4559 bool builtContainerItem = false; 4560 if (isStackingContext) { 4561 // True stacking context. 4562 // For stacking contexts, BuildDisplayListForStackingContext handles 4563 // clipping and MarkAbsoluteFramesForDisplayList. 4564 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder); 4565 child->BuildDisplayListForStackingContext(aBuilder, &list, 4566 &builtContainerItem); 4567 wrapListASR = contASRTracker.GetContainerASR(); 4568 if (!aBuilder->IsReusingStackingContextItems() && 4569 aBuilder->GetCaretFrame() == child) { 4570 builtContainerItem = false; 4571 } 4572 } else { 4573 Maybe<nsRect> clipPropClip = 4574 child->GetClipPropClipRect(disp, effects, child->GetSize()); 4575 if (clipPropClip) { 4576 aBuilder->IntersectVisibleRect(*clipPropClip); 4577 aBuilder->IntersectDirtyRect(*clipPropClip); 4578 clipState.ClipContentDescendants(*clipPropClip + 4579 aBuilder->ToReferenceFrame(child)); 4580 awayFromCommonPath = true; 4581 } 4582 4583 child->MarkAbsoluteFramesForDisplayList(aBuilder); 4584 child->SetBuiltDisplayList(true); 4585 4586 // Some SVG frames might change opacity without invalidating the frame, so 4587 // exclude them from the fast-path. 4588 if (!awayFromCommonPath && !child->IsSVGFrame()) { 4589 // The shortcut is available for the child for next time. 4590 child->AddStateBits(NS_FRAME_SIMPLE_DISPLAYLIST); 4591 } 4592 4593 if (!pseudoStackingContext) { 4594 // THIS IS THE COMMON CASE. 4595 // Not a pseudo or real stacking context. Do the simple thing and 4596 // return early. 4597 aBuilder->AdjustWindowDraggingRegion(child); 4598 aBuilder->Check(); 4599 child->BuildDisplayList(aBuilder, aLists); 4600 aBuilder->Check(); 4601 aBuilder->DisplayCaret(child, aLists.Outlines()); 4602 return; 4603 } 4604 4605 // A pseudo-stacking context (e.g., a positioned element with z-index auto). 4606 // We allow positioned descendants of the child to escape to our parent 4607 // stacking context's positioned descendant list, because they might be 4608 // z-index:non-auto 4609 nsDisplayListCollection pseudoStack(aBuilder); 4610 4611 aBuilder->AdjustWindowDraggingRegion(child); 4612 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder); 4613 aBuilder->Check(); 4614 child->BuildDisplayList(aBuilder, pseudoStack); 4615 aBuilder->Check(); 4616 if (aBuilder->DisplayCaret(child, pseudoStack.Outlines())) { 4617 builtContainerItem = false; 4618 } 4619 wrapListASR = contASRTracker.GetContainerASR(); 4620 4621 list.AppendToTop(pseudoStack.BorderBackground()); 4622 list.AppendToTop(pseudoStack.BlockBorderBackgrounds()); 4623 list.AppendToTop(pseudoStack.Floats()); 4624 list.AppendToTop(pseudoStack.Content()); 4625 list.AppendToTop(pseudoStack.Outlines()); 4626 extraPositionedDescendants.AppendToTop(pseudoStack.PositionedDescendants()); 4627 } 4628 4629 buildingForChild.RestoreBuildingInvisibleItemsValue(); 4630 4631 if (!list.IsEmpty()) { 4632 if (isPositioned || isStackingContext) { 4633 // Genuine stacking contexts, and positioned pseudo-stacking-contexts, 4634 // go in this level. 4635 nsDisplayItem* item = WrapInWrapList(aBuilder, child, &list, wrapListASR, 4636 builtContainerItem); 4637 if (isSVG) { 4638 aLists.Content()->AppendToTop(item); 4639 } else { 4640 aLists.PositionedDescendants()->AppendToTop(item); 4641 } 4642 } else if (!isSVG && disp->IsFloating(child)) { 4643 aLists.Floats()->AppendToTop( 4644 WrapInWrapList(aBuilder, child, &list, wrapListASR)); 4645 } else { 4646 aLists.Content()->AppendToTop(&list); 4647 } 4648 } 4649 // We delay placing the positioned descendants of positioned frames to here, 4650 // because in the absence of z-index this is the correct order for them. 4651 // This doesn't affect correctness because the positioned descendants list 4652 // is sorted by z-order and content in BuildDisplayListForStackingContext, 4653 // but it means that sort routine needs to do less work. 4654 aLists.PositionedDescendants()->AppendToTop(&extraPositionedDescendants); 4655 } 4656 4657 void nsIFrame::MarkAbsoluteFramesForDisplayList( 4658 nsDisplayListBuilder* aBuilder) { 4659 if (const auto* absCB = GetAbsoluteContainingBlock()) { 4660 aBuilder->MarkFramesForDisplayList(this, absCB->GetChildList()); 4661 } 4662 } 4663 4664 nsIContent* nsIFrame::GetContentForEvent(const WidgetEvent* aEvent) const { 4665 if (!IsGeneratedContentFrame()) { 4666 return GetContent(); 4667 } 4668 const nsIFrame* generatedRoot = this; 4669 while (true) { 4670 auto* parent = nsLayoutUtils::GetParentOrPlaceholderFor(generatedRoot); 4671 if (!parent || !parent->IsGeneratedContentFrame()) { 4672 break; 4673 } 4674 generatedRoot = parent; 4675 } 4676 // Return the non-generated ancestor. 4677 return generatedRoot->GetContent()->GetParent(); 4678 } 4679 4680 void nsIFrame::FireDOMEvent(const nsAString& aDOMEventName, 4681 nsIContent* aContent) { 4682 nsIContent* target = aContent ? aContent : GetContent(); 4683 4684 if (target) { 4685 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher( 4686 target, aDOMEventName, CanBubble::eYes, ChromeOnlyDispatch::eNo); 4687 DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent(); 4688 NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch"); 4689 } 4690 } 4691 4692 nsresult nsIFrame::HandleEvent(nsPresContext* aPresContext, 4693 WidgetGUIEvent* aEvent, 4694 nsEventStatus* aEventStatus) { 4695 if (aEvent->mMessage == eMouseMove) { 4696 // XXX If the second argument of HandleDrag() is WidgetMouseEvent, 4697 // the implementation becomes simpler. 4698 return HandleDrag(aPresContext, aEvent, aEventStatus); 4699 } 4700 4701 if ((aEvent->mClass == eMouseEventClass && 4702 aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) || 4703 aEvent->mClass == eTouchEventClass) { 4704 if (aEvent->mMessage == eMouseDown || aEvent->mMessage == eTouchStart) { 4705 HandlePress(aPresContext, aEvent, aEventStatus); 4706 } else if (aEvent->mMessage == eMouseUp || aEvent->mMessage == eTouchEnd) { 4707 HandleRelease(aPresContext, aEvent, aEventStatus); 4708 } 4709 return NS_OK; 4710 } 4711 4712 // When secondary buttion is down, we need to move selection to make users 4713 // possible to paste something at click point quickly. 4714 // When middle button is down, we need to just move selection and focus at 4715 // the clicked point. Note that even if middle click paste is not enabled, 4716 // Chrome moves selection at middle mouse button down. So, we should follow 4717 // the behavior for the compatibility. 4718 if (aEvent->mMessage == eMouseDown) { 4719 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); 4720 if (mouseEvent && (mouseEvent->mButton == MouseButton::eSecondary || 4721 mouseEvent->mButton == MouseButton::eMiddle)) { 4722 if (*aEventStatus == nsEventStatus_eConsumeNoDefault) { 4723 return NS_OK; 4724 } 4725 return MoveCaretToEventPoint(aPresContext, mouseEvent, aEventStatus); 4726 } 4727 } 4728 4729 return NS_OK; 4730 } 4731 4732 nsresult nsIFrame::GetDataForTableSelection( 4733 const nsFrameSelection* aFrameSelection, mozilla::PresShell* aPresShell, 4734 WidgetMouseEvent* aMouseEvent, nsIContent** aParentContent, 4735 int32_t* aContentOffset, TableSelectionMode* aTarget) { 4736 if (!aFrameSelection || !aPresShell || !aMouseEvent || !aParentContent || 4737 !aContentOffset || !aTarget) { 4738 return NS_ERROR_NULL_POINTER; 4739 } 4740 4741 *aParentContent = nullptr; 4742 *aContentOffset = 0; 4743 *aTarget = TableSelectionMode::None; 4744 4745 int16_t displaySelection = aPresShell->GetSelectionFlags(); 4746 4747 bool selectingTableCells = aFrameSelection->IsInTableSelectionMode(); 4748 4749 // DISPLAY_ALL means we're in an editor. 4750 // If already in cell selection mode, 4751 // continue selecting with mouse drag or end on mouse up, 4752 // or when using shift key to extend block of cells 4753 // (Mouse down does normal selection unless Ctrl/Cmd is pressed) 4754 bool doTableSelection = 4755 displaySelection == nsISelectionDisplay::DISPLAY_ALL && 4756 selectingTableCells && 4757 (aMouseEvent->mMessage == eMouseMove || 4758 (aMouseEvent->mMessage == eMouseUp && 4759 aMouseEvent->mButton == MouseButton::ePrimary) || 4760 aMouseEvent->IsShift()); 4761 4762 if (!doTableSelection) { 4763 // In Browser, special 'table selection' key must be pressed for table 4764 // selection or when just Shift is pressed and we're already in table/cell 4765 // selection mode 4766 #ifdef XP_MACOSX 4767 doTableSelection = aMouseEvent->IsMeta() || 4768 (aMouseEvent->IsShift() && selectingTableCells); 4769 #else 4770 doTableSelection = aMouseEvent->IsControl() || 4771 (aMouseEvent->IsShift() && selectingTableCells); 4772 #endif 4773 } 4774 if (!doTableSelection) { 4775 return NS_OK; 4776 } 4777 4778 // Get the cell frame or table frame (or parent) of the current content node 4779 nsIFrame* frame = this; 4780 bool foundCell = false; 4781 bool foundTable = false; 4782 4783 // Get the limiting node to stop parent frame search 4784 const Element* const independentSelectionLimiter = 4785 aFrameSelection->GetIndependentSelectionRootElement(); 4786 4787 // If our content node is an ancestor of the limiting node, 4788 // we should stop the search right now. 4789 if (independentSelectionLimiter && 4790 independentSelectionLimiter->IsInclusiveDescendantOf(GetContent())) { 4791 return NS_OK; 4792 } 4793 4794 // We don't initiate row/col selection from here now, 4795 // but we may in future 4796 // bool selectColumn = false; 4797 // bool selectRow = false; 4798 4799 while (frame) { 4800 // Check for a table cell by querying to a known CellFrame interface 4801 nsITableCellLayout* cellElement = do_QueryFrame(frame); 4802 if (cellElement) { 4803 foundCell = true; 4804 // TODO: If we want to use proximity to top or left border 4805 // for row and column selection, this is the place to do it 4806 break; 4807 } else { 4808 // If not a cell, check for table 4809 // This will happen when starting frame is the table or child of a table, 4810 // such as a row (we were inbetween cells or in table border) 4811 nsTableWrapperFrame* tableFrame = do_QueryFrame(frame); 4812 if (tableFrame) { 4813 foundTable = true; 4814 // TODO: How can we select row when along left table edge 4815 // or select column when along top edge? 4816 break; 4817 } else { 4818 frame = frame->GetParent(); 4819 // Stop if we have hit the selection's limiting content node 4820 if (frame && frame->GetContent() == independentSelectionLimiter) { 4821 break; 4822 } 4823 } 4824 } 4825 } 4826 // We aren't in a cell or table 4827 if (!foundCell && !foundTable) { 4828 return NS_OK; 4829 } 4830 4831 nsIContent* tableOrCellContent = frame->GetContent(); 4832 if (!tableOrCellContent) { 4833 return NS_ERROR_FAILURE; 4834 } 4835 4836 nsCOMPtr<nsIContent> parentContent = tableOrCellContent->GetParent(); 4837 if (!parentContent) { 4838 return NS_ERROR_FAILURE; 4839 } 4840 4841 const int32_t offset = 4842 parentContent->ComputeIndexOf_Deprecated(tableOrCellContent); 4843 // Not likely? 4844 if (offset < 0) { 4845 return NS_ERROR_FAILURE; 4846 } 4847 4848 // Everything is OK -- set the return values 4849 parentContent.forget(aParentContent); 4850 4851 *aContentOffset = offset; 4852 4853 #if 0 4854 if (selectRow) 4855 *aTarget = TableSelectionMode::Row; 4856 else if (selectColumn) 4857 *aTarget = TableSelectionMode::Column; 4858 else 4859 #endif 4860 if (foundCell) { 4861 *aTarget = TableSelectionMode::Cell; 4862 } else if (foundTable) { 4863 *aTarget = TableSelectionMode::Table; 4864 } 4865 4866 return NS_OK; 4867 } 4868 4869 static bool IsEditingHost(const nsIFrame* aFrame) { 4870 if (aFrame->Style()->GetPseudoType() == 4871 PseudoStyleType::mozTextControlEditingRoot) { 4872 return true; 4873 } 4874 nsIContent* content = aFrame->GetContent(); 4875 return content && content->IsEditingHost(); 4876 } 4877 4878 static StyleUserSelect UsedUserSelect(const nsIFrame* aFrame) { 4879 if (aFrame->IsGeneratedContentFrame()) { 4880 return StyleUserSelect::None; 4881 } 4882 4883 // Per https://drafts.csswg.org/css-ui-4/#content-selection: 4884 // 4885 // The used value is the same as the computed value, except: 4886 // 4887 // 1 - on editable elements where the used value is always 'contain' 4888 // regardless of the computed value 4889 // 2 - when the computed value is auto, in which case the used value is one 4890 // of the other values... 4891 // 4892 // See https://github.com/w3c/csswg-drafts/issues/3344 to see why we do this 4893 // at used-value time instead of at computed-value time. 4894 4895 // Although the draft does not define that editable elements under an editing 4896 // host should ignore `user-select`, Chrome ignores `user-select:none` and 4897 // `user-select:all` and we've already considered as ignoring the 4898 // `user-select` according to this test: 4899 // https://searchfox.org/firefox-main/rev/6abddcb0a5076c3b888686ede6f4cf7d082460d3/dom/base/test/test_bug1101364.html#73-85 4900 // https://searchfox.org/firefox-main/rev/4fd0fa7e5814c0b51f1dd075821988377bc56cc1/testing/web-platform/tests/css/css-ui/user-select-none-in-editable.html#23-24,32-36 4901 // Therefore, we check aFrame->ContentIsEditable() instead of 4902 // IsEditingHost(aFrame). 4903 if (aFrame->IsTextInputFrame() || aFrame->ContentIsEditable()) { 4904 // We don't implement 'contain' itself, but we make 'text' behave as 4905 // 'contain' for contenteditable and <input> / <textarea> elements anyway so 4906 // this is ok. 4907 return StyleUserSelect::Text; 4908 } 4909 4910 auto style = aFrame->Style()->UserSelect(); 4911 if (style != StyleUserSelect::Auto) { 4912 return style; 4913 } 4914 4915 auto* parent = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame); 4916 return parent ? UsedUserSelect(parent) : StyleUserSelect::Text; 4917 } 4918 4919 bool nsIFrame::IsSelectable(StyleUserSelect* aSelectStyle) const { 4920 auto style = UsedUserSelect(this); 4921 if (aSelectStyle) { 4922 *aSelectStyle = style; 4923 } 4924 return style != StyleUserSelect::None; 4925 } 4926 4927 bool nsIFrame::ShouldPaintNormalSelection() const { 4928 if (IsSelectable()) { 4929 // NOTE: Ideally, we should return false if display selection is "OFF". 4930 // However, here is a hot path at painting. Therefore, it should be checked 4931 // before and we shouldn't need to check it. 4932 return true; 4933 } 4934 // If we're not selectable by user, we should paint selection only while the 4935 // normal selection is styled as "attention" by "Find in Page" or something. 4936 return GetDisplaySelection() == nsISelectionController::SELECTION_ATTENTION; 4937 } 4938 4939 bool nsIFrame::ShouldHaveLineIfEmpty() const { 4940 switch (Style()->GetPseudoType()) { 4941 case PseudoStyleType::NotPseudo: 4942 break; 4943 case PseudoStyleType::scrolledContent: 4944 return GetParent()->ShouldHaveLineIfEmpty(); 4945 default: 4946 return false; 4947 } 4948 return IsInputButtonControlFrame() || IsEditingHost(this); 4949 } 4950 4951 /** 4952 * Handles the Mouse Press Event for the frame 4953 */ 4954 NS_IMETHODIMP 4955 nsIFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent, 4956 nsEventStatus* aEventStatus) { 4957 NS_ENSURE_ARG_POINTER(aEventStatus); 4958 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { 4959 return NS_OK; 4960 } 4961 4962 NS_ENSURE_ARG_POINTER(aEvent); 4963 if (aEvent->mClass == eTouchEventClass) { 4964 return NS_OK; 4965 } 4966 4967 return MoveCaretToEventPoint(aPresContext, aEvent->AsMouseEvent(), 4968 aEventStatus); 4969 } 4970 4971 nsresult nsIFrame::MoveCaretToEventPoint(nsPresContext* aPresContext, 4972 WidgetMouseEvent* aMouseEvent, 4973 nsEventStatus* aEventStatus) { 4974 MOZ_ASSERT(aPresContext); 4975 MOZ_ASSERT(aMouseEvent); 4976 MOZ_ASSERT(aMouseEvent->mMessage == eMouseDown); 4977 MOZ_ASSERT(aEventStatus); 4978 MOZ_ASSERT(nsEventStatus_eConsumeNoDefault != *aEventStatus); 4979 4980 mozilla::PresShell* presShell = aPresContext->GetPresShell(); 4981 if (!presShell) { 4982 return NS_ERROR_FAILURE; 4983 } 4984 4985 // We often get out of sync state issues with mousedown events that 4986 // get interrupted by alerts/dialogs. 4987 // Check with the ESM to see if we should process this one 4988 if (!aPresContext->EventStateManager()->EventStatusOK(aMouseEvent)) { 4989 return NS_OK; 4990 } 4991 4992 EventStateManager* const esm = aPresContext->EventStateManager(); 4993 if (nsIContent* dragGestureContent = esm->GetTrackingDragGestureContent()) { 4994 if (dragGestureContent != this->GetContent()) { 4995 // When the current tracked dragging gesture is different 4996 // than this frame, it means this frame was being dragged, however 4997 // it got moved/destroyed. So we should consider the drag is 4998 // still happening, so return early here. 4999 return NS_OK; 5000 } 5001 } 5002 5003 const nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo( 5004 aMouseEvent, RelativeTo{this}); 5005 5006 // When not using `alt`, and clicking on a draggable, but non-editable 5007 // element, don't do anything, and let d&d handle the event. 5008 // 5009 // See bug 48876, bug 388659 and bug 55921 for context here. 5010 // 5011 // FIXME(emilio): The .Contains(pt) check looks a bit fishy. When would it be 5012 // false given we're the event target? If it is needed, why not checking the 5013 // actual draggable node rect instead? 5014 if (!aMouseEvent->IsAlt() && GetRectRelativeToSelf().Contains(pt)) { 5015 for (nsIContent* content = mContent; content; 5016 content = content->GetFlattenedTreeParent()) { 5017 if (nsContentUtils::ContentIsDraggable(content) && 5018 !content->IsEditable()) { 5019 return NS_OK; 5020 } 5021 } 5022 } 5023 5024 // If we are in Navigator and the click is in a draggable node, we don't want 5025 // to start selection because we don't want to interfere with a potential 5026 // drag of said node and steal all its glory. 5027 const bool isEditor = 5028 presShell->GetSelectionFlags() == nsISelectionDisplay::DISPLAY_ALL; 5029 5030 // Don't do something if it's middle button down event. 5031 const bool isPrimaryButtonDown = 5032 aMouseEvent->mButton == MouseButton::ePrimary; 5033 5034 // Check whether this frame should handle selection events. If not, don't 5035 // tell selection the mouse event even occurred. 5036 if (!ShouldHandleSelectionMovementEvents()) { 5037 return NS_OK; 5038 } 5039 5040 if (isPrimaryButtonDown) { 5041 // If the mouse is dragged outside the nearest enclosing scrollable area 5042 // while making a selection, the area will be scrolled. To do this, capture 5043 // the mouse on the nearest scroll container frame. If there isn't a scroll 5044 // container frame, or something else is already capturing the mouse, 5045 // there's no reason to capture. 5046 if (!PresShell::GetCapturingContent()) { 5047 ScrollContainerFrame* scrollContainerFrame = 5048 nsLayoutUtils::GetNearestScrollContainerFrame( 5049 this, nsLayoutUtils::SCROLLABLE_SAME_DOC | 5050 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); 5051 if (scrollContainerFrame) { 5052 nsIFrame* capturingFrame = scrollContainerFrame; 5053 PresShell::SetCapturingContent(capturingFrame->GetContent(), 5054 CaptureFlags::IgnoreAllowedState); 5055 } 5056 } 5057 } 5058 5059 // XXX This is screwy; it really should use the selection frame, not the 5060 // event frame 5061 const nsFrameSelection* frameselection = GetConstFrameSelection(); 5062 if (!frameselection || frameselection->GetDisplaySelection() == 5063 nsISelectionController::SELECTION_OFF) { 5064 return NS_OK; // nothing to do we cannot affect selection from here 5065 } 5066 5067 #ifdef XP_MACOSX 5068 // If Control key is pressed on macOS, it should be treated as right click. 5069 // So, don't change selection. 5070 if (aMouseEvent->IsControl()) { 5071 return NS_OK; 5072 } 5073 const bool control = aMouseEvent->IsMeta(); 5074 #else 5075 const bool control = aMouseEvent->IsControl(); 5076 #endif 5077 5078 RefPtr<nsFrameSelection> fc = const_cast<nsFrameSelection*>(frameselection); 5079 if (isPrimaryButtonDown && aMouseEvent->mClickCount > 1) { 5080 // These methods aren't const but can't actually delete anything, 5081 // so no need for AutoWeakFrame. 5082 fc->SetDragState(true); 5083 return HandleMultiplePress(aPresContext, aMouseEvent, aEventStatus, 5084 control); 5085 } 5086 5087 ContentOffsets offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN); 5088 5089 if (!offsets.content) { 5090 return NS_ERROR_FAILURE; 5091 } 5092 5093 const bool isSecondaryButton = 5094 aMouseEvent->mButton == MouseButton::eSecondary; 5095 if (isSecondaryButton && 5096 !MovingCaretToEventPointAllowedIfSecondaryButtonEvent( 5097 *frameselection, *aMouseEvent, *offsets.content, 5098 // When we collapse selection in nsFrameSelection::TakeFocus, 5099 // we always collapse selection to the start offset. Therefore, 5100 // we can ignore the end offset here. E.g., when an <img> is clicked, 5101 // set the primary offset to after it, but the the secondary offset 5102 // may be before it, see OffsetsForSingleFrame for the detail. 5103 offsets.StartOffset())) { 5104 return NS_OK; 5105 } 5106 5107 if (aMouseEvent->mMessage == eMouseDown && 5108 aMouseEvent->mButton == MouseButton::eMiddle && 5109 !offsets.content->IsEditable()) { 5110 // However, some users don't like the Chrome compatible behavior of 5111 // middle mouse click. They want to keep selection after starting 5112 // autoscroll. However, the selection change is important for middle 5113 // mouse past. Therefore, we should allow users to take the traditional 5114 // behavior back by themselves unless middle click paste is enabled or 5115 // autoscrolling is disabled. 5116 if (!Preferences::GetBool("middlemouse.paste", false) && 5117 Preferences::GetBool("general.autoScroll", false) && 5118 Preferences::GetBool("general.autoscroll.prevent_to_collapse_selection_" 5119 "by_middle_mouse_down", 5120 false)) { 5121 return NS_OK; 5122 } 5123 } 5124 5125 if (isPrimaryButtonDown) { 5126 // Let Ctrl/Cmd + left mouse down do table selection instead of drag 5127 // initiation. 5128 nsCOMPtr<nsIContent> parentContent; 5129 int32_t contentOffset; 5130 TableSelectionMode target; 5131 nsresult rv = GetDataForTableSelection( 5132 frameselection, presShell, aMouseEvent, getter_AddRefs(parentContent), 5133 &contentOffset, &target); 5134 if (NS_SUCCEEDED(rv) && parentContent) { 5135 fc->SetDragState(true); 5136 return fc->HandleTableSelection(parentContent, contentOffset, target, 5137 aMouseEvent); 5138 } 5139 } 5140 5141 fc->SetDelayedCaretData(0); 5142 5143 if (isPrimaryButtonDown) { 5144 // Check if any part of this frame is selected, and if the user clicked 5145 // inside the selected region, and if it's the left button. If so, we delay 5146 // starting a new selection since the user may be trying to drag the 5147 // selected region to some other app. 5148 5149 if (GetContent() && GetContent()->IsMaybeSelected()) { 5150 bool inSelection = false; 5151 UniquePtr<SelectionDetails> details = frameselection->LookUpSelection( 5152 offsets.content, 0, offsets.EndOffset(), 5153 nsFrameSelection::IgnoreNormalSelection::No); 5154 5155 // 5156 // If there are any details, check to see if the user clicked 5157 // within any selected region of the frame. 5158 // 5159 5160 for (SelectionDetails* curDetail = details.get(); curDetail; 5161 curDetail = curDetail->mNext.get()) { 5162 // 5163 // If the user clicked inside a selection, then just 5164 // return without doing anything. We will handle placing 5165 // the caret later on when the mouse is released. We ignore 5166 // the spellcheck, find and url formatting selections. 5167 // 5168 if (curDetail->mSelectionType != SelectionType::eSpellCheck && 5169 curDetail->mSelectionType != SelectionType::eFind && 5170 curDetail->mSelectionType != SelectionType::eURLSecondary && 5171 curDetail->mSelectionType != SelectionType::eURLStrikeout && 5172 curDetail->mSelectionType != SelectionType::eHighlight && 5173 curDetail->mSelectionType != SelectionType::eTargetText && 5174 curDetail->mStart <= offsets.StartOffset() && 5175 offsets.EndOffset() <= curDetail->mEnd) { 5176 inSelection = true; 5177 } 5178 } 5179 5180 if (inSelection) { 5181 fc->SetDragState(false); 5182 fc->SetDelayedCaretData(aMouseEvent); 5183 return NS_OK; 5184 } 5185 } 5186 5187 fc->SetDragState(true); 5188 } 5189 5190 // Do not touch any nsFrame members after this point without adding 5191 // weakFrame checks. 5192 const nsFrameSelection::FocusMode focusMode = [&]() { 5193 // If "Shift" and "Ctrl" are both pressed, "Shift" is given precedence. This 5194 // mimics the old behaviour. 5195 const bool isShift = 5196 aMouseEvent->IsShift() && 5197 // If Shift + secondary button press shoud open context menu without a 5198 // contextmenu event, user wants to open context menu like as a 5199 // secondary button press without Shift key. 5200 !(isSecondaryButton && 5201 StaticPrefs::dom_event_contextmenu_shift_suppresses_event()); 5202 if (isShift) { 5203 // If clicked in a link when focused content is editable, we should 5204 // collapse selection in the link for compatibility with Blink. 5205 if (isEditor) { 5206 for (Element* element : mContent->InclusiveAncestorsOfType<Element>()) { 5207 if (element->IsLink()) { 5208 return nsFrameSelection::FocusMode::kCollapseToNewPoint; 5209 } 5210 } 5211 } 5212 return nsFrameSelection::FocusMode::kExtendSelection; 5213 } 5214 5215 if (isPrimaryButtonDown && control) { 5216 return nsFrameSelection::FocusMode::kMultiRangeSelection; 5217 } 5218 5219 return nsFrameSelection::FocusMode::kCollapseToNewPoint; 5220 }(); 5221 5222 nsresult rv = fc->HandleClick( 5223 MOZ_KnownLive(offsets.content) /* bug 1636889 */, offsets.StartOffset(), 5224 offsets.EndOffset(), focusMode, offsets.associate); 5225 if (NS_FAILED(rv)) { 5226 return rv; 5227 } 5228 5229 // We don't handle mouse button up if it's middle button. 5230 if (isPrimaryButtonDown && offsets.offset != offsets.secondaryOffset) { 5231 fc->MaintainSelection(); 5232 } 5233 5234 if (isPrimaryButtonDown && isEditor && !aMouseEvent->IsShift() && 5235 (offsets.EndOffset() - offsets.StartOffset()) == 1) { 5236 // A single node is selected and we aren't extending an existing selection, 5237 // which means the user clicked directly on an object (either 5238 // `user-select: all` or a non-text node without children). Therefore, 5239 // disable selection extension during mouse moves. 5240 // XXX This is a bit hacky; shouldn't editor be able to deal with this? 5241 fc->SetDragState(false); 5242 } 5243 5244 return NS_OK; 5245 } 5246 5247 bool nsIFrame::MovingCaretToEventPointAllowedIfSecondaryButtonEvent( 5248 const nsFrameSelection& aFrameSelection, 5249 WidgetMouseEvent& aSecondaryButtonEvent, 5250 const nsIContent& aContentAtEventPoint, int32_t aOffsetAtEventPoint) const { 5251 MOZ_ASSERT(aSecondaryButtonEvent.mButton == MouseButton::eSecondary); 5252 5253 if (NS_WARN_IF(aOffsetAtEventPoint < 0)) { 5254 return false; 5255 } 5256 5257 const bool contentIsEditable = aContentAtEventPoint.IsEditable(); 5258 const TextControlElement* const contentAsTextControl = 5259 TextControlElement::FromNodeOrNull( 5260 aContentAtEventPoint.IsTextControlElement() 5261 ? &aContentAtEventPoint 5262 : aContentAtEventPoint.GetClosestNativeAnonymousSubtreeRoot()); 5263 const Selection& selection = aFrameSelection.NormalSelection(); 5264 const bool selectionIsCollapsed = 5265 selection.AreNormalAndCrossShadowBoundaryRangesCollapsed(); 5266 // If right click in a selection range, we should not collapse 5267 // selection. 5268 if (!selectionIsCollapsed && nsContentUtils::IsPointInSelection( 5269 selection, aContentAtEventPoint, 5270 static_cast<uint32_t>(aOffsetAtEventPoint), 5271 true /* aAllowCrossShadowBoundary */)) { 5272 return false; 5273 } 5274 const bool wantToPreventMoveCaret = 5275 StaticPrefs:: 5276 ui_mouse_right_click_move_caret_stop_if_in_focused_editable_node() && 5277 selectionIsCollapsed && (contentIsEditable || contentAsTextControl); 5278 const bool wantToPreventCollapseSelection = 5279 StaticPrefs:: 5280 ui_mouse_right_click_collapse_selection_stop_if_non_collapsed_selection() && 5281 !selectionIsCollapsed; 5282 if (wantToPreventMoveCaret || wantToPreventCollapseSelection) { 5283 // If currently selection is limited in an editing host, we should not 5284 // collapse selection nor move caret if the clicked point is in the 5285 // ancestor limiter. Otherwise, this mouse click moves focus from the 5286 // editing host to different one or blur the editing host. In this case, 5287 // we need to update selection because keeping current selection in the 5288 // editing host looks like it's not blurred. 5289 // FIXME: If the active editing host is the document element, editor 5290 // does not set ancestor limiter properly. Fix it in the editor side. 5291 if (nsIContent* ancestorLimiter = selection.GetAncestorLimiter()) { 5292 MOZ_ASSERT(ancestorLimiter->IsEditable()); 5293 return !aContentAtEventPoint.IsInclusiveDescendantOf(ancestorLimiter); 5294 } 5295 } 5296 // If selection is editable and `stop_if_in_focused_editable_node` pref is 5297 // set to true, user does not want to move caret to right click place if 5298 // clicked in the focused text control element. 5299 if (wantToPreventMoveCaret && contentAsTextControl && 5300 contentAsTextControl == nsFocusManager::GetFocusedElementStatic()) { 5301 return false; 5302 } 5303 // If currently selection is not limited in an editing host, we should 5304 // collapse selection only when this click moves focus to an editing 5305 // host because we need to update selection in this case. 5306 if (wantToPreventCollapseSelection && !contentIsEditable) { 5307 return false; 5308 } 5309 5310 return !StaticPrefs:: 5311 ui_mouse_right_click_collapse_selection_stop_if_non_editable_node() || 5312 // The user does not want to collapse selection into non-editable 5313 // content by a right button click. 5314 contentIsEditable || 5315 // Treat clicking in a text control as always clicked on editable 5316 // content because we want a hack only for clicking in normal text 5317 // nodes which is outside any editing hosts. 5318 contentAsTextControl; 5319 } 5320 5321 nsresult nsIFrame::SelectByTypeAtPoint(const nsPoint& aPoint, 5322 nsSelectionAmount aBeginAmountType, 5323 nsSelectionAmount aEndAmountType, 5324 uint32_t aSelectFlags) { 5325 // No point in selecting if selection is turned off 5326 if (!ShouldHandleSelectionMovementEvents()) { 5327 return NS_OK; 5328 } 5329 5330 ContentOffsets offsets = GetContentOffsetsFromPoint( 5331 aPoint, SKIP_HIDDEN | IGNORE_NATIVE_ANONYMOUS_SUBTREE); 5332 if (!offsets.content) { 5333 return NS_ERROR_FAILURE; 5334 } 5335 5336 FrameAndOffset frameAndOffset = SelectionMovementUtils::GetFrameForNodeOffset( 5337 offsets.content, offsets.offset, offsets.associate); 5338 if (!frameAndOffset) { 5339 return NS_ERROR_FAILURE; 5340 } 5341 return frameAndOffset->PeekBackwardAndForwardForSelection( 5342 aBeginAmountType, aEndAmountType, 5343 static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent), 5344 aBeginAmountType != eSelectWord, aSelectFlags); 5345 } 5346 5347 /** 5348 * Multiple Mouse Press -- line or paragraph selection -- for the frame. 5349 * Wouldn't it be nice if this didn't have to be hardwired into Frame code? 5350 */ 5351 NS_IMETHODIMP 5352 nsIFrame::HandleMultiplePress(nsPresContext* aPresContext, 5353 WidgetGUIEvent* aEvent, 5354 nsEventStatus* aEventStatus, bool aControlHeld) { 5355 NS_ENSURE_ARG_POINTER(aEvent); 5356 NS_ENSURE_ARG_POINTER(aEventStatus); 5357 5358 if (nsEventStatus_eConsumeNoDefault == *aEventStatus || 5359 !ShouldHandleSelectionMovementEvents()) { 5360 return NS_OK; 5361 } 5362 5363 // Find out whether we're doing line or paragraph selection. 5364 // If browser.triple_click_selects_paragraph is true, triple-click selects 5365 // paragraph. Otherwise, triple-click selects line, and quadruple-click 5366 // selects paragraph (on platforms that support quadruple-click). 5367 nsSelectionAmount beginAmount, endAmount; 5368 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); 5369 if (!mouseEvent) { 5370 return NS_OK; 5371 } 5372 5373 if (mouseEvent->mClickCount == 4) { 5374 beginAmount = endAmount = eSelectParagraph; 5375 } else if (mouseEvent->mClickCount == 3) { 5376 if (Preferences::GetBool("browser.triple_click_selects_paragraph")) { 5377 beginAmount = endAmount = eSelectParagraph; 5378 } else { 5379 beginAmount = eSelectBeginLine; 5380 endAmount = eSelectEndLine; 5381 } 5382 } else if (mouseEvent->mClickCount == 2) { 5383 // We only want inline frames; PeekBackwardAndForward dislikes blocks 5384 beginAmount = endAmount = eSelectWord; 5385 } else { 5386 return NS_OK; 5387 } 5388 5389 nsPoint relPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo( 5390 mouseEvent, RelativeTo{this}); 5391 return SelectByTypeAtPoint(relPoint, beginAmount, endAmount, 5392 (aControlHeld ? SELECT_ACCUMULATE : 0)); 5393 } 5394 5395 nsresult nsIFrame::PeekBackwardAndForwardForSelection( 5396 nsSelectionAmount aAmountBack, nsSelectionAmount aAmountForward, 5397 int32_t aStartPos, bool aJumpLines, uint32_t aSelectFlags) { 5398 nsIFrame* baseFrame = this; 5399 int32_t baseOffset = aStartPos; 5400 nsresult rv; 5401 5402 PeekOffsetOptions peekOffsetOptions{PeekOffsetOption::StopAtScroller}; 5403 if (aJumpLines) { 5404 peekOffsetOptions += PeekOffsetOption::JumpLines; 5405 } 5406 5407 Element* const ancestorLimiter = [&]() -> Element* { 5408 const nsFrameSelection* const frameSelection = GetConstFrameSelection(); 5409 return frameSelection 5410 ? frameSelection 5411 ->GetAncestorLimiterOrIndependentSelectionRootElement() 5412 : nullptr; 5413 }(); 5414 5415 if (aAmountBack == eSelectWord) { 5416 // To avoid selecting the previous word when at start of word, 5417 // first move one character forward. 5418 PeekOffsetStruct pos(eSelectCharacter, eDirNext, aStartPos, nsPoint(0, 0), 5419 peekOffsetOptions, eDefaultBehavior, ancestorLimiter); 5420 rv = PeekOffset(&pos); 5421 if (NS_SUCCEEDED(rv)) { 5422 baseFrame = pos.mResultFrame; 5423 baseOffset = pos.mContentOffset; 5424 } 5425 } 5426 5427 // Search backward for a boundary. 5428 PeekOffsetStruct startpos(aAmountBack, eDirPrevious, baseOffset, 5429 nsPoint(0, 0), peekOffsetOptions, eDefaultBehavior, 5430 ancestorLimiter); 5431 rv = baseFrame->PeekOffset(&startpos); 5432 if (NS_FAILED(rv)) { 5433 return rv; 5434 } 5435 5436 // If the backward search stayed within the same frame, search forward from 5437 // that position for the end boundary; but if it crossed out to a sibling or 5438 // ancestor, start from the original position. 5439 if (startpos.mResultFrame == baseFrame) { 5440 baseOffset = startpos.mContentOffset; 5441 } else { 5442 baseFrame = this; 5443 baseOffset = aStartPos; 5444 } 5445 5446 PeekOffsetStruct endpos(aAmountForward, eDirNext, baseOffset, nsPoint(0, 0), 5447 peekOffsetOptions, eDefaultBehavior, ancestorLimiter); 5448 rv = baseFrame->PeekOffset(&endpos); 5449 if (NS_FAILED(rv)) { 5450 return rv; 5451 } 5452 5453 // Keep frameSelection alive. 5454 RefPtr<nsFrameSelection> frameSelection = GetFrameSelection(); 5455 5456 const nsFrameSelection::FocusMode focusMode = 5457 (aSelectFlags & SELECT_ACCUMULATE) 5458 ? nsFrameSelection::FocusMode::kMultiRangeSelection 5459 : nsFrameSelection::FocusMode::kCollapseToNewPoint; 5460 rv = frameSelection->HandleClick( 5461 MOZ_KnownLive(startpos.mResultContent) /* bug 1636889 */, 5462 startpos.mContentOffset, startpos.mContentOffset, focusMode, 5463 CaretAssociationHint::After); 5464 if (NS_FAILED(rv)) { 5465 return rv; 5466 } 5467 5468 rv = frameSelection->HandleClick( 5469 MOZ_KnownLive(endpos.mResultContent) /* bug 1636889 */, 5470 endpos.mContentOffset, endpos.mContentOffset, 5471 nsFrameSelection::FocusMode::kExtendSelection, 5472 CaretAssociationHint::Before); 5473 if (NS_FAILED(rv)) { 5474 return rv; 5475 } 5476 if (aAmountBack == eSelectWord) { 5477 frameSelection->SetClickSelectionType(ClickSelectionType::Double); 5478 } else if (aAmountBack == eSelectParagraph) { 5479 frameSelection->SetClickSelectionType(ClickSelectionType::Triple); 5480 } 5481 5482 // maintain selection 5483 return frameSelection->MaintainSelection(aAmountBack); 5484 } 5485 5486 NS_IMETHODIMP nsIFrame::HandleDrag(nsPresContext* aPresContext, 5487 WidgetGUIEvent* aEvent, 5488 nsEventStatus* aEventStatus) { 5489 MOZ_ASSERT(aEvent->mClass == eMouseEventClass, 5490 "HandleDrag can only handle mouse event"); 5491 5492 NS_ENSURE_ARG_POINTER(aEventStatus); 5493 5494 RefPtr<nsFrameSelection> frameselection = GetFrameSelection(); 5495 if (!frameselection) { 5496 return NS_OK; 5497 } 5498 5499 bool mouseDown = frameselection->GetDragState(); 5500 if (!mouseDown) { 5501 return NS_OK; 5502 } 5503 5504 nsIFrame* scrollbar = 5505 nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::Scrollbar); 5506 if (!scrollbar && !ShouldHandleSelectionMovementEvents()) { 5507 // XXX Do we really need to exclude non-selectable content here? 5508 // GetContentOffsetsFromPoint can handle it just fine, although some 5509 // other stuff might not like it. 5510 return NS_OK; 5511 } 5512 5513 frameselection->StopAutoScrollTimer(); 5514 5515 // Check if we are dragging in a table cell 5516 nsCOMPtr<nsIContent> parentContent; 5517 int32_t contentOffset; 5518 TableSelectionMode target; 5519 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); 5520 mozilla::PresShell* presShell = aPresContext->PresShell(); 5521 nsresult result; 5522 result = GetDataForTableSelection(frameselection, presShell, mouseEvent, 5523 getter_AddRefs(parentContent), 5524 &contentOffset, &target); 5525 5526 AutoWeakFrame weakThis = this; 5527 if (NS_SUCCEEDED(result) && parentContent) { 5528 result = frameselection->HandleTableSelection(parentContent, contentOffset, 5529 target, mouseEvent); 5530 if (NS_WARN_IF(NS_FAILED(result))) { 5531 return result; 5532 } 5533 } else { 5534 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent, 5535 RelativeTo{this}); 5536 frameselection->HandleDrag(this, pt); 5537 } 5538 5539 // The frameselection object notifies selection listeners synchronously above 5540 // which might have killed us. 5541 if (!weakThis.IsAlive()) { 5542 return NS_OK; 5543 } 5544 5545 // Get the nearest scroll container frame. 5546 ScrollContainerFrame* scrollContainerFrame = 5547 nsLayoutUtils::GetNearestScrollContainerFrame( 5548 this, nsLayoutUtils::SCROLLABLE_SAME_DOC | 5549 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); 5550 5551 if (scrollContainerFrame) { 5552 nsIFrame* capturingFrame = scrollContainerFrame->GetScrolledFrame(); 5553 if (capturingFrame) { 5554 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo( 5555 mouseEvent, RelativeTo{capturingFrame}); 5556 frameselection->StartAutoScrollTimer(capturingFrame, pt, 30); 5557 } 5558 } 5559 5560 return NS_OK; 5561 } 5562 5563 /** 5564 * This static method handles part of the nsIFrame::HandleRelease in a way 5565 * which doesn't rely on the nsFrame object to stay alive. 5566 */ 5567 MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult HandleFrameSelection( 5568 nsFrameSelection* aFrameSelection, nsIFrame::ContentOffsets& aOffsets, 5569 bool aHandleTableSel, int32_t aContentOffsetForTableSel, 5570 TableSelectionMode aTargetForTableSel, 5571 nsIContent* aParentContentForTableSel, WidgetGUIEvent* aEvent, 5572 const nsEventStatus* aEventStatus) { 5573 if (!aFrameSelection) { 5574 return NS_OK; 5575 } 5576 5577 nsresult rv = NS_OK; 5578 5579 if (nsEventStatus_eConsumeNoDefault != *aEventStatus) { 5580 if (!aHandleTableSel) { 5581 if (!aOffsets.content || !aFrameSelection->HasDelayedCaretData()) { 5582 return NS_ERROR_FAILURE; 5583 } 5584 5585 // We are doing this to simulate what we would have done on HandlePress. 5586 // We didn't do it there to give the user an opportunity to drag 5587 // the text, but since they didn't drag, we want to place the 5588 // caret. 5589 // However, we'll use the mouse position from the release, since: 5590 // * it's easier 5591 // * that's the normal click position to use (although really, in 5592 // the normal case, small movements that don't count as a drag 5593 // can do selection) 5594 aFrameSelection->SetDragState(true); 5595 5596 const nsFrameSelection::FocusMode focusMode = 5597 aFrameSelection->IsShiftDownInDelayedCaretData() 5598 ? nsFrameSelection::FocusMode::kExtendSelection 5599 : nsFrameSelection::FocusMode::kCollapseToNewPoint; 5600 rv = aFrameSelection->HandleClick( 5601 MOZ_KnownLive(aOffsets.content) /* bug 1636889 */, 5602 aOffsets.StartOffset(), aOffsets.EndOffset(), focusMode, 5603 aOffsets.associate); 5604 if (NS_FAILED(rv)) { 5605 return rv; 5606 } 5607 } else if (aParentContentForTableSel) { 5608 aFrameSelection->SetDragState(false); 5609 rv = aFrameSelection->HandleTableSelection( 5610 aParentContentForTableSel, aContentOffsetForTableSel, 5611 aTargetForTableSel, aEvent->AsMouseEvent()); 5612 if (NS_FAILED(rv)) { 5613 return rv; 5614 } 5615 } 5616 aFrameSelection->SetDelayedCaretData(0); 5617 } 5618 5619 aFrameSelection->SetDragState(false); 5620 aFrameSelection->StopAutoScrollTimer(); 5621 5622 return NS_OK; 5623 } 5624 5625 NS_IMETHODIMP nsIFrame::HandleRelease(nsPresContext* aPresContext, 5626 WidgetGUIEvent* aEvent, 5627 nsEventStatus* aEventStatus) { 5628 if (aEvent->mClass != eMouseEventClass) { 5629 return NS_OK; 5630 } 5631 5632 nsIFrame* activeFrame = GetActiveSelectionFrame(aPresContext, this); 5633 5634 nsCOMPtr<nsIContent> captureContent = PresShell::GetCapturingContent(); 5635 5636 const bool selectionOff = !ShouldHandleSelectionMovementEvents(); 5637 5638 RefPtr<nsFrameSelection> frameselection; 5639 ContentOffsets offsets; 5640 nsCOMPtr<nsIContent> parentContent; 5641 int32_t contentOffsetForTableSel = 0; 5642 TableSelectionMode targetForTableSel = TableSelectionMode::None; 5643 bool handleTableSelection = true; 5644 5645 if (!selectionOff) { 5646 frameselection = GetFrameSelection(); 5647 if (nsEventStatus_eConsumeNoDefault != *aEventStatus && frameselection) { 5648 // Check if the frameselection recorded the mouse going down. 5649 // If not, the user must have clicked in a part of the selection. 5650 // Place the caret before continuing! 5651 5652 if (frameselection->MouseDownRecorded()) { 5653 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo( 5654 aEvent, RelativeTo{this}); 5655 offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN); 5656 handleTableSelection = false; 5657 } else { 5658 GetDataForTableSelection(frameselection, PresShell(), 5659 aEvent->AsMouseEvent(), 5660 getter_AddRefs(parentContent), 5661 &contentOffsetForTableSel, &targetForTableSel); 5662 } 5663 } 5664 } 5665 5666 // We might be capturing in some other document and the event just happened to 5667 // trickle down here. Make sure that document's frame selection is notified. 5668 // Note, this may cause the current nsFrame object to be deleted, bug 336592. 5669 RefPtr<nsFrameSelection> frameSelection; 5670 if (activeFrame != this && 5671 activeFrame->ShouldHandleSelectionMovementEvents()) { 5672 frameSelection = activeFrame->GetFrameSelection(); 5673 } 5674 5675 // Also check the selection of the capturing content which might be in a 5676 // different document. 5677 if (!frameSelection && captureContent) { 5678 if (Document* doc = captureContent->GetComposedDoc()) { 5679 mozilla::PresShell* capturingPresShell = doc->GetPresShell(); 5680 if (capturingPresShell && 5681 capturingPresShell != PresContext()->GetPresShell()) { 5682 frameSelection = capturingPresShell->FrameSelection(); 5683 } 5684 } 5685 } 5686 5687 if (frameSelection) { 5688 AutoWeakFrame wf(this); 5689 frameSelection->SetDragState(false); 5690 frameSelection->StopAutoScrollTimer(); 5691 if (wf.IsAlive()) { 5692 ScrollContainerFrame* scrollContainerFrame = 5693 nsLayoutUtils::GetNearestScrollContainerFrame( 5694 this, nsLayoutUtils::SCROLLABLE_SAME_DOC | 5695 nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); 5696 if (scrollContainerFrame) { 5697 // Perform any additional scrolling needed to maintain CSS snap point 5698 // requirements when autoscrolling is over. 5699 scrollContainerFrame->ScrollSnap(); 5700 } 5701 } 5702 } 5703 5704 // Do not call any methods of the current object after this point!!! 5705 // The object is perhaps dead! 5706 5707 return selectionOff ? NS_OK 5708 : HandleFrameSelection( 5709 frameselection, offsets, handleTableSelection, 5710 contentOffsetForTableSel, targetForTableSel, 5711 parentContent, aEvent, aEventStatus); 5712 } 5713 5714 struct MOZ_STACK_CLASS FrameContentRange { 5715 FrameContentRange(nsIContent* aContent, int32_t aStart, int32_t aEnd) 5716 : content(aContent), start(aStart), end(aEnd) {} 5717 nsCOMPtr<nsIContent> content; 5718 int32_t start; 5719 int32_t end; 5720 }; 5721 5722 static bool IsRelevantBlockFrame(const nsIFrame* aFrame) { 5723 if (!aFrame->IsBlockOutside() && !aFrame->IsTableCaption()) { 5724 return false; 5725 } 5726 if (aFrame->GetContent() && 5727 aFrame->GetContent()->IsInNativeAnonymousSubtree() && 5728 !aFrame->GetContent()->HasBeenInUAWidget()) { 5729 // This helps skipping things like scrollbar parts. 5730 return false; 5731 } 5732 auto pseudoType = aFrame->Style()->GetPseudoType(); 5733 if (PseudoStyle::IsAnonBox(pseudoType)) { 5734 // Table cell contents should be considered block boundaries for this 5735 // purpose. 5736 return pseudoType == PseudoStyleType::cellContent; 5737 } 5738 return true; 5739 } 5740 5741 // Retrieve the content offsets of a frame 5742 static FrameContentRange GetRangeForFrame(const nsIFrame* aFrame) { 5743 nsIContent* content = aFrame->GetContent(); 5744 if (!content) { 5745 NS_WARNING("Frame has no content"); 5746 return FrameContentRange(nullptr, -1, -1); 5747 } 5748 5749 LayoutFrameType type = aFrame->Type(); 5750 if (type == LayoutFrameType::Text) { 5751 auto [offset, offsetEnd] = aFrame->GetOffsets(); 5752 return FrameContentRange(content, offset, offsetEnd); 5753 } 5754 5755 if (type == LayoutFrameType::Br) { 5756 nsIContent* parent = content->GetParent(); 5757 const int32_t beginOffset = parent->ComputeIndexOf_Deprecated(content); 5758 return FrameContentRange(parent, beginOffset, beginOffset); 5759 } 5760 5761 while (content->IsRootOfNativeAnonymousSubtree()) { 5762 content = content->GetParent(); 5763 } 5764 5765 if (aFrame->IsReplaced()) { 5766 if (auto* parent = content->GetParent()) { 5767 // TODO(emilio): Revise this in presence of Shadow DOM / display: 5768 // contents, it's likely that we don't want to just walk the light tree, 5769 // and we need to change the representation of FrameContentRange. 5770 Maybe<uint32_t> index = parent->ComputeIndexOf(content); 5771 MOZ_ASSERT(index.isSome()); 5772 return FrameContentRange(parent, static_cast<int32_t>(*index), 5773 static_cast<int32_t>(*index + 1)); 5774 } 5775 } 5776 5777 return FrameContentRange(content, 0, content->GetChildCount()); 5778 } 5779 5780 // The FrameTarget represents the closest frame to a point that can be selected 5781 // The frame is the frame represented, frameEdge says whether one end of the 5782 // frame is the result (in which case different handling is needed), and 5783 // afterFrame says which end is represented if frameEdge is true 5784 struct FrameTarget { 5785 explicit operator bool() const { return !!frame; } 5786 5787 nsIFrame* frame = nullptr; 5788 bool frameEdge = false; 5789 bool afterFrame = false; 5790 }; 5791 5792 // See function implementation for information 5793 static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame, 5794 const nsPoint& aPoint, 5795 uint32_t aFlags); 5796 5797 static bool SelfIsSelectable(nsIFrame* aFrame, nsIFrame* aParentFrame, 5798 uint32_t aFlags) { 5799 // We should not move selection into a native anonymous subtree when handling 5800 // selection outside it. 5801 if ((aFlags & nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE) && 5802 aParentFrame->GetClosestNativeAnonymousSubtreeRoot() != 5803 aFrame->GetClosestNativeAnonymousSubtreeRoot()) { 5804 return false; 5805 } 5806 if ((aFlags & nsIFrame::SKIP_HIDDEN) && 5807 !aFrame->StyleVisibility()->IsVisible()) { 5808 return false; 5809 } 5810 if (aFrame->IsGeneratedContentFrame()) { 5811 return false; 5812 } 5813 if (aFrame->Style()->UserSelect() == StyleUserSelect::None) { 5814 return false; 5815 } 5816 if (aFrame->IsEmpty() && 5817 (!aFrame->IsTextFrame() || !aFrame->ContentIsEditable())) { 5818 // FIXME(emilio): Historically we haven't treated empty frames as 5819 // selectable, but also we had special-cases so that editable empty text 5820 // frames returned false from IsEmpty(). Sort this out (probably by 5821 // removing the IsEmpty() condition altogether). 5822 return false; 5823 } 5824 return true; 5825 } 5826 5827 static bool FrameContentCanHaveParentSelectionRange(nsIFrame* aFrame) { 5828 // If we are only near (not directly over) then don't traverse frames with 5829 // independent selection (e.g. text controls, see bug 268497). Note that this 5830 // prevents any of the users of this method from entering form controls. 5831 // XXX We might want some way to allow using the up-arrow to go into a form 5832 // control, but the focus didn't work right anyway; it'd probably be enough 5833 // if the left and right arrows could enter textboxes (which I don't believe 5834 // they can at the moment) 5835 if (aFrame->IsTextInputFrame()) { 5836 return false; 5837 } 5838 return !aFrame->IsGeneratedContentFrame(); 5839 } 5840 5841 static bool SelectionDescendToKids(nsIFrame* aFrame) { 5842 if (!FrameContentCanHaveParentSelectionRange(aFrame)) { 5843 return false; 5844 } 5845 auto style = aFrame->Style()->UserSelect(); 5846 return style != StyleUserSelect::All && style != StyleUserSelect::None; 5847 } 5848 5849 static FrameTarget GetSelectionClosestFrameForChild(nsIFrame* aChild, 5850 const nsPoint& aPoint, 5851 uint32_t aFlags) { 5852 nsIFrame* parent = aChild->GetParent(); 5853 if (SelectionDescendToKids(aChild)) { 5854 nsPoint pt = aPoint - aChild->GetOffsetTo(parent); 5855 return GetSelectionClosestFrame(aChild, pt, aFlags); 5856 } 5857 return FrameTarget{aChild, false, false}; 5858 } 5859 5860 // When the cursor needs to be at the beginning of a block, it shouldn't be 5861 // before the first child. A click on a block whose first child is a block 5862 // should put the cursor in the child. The cursor shouldn't be between the 5863 // blocks, because that's not where it's expected. 5864 // Note that this method is guaranteed to succeed. 5865 static FrameTarget DrillDownToSelectionFrame(nsIFrame* aFrame, bool aEndFrame, 5866 uint32_t aFlags) { 5867 if (SelectionDescendToKids(aFrame)) { 5868 nsIFrame* result = nullptr; 5869 nsIFrame* frame = aFrame->PrincipalChildList().FirstChild(); 5870 if (!aEndFrame) { 5871 while (frame && !SelfIsSelectable(frame, aFrame, aFlags)) { 5872 frame = frame->GetNextSibling(); 5873 } 5874 if (frame) { 5875 result = frame; 5876 } 5877 } else { 5878 // Because the frame tree is singly linked, to find the last frame, 5879 // we have to iterate through all the frames 5880 // XXX I have a feeling this could be slow for long blocks, although 5881 // I can't find any slowdowns 5882 while (frame) { 5883 if (SelfIsSelectable(frame, aFrame, aFlags)) { 5884 result = frame; 5885 } 5886 frame = frame->GetNextSibling(); 5887 } 5888 } 5889 if (result) { 5890 return DrillDownToSelectionFrame(result, aEndFrame, aFlags); 5891 } 5892 } 5893 // If the current frame has no targetable children, target the current frame 5894 return FrameTarget{aFrame, true, aEndFrame}; 5895 } 5896 5897 // This method finds the closest valid FrameTarget on a given line; if there is 5898 // no valid FrameTarget on the line, it returns a null FrameTarget 5899 static FrameTarget GetSelectionClosestFrameForLine( 5900 nsBlockFrame* aParent, nsBlockFrame::LineIterator aLine, 5901 const nsPoint& aPoint, uint32_t aFlags) { 5902 // Account for end of lines (any iterator from the block is valid) 5903 if (aLine == aParent->LinesEnd()) { 5904 return DrillDownToSelectionFrame(aParent, true, aFlags); 5905 } 5906 nsIFrame* frame = aLine->mFirstChild; 5907 nsIFrame* closestFromIStart = nullptr; 5908 nsIFrame* closestFromIEnd = nullptr; 5909 nscoord closestIStart = aLine->IStart(), closestIEnd = aLine->IEnd(); 5910 WritingMode wm = aLine->mWritingMode; 5911 LogicalPoint pt(wm, aPoint, aLine->mContainerSize); 5912 bool canSkipBr = false; 5913 bool lastFrameWasEditable = false; 5914 for (int32_t n = aLine->GetChildCount(); n; 5915 --n, frame = frame->GetNextSibling()) { 5916 // Skip brFrames. Can only skip if the line contains at least 5917 // one selectable and non-empty frame before. Also, avoid skipping brs if 5918 // the previous thing had a different editableness than us, since then we 5919 // may end up not being able to select after it if the br is the last thing 5920 // on the line. 5921 if (!SelfIsSelectable(frame, aParent, aFlags) || 5922 (canSkipBr && frame->IsBrFrame() && 5923 lastFrameWasEditable == frame->GetContent()->IsEditable())) { 5924 continue; 5925 } 5926 canSkipBr = true; 5927 lastFrameWasEditable = 5928 frame->GetContent() && frame->GetContent()->IsEditable(); 5929 LogicalRect frameRect = 5930 LogicalRect(wm, frame->GetRect(), aLine->mContainerSize); 5931 if (pt.I(wm) >= frameRect.IStart(wm)) { 5932 if (pt.I(wm) < frameRect.IEnd(wm)) { 5933 return GetSelectionClosestFrameForChild(frame, aPoint, aFlags); 5934 } 5935 if (frameRect.IEnd(wm) >= closestIStart) { 5936 closestFromIStart = frame; 5937 closestIStart = frameRect.IEnd(wm); 5938 } 5939 } else { 5940 if (frameRect.IStart(wm) <= closestIEnd) { 5941 closestFromIEnd = frame; 5942 closestIEnd = frameRect.IStart(wm); 5943 } 5944 } 5945 } 5946 if (!closestFromIStart && !closestFromIEnd) { 5947 // We should only get here if there are no selectable frames on a line 5948 // XXX Do we need more elaborate handling here? 5949 return FrameTarget(); 5950 } 5951 if (closestFromIStart && 5952 (!closestFromIEnd || 5953 (abs(pt.I(wm) - closestIStart) <= abs(pt.I(wm) - closestIEnd)))) { 5954 return GetSelectionClosestFrameForChild(closestFromIStart, aPoint, aFlags); 5955 } 5956 return GetSelectionClosestFrameForChild(closestFromIEnd, aPoint, aFlags); 5957 } 5958 5959 // This method is for the special handling we do for block frames; they're 5960 // special because they represent paragraphs and because they are organized 5961 // into lines, which have bounds that are not stored elsewhere in the 5962 // frame tree. Returns a null FrameTarget for frames which are not 5963 // blocks or blocks with no lines except editable one. 5964 static FrameTarget GetSelectionClosestFrameForBlock(nsIFrame* aFrame, 5965 const nsPoint& aPoint, 5966 uint32_t aFlags) { 5967 nsBlockFrame* bf = do_QueryFrame(aFrame); 5968 if (!bf) { 5969 return FrameTarget(); 5970 } 5971 5972 // This code searches for the correct line 5973 nsBlockFrame::LineIterator end = bf->LinesEnd(); 5974 nsBlockFrame::LineIterator curLine = bf->LinesBegin(); 5975 nsBlockFrame::LineIterator closestLine = end; 5976 5977 if (curLine != end) { 5978 // Convert aPoint into a LogicalPoint in the writing-mode of this block 5979 WritingMode wm = curLine->mWritingMode; 5980 LogicalPoint pt(wm, aPoint, curLine->mContainerSize); 5981 do { 5982 // Check to see if our point lies within the line's block-direction bounds 5983 nscoord BCoord = pt.B(wm) - curLine->BStart(); 5984 nscoord BSize = curLine->BSize(); 5985 if (BCoord >= 0 && BCoord < BSize) { 5986 closestLine = curLine; 5987 break; // We found the line; stop looking 5988 } 5989 if (BCoord < 0) { 5990 break; 5991 } 5992 ++curLine; 5993 } while (curLine != end); 5994 5995 if (closestLine == end) { 5996 nsBlockFrame::LineIterator prevLine = curLine.prev(); 5997 nsBlockFrame::LineIterator nextLine = curLine; 5998 // Avoid empty lines 5999 while (nextLine != end && nextLine->IsEmpty()) { 6000 ++nextLine; 6001 } 6002 while (prevLine != end && prevLine->IsEmpty()) { 6003 --prevLine; 6004 } 6005 6006 // This hidden pref dictates whether a point above or below all lines 6007 // comes up with a line or the beginning or end of the frame; 0 on 6008 // Windows, 1 on other platforms by default at the writing of this code 6009 int32_t dragOutOfFrame = 6010 Preferences::GetInt("browser.drag_out_of_frame_style"); 6011 6012 if (prevLine == end) { 6013 if (dragOutOfFrame == 1 || nextLine == end) { 6014 return DrillDownToSelectionFrame(aFrame, false, aFlags); 6015 } 6016 closestLine = nextLine; 6017 } else if (nextLine == end) { 6018 if (dragOutOfFrame == 1) { 6019 return DrillDownToSelectionFrame(aFrame, true, aFlags); 6020 } 6021 closestLine = prevLine; 6022 } else { // Figure out which line is closer 6023 if (pt.B(wm) - prevLine->BEnd() < nextLine->BStart() - pt.B(wm)) { 6024 closestLine = prevLine; 6025 } else { 6026 closestLine = nextLine; 6027 } 6028 } 6029 } 6030 } 6031 6032 do { 6033 if (auto target = 6034 GetSelectionClosestFrameForLine(bf, closestLine, aPoint, aFlags)) { 6035 return target; 6036 } 6037 ++closestLine; 6038 } while (closestLine != end); 6039 6040 // Fall back to just targeting the last targetable place 6041 return DrillDownToSelectionFrame(aFrame, true, aFlags); 6042 } 6043 6044 // Use frame edge for grid, flex, table, and non-editable images. Choose the 6045 // edge based on the point position past the frame rect. If past the middle, 6046 // caret should be at end, otherwise at start. This behavior matches Blink. 6047 // 6048 // TODO(emilio): Can we use this code path for all other frames? We only get 6049 // there when we didn't find selectable children... Editable images need _not_ 6050 // to use the frame edge tho, see below. 6051 static bool UseFrameEdge(nsIFrame* aFrame) { 6052 if (aFrame->IsFlexOrGridContainer() || aFrame->IsTableFrame()) { 6053 return true; 6054 } 6055 // FIXME(bug 713387): The text frame check here shouldn't be needed. 6056 if (aFrame->IsReplaced() && !aFrame->IsTextFrame() && 6057 !aFrame->GetContent()->IsEditable()) { 6058 // Editable replaced elements are a special-case because editing relies 6059 // on clicking on an editable image selecting it, for it to show resizers. 6060 return true; 6061 } 6062 return false; 6063 } 6064 6065 static FrameTarget LastResortFrameTargetForFrame(nsIFrame* aFrame, 6066 const nsPoint& aPoint) { 6067 if (!UseFrameEdge(aFrame)) { 6068 return {aFrame, false, false}; 6069 } 6070 const auto& rect = aFrame->GetRectRelativeToSelf(); 6071 nscoord reference; 6072 nscoord middle; 6073 if (aFrame->GetWritingMode().IsVertical()) { 6074 reference = aPoint.y; 6075 middle = rect.Height() / 2; 6076 } else { 6077 reference = aPoint.x; 6078 middle = rect.Width() / 2; 6079 } 6080 const bool afterFrame = reference > middle; 6081 return {aFrame, true, afterFrame}; 6082 } 6083 6084 // GetSelectionClosestFrame is the helper function that calculates the closest 6085 // frame to the given point. 6086 // It doesn't completely account for offset styles, so needs to be used in 6087 // restricted environments. 6088 // Cannot handle overlapping frames correctly, so it should receive the output 6089 // of GetFrameForPoint 6090 // Guaranteed to return a valid FrameTarget. 6091 // aPoint is relative to aFrame. 6092 static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame, 6093 const nsPoint& aPoint, 6094 uint32_t aFlags) { 6095 // Handle blocks; if the frame isn't a block, the method fails 6096 if (auto target = GetSelectionClosestFrameForBlock(aFrame, aPoint, aFlags)) { 6097 return target; 6098 } 6099 6100 if (aFlags & nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE && 6101 !FrameContentCanHaveParentSelectionRange(aFrame)) { 6102 return LastResortFrameTargetForFrame(aFrame, aPoint); 6103 } 6104 6105 if (nsIFrame* kid = aFrame->PrincipalChildList().FirstChild()) { 6106 // Go through all the child frames to find the closest one 6107 nsIFrame::FrameWithDistance closest = {nullptr, nscoord_MAX, nscoord_MAX}; 6108 for (; kid; kid = kid->GetNextSibling()) { 6109 if (!SelfIsSelectable(kid, aFrame, aFlags)) { 6110 continue; 6111 } 6112 6113 kid->FindCloserFrameForSelection(aPoint, &closest); 6114 } 6115 if (closest.mFrame) { 6116 if (closest.mFrame->IsInSVGTextSubtree()) { 6117 return FrameTarget{closest.mFrame, false, false}; 6118 } 6119 return GetSelectionClosestFrameForChild(closest.mFrame, aPoint, aFlags); 6120 } 6121 } 6122 6123 return LastResortFrameTargetForFrame(aFrame, aPoint); 6124 } 6125 6126 static nsIFrame::ContentOffsets OffsetsForSingleFrame(nsIFrame* aFrame, 6127 const nsPoint& aPoint) { 6128 nsIFrame::ContentOffsets offsets; 6129 FrameContentRange range = GetRangeForFrame(aFrame); 6130 offsets.content = range.content; 6131 // If there are continuations (meaning it's not one rectangle), this is the 6132 // best this function can do 6133 if (aFrame->GetNextContinuation() || aFrame->GetPrevContinuation()) { 6134 offsets.offset = range.start; 6135 offsets.secondaryOffset = range.end; 6136 offsets.associate = CaretAssociationHint::After; 6137 return offsets; 6138 } 6139 6140 // Figure out whether the offsets should be over, after, or before the frame 6141 nsRect rect(nsPoint(0, 0), aFrame->GetSize()); 6142 6143 bool isBlock = !aFrame->StyleDisplay()->IsInlineFlow(); 6144 bool isRtl = (aFrame->StyleVisibility()->mDirection == StyleDirection::Rtl); 6145 if ((isBlock && rect.y < aPoint.y) || 6146 (!isBlock && ((isRtl && rect.x + rect.width / 2 > aPoint.x) || 6147 (!isRtl && rect.x + rect.width / 2 < aPoint.x)))) { 6148 offsets.offset = range.end; 6149 if (rect.Contains(aPoint)) { 6150 offsets.secondaryOffset = range.start; 6151 } else { 6152 offsets.secondaryOffset = range.end; 6153 } 6154 } else { 6155 offsets.offset = range.start; 6156 if (rect.Contains(aPoint)) { 6157 offsets.secondaryOffset = range.end; 6158 } else { 6159 offsets.secondaryOffset = range.start; 6160 } 6161 } 6162 offsets.associate = offsets.offset == range.start 6163 ? CaretAssociationHint::After 6164 : CaretAssociationHint::Before; 6165 return offsets; 6166 } 6167 6168 static nsIFrame* AdjustFrameForSelectionStyles(nsIFrame* aFrame) { 6169 nsIFrame* adjustedFrame = aFrame; 6170 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { 6171 // These are the conditions that make all children not able to handle 6172 // a cursor. 6173 auto userSelect = frame->Style()->UserSelect(); 6174 if (userSelect != StyleUserSelect::Auto && 6175 userSelect != StyleUserSelect::All) { 6176 break; 6177 } 6178 if (userSelect == StyleUserSelect::All || 6179 frame->IsGeneratedContentFrame()) { 6180 adjustedFrame = frame; 6181 } 6182 } 6183 return adjustedFrame; 6184 } 6185 6186 nsIFrame::ContentOffsets nsIFrame::GetContentOffsetsFromPoint( 6187 const nsPoint& aPoint, uint32_t aFlags) { 6188 nsIFrame* adjustedFrame; 6189 if (aFlags & IGNORE_SELECTION_STYLE) { 6190 adjustedFrame = this; 6191 } else { 6192 // This section of code deals with special selection styles. Note that 6193 // -moz-all exists, even though it doesn't need to be explicitly handled. 6194 // 6195 // The offset is forced not to end up in generated content; content offsets 6196 // cannot represent content outside of the document's content tree. 6197 6198 adjustedFrame = AdjustFrameForSelectionStyles(this); 6199 6200 // `user-select: all` needs special handling, because clicking on it should 6201 // lead to the whole frame being selected. 6202 if (adjustedFrame->Style()->UserSelect() == StyleUserSelect::All) { 6203 nsPoint adjustedPoint = aPoint + GetOffsetTo(adjustedFrame); 6204 return OffsetsForSingleFrame(adjustedFrame, adjustedPoint); 6205 } 6206 6207 // For other cases, try to find a closest frame starting from the parent of 6208 // the unselectable frame 6209 if (adjustedFrame != this) { 6210 adjustedFrame = adjustedFrame->GetParent(); 6211 } 6212 } 6213 6214 nsPoint adjustedPoint = aPoint + GetOffsetTo(adjustedFrame); 6215 6216 FrameTarget closest = 6217 GetSelectionClosestFrame(adjustedFrame, adjustedPoint, aFlags); 6218 6219 // If the correct offset is at one end of a frame, use offset-based 6220 // calculation method 6221 if (closest.frameEdge) { 6222 ContentOffsets offsets; 6223 FrameContentRange range = GetRangeForFrame(closest.frame); 6224 offsets.content = range.content; 6225 if (closest.afterFrame) { 6226 offsets.offset = range.end; 6227 } else { 6228 offsets.offset = range.start; 6229 } 6230 offsets.secondaryOffset = offsets.offset; 6231 offsets.associate = offsets.offset == range.start 6232 ? CaretAssociationHint::After 6233 : CaretAssociationHint::Before; 6234 return offsets; 6235 } 6236 6237 nsPoint pt; 6238 if (closest.frame != this) { 6239 if (closest.frame->IsInSVGTextSubtree()) { 6240 pt = nsLayoutUtils::TransformAncestorPointToFrame( 6241 RelativeTo{closest.frame}, aPoint, RelativeTo{this}); 6242 } else { 6243 pt = aPoint - closest.frame->GetOffsetTo(this); 6244 } 6245 } else { 6246 pt = aPoint; 6247 } 6248 return closest.frame->CalcContentOffsetsFromFramePoint(pt); 6249 6250 // XXX should I add some kind of offset standardization? 6251 // consider <b>xxxxx</b><i>zzzzz</i>; should any click between the last 6252 // x and first z put the cursor in the same logical position in addition 6253 // to the same visual position? 6254 } 6255 6256 nsIFrame::ContentOffsets nsIFrame::CalcContentOffsetsFromFramePoint( 6257 const nsPoint& aPoint) { 6258 return OffsetsForSingleFrame(this, aPoint); 6259 } 6260 6261 bool nsIFrame::AssociateImage(const StyleImage& aImage) { 6262 imgRequestProxy* req = aImage.GetImageRequest(); 6263 if (!req) { 6264 return false; 6265 } 6266 6267 PresContext()->Document()->EnsureStyleImageLoader().AssociateRequestToFrame( 6268 req, this); 6269 return true; 6270 } 6271 6272 void nsIFrame::DisassociateImage(const StyleImage& aImage) { 6273 imgRequestProxy* req = aImage.GetImageRequest(); 6274 if (!req) { 6275 return; 6276 } 6277 6278 PresContext() 6279 ->Document() 6280 ->EnsureStyleImageLoader() 6281 .DisassociateRequestFromFrame(req, this); 6282 } 6283 6284 StyleImageRendering nsIFrame::UsedImageRendering() const { 6285 ComputedStyle* style; 6286 if (IsCanvasFrame()) { 6287 // XXXdholbert Maybe we should use FindCanvasBackground here (instead of 6288 // FindBackground), since we're inside an IsCanvasFrame check? Though then 6289 // we'd also have to copypaste or abstract-away the multi-part root-frame 6290 // lookup that the canvas-flavored API requires. 6291 style = nsCSSRendering::FindBackground(this); 6292 } else { 6293 style = Style(); 6294 } 6295 return style->StyleVisibility()->mImageRendering; 6296 } 6297 6298 // The touch-action CSS property applies to: all elements except: non-replaced 6299 // inline elements, table rows, row groups, table columns, and column groups. 6300 StyleTouchAction nsIFrame::UsedTouchAction() const { 6301 if (IsLineParticipant()) { 6302 return StyleTouchAction::AUTO; 6303 } 6304 auto& disp = *StyleDisplay(); 6305 if (disp.IsInternalTableStyleExceptCell()) { 6306 return StyleTouchAction::AUTO; 6307 } 6308 return disp.mTouchAction; 6309 } 6310 6311 nsIFrame::Cursor nsIFrame::GetCursor(const nsPoint&) { 6312 StyleCursorKind kind = StyleUI()->Cursor().keyword; 6313 if (kind == StyleCursorKind::Auto) { 6314 // If this is editable, I-beam cursor is better for most elements. 6315 kind = (mContent && mContent->IsEditable()) ? StyleCursorKind::Text 6316 : StyleCursorKind::Default; 6317 } 6318 if (kind == StyleCursorKind::Text && GetWritingMode().IsVertical()) { 6319 // Per CSS UI spec, UA may treat value 'text' as 6320 // 'vertical-text' for vertical text. 6321 kind = StyleCursorKind::VerticalText; 6322 } 6323 6324 return Cursor{kind, AllowCustomCursorImage::Yes}; 6325 } 6326 6327 // Resize and incremental reflow 6328 6329 /* virtual */ 6330 void nsIFrame::MarkIntrinsicISizesDirty() { 6331 // If we're a flex item, clear our flex-item-specific cached measurements 6332 // (which likely depended on our now-stale intrinsic isize). 6333 if (IsFlexItem()) { 6334 nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(this); 6335 } 6336 6337 if (IsGridItem()) { 6338 nsGridContainerFrame::MarkCachedGridMeasurementsDirty(this); 6339 } 6340 6341 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) { 6342 nsFontInflationData::MarkFontInflationDataTextDirty(this); 6343 } 6344 } 6345 6346 void nsIFrame::MarkSubtreeDirty() { 6347 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 6348 return; 6349 } 6350 // Unconditionally mark given frame dirty. 6351 AddStateBits(NS_FRAME_IS_DIRTY); 6352 6353 // Mark all descendants dirty, unless: 6354 // - Already dirty. 6355 // - TableColGroup 6356 AutoTArray<nsIFrame*, 32> stack; 6357 for (const auto& childLists : ChildLists()) { 6358 for (nsIFrame* kid : childLists.mList) { 6359 stack.AppendElement(kid); 6360 } 6361 } 6362 while (!stack.IsEmpty()) { 6363 nsIFrame* f = stack.PopLastElement(); 6364 if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY) || f->IsTableColGroupFrame()) { 6365 continue; 6366 } 6367 6368 f->AddStateBits(NS_FRAME_IS_DIRTY); 6369 6370 for (const auto& childLists : f->ChildLists()) { 6371 for (nsIFrame* kid : childLists.mList) { 6372 stack.AppendElement(kid); 6373 } 6374 } 6375 } 6376 } 6377 6378 /* virtual */ 6379 void nsIFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput, 6380 InlineMinISizeData* aData) { 6381 // Note: we are one of the children that mPercentageBasisForChildren was 6382 // prepared for (i.e. our parent frame prepares the percentage basis for us, 6383 // not for our own children). Hence it's fine that we're resolving our 6384 // percentages sizes against this basis in IntrinsicForContainer(). 6385 nscoord isize = nsLayoutUtils::IntrinsicForContainer( 6386 aInput.mContext, this, IntrinsicISizeType::MinISize, 6387 aInput.mPercentageBasisForChildren); 6388 aData->DefaultAddInlineMinISize(this, isize); 6389 } 6390 6391 /* virtual */ 6392 void nsIFrame::AddInlinePrefISize(const IntrinsicSizeInput& aInput, 6393 nsIFrame::InlinePrefISizeData* aData) { 6394 // Note: we are one of the children that mPercentageBasisForChildren was 6395 // prepared for (i.e. our parent frame prepares the percentage basis for us, 6396 // not for our own children). Hence it's fine that we're resolving our 6397 // percentages sizes against this basis in IntrinsicForContainer(). 6398 nscoord isize = nsLayoutUtils::IntrinsicForContainer( 6399 aInput.mContext, this, IntrinsicISizeType::PrefISize, 6400 aInput.mPercentageBasisForChildren); 6401 aData->DefaultAddInlinePrefISize(isize); 6402 } 6403 6404 void nsIFrame::InlineMinISizeData::DefaultAddInlineMinISize(nsIFrame* aFrame, 6405 nscoord aISize, 6406 bool aAllowBreak) { 6407 auto parent = aFrame->GetParent(); 6408 MOZ_ASSERT(parent, "Must have a parent if we get here!"); 6409 const bool mayBreak = aAllowBreak && !aFrame->CanContinueTextRun() && 6410 !parent->Style()->ShouldSuppressLineBreak() && 6411 parent->StyleText()->WhiteSpaceCanWrap(parent); 6412 if (mayBreak) { 6413 OptionallyBreak(); 6414 } 6415 mTrailingWhitespace = 0; 6416 mSkipWhitespace = false; 6417 mCurrentLine += aISize; 6418 mAtStartOfLine = false; 6419 if (mayBreak) { 6420 OptionallyBreak(); 6421 } 6422 } 6423 6424 void nsIFrame::InlinePrefISizeData::DefaultAddInlinePrefISize(nscoord aISize) { 6425 mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, aISize); 6426 mTrailingWhitespace = 0; 6427 mSkipWhitespace = false; 6428 mLineIsEmpty = false; 6429 } 6430 6431 void nsIFrame::InlineMinISizeData::ForceBreak() { 6432 mCurrentLine -= mTrailingWhitespace; 6433 mPrevLines = std::max(mPrevLines, mCurrentLine); 6434 mCurrentLine = mTrailingWhitespace = 0; 6435 6436 for (const FloatInfo& floatInfo : mFloats) { 6437 mPrevLines = std::max(floatInfo.ISize(), mPrevLines); 6438 } 6439 mFloats.Clear(); 6440 mSkipWhitespace = true; 6441 } 6442 6443 void nsIFrame::InlineMinISizeData::OptionallyBreak(nscoord aHyphenWidth) { 6444 // If we can fit more content into a smaller width by staying on this 6445 // line (because we're still at a negative offset due to negative 6446 // text-indent or negative margin), don't break. Otherwise, do the 6447 // same as ForceBreak. it doesn't really matter when we accumulate 6448 // floats. 6449 if (mCurrentLine + aHyphenWidth < 0 || mAtStartOfLine) { 6450 return; 6451 } 6452 mCurrentLine += aHyphenWidth; 6453 ForceBreak(); 6454 } 6455 6456 void nsIFrame::InlinePrefISizeData::ForceBreak(UsedClear aClearType) { 6457 // If this force break is not clearing any float, we can leave all the 6458 // floats to the next force break. 6459 if (!mFloats.IsEmpty() && aClearType != UsedClear::None) { 6460 // Preferred isize accumulated for floats that have already 6461 // been cleared past 6462 nscoord floatsDone = 0; 6463 // Preferred isize accumulated for floats that have not yet 6464 // been cleared past 6465 nscoord floatsCurLeft = 0, floatsCurRight = 0; 6466 6467 for (const FloatInfo& floatInfo : mFloats) { 6468 const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay(); 6469 auto cbWM = floatInfo.Frame()->GetParent()->GetWritingMode(); 6470 UsedClear clearType = floatDisp->UsedClear(cbWM); 6471 if (clearType == UsedClear::Left || clearType == UsedClear::Right || 6472 clearType == UsedClear::Both) { 6473 nscoord floatsCur = NSCoordSaturatingAdd(floatsCurLeft, floatsCurRight); 6474 if (floatsCur > floatsDone) { 6475 floatsDone = floatsCur; 6476 } 6477 if (clearType != UsedClear::Right) { 6478 floatsCurLeft = 0; 6479 } 6480 if (clearType != UsedClear::Left) { 6481 floatsCurRight = 0; 6482 } 6483 } 6484 6485 UsedFloat floatStyle = floatDisp->UsedFloat(cbWM); 6486 nscoord& floatsCur = 6487 floatStyle == UsedFloat::Left ? floatsCurLeft : floatsCurRight; 6488 nscoord floatISize = floatInfo.ISize(); 6489 // Negative-width floats don't change the available space so they 6490 // shouldn't change our intrinsic line isize either. 6491 floatsCur = NSCoordSaturatingAdd(floatsCur, std::max(0, floatISize)); 6492 } 6493 6494 nscoord floatsCur = NSCoordSaturatingAdd(floatsCurLeft, floatsCurRight); 6495 if (floatsCur > floatsDone) { 6496 floatsDone = floatsCur; 6497 } 6498 6499 mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, floatsDone); 6500 6501 if (aClearType == UsedClear::Both) { 6502 mFloats.Clear(); 6503 } else { 6504 // If the break type does not clear all floats, it means there may 6505 // be some floats whose isize should contribute to the intrinsic 6506 // isize of the next line. The code here scans the current mFloats 6507 // and keeps floats which are not cleared by this break. Note that 6508 // floats may be cleared directly or indirectly. See below. 6509 nsTArray<FloatInfo> newFloats; 6510 MOZ_ASSERT( 6511 aClearType == UsedClear::Left || aClearType == UsedClear::Right, 6512 "Other values should have been handled in other branches"); 6513 UsedFloat clearFloatType = 6514 aClearType == UsedClear::Left ? UsedFloat::Left : UsedFloat::Right; 6515 // Iterate the array in reverse so that we can stop when there are 6516 // no longer any floats we need to keep. See below. 6517 for (FloatInfo& floatInfo : Reversed(mFloats)) { 6518 const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay(); 6519 auto cbWM = floatInfo.Frame()->GetParent()->GetWritingMode(); 6520 if (floatDisp->UsedFloat(cbWM) != clearFloatType) { 6521 newFloats.AppendElement(floatInfo); 6522 } else { 6523 // This is a float on the side that this break directly clears 6524 // which means we're not keeping it in mFloats. However, if 6525 // this float clears floats on the opposite side (via a value 6526 // of either 'both' or one of 'left'/'right'), any remaining 6527 // (earlier) floats on that side would be indirectly cleared 6528 // as well. Thus, we should break out of this loop and stop 6529 // considering earlier floats to be kept in mFloats. 6530 UsedClear clearType = floatDisp->UsedClear(cbWM); 6531 if (clearType != aClearType && clearType != UsedClear::None) { 6532 break; 6533 } 6534 } 6535 } 6536 newFloats.Reverse(); 6537 mFloats = std::move(newFloats); 6538 } 6539 } 6540 6541 mCurrentLine = 6542 NSCoordSaturatingSubtract(mCurrentLine, mTrailingWhitespace, nscoord_MAX); 6543 mPrevLines = std::max(mPrevLines, mCurrentLine); 6544 mCurrentLine = mTrailingWhitespace = 0; 6545 mSkipWhitespace = true; 6546 mLineIsEmpty = true; 6547 } 6548 6549 static nscoord ResolvePadding(const LengthPercentage& aStyle, 6550 nscoord aPercentageBasis) { 6551 return nsLayoutUtils::ResolveToLength<true>(aStyle, aPercentageBasis); 6552 } 6553 6554 static nscoord ResolveMargin(const AnchorResolvedMargin& aStyle, 6555 nscoord aPercentageBasis) { 6556 if (!aStyle->IsLengthPercentage()) { 6557 return nscoord(0); 6558 } 6559 return nsLayoutUtils::ResolveToLength<false>(aStyle->AsLengthPercentage(), 6560 aPercentageBasis); 6561 } 6562 static nsIFrame::IntrinsicSizeOffsetData IntrinsicSizeOffsets( 6563 nsIFrame* aFrame, nscoord aPercentageBasis, bool aForISize) { 6564 nsIFrame::IntrinsicSizeOffsetData result; 6565 WritingMode wm = aFrame->GetWritingMode(); 6566 bool verticalAxis = aForISize == wm.IsVertical(); 6567 const auto* styleMargin = aFrame->StyleMargin(); 6568 const auto anchorResolutionParams = AnchorPosResolutionParams::From(aFrame); 6569 if (verticalAxis) { 6570 result.margin += 6571 ResolveMargin(styleMargin->GetMargin(eSideTop, anchorResolutionParams), 6572 aPercentageBasis); 6573 result.margin += ResolveMargin( 6574 styleMargin->GetMargin(eSideBottom, anchorResolutionParams), 6575 aPercentageBasis); 6576 } else { 6577 result.margin += 6578 ResolveMargin(styleMargin->GetMargin(eSideLeft, anchorResolutionParams), 6579 aPercentageBasis); 6580 result.margin += ResolveMargin( 6581 styleMargin->GetMargin(eSideRight, anchorResolutionParams), 6582 aPercentageBasis); 6583 } 6584 6585 const auto& padding = aFrame->StylePadding()->mPadding; 6586 if (verticalAxis) { 6587 result.padding += ResolvePadding(padding.Get(eSideTop), aPercentageBasis); 6588 result.padding += 6589 ResolvePadding(padding.Get(eSideBottom), aPercentageBasis); 6590 } else { 6591 result.padding += ResolvePadding(padding.Get(eSideLeft), aPercentageBasis); 6592 result.padding += ResolvePadding(padding.Get(eSideRight), aPercentageBasis); 6593 } 6594 6595 const nsStyleBorder* styleBorder = aFrame->StyleBorder(); 6596 if (verticalAxis) { 6597 result.border += styleBorder->GetComputedBorderWidth(eSideTop); 6598 result.border += styleBorder->GetComputedBorderWidth(eSideBottom); 6599 } else { 6600 result.border += styleBorder->GetComputedBorderWidth(eSideLeft); 6601 result.border += styleBorder->GetComputedBorderWidth(eSideRight); 6602 } 6603 6604 const nsStyleDisplay* disp = aFrame->StyleDisplay(); 6605 if (aFrame->IsThemed(disp)) { 6606 nsPresContext* presContext = aFrame->PresContext(); 6607 6608 LayoutDeviceIntMargin border = presContext->Theme()->GetWidgetBorder( 6609 presContext->DeviceContext(), aFrame, disp->EffectiveAppearance()); 6610 result.border = presContext->DevPixelsToAppUnits( 6611 verticalAxis ? border.TopBottom() : border.LeftRight()); 6612 6613 LayoutDeviceIntMargin padding; 6614 if (presContext->Theme()->GetWidgetPadding( 6615 presContext->DeviceContext(), aFrame, disp->EffectiveAppearance(), 6616 &padding)) { 6617 result.padding = presContext->DevPixelsToAppUnits( 6618 verticalAxis ? padding.TopBottom() : padding.LeftRight()); 6619 } 6620 } 6621 return result; 6622 } 6623 6624 /* virtual */ nsIFrame::IntrinsicSizeOffsetData nsIFrame::IntrinsicISizeOffsets( 6625 nscoord aPercentageBasis) { 6626 return IntrinsicSizeOffsets(this, aPercentageBasis, true); 6627 } 6628 6629 nsIFrame::IntrinsicSizeOffsetData nsIFrame::IntrinsicBSizeOffsets( 6630 nscoord aPercentageBasis) { 6631 return IntrinsicSizeOffsets(this, aPercentageBasis, false); 6632 } 6633 6634 /* virtual */ 6635 IntrinsicSize nsIFrame::GetIntrinsicSize() { 6636 // Defaults to no intrinsic size. 6637 return IntrinsicSize(); 6638 } 6639 6640 AspectRatio nsIFrame::GetAspectRatio() const { 6641 // Per spec, 'aspect-ratio' property applies to all elements except inline 6642 // boxes and internal ruby or table boxes. 6643 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio 6644 // For those frame types that don't support aspect-ratio, they must not have 6645 // the natural ratio, so this early return is fine. 6646 if (!SupportsAspectRatio()) { 6647 return AspectRatio(); 6648 } 6649 6650 const StyleAspectRatio& aspectRatio = StylePosition()->mAspectRatio; 6651 // If aspect-ratio is zero or infinite, it's a degenerate ratio and behaves 6652 // as auto. 6653 // https://drafts.csswg.org/css-sizing-4/#valdef-aspect-ratio-ratio 6654 if (!aspectRatio.BehavesAsAuto()) { 6655 // Non-auto. Return the preferred aspect ratio from the aspect-ratio style. 6656 return aspectRatio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::Yes); 6657 } 6658 6659 // The rest of the cases are when aspect-ratio has 'auto'. 6660 if (auto intrinsicRatio = GetIntrinsicRatio()) { 6661 return intrinsicRatio; 6662 } 6663 6664 if (aspectRatio.HasRatio()) { 6665 // If it's a degenerate ratio, this returns 0. Just the same as the auto 6666 // case. 6667 return aspectRatio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::No); 6668 } 6669 6670 return AspectRatio(); 6671 } 6672 6673 /* virtual */ 6674 AspectRatio nsIFrame::GetIntrinsicRatio() const { return AspectRatio(); } 6675 6676 static bool ShouldApplyAutomaticMinimumOnInlineAxis( 6677 WritingMode aWM, bool aIsScrollableOverflow, 6678 const AnchorPosResolutionParams& aParams, 6679 const nsStylePosition* aPosition) { 6680 // Apply the automatic minimum size for aspect ratio: 6681 // Note: The replaced elements shouldn't be here, so we only check the scroll 6682 // container. 6683 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum 6684 return !aIsScrollableOverflow && aPosition->MinISize(aWM, aParams)->IsAuto(); 6685 } 6686 6687 /* virtual */ 6688 nsIFrame::SizeComputationResult nsIFrame::ComputeSize( 6689 const SizeComputationInput& aSizingInput, WritingMode aWM, 6690 const LogicalSize& aCBSize, nscoord aAvailableISize, 6691 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 6692 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 6693 MOZ_ASSERT(!GetIntrinsicRatio(), 6694 "Please override this method and call " 6695 "nsContainerFrame::ComputeSizeWithIntrinsicDimensions instead."); 6696 LogicalSize result = 6697 ComputeAutoSize(aSizingInput, aWM, aCBSize, aAvailableISize, aMargin, 6698 aBorderPadding, aSizeOverrides, aFlags); 6699 const nsStylePosition* stylePos = StylePosition(); 6700 const nsStyleDisplay* disp = StyleDisplay(); 6701 const auto anchorResolutionParams = AnchorPosResolutionParams::From(this); 6702 auto aspectRatioUsage = AspectRatioUsage::None; 6703 6704 const auto boxSizingAdjust = stylePos->mBoxSizing == StyleBoxSizing::Border 6705 ? aBorderPadding 6706 : LogicalSize(aWM); 6707 nscoord boxSizingToMarginEdgeISize = aMargin.ISize(aWM) + 6708 aBorderPadding.ISize(aWM) - 6709 boxSizingAdjust.ISize(aWM); 6710 6711 const auto& aspectRatio = aSizeOverrides.mAspectRatio 6712 ? *aSizeOverrides.mAspectRatio 6713 : GetAspectRatio(); 6714 const auto styleISize = 6715 aSizeOverrides.mStyleISize 6716 ? AnchorResolvedSizeHelper::Overridden(*aSizeOverrides.mStyleISize) 6717 : stylePos->ISize(aWM, anchorResolutionParams); 6718 // For bsize, we consider overrides *and then* we resolve 'stretch' to a 6719 // nscoord value, for convenience (so that we can assume that either 6720 // isAutoBSize is true, or styleBSize is of type LengthPercentage()). 6721 const auto styleBSize = [&] { 6722 auto styleBSizeConsideringOverrides = 6723 aSizeOverrides.mStyleBSize 6724 ? AnchorResolvedSizeHelper::Overridden(*aSizeOverrides.mStyleBSize) 6725 : stylePos->BSize(aWM, anchorResolutionParams); 6726 if (styleBSizeConsideringOverrides->BehavesLikeStretchOnBlockAxis() && 6727 aCBSize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { 6728 // We've got a 'stretch' BSize; resolve it to a length: 6729 nscoord stretchBSize = nsLayoutUtils::ComputeStretchBSize( 6730 aCBSize.BSize(aWM), aMargin.BSize(aWM), aBorderPadding.BSize(aWM), 6731 stylePos->mBoxSizing); 6732 // Note(dshin): This allocates. 6733 return AnchorResolvedSizeHelper::LengthPercentage( 6734 LengthPercentage::FromAppUnits(stretchBSize)); 6735 } 6736 return styleBSizeConsideringOverrides; 6737 }(); 6738 6739 auto parentFrame = GetParent(); 6740 auto alignCB = parentFrame; 6741 bool isGridItem = IsGridItem(); 6742 const bool isSubgrid = IsSubgrid(); 6743 if (parentFrame && parentFrame->IsTableWrapperFrame() && IsTableFrame()) { 6744 // An inner table frame is sized as a grid item if its table wrapper is, 6745 // because they actually have the same CB (the wrapper's CB). 6746 // @see ReflowInput::InitCBReflowInput 6747 auto tableWrapper = GetParent(); 6748 auto grandParent = tableWrapper->GetParent(); 6749 isGridItem = grandParent->IsGridContainerFrame() && 6750 !tableWrapper->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); 6751 if (isGridItem) { 6752 // When resolving justify/align-self below, we want to use the grid 6753 // container's justify/align-items value and WritingMode. 6754 alignCB = grandParent; 6755 } 6756 } 6757 6758 // flexItemMainAxis is set if this frame is a flex item in a modern flexbox 6759 // layout. It indicates which logical axis (in this frame's own WM) 6760 // corresponds to its flex container's main axis. 6761 Maybe<LogicalAxis> flexItemMainAxis; 6762 if (IsFlexItem() && !parentFrame->HasAnyStateBits( 6763 NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX)) { 6764 flexItemMainAxis = Some(nsFlexContainerFrame::IsItemInlineAxisMainAxis(this) 6765 ? LogicalAxis::Inline 6766 : LogicalAxis::Block); 6767 } 6768 6769 const bool isAutoISize = styleISize->IsAuto(); 6770 const bool isAutoBSize = 6771 nsLayoutUtils::IsAutoBSize(*styleBSize, aCBSize.BSize(aWM)); 6772 6773 MOZ_ASSERT(isAutoBSize || styleBSize->IsLengthPercentage(), 6774 "We should have resolved away any non-'auto'-like flavors " 6775 "of styleBSize into a LengthPercentage. (If this fails, we " 6776 "might run afoul of some AsLengthPercentage() call below.)"); 6777 6778 // Compute inline-axis size 6779 const bool isSubgriddedInInlineAxis = 6780 isSubgrid && static_cast<nsGridContainerFrame*>(this)->IsColSubgrid(); 6781 6782 // Per https://drafts.csswg.org/css-grid/#subgrid-box-alignment, if we are 6783 // subgridded in the inline-axis, ignore our style inline-size, and stretch to 6784 // fill the CB. 6785 const bool shouldComputeISize = !isAutoISize && !isSubgriddedInInlineAxis; 6786 if (shouldComputeISize) { 6787 auto iSizeResult = 6788 ComputeISizeValue(aSizingInput.mRenderingContext, aWM, aCBSize, 6789 boxSizingAdjust, boxSizingToMarginEdgeISize, 6790 *styleISize, *styleBSize, aspectRatio, aFlags); 6791 result.ISize(aWM) = iSizeResult.mISize; 6792 aspectRatioUsage = iSizeResult.mAspectRatioUsage; 6793 } else if (MOZ_UNLIKELY(isGridItem) && !IsTrueOverflowContainer()) { 6794 // 'auto' inline-size for grid-level box - fill the CB for 'stretch' / 6795 // 'normal' and clamp it to the CB if requested: 6796 bool isStretchAligned = false; 6797 bool mayUseAspectRatio = aspectRatio && !isAutoBSize; 6798 if (!aFlags.contains(ComputeSizeFlag::ShrinkWrap) && 6799 !StyleMargin()->HasInlineAxisAuto(aWM, anchorResolutionParams) && 6800 !alignCB->IsMasonry(aWM, LogicalAxis::Inline)) { 6801 auto inlineAxisAlignment = stylePos->UsedSelfAlignment( 6802 aWM, LogicalAxis::Inline, alignCB->GetWritingMode(), 6803 alignCB->Style()); 6804 isStretchAligned = inlineAxisAlignment == StyleAlignFlags::STRETCH || 6805 (inlineAxisAlignment == StyleAlignFlags::NORMAL && 6806 !mayUseAspectRatio); 6807 } 6808 6809 // Apply the preferred aspect ratio for alignments other than *stretch* and 6810 // *normal without aspect ratio*. 6811 // The spec says all other values should size the items as fit-content, and 6812 // the intrinsic size should respect the preferred aspect ratio, so we also 6813 // apply aspect ratio for all other values. 6814 // https://drafts.csswg.org/css-grid/#grid-item-sizing 6815 if (!isStretchAligned && mayUseAspectRatio) { 6816 result.ISize(aWM) = ComputeISizeValueFromAspectRatio( 6817 aWM, aCBSize, boxSizingAdjust, styleBSize->AsLengthPercentage(), 6818 aspectRatio); 6819 aspectRatioUsage = AspectRatioUsage::ToComputeISize; 6820 } 6821 6822 if (isStretchAligned || 6823 aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) { 6824 auto iSizeToFillCB = 6825 std::max(nscoord(0), aCBSize.ISize(aWM) - aBorderPadding.ISize(aWM) - 6826 aMargin.ISize(aWM)); 6827 if (isStretchAligned || result.ISize(aWM) > iSizeToFillCB) { 6828 result.ISize(aWM) = iSizeToFillCB; 6829 } 6830 } 6831 } else if (aspectRatio && !isAutoBSize) { 6832 // Note: if both the inline size and the block size are auto, the block axis 6833 // is the ratio-dependent axis by default. That means we only need to 6834 // transfer the resolved inline size via aspect-ratio to block axis later in 6835 // this method, but not the other way around. 6836 // 6837 // In this branch, we transfer the non-auto block size via aspect-ration to 6838 // inline axis. 6839 result.ISize(aWM) = ComputeISizeValueFromAspectRatio( 6840 aWM, aCBSize, boxSizingAdjust, styleBSize->AsLengthPercentage(), 6841 aspectRatio); 6842 aspectRatioUsage = AspectRatioUsage::ToComputeISize; 6843 } 6844 6845 // Calculate and apply transferred min & max size contraints. 6846 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-size-transfers 6847 // 6848 // Note: The basic principle is that sizing constraints transfer through the 6849 // aspect-ratio to the other side to preserve the aspect ratio to the extent 6850 // that they can without violating any sizes specified explicitly on that 6851 // affected axis. 6852 // 6853 // FIXME: The spec words may not be correct, so we may have to update this 6854 // tentative solution once this spec issue gets resolved. Here, we clamp the 6855 // flex base size by the transferred min and max sizes, and don't include 6856 // the transferred min & max sizes into its used min & max sizes. So this 6857 // lets us match other browsers' current behaviors. 6858 // https://github.com/w3c/csswg-drafts/issues/6071 6859 // 6860 // Note: This may make more sense if we clamp the flex base size in 6861 // FlexItem::ResolveFlexBaseSizeFromAspectRatio(). However, the result should 6862 // be identical. FlexItem::ResolveFlexBaseSizeFromAspectRatio() only handles 6863 // the case of the definite cross size, and the definite cross size is clamped 6864 // by the min & max cross sizes below in this function. This means its flex 6865 // base size has been clamped by the transferred min & max size already after 6866 // generating the flex items. So here we make the code more general for both 6867 // definite cross size and indefinite cross size. 6868 const bool isDefiniteISize = styleISize->IsLengthPercentage(); 6869 const auto minBSizeCoord = stylePos->MinBSize(aWM, anchorResolutionParams); 6870 const auto maxBSizeCoord = stylePos->MaxBSize(aWM, anchorResolutionParams); 6871 const bool isAutoMinBSize = 6872 nsLayoutUtils::IsAutoBSize(*minBSizeCoord, aCBSize.BSize(aWM)); 6873 const bool isAutoMaxBSize = 6874 nsLayoutUtils::IsAutoBSize(*maxBSizeCoord, aCBSize.BSize(aWM)); 6875 if (aspectRatio && !isDefiniteISize) { 6876 // Note: the spec mentions that 6877 // 1. This transferred minimum is capped by any definite preferred or 6878 // maximum size in the destination axis. 6879 // 2. This transferred maximum is floored by any definite preferred or 6880 // minimum size in the destination axis. 6881 // 6882 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-size-transfers 6883 // 6884 // The spec requires us to clamp these by the specified size (it calls it 6885 // the preferred size). However, we actually don't need to worry about that, 6886 // because we are here only if the inline size is indefinite. 6887 // 6888 // We do not need to clamp the transferred minimum and maximum as long as we 6889 // always apply the transferred min/max size before the explicit min/max 6890 // size; the result will be identical. 6891 const nscoord transferredMinISize = 6892 isAutoMinBSize ? 0 6893 : ComputeISizeValueFromAspectRatio( 6894 aWM, aCBSize, boxSizingAdjust, 6895 minBSizeCoord->AsLengthPercentage(), aspectRatio); 6896 const nscoord transferredMaxISize = 6897 isAutoMaxBSize ? nscoord_MAX 6898 : ComputeISizeValueFromAspectRatio( 6899 aWM, aCBSize, boxSizingAdjust, 6900 maxBSizeCoord->AsLengthPercentage(), aspectRatio); 6901 6902 result.ISize(aWM) = 6903 CSSMinMax(result.ISize(aWM), transferredMinISize, transferredMaxISize); 6904 } 6905 6906 // Flex items ignore their min & max sizing properties in their flex 6907 // container's main-axis. (Those properties get applied later in the flexbox 6908 // algorithm.) 6909 const bool isFlexItemInlineAxisMainAxis = 6910 flexItemMainAxis && *flexItemMainAxis == LogicalAxis::Inline; 6911 // Grid items that are subgridded in inline-axis also ignore their min & max 6912 // sizing properties in that axis. 6913 const bool shouldIgnoreMinMaxISize = 6914 isFlexItemInlineAxisMainAxis || isSubgriddedInInlineAxis; 6915 const auto maxISizeCoord = stylePos->MaxISize(aWM, anchorResolutionParams); 6916 nscoord maxISize = NS_UNCONSTRAINEDSIZE; 6917 if (!maxISizeCoord->IsNone() && !shouldIgnoreMinMaxISize) { 6918 maxISize = 6919 ComputeISizeValue(aSizingInput.mRenderingContext, aWM, aCBSize, 6920 boxSizingAdjust, boxSizingToMarginEdgeISize, 6921 *maxISizeCoord, *styleBSize, aspectRatio, aFlags) 6922 .mISize; 6923 result.ISize(aWM) = std::min(maxISize, result.ISize(aWM)); 6924 } 6925 6926 const nscoord bSizeAsPercentageBasis = ComputeBSizeValueAsPercentageBasis( 6927 *styleBSize, *minBSizeCoord, *maxBSizeCoord, aCBSize.BSize(aWM), 6928 boxSizingAdjust.BSize(aWM)); 6929 const IntrinsicSizeInput input( 6930 aSizingInput.mRenderingContext, 6931 Some(aCBSize.ConvertTo(GetWritingMode(), aWM)), 6932 Some(LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, bSizeAsPercentageBasis) 6933 .ConvertTo(GetWritingMode(), aWM))); 6934 const auto minISizeCoord = stylePos->MinISize(aWM, anchorResolutionParams); 6935 nscoord minISize; 6936 if (!minISizeCoord->IsAuto() && !shouldIgnoreMinMaxISize) { 6937 minISize = 6938 ComputeISizeValue(aSizingInput.mRenderingContext, aWM, aCBSize, 6939 boxSizingAdjust, boxSizingToMarginEdgeISize, 6940 *minISizeCoord, *styleBSize, aspectRatio, aFlags) 6941 .mISize; 6942 } else if (MOZ_UNLIKELY( 6943 aFlags.contains(ComputeSizeFlag::IApplyAutoMinSize))) { 6944 // This implements "Implied Minimum Size of Grid Items". 6945 // https://drafts.csswg.org/css-grid/#min-size-auto 6946 minISize = std::min(maxISize, GetMinISize(input)); 6947 if (styleISize->IsLengthPercentage()) { 6948 minISize = std::min(minISize, result.ISize(aWM)); 6949 } else if (aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) { 6950 // "if the grid item spans only grid tracks that have a fixed max track 6951 // sizing function, its automatic minimum size in that dimension is 6952 // further clamped to less than or equal to the size necessary to fit 6953 // its margin box within the resulting grid area (flooring at zero)" 6954 // https://drafts.csswg.org/css-grid/#min-size-auto 6955 auto maxMinISize = 6956 std::max(nscoord(0), aCBSize.ISize(aWM) - aBorderPadding.ISize(aWM) - 6957 aMargin.ISize(aWM)); 6958 minISize = std::min(minISize, maxMinISize); 6959 } 6960 } else if (aspectRatioUsage == AspectRatioUsage::ToComputeISize && 6961 ShouldApplyAutomaticMinimumOnInlineAxis( 6962 aWM, disp->IsScrollableOverflow(), anchorResolutionParams, 6963 stylePos)) { 6964 // This means we successfully applied aspect-ratio and now need to check 6965 // if we need to apply the automatic content-based minimum size: 6966 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum 6967 MOZ_ASSERT(!HasReplacedSizing(), 6968 "aspect-ratio minimums should not apply to replaced elements"); 6969 // The inline size computed by aspect-ratio shouldn't less than the 6970 // min-content size, which should be capped by its maximum inline size. 6971 minISize = std::min(GetMinISize(input), maxISize); 6972 } else { 6973 // Treat "min-width: auto" as 0. 6974 // NOTE: Technically, "auto" is supposed to behave like "min-content" on 6975 // flex items. However, we don't need to worry about that here, because 6976 // flex items' min-sizes are intentionally ignored until the flex 6977 // container explicitly considers them during space distribution. 6978 minISize = 0; 6979 } 6980 result.ISize(aWM) = std::max(minISize, result.ISize(aWM)); 6981 6982 // Compute block-axis size 6983 // (but not if we have auto bsize -- then, we'll just stick with the bsize 6984 // that we already calculated in the initial ComputeAutoSize() call. However, 6985 // if we have a valid preferred aspect ratio, we still have to compute the 6986 // block size because aspect ratio affects the intrinsic content size.) 6987 const bool isSubgriddedInBlockAxis = 6988 isSubgrid && static_cast<nsGridContainerFrame*>(this)->IsRowSubgrid(); 6989 6990 // Per https://drafts.csswg.org/css-grid/#subgrid-box-alignment, if we are 6991 // subgridded in the block-axis, ignore our style block-size, and stretch to 6992 // fill the CB. 6993 const bool shouldComputeBSize = !isAutoBSize && !isSubgriddedInBlockAxis; 6994 if (shouldComputeBSize) { 6995 result.BSize(aWM) = nsLayoutUtils::ComputeBSizeValue( 6996 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM), 6997 styleBSize->AsLengthPercentage()); 6998 } else if (MOZ_UNLIKELY(isGridItem) && styleBSize->IsAuto() && 6999 !aFlags.contains(ComputeSizeFlag::IsGridMeasuringReflow) && 7000 !IsTrueOverflowContainer() && 7001 !alignCB->IsMasonry(aWM, LogicalAxis::Block)) { 7002 auto cbSize = aCBSize.BSize(aWM); 7003 if (cbSize != NS_UNCONSTRAINEDSIZE) { 7004 // 'auto' block-size for grid-level box - fill the CB for 'stretch' / 7005 // 'normal' and clamp it to the CB if requested: 7006 bool isStretchAligned = false; 7007 bool mayUseAspectRatio = 7008 aspectRatio && result.ISize(aWM) != NS_UNCONSTRAINEDSIZE; 7009 if (!StyleMargin()->HasBlockAxisAuto(aWM, anchorResolutionParams)) { 7010 auto blockAxisAlignment = stylePos->UsedSelfAlignment( 7011 aWM, LogicalAxis::Block, alignCB->GetWritingMode(), 7012 alignCB->Style()); 7013 isStretchAligned = blockAxisAlignment == StyleAlignFlags::STRETCH || 7014 (blockAxisAlignment == StyleAlignFlags::NORMAL && 7015 !mayUseAspectRatio); 7016 } 7017 7018 // Apply the preferred aspect ratio for alignments other than *stretch* 7019 // and *normal without aspect ratio*. 7020 // The spec says all other values should size the items as fit-content, 7021 // and the intrinsic size should respect the preferred aspect ratio, so 7022 // we also apply aspect ratio for all other values. 7023 // https://drafts.csswg.org/css-grid/#grid-item-sizing 7024 if (!isStretchAligned && mayUseAspectRatio) { 7025 result.BSize(aWM) = aspectRatio.ComputeRatioDependentSize( 7026 LogicalAxis::Block, aWM, result.ISize(aWM), boxSizingAdjust); 7027 MOZ_ASSERT(aspectRatioUsage == AspectRatioUsage::None); 7028 aspectRatioUsage = AspectRatioUsage::ToComputeBSize; 7029 } 7030 7031 if (isStretchAligned || 7032 aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize)) { 7033 auto bSizeToFillCB = nsLayoutUtils::ComputeStretchContentBoxBSize( 7034 cbSize, aMargin.BSize(aWM), aBorderPadding.BSize(aWM)); 7035 if (isStretchAligned || (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE && 7036 result.BSize(aWM) > bSizeToFillCB)) { 7037 result.BSize(aWM) = bSizeToFillCB; 7038 } 7039 } 7040 } 7041 } else if (aspectRatio) { 7042 // If both inline and block dimensions are auto, the block axis is the 7043 // ratio-dependent axis by default. 7044 // If we have a super large inline size, aspect-ratio should still be 7045 // applied (so aspectRatioUsage flag is set as expected). That's why we 7046 // apply aspect-ratio unconditionally for auto block size here. 7047 result.BSize(aWM) = aspectRatio.ComputeRatioDependentSize( 7048 LogicalAxis::Block, aWM, result.ISize(aWM), boxSizingAdjust); 7049 MOZ_ASSERT(aspectRatioUsage == AspectRatioUsage::None); 7050 aspectRatioUsage = AspectRatioUsage::ToComputeBSize; 7051 } 7052 7053 if (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { 7054 // Flex items ignore their min & max sizing properties in their flex 7055 // container's main-axis. (Those properties get applied later in the flexbox 7056 // algorithm.) 7057 const bool isFlexItemBlockAxisMainAxis = 7058 flexItemMainAxis && *flexItemMainAxis == LogicalAxis::Block; 7059 // Grid items that are subgridded in block-axis also ignore their min & max 7060 // sizing properties in that axis. 7061 const bool shouldIgnoreMinMaxBSize = 7062 isFlexItemBlockAxisMainAxis || isSubgriddedInBlockAxis; 7063 if (!isAutoMaxBSize && !shouldIgnoreMinMaxBSize) { 7064 nscoord maxBSize = nsLayoutUtils::ComputeBSizeValueHandlingStretch( 7065 aCBSize.BSize(aWM), aMargin.BSize(aWM), aBorderPadding.BSize(aWM), 7066 boxSizingAdjust.BSize(aWM), *maxBSizeCoord); 7067 result.BSize(aWM) = std::min(maxBSize, result.BSize(aWM)); 7068 } 7069 7070 if (!isAutoMinBSize && !shouldIgnoreMinMaxBSize) { 7071 nscoord minBSize = nsLayoutUtils::ComputeBSizeValueHandlingStretch( 7072 aCBSize.BSize(aWM), aMargin.BSize(aWM), aBorderPadding.BSize(aWM), 7073 boxSizingAdjust.BSize(aWM), *minBSizeCoord); 7074 result.BSize(aWM) = std::max(minBSize, result.BSize(aWM)); 7075 } 7076 } 7077 7078 if (IsThemed(disp)) { 7079 nsPresContext* pc = PresContext(); 7080 const LayoutDeviceIntSize widget = pc->Theme()->GetMinimumWidgetSize( 7081 pc, this, disp->EffectiveAppearance()); 7082 7083 // Convert themed widget's physical dimensions to logical coords 7084 LogicalSize size(aWM, LayoutDeviceIntSize::ToAppUnits( 7085 widget, pc->AppUnitsPerDevPixel())); 7086 7087 // GetMinimumWidgetSize() returns border-box; we need content-box. 7088 size -= aBorderPadding; 7089 7090 if (size.BSize(aWM) > result.BSize(aWM)) { 7091 result.BSize(aWM) = size.BSize(aWM); 7092 } 7093 if (size.ISize(aWM) > result.ISize(aWM)) { 7094 result.ISize(aWM) = size.ISize(aWM); 7095 } 7096 } 7097 7098 result.ISize(aWM) = std::max(0, result.ISize(aWM)); 7099 result.BSize(aWM) = std::max(0, result.BSize(aWM)); 7100 7101 return {result, aspectRatioUsage}; 7102 } 7103 7104 nscoord nsIFrame::ComputeBSizeValueAsPercentageBasis( 7105 const StyleSize& aStyleBSize, const StyleSize& aStyleMinBSize, 7106 const StyleMaxSize& aStyleMaxBSize, nscoord aCBBSize, 7107 nscoord aContentEdgeToBoxSizingBSize) { 7108 if (nsLayoutUtils::IsAutoBSize(aStyleBSize, aCBBSize)) { 7109 return NS_UNCONSTRAINEDSIZE; 7110 } 7111 7112 // TODO(dholbert): This is a temporary hack, to be fixed up in bug 1933604. 7113 // We don't know have aMargin or aBorderPadding args available, 7114 // so we use these dummy zero-valued variables as placeholders in 7115 // our call to ComputeBSizeValueHandlingStretch. (This might mean we 7116 // end up resolving 'stretch' to something slighlty-too-large for the 7117 // purposes of this call, if there's actually nonzero margin/border/padding). 7118 const nscoord dummyMargin = 0; 7119 const nscoord dummyBorderPadding = 0; 7120 7121 const nscoord bSize = nsLayoutUtils::ComputeBSizeValueHandlingStretch( 7122 aCBBSize, dummyMargin, dummyBorderPadding, aContentEdgeToBoxSizingBSize, 7123 aStyleBSize); 7124 7125 const nscoord minBSize = 7126 nsLayoutUtils::IsAutoBSize(aStyleMinBSize, aCBBSize) 7127 ? 0 7128 : nsLayoutUtils::ComputeBSizeValueHandlingStretch( 7129 aCBBSize, dummyMargin, dummyBorderPadding, 7130 aContentEdgeToBoxSizingBSize, aStyleMinBSize); 7131 7132 const nscoord maxBSize = 7133 nsLayoutUtils::IsAutoBSize(aStyleMaxBSize, aCBBSize) 7134 ? NS_UNCONSTRAINEDSIZE 7135 : nsLayoutUtils::ComputeBSizeValueHandlingStretch( 7136 aCBBSize, dummyMargin, dummyBorderPadding, 7137 aContentEdgeToBoxSizingBSize, aStyleMaxBSize); 7138 7139 return CSSMinMax(bSize, minBSize, maxBSize); 7140 } 7141 7142 nsRect nsIFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const { 7143 return InkOverflowRect(); 7144 } 7145 7146 /* virtual */ 7147 nsresult nsIFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX, 7148 nscoord* aXMost) { 7149 return NS_ERROR_NOT_IMPLEMENTED; 7150 } 7151 7152 /* virtual */ 7153 LogicalSize nsIFrame::ComputeAutoSize( 7154 const SizeComputationInput& aSizingInput, WritingMode aWM, 7155 const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize, 7156 const mozilla::LogicalSize& aMargin, 7157 const mozilla::LogicalSize& aBorderPadding, 7158 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 7159 if (IsAbsolutelyPositionedWithDefiniteContainingBlock()) { 7160 return ComputeAbsolutePosAutoSize(aSizingInput, aWM, aCBSize, 7161 aAvailableISize, aMargin, aBorderPadding, 7162 aSizeOverrides, aFlags); 7163 } 7164 7165 // Use basic shrink-wrapping as a default implementation. 7166 LogicalSize result(aWM, 0xdeadbeef, NS_UNCONSTRAINEDSIZE); 7167 7168 const auto anchorResolutionParams = AnchorPosResolutionParams::From(this); 7169 // don't bother setting it if the result won't be used 7170 const auto styleISize = 7171 aSizeOverrides.mStyleISize 7172 ? AnchorResolvedSizeHelper::Overridden(*aSizeOverrides.mStyleISize) 7173 : StylePosition()->ISize(aWM, anchorResolutionParams); 7174 if (styleISize->IsAuto()) { 7175 nscoord availBased = nsLayoutUtils::ComputeStretchContentBoxISize( 7176 aAvailableISize, aMargin.ISize(aWM), aBorderPadding.ISize(aWM)); 7177 const auto* stylePos = StylePosition(); 7178 const auto styleBSize = 7179 aSizeOverrides.mStyleBSize 7180 ? AnchorResolvedSizeHelper::Overridden(*aSizeOverrides.mStyleBSize) 7181 : stylePos->BSize(aWM, anchorResolutionParams); 7182 const LogicalSize contentEdgeToBoxSizing = 7183 stylePos->mBoxSizing == StyleBoxSizing::Border ? aBorderPadding 7184 : LogicalSize(aWM); 7185 const nscoord bSize = ComputeBSizeValueAsPercentageBasis( 7186 *styleBSize, *stylePos->MinBSize(aWM, anchorResolutionParams), 7187 *stylePos->MaxBSize(aWM, anchorResolutionParams), aCBSize.BSize(aWM), 7188 contentEdgeToBoxSizing.BSize(aWM)); 7189 const IntrinsicSizeInput input( 7190 aSizingInput.mRenderingContext, 7191 Some(aCBSize.ConvertTo(GetWritingMode(), aWM)), 7192 Some(LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, bSize) 7193 .ConvertTo(GetWritingMode(), aWM))); 7194 result.ISize(aWM) = ShrinkISizeToFit(input, availBased, aFlags); 7195 } 7196 return result; 7197 } 7198 7199 bool nsIFrame::IsAbsolutelyPositionedWithDefiniteContainingBlock() const { 7200 // TODO(dshin, Bug 1927861): Even if an absolute container should have a 7201 // definite size, in a continuation context, the full extent of the containing 7202 // block is not known. 7203 return MOZ_UNLIKELY(IsAbsolutelyPositioned()) && !GetPrevInFlow(); 7204 } 7205 7206 LogicalSize nsIFrame::ComputeAbsolutePosAutoSize( 7207 const SizeComputationInput& aSizingInput, WritingMode aWM, 7208 const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize, 7209 const mozilla::LogicalSize& aMargin, 7210 const mozilla::LogicalSize& aBorderPadding, 7211 const StyleSizeOverrides& aSizeOverrides, const ComputeSizeFlags& aFlags) { 7212 MOZ_ASSERT(IsAbsolutelyPositionedWithDefiniteContainingBlock(), 7213 "Asking for absolute auto size when not absolute"); 7214 // Ideally, this is an assertion, but the containing block could just be 7215 // really big. 7216 NS_WARNING_ASSERTION(aCBSize.ISize(aWM) != NS_UNCONSTRAINEDSIZE && 7217 aCBSize.BSize(aWM) != NS_UNCONSTRAINEDSIZE, 7218 "Absolute containing block size not definite?"); 7219 LogicalSize result(aWM, static_cast<nscoord>(0xdeadbeef), 7220 NS_UNCONSTRAINEDSIZE); 7221 7222 const auto* stylePos = StylePosition(); 7223 const auto anchorResolutionParams = 7224 AnchorPosOffsetResolutionParams::UseCBFrameSize( 7225 AnchorPosResolutionParams::From(&aSizingInput)); 7226 const auto& styleISize = 7227 aSizeOverrides.mStyleISize 7228 ? AnchorResolvedSizeHelper::Overridden(*aSizeOverrides.mStyleISize) 7229 : stylePos->ISize(aWM, anchorResolutionParams.mBaseParams); 7230 const auto& styleBSize = 7231 aSizeOverrides.mStyleBSize 7232 ? AnchorResolvedSizeHelper::Overridden(*aSizeOverrides.mStyleBSize) 7233 : stylePos->BSize(aWM, anchorResolutionParams.mBaseParams); 7234 const auto iStartOffsetIsAuto = 7235 stylePos 7236 ->GetAnchorResolvedInset(LogicalSide::IStart, aWM, 7237 anchorResolutionParams) 7238 ->IsAuto(); 7239 const auto iEndOffsetIsAuto = 7240 stylePos 7241 ->GetAnchorResolvedInset(LogicalSide::IEnd, aWM, 7242 anchorResolutionParams) 7243 ->IsAuto(); 7244 const auto bStartOffsetIsAuto = 7245 stylePos 7246 ->GetAnchorResolvedInset(LogicalSide::BStart, aWM, 7247 anchorResolutionParams) 7248 ->IsAuto(); 7249 const auto bEndOffsetIsAuto = 7250 stylePos 7251 ->GetAnchorResolvedInset(LogicalSide::BEnd, aWM, 7252 anchorResolutionParams) 7253 ->IsAuto(); 7254 const auto boxSizingAdjust = stylePos->mBoxSizing == StyleBoxSizing::Border 7255 ? aBorderPadding 7256 : LogicalSize(aWM); 7257 auto shouldStretch = [](StyleAlignFlags aAlignment, const nsIFrame* aFrame, 7258 bool aStartIsAuto, bool aEndIsAuto) { 7259 if (aStartIsAuto || aEndIsAuto) { 7260 // Note(dshin, bug 1930427): This is not part of the current spec [1]; 7261 // however, no one implements the new inset behaviour [2], and the old 7262 // behaviour [3] ends up computing the static size if both or one inset is 7263 // auto. 7264 // 7265 // [1]: https://drafts.csswg.org/css-position-3/#abspos-auto-size 7266 // [2]: https://drafts.csswg.org/css-position-3/#resolving-insets 7267 // [3]: https://drafts.csswg.org/css-position-3/#abspos-old 7268 return false; 7269 } 7270 // Don't care about flag bits for auto-sizing. 7271 aAlignment &= ~StyleAlignFlags::FLAG_BITS; 7272 7273 if (aAlignment == StyleAlignFlags::STRETCH) { 7274 return true; 7275 } 7276 7277 if (aAlignment == StyleAlignFlags::NORMAL) { 7278 // Some replaced elements behave as semi-replaced elements - we want them 7279 // to stretch (See bug 1740580). 7280 return !aFrame->HasReplacedSizing() && !aFrame->IsTableWrapperFrame(); 7281 } 7282 7283 return false; 7284 }; 7285 7286 // i.e. Absolute containing block 7287 7288 // Self alignment properties translate `auto` to normal for this purpose. 7289 // https://drafts.csswg.org/css-align-3/#valdef-justify-self-auto 7290 nsContainerFrame* contFrame = static_cast<nsContainerFrame*>(this); 7291 const StylePositionArea posArea = stylePos->mPositionArea; 7292 const auto containerWM = GetParent()->GetWritingMode(); 7293 auto containerAxis = [&](LogicalAxis aSubjectAxis) { 7294 return aWM.ConvertAxisTo(aSubjectAxis, containerWM); 7295 }; 7296 const auto inlineSelfAlign = 7297 contFrame->CSSAlignmentForAbsPosChildWithinContainingBlock( 7298 aSizingInput, containerAxis(LogicalAxis::Inline), posArea, aCBSize); 7299 const auto blockSelfAlign = 7300 contFrame->CSSAlignmentForAbsPosChildWithinContainingBlock( 7301 aSizingInput, containerAxis(LogicalAxis::Block), posArea, aCBSize); 7302 const auto iShouldStretch = shouldStretch( 7303 inlineSelfAlign, this, iStartOffsetIsAuto, iEndOffsetIsAuto); 7304 const auto bShouldStretch = 7305 shouldStretch(blockSelfAlign, this, bStartOffsetIsAuto, bEndOffsetIsAuto); 7306 const auto iSizeIsAuto = styleISize->IsAuto(); 7307 // Note(dshin, bug 1789477): `auto` in the context of abs-element uses 7308 // stretch-fit sizing, given specific alignment conditions [1]. Effectively, 7309 // `auto` is `stretch`. `nsLayoutUtils::IsAutoBSize` is not the right tool 7310 // here, since the mapping is explicit, and it's incorrect to e.g. map 7311 // `fit-content` to `stretch`. 7312 // `-moz-available` behaves like `auto` in general, so map the same way. 7313 // When Bug 567039 brings `-moz-available` into alignment with `stretch`, this 7314 // special check can be removed. TODO(dshin): we're probably duplicating the 7315 // `stretch` logic here, since `stretch` is `stretch-fit` sizing [2]. 7316 // 7317 // [1]: https://drafts.csswg.org/css-position/#abspos-auto-size 7318 // [2]: https://drafts.csswg.org/css-sizing-4/#valdef-width-stretch 7319 const auto bSizeIsAuto = styleBSize->IsAuto() || styleBSize->IsMozAvailable(); 7320 if (bSizeIsAuto && bShouldStretch) { 7321 result.BSize(aWM) = nsLayoutUtils::ComputeStretchContentBoxBSize( 7322 aCBSize.BSize(aWM), aMargin.BSize(aWM), aBorderPadding.BSize(aWM)); 7323 } 7324 if (iSizeIsAuto) { 7325 if (iShouldStretch) { 7326 // inline-size to make our margin-box fill the containing block: 7327 result.ISize(aWM) = nsLayoutUtils::ComputeStretchContentBoxISize( 7328 aCBSize.ISize(aWM), aMargin.ISize(aWM), aBorderPadding.ISize(aWM)); 7329 } else { 7330 // inline-size to make our margin-box fill aAvailableISize: 7331 nscoord availBased = nsLayoutUtils::ComputeStretchContentBoxISize( 7332 aAvailableISize, aMargin.ISize(aWM), aBorderPadding.ISize(aWM)); 7333 7334 const nscoord bSize = ComputeBSizeValueAsPercentageBasis( 7335 styleBSize->IsAuto() && result.BSize(aWM) != NS_UNCONSTRAINEDSIZE 7336 ? StyleSize::LengthPercentage( 7337 StyleLengthPercentage::FromAppUnits(result.BSize(aWM))) 7338 : *styleBSize, 7339 *stylePos->MinBSize(aWM, anchorResolutionParams.mBaseParams), 7340 *stylePos->MaxBSize(aWM, anchorResolutionParams.mBaseParams), 7341 aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM)); 7342 7343 const IntrinsicSizeInput input( 7344 aSizingInput.mRenderingContext, 7345 Some(aCBSize.ConvertTo(GetWritingMode(), aWM)), 7346 Some(LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, bSize) 7347 .ConvertTo(GetWritingMode(), aWM))); 7348 result.ISize(aWM) = ShrinkISizeToFit(input, availBased, aFlags); 7349 } 7350 } 7351 7352 const auto& aspectRatio = aSizeOverrides.mAspectRatio 7353 ? *aSizeOverrides.mAspectRatio 7354 : GetAspectRatio(); 7355 if (aspectRatio) { 7356 auto aspectRatioUsage = AspectRatioUsage::None; 7357 if (iSizeIsAuto != bSizeIsAuto) { 7358 // Auto axis is dependent. 7359 if (iSizeIsAuto) { 7360 aspectRatioUsage = AspectRatioUsage::ToComputeBSize; 7361 } else { 7362 aspectRatioUsage = AspectRatioUsage::ToComputeISize; 7363 } 7364 } else if (iSizeIsAuto) { 7365 // Both axes are `auto`. 7366 if (iShouldStretch != bShouldStretch) { 7367 // If an axis has stretch, that behaves like a definite size. 7368 aspectRatioUsage = iShouldStretch ? AspectRatioUsage::ToComputeBSize 7369 : AspectRatioUsage::ToComputeISize; 7370 } else if (!iShouldStretch) { 7371 // If one axis has `auto` inset, that is the ratio dependent axis, 7372 // otherwise the block axis is. 7373 const bool inlineInsetHasAuto = iStartOffsetIsAuto || iEndOffsetIsAuto; 7374 const bool blockInsetHasAuto = bStartOffsetIsAuto || bEndOffsetIsAuto; 7375 aspectRatioUsage = inlineInsetHasAuto && !blockInsetHasAuto 7376 ? AspectRatioUsage::ToComputeISize 7377 : AspectRatioUsage::ToComputeBSize; 7378 } 7379 } 7380 7381 if (aspectRatioUsage == AspectRatioUsage::ToComputeBSize && 7382 !bShouldStretch) { 7383 result.BSize(aWM) = aspectRatio.ComputeRatioDependentSize( 7384 LogicalAxis::Block, aWM, result.ISize(aWM), boxSizingAdjust); 7385 } else if (aspectRatioUsage == AspectRatioUsage::ToComputeISize && 7386 !iShouldStretch && result.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { 7387 result.ISize(aWM) = aspectRatio.ComputeRatioDependentSize( 7388 LogicalAxis::Inline, aWM, result.BSize(aWM), boxSizingAdjust); 7389 } 7390 } 7391 7392 return result; 7393 } 7394 7395 nscoord nsIFrame::ShrinkISizeToFit(const IntrinsicSizeInput& aInput, 7396 nscoord aISizeInCB, 7397 ComputeSizeFlags aFlags) { 7398 // If we're a container for font size inflation, then shrink 7399 // wrapping inside of us should not apply font size inflation. 7400 AutoMaybeDisableFontInflation an(this); 7401 7402 nscoord result; 7403 nscoord minISize = GetMinISize(aInput); 7404 if (minISize > aISizeInCB) { 7405 const bool clamp = aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize); 7406 result = MOZ_UNLIKELY(clamp) ? aISizeInCB : minISize; 7407 } else { 7408 nscoord prefISize = GetPrefISize(aInput); 7409 if (prefISize > aISizeInCB) { 7410 result = aISizeInCB; 7411 } else { 7412 result = prefISize; 7413 } 7414 } 7415 return result; 7416 } 7417 7418 nscoord nsIFrame::IntrinsicISizeFromInline(const IntrinsicSizeInput& aInput, 7419 IntrinsicISizeType aType) { 7420 MOZ_ASSERT(!IsContainerForFontSizeInflation(), 7421 "Should not be a container for font size inflation!"); 7422 7423 if (aType == IntrinsicISizeType::MinISize) { 7424 InlineMinISizeData data; 7425 AddInlineMinISize(aInput, &data); 7426 data.ForceBreak(); 7427 return data.mPrevLines; 7428 } 7429 7430 InlinePrefISizeData data; 7431 AddInlinePrefISize(aInput, &data); 7432 data.ForceBreak(); 7433 return data.mPrevLines; 7434 } 7435 7436 nscoord nsIFrame::ComputeISizeValueFromAspectRatio( 7437 WritingMode aWM, const LogicalSize& aCBSize, 7438 const LogicalSize& aContentEdgeToBoxSizing, const LengthPercentage& aBSize, 7439 const AspectRatio& aAspectRatio) const { 7440 MOZ_ASSERT(aAspectRatio, "Must have a valid AspectRatio!"); 7441 const nscoord bSize = nsLayoutUtils::ComputeBSizeValue( 7442 aCBSize.BSize(aWM), aContentEdgeToBoxSizing.BSize(aWM), aBSize); 7443 return aAspectRatio.ComputeRatioDependentSize(LogicalAxis::Inline, aWM, bSize, 7444 aContentEdgeToBoxSizing); 7445 } 7446 7447 nsIFrame::ISizeComputationResult nsIFrame::ComputeISizeValue( 7448 gfxContext* aRenderingContext, const WritingMode aWM, 7449 const LogicalSize& aCBSize, const LogicalSize& aContentEdgeToBoxSizing, 7450 nscoord aBoxSizingToMarginEdge, ExtremumLength aSize, 7451 Maybe<nscoord> aAvailableISizeOverride, const StyleSize& aStyleBSize, 7452 const AspectRatio& aAspectRatio, ComputeSizeFlags aFlags) { 7453 auto GetAvailableISize = [&]() { 7454 return aCBSize.ISize(aWM) - aBoxSizingToMarginEdge - 7455 aContentEdgeToBoxSizing.ISize(aWM); 7456 }; 7457 7458 // If 'this' is a container for font size inflation, then shrink 7459 // wrapping inside of it should not apply font size inflation. 7460 AutoMaybeDisableFontInflation an(this); 7461 // If we have an aspect-ratio and a definite block size, we should use them to 7462 // resolve the sizes with intrinsic keywords. 7463 // https://github.com/w3c/csswg-drafts/issues/5032 7464 Maybe<nscoord> iSizeFromAspectRatio = [&]() -> Maybe<nscoord> { 7465 if (aSize == ExtremumLength::MozAvailable || 7466 aSize == ExtremumLength::Stretch) { 7467 return Nothing(); 7468 } 7469 if (!aAspectRatio) { 7470 return Nothing(); 7471 } 7472 if (nsLayoutUtils::IsAutoBSize(aStyleBSize, aCBSize.BSize(aWM))) { 7473 return Nothing(); 7474 } 7475 7476 // Helper used below to resolve aStyleBSize if it's 'stretch' or an alias. 7477 // XXXdholbert Really we should be resolving 'stretch' and its aliases 7478 // sooner; see bug 2000035. 7479 auto ResolveStretchBSize = [&]() { 7480 MOZ_ASSERT(aStyleBSize.BehavesLikeStretchOnBlockAxis(), 7481 "Only call me for 'stretch'-like BSizes"); 7482 MOZ_ASSERT(aCBSize.BSize(aWM) != NS_UNCONSTRAINEDSIZE, 7483 "If aStyleBSize is stretch-like, then unconstrained " 7484 "aCBSize.BSize should make us return via the IsAutoBSize " 7485 "check above"); 7486 7487 // NOTE: the borderPadding and margin variables might be zero-filled 7488 // instead of having the true values, if those values haven't been 7489 // stashed in our property-table yet (e.g. if we're in the midst of 7490 // setting up a ReflowInput for our first reflow). So ideally, we should 7491 // be resolving 'stretch' **in our callers** rather than here, if those 7492 // callers have more up-to-date resolved margin/border/padding values. 7493 // We'll still make a best-effort attempt to resolve 'stretch' here, 7494 // though, for the benefit of callers that might not have handled it, to 7495 // be sure we don't abort in aStyleBSize.AsLengthPercentage(). Ultimately 7496 // this all can be removed when we fix bug 2000035. 7497 const auto borderPadding = GetLogicalUsedBorderAndPadding(aWM); 7498 const auto margin = GetLogicalUsedMargin(aWM); 7499 nscoord stretchBSize = nsLayoutUtils::ComputeStretchBSize( 7500 aCBSize.BSize(aWM), margin.BStartEnd(aWM), 7501 borderPadding.BStartEnd(aWM), StylePosition()->mBoxSizing); 7502 return LengthPercentage::FromAppUnits(stretchBSize); 7503 }; 7504 7505 return Some(ComputeISizeValueFromAspectRatio( 7506 aWM, aCBSize, aContentEdgeToBoxSizing, 7507 aStyleBSize.BehavesLikeStretchOnBlockAxis() 7508 ? ResolveStretchBSize() 7509 : aStyleBSize.AsLengthPercentage(), 7510 aAspectRatio)); 7511 }(); 7512 7513 const auto* stylePos = StylePosition(); 7514 const auto anchorResolutionParams = AnchorPosResolutionParams::From(this); 7515 const nscoord bSize = ComputeBSizeValueAsPercentageBasis( 7516 aStyleBSize, *stylePos->MinBSize(aWM, anchorResolutionParams), 7517 *stylePos->MaxBSize(aWM, anchorResolutionParams), aCBSize.BSize(aWM), 7518 aContentEdgeToBoxSizing.BSize(aWM)); 7519 const IntrinsicSizeInput input( 7520 aRenderingContext, Some(aCBSize.ConvertTo(GetWritingMode(), aWM)), 7521 Some(LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, bSize) 7522 .ConvertTo(GetWritingMode(), aWM))); 7523 nscoord result; 7524 switch (aSize) { 7525 case ExtremumLength::MaxContent: 7526 result = 7527 iSizeFromAspectRatio ? *iSizeFromAspectRatio : GetPrefISize(input); 7528 NS_ASSERTION(result >= 0, "inline-size less than zero"); 7529 return {result, iSizeFromAspectRatio ? AspectRatioUsage::ToComputeISize 7530 : AspectRatioUsage::None}; 7531 case ExtremumLength::MinContent: 7532 result = 7533 iSizeFromAspectRatio ? *iSizeFromAspectRatio : GetMinISize(input); 7534 NS_ASSERTION(result >= 0, "inline-size less than zero"); 7535 if (MOZ_UNLIKELY( 7536 aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize))) { 7537 result = std::min(GetAvailableISize(), result); 7538 } 7539 return {result, iSizeFromAspectRatio ? AspectRatioUsage::ToComputeISize 7540 : AspectRatioUsage::None}; 7541 case ExtremumLength::FitContentFunction: 7542 case ExtremumLength::FitContent: { 7543 nscoord pref = NS_UNCONSTRAINEDSIZE; 7544 nscoord min = 0; 7545 if (iSizeFromAspectRatio) { 7546 // The min-content and max-content size are identical and equal to the 7547 // size computed from the block size and the aspect ratio. 7548 pref = min = *iSizeFromAspectRatio; 7549 } else { 7550 pref = GetPrefISize(input); 7551 min = GetMinISize(input); 7552 } 7553 7554 const nscoord fill = aAvailableISizeOverride ? *aAvailableISizeOverride 7555 : GetAvailableISize(); 7556 if (MOZ_UNLIKELY( 7557 aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize))) { 7558 min = std::min(min, fill); 7559 } 7560 result = std::max(min, std::min(pref, fill)); 7561 NS_ASSERTION(result >= 0, "inline-size less than zero"); 7562 return {result}; 7563 } 7564 case ExtremumLength::MozAvailable: 7565 case ExtremumLength::Stretch: 7566 return {GetAvailableISize()}; 7567 } 7568 MOZ_ASSERT_UNREACHABLE("Unknown extremum length?"); 7569 return {}; 7570 } 7571 7572 nscoord nsIFrame::ComputeISizeValue(const WritingMode aWM, 7573 const LogicalSize& aCBSize, 7574 const LogicalSize& aContentEdgeToBoxSizing, 7575 const LengthPercentage& aSize) const { 7576 LAYOUT_WARN_IF_FALSE( 7577 aCBSize.ISize(aWM) != NS_UNCONSTRAINEDSIZE, 7578 "have unconstrained inline-size; this should only result from " 7579 "very large sizes, not attempts at intrinsic inline-size " 7580 "calculation"); 7581 NS_ASSERTION(aCBSize.ISize(aWM) >= 0, "inline-size less than zero"); 7582 7583 nscoord result = aSize.Resolve(aCBSize.ISize(aWM)); 7584 // The result of a calc() expression might be less than 0; we 7585 // should clamp at runtime (below). (Percentages and coords that 7586 // are less than 0 have already been dropped by the parser.) 7587 result -= aContentEdgeToBoxSizing.ISize(aWM); 7588 return std::max(0, result); 7589 } 7590 7591 void nsIFrame::DidReflow(nsPresContext* aPresContext, 7592 const ReflowInput* aReflowInput) { 7593 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsIFrame::DidReflow")); 7594 7595 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) { 7596 RemoveStateBits(NS_FRAME_IN_REFLOW); 7597 return; 7598 } 7599 7600 SVGObserverUtils::InvalidateDirectRenderingObservers( 7601 this, SVGObserverUtils::INVALIDATE_REFLOW); 7602 7603 RemoveStateBits(NS_FRAME_IN_REFLOW | NS_FRAME_FIRST_REFLOW | 7604 NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); 7605 7606 // Clear bits that were used in ReflowInput::InitResizeFlags (see 7607 // comment there for why we can't clear it there). 7608 SetHasBSizeChange(false); 7609 SetHasPaddingChange(false); 7610 7611 // Notify the percent bsize observer if there is a percent bsize. 7612 // The observer may be able to initiate another reflow with a computed 7613 // bsize. This happens in the case where a table cell has no computed 7614 // bsize but can fabricate one when the cell bsize is known. 7615 if (aReflowInput && aReflowInput->mPercentBSizeObserver && !GetPrevInFlow()) { 7616 const auto bsize = aReflowInput->mStylePosition->BSize( 7617 aReflowInput->GetWritingMode(), 7618 AnchorPosResolutionParams::From(aReflowInput)); 7619 if (bsize->HasPercent()) { 7620 aReflowInput->mPercentBSizeObserver->NotifyPercentBSize(*aReflowInput); 7621 } 7622 } 7623 7624 aPresContext->ReflowedFrame(); 7625 } 7626 7627 /* virtual */ 7628 bool nsIFrame::CanContinueTextRun() const { 7629 // By default, a frame will *not* allow a text run to be continued 7630 // through it. 7631 return false; 7632 } 7633 7634 void nsIFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, 7635 const ReflowInput& aReflowInput, 7636 nsReflowStatus& aStatus) { 7637 MarkInReflow(); 7638 DO_GLOBAL_REFLOW_COUNT("nsFrame"); 7639 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 7640 aDesiredSize.ClearSize(); 7641 } 7642 7643 bool nsIFrame::IsContentDisabled() const { 7644 auto* element = nsGenericHTMLElement::FromNodeOrNull(GetContent()); 7645 return element && element->IsDisabled(); 7646 } 7647 7648 bool nsIFrame::IsContentRelevant() const { 7649 MOZ_ASSERT(StyleDisplay()->ContentVisibility(*this) == 7650 StyleContentVisibility::Auto); 7651 7652 auto* element = Element::FromNodeOrNull(GetContent()); 7653 MOZ_ASSERT(element); 7654 7655 Maybe<ContentRelevancy> relevancy = element->GetContentRelevancy(); 7656 return relevancy.isSome() && !relevancy->isEmpty(); 7657 } 7658 7659 bool nsIFrame::HidesContent( 7660 const EnumSet<IncludeContentVisibility>& aInclude) const { 7661 auto effectiveContentVisibility = StyleDisplay()->ContentVisibility(*this); 7662 if (aInclude.contains(IncludeContentVisibility::Hidden) && 7663 effectiveContentVisibility == StyleContentVisibility::Hidden) { 7664 return true; 7665 } 7666 7667 if (aInclude.contains(IncludeContentVisibility::Auto) && 7668 effectiveContentVisibility == StyleContentVisibility::Auto) { 7669 return !IsContentRelevant(); 7670 } 7671 7672 return false; 7673 } 7674 7675 bool nsIFrame::HidesContentForLayout() const { 7676 return HidesContent() && !PresShell()->IsForcingLayoutForHiddenContent(this); 7677 } 7678 7679 bool nsIFrame::IsHiddenByContentVisibilityOfInFlowParentForLayout() const { 7680 const auto* parent = GetInFlowParent(); 7681 // The anonymous children owned by parent are important for properly sizing 7682 // their parents. 7683 return parent && parent->HidesContentForLayout() && 7684 !(parent->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES) && 7685 Style()->IsAnonBox()); 7686 } 7687 7688 nsIFrame* nsIFrame::GetClosestContentVisibilityAncestor( 7689 const EnumSet<IncludeContentVisibility>& aInclude) const { 7690 auto* parent = GetInFlowParent(); 7691 bool isAnonymousBlock = Style()->IsAnonBox() && parent && 7692 parent->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES); 7693 for (nsIFrame* cur = parent; cur; cur = cur->GetInFlowParent()) { 7694 if (!isAnonymousBlock && cur->HidesContent(aInclude)) { 7695 return cur; 7696 } 7697 7698 // Anonymous boxes are not hidden by the content-visibility of their first 7699 // non-anonymous ancestor, but can be hidden by ancestors further up the 7700 // tree. 7701 isAnonymousBlock = false; 7702 } 7703 7704 return nullptr; 7705 } 7706 7707 static bool IsClosedDetailsSlot(const Element* aElement) { 7708 const auto* slot = HTMLSlotElement::FromNodeOrNull(aElement); 7709 if (!slot || slot->HasName()) { 7710 return false; 7711 } 7712 const auto* details = 7713 HTMLDetailsElement::FromNodeOrNull(slot->GetContainingShadowHost()); 7714 return details && !details->GetBoolAttr(nsGkAtoms::open); 7715 } 7716 7717 bool nsIFrame::IsHiddenUntilFoundOrClosedDetails() const { 7718 for (const auto* f = this; f; f = f->GetInFlowParent()) { 7719 if (f->HidesContent(nsIFrame::IncludeContentVisibility::Hidden)) { 7720 if (const auto* element = Element::FromNode(f->GetContent()); 7721 element && 7722 !element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, 7723 nsGkAtoms::untilFound, eIgnoreCase) && 7724 !IsClosedDetailsSlot(element)) { 7725 return false; 7726 } 7727 } 7728 } 7729 return true; 7730 } 7731 7732 bool nsIFrame::IsHiddenByContentVisibilityOnAnyAncestor( 7733 const EnumSet<IncludeContentVisibility>& aInclude) const { 7734 return !!GetClosestContentVisibilityAncestor(aInclude); 7735 } 7736 7737 bool nsIFrame::HasSelectionInSubtree() { 7738 if (IsSelected()) { 7739 return true; 7740 } 7741 7742 RefPtr<nsFrameSelection> frameSelection = GetFrameSelection(); 7743 if (!frameSelection) { 7744 return false; 7745 } 7746 7747 const Selection& selection = frameSelection->NormalSelection(); 7748 7749 for (uint32_t i = 0; i < selection.RangeCount(); i++) { 7750 auto* range = selection.GetRangeAt(i); 7751 MOZ_ASSERT(range); 7752 7753 const auto* commonAncestorNode = 7754 range->GetRegisteredClosestCommonInclusiveAncestor(); 7755 if (commonAncestorNode && 7756 commonAncestorNode->IsInclusiveDescendantOf(GetContent())) { 7757 return true; 7758 } 7759 } 7760 7761 return false; 7762 } 7763 7764 bool nsIFrame::UpdateIsRelevantContent( 7765 const ContentRelevancy& aRelevancyToUpdate) { 7766 MOZ_ASSERT(StyleDisplay()->ContentVisibility(*this) == 7767 StyleContentVisibility::Auto); 7768 7769 auto* element = Element::FromNodeOrNull(GetContent()); 7770 MOZ_ASSERT(element); 7771 7772 ContentRelevancy newRelevancy; 7773 Maybe<ContentRelevancy> oldRelevancy = element->GetContentRelevancy(); 7774 if (oldRelevancy.isSome()) { 7775 newRelevancy = *oldRelevancy; 7776 } 7777 7778 auto setRelevancyValue = [&](ContentRelevancyReason reason, bool value) { 7779 if (value) { 7780 newRelevancy += reason; 7781 } else { 7782 newRelevancy -= reason; 7783 } 7784 }; 7785 7786 if (!oldRelevancy || 7787 aRelevancyToUpdate.contains(ContentRelevancyReason::Visible)) { 7788 Maybe<bool> visible = element->GetVisibleForContentVisibility(); 7789 if (visible.isSome()) { 7790 setRelevancyValue(ContentRelevancyReason::Visible, *visible); 7791 } 7792 } 7793 7794 if (!oldRelevancy || 7795 aRelevancyToUpdate.contains(ContentRelevancyReason::FocusInSubtree)) { 7796 setRelevancyValue(ContentRelevancyReason::FocusInSubtree, 7797 element->State().HasAtLeastOneOfStates( 7798 ElementState::FOCUS_WITHIN | ElementState::FOCUS)); 7799 } 7800 7801 if (!oldRelevancy || 7802 aRelevancyToUpdate.contains(ContentRelevancyReason::Selected)) { 7803 setRelevancyValue(ContentRelevancyReason::Selected, 7804 HasSelectionInSubtree()); 7805 } 7806 7807 // If the proximity to the viewport has not been determined yet, 7808 // and neither the element nor its contents are focused or selected, 7809 // we should wait for the determination of the proximity. Otherwise, 7810 // there might be a redundant contentvisibilityautostatechange event. 7811 // See https://github.com/w3c/csswg-drafts/issues/9803 7812 bool isProximityToViewportDetermined = 7813 oldRelevancy ? true : element->GetVisibleForContentVisibility().isSome(); 7814 if (!isProximityToViewportDetermined && newRelevancy.isEmpty()) { 7815 return false; 7816 } 7817 7818 bool overallRelevancyChanged = 7819 !oldRelevancy || oldRelevancy->isEmpty() != newRelevancy.isEmpty(); 7820 if (!oldRelevancy || *oldRelevancy != newRelevancy) { 7821 element->SetContentRelevancy(newRelevancy); 7822 } 7823 7824 if (!overallRelevancyChanged) { 7825 return false; 7826 } 7827 7828 HandleLastRememberedSize(); 7829 PresContext()->SetNeedsToUpdateHiddenByContentVisibilityForAnimations(); 7830 PresShell()->FrameNeedsReflow( 7831 this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); 7832 InvalidateFrame(); 7833 7834 ContentVisibilityAutoStateChangeEventInit init; 7835 init.mSkipped = newRelevancy.isEmpty(); 7836 RefPtr<ContentVisibilityAutoStateChangeEvent> event = 7837 ContentVisibilityAutoStateChangeEvent::Constructor( 7838 element, u"contentvisibilityautostatechange"_ns, init); 7839 7840 // Per 7841 // https://drafts.csswg.org/css-contain/#content-visibility-auto-state-changed 7842 // "This event is dispatched by posting a task at the time when the state 7843 // change occurs." 7844 RefPtr<AsyncEventDispatcher> asyncDispatcher = 7845 new AsyncEventDispatcher(element, event.forget()); 7846 DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent(); 7847 NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch"); 7848 return true; 7849 } 7850 7851 nsresult nsIFrame::CharacterDataChanged(const CharacterDataChangeInfo&) { 7852 MOZ_ASSERT_UNREACHABLE("should only be called for text frames"); 7853 return NS_OK; 7854 } 7855 7856 nsresult nsIFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, 7857 AttrModType) { 7858 return NS_OK; 7859 } 7860 7861 nsIFrame* nsIFrame::GetPrevContinuation() const { return nullptr; } 7862 7863 void nsIFrame::SetPrevContinuation(nsIFrame*) { 7864 MOZ_ASSERT_UNREACHABLE("Not splittable!"); 7865 } 7866 7867 nsIFrame* nsIFrame::GetNextContinuation() const { return nullptr; } 7868 7869 void nsIFrame::SetNextContinuation(nsIFrame*) { 7870 MOZ_ASSERT_UNREACHABLE("Not splittable!"); 7871 } 7872 7873 nsIFrame* nsIFrame::GetPrevInFlow() const { return nullptr; } 7874 7875 void nsIFrame::SetPrevInFlow(nsIFrame*) { 7876 MOZ_ASSERT_UNREACHABLE("Not splittable!"); 7877 } 7878 7879 nsIFrame* nsIFrame::GetNextInFlow() const { return nullptr; } 7880 7881 void nsIFrame::SetNextInFlow(nsIFrame*) { 7882 MOZ_ASSERT_UNREACHABLE("Not splittable!"); 7883 } 7884 7885 nsIFrame* nsIFrame::GetTailContinuation() { 7886 nsIFrame* frame = this; 7887 while (frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { 7888 frame = frame->GetPrevContinuation(); 7889 NS_ASSERTION(frame, "first continuation can't be overflow container"); 7890 } 7891 for (nsIFrame* next = frame->GetNextContinuation(); 7892 next && !next->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); 7893 next = frame->GetNextContinuation()) { 7894 frame = next; 7895 } 7896 7897 MOZ_ASSERT(frame, "illegal state in continuation chain."); 7898 return frame; 7899 } 7900 7901 nsIWidget* nsIFrame::GetOwnWidget() const { 7902 if (IsMenuPopupFrame()) { 7903 return static_cast<const nsMenuPopupFrame*>(this)->GetWidget(); 7904 } 7905 if (!GetParent()) { 7906 return PresShell()->GetOwnWidget(); 7907 } 7908 return nullptr; 7909 } 7910 7911 template <nsPoint (nsIFrame::*PositionGetter)() const> 7912 static nsPoint OffsetCalculator(const nsIFrame* aThis, const nsIFrame* aOther) { 7913 MOZ_ASSERT(aOther, "Must have frame for destination coordinate system!"); 7914 7915 NS_ASSERTION(aThis->PresContext() == aOther->PresContext(), 7916 "GetOffsetTo called on frames in different documents"); 7917 7918 nsPoint offset(0, 0); 7919 const nsIFrame* f; 7920 for (f = aThis; f != aOther && f; f = f->GetParent()) { 7921 offset += (f->*PositionGetter)(); 7922 } 7923 7924 if (f != aOther) { 7925 // Looks like aOther wasn't an ancestor of |this|. So now we have 7926 // the root-frame-relative position of |this| in |offset|. Convert back 7927 // to the coordinates of aOther 7928 while (aOther) { 7929 offset -= (aOther->*PositionGetter)(); 7930 aOther = aOther->GetParent(); 7931 } 7932 } 7933 7934 return offset; 7935 } 7936 7937 nsPoint nsIFrame::GetOffsetTo(const nsIFrame* aOther) const { 7938 return OffsetCalculator<&nsIFrame::GetPosition>(this, aOther); 7939 } 7940 7941 nsPoint nsIFrame::GetOffsetToRootFrame() const { 7942 return GetOffsetTo(PresShell()->GetRootFrame()); 7943 } 7944 7945 nsPoint nsIFrame::GetOffsetToIgnoringScrolling(const nsIFrame* aOther) const { 7946 return OffsetCalculator<&nsIFrame::GetPositionIgnoringScrolling>(this, 7947 aOther); 7948 } 7949 7950 nsPoint nsIFrame::GetOffsetToCrossDoc(const nsIFrame* aOther) const { 7951 return GetOffsetToCrossDoc(aOther, PresContext()->AppUnitsPerDevPixel()); 7952 } 7953 7954 nsPoint nsIFrame::GetOffsetToCrossDoc(const nsIFrame* aOther, 7955 const int32_t aAPD) const { 7956 MOZ_ASSERT(aOther, "Must have frame for destination coordinate system!"); 7957 MOZ_DIAGNOSTIC_ASSERT( 7958 PresContext()->GetRootPresContext() == 7959 aOther->PresContext()->GetRootPresContext(), 7960 "trying to get the offset between frames in different document " 7961 "hierarchies?"); 7962 7963 const nsIFrame* root = nullptr; 7964 // offset will hold the final offset 7965 // docOffset holds the currently accumulated offset at the current APD, it 7966 // will be converted and added to offset when the current APD changes. 7967 nsPoint offset(0, 0), docOffset(0, 0); 7968 const nsIFrame* f = this; 7969 int32_t currAPD = PresContext()->AppUnitsPerDevPixel(); 7970 while (f && f != aOther) { 7971 docOffset += f->GetPosition(); 7972 nsIFrame* parent = f->GetParent(); 7973 if (parent) { 7974 f = parent; 7975 } else { 7976 nsPoint newOffset(0, 0); 7977 root = f; 7978 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f, &newOffset); 7979 int32_t newAPD = f ? f->PresContext()->AppUnitsPerDevPixel() : 0; 7980 if (!f || newAPD != currAPD) { 7981 // Convert docOffset to the right APD and add it to offset. 7982 offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD); 7983 docOffset.x = docOffset.y = 0; 7984 } 7985 currAPD = newAPD; 7986 docOffset += newOffset; 7987 } 7988 } 7989 if (f == aOther) { 7990 offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD); 7991 } else { 7992 // Looks like aOther wasn't an ancestor of |this|. So now we have 7993 // the root-document-relative position of |this| in |offset|. Subtract the 7994 // root-document-relative position of |aOther| from |offset|. 7995 // This call won't try to recurse again because root is an ancestor of 7996 // aOther. 7997 nsPoint negOffset = aOther->GetOffsetToCrossDoc(root, aAPD); 7998 offset -= negOffset; 7999 } 8000 8001 return offset; 8002 } 8003 8004 CSSIntRect nsIFrame::GetScreenRect() const { 8005 return CSSIntRect::FromAppUnitsToNearest(GetScreenRectInAppUnits()); 8006 } 8007 8008 nsRect nsIFrame::GetScreenRectInAppUnits() const { 8009 nsPresContext* presContext = PresContext(); 8010 nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame(); 8011 nsPoint rootScreenPos(0, 0); 8012 nsPoint rootFrameOffsetInParent(0, 0); 8013 nsIFrame* rootFrameParent = nsLayoutUtils::GetCrossDocParentFrameInProcess( 8014 rootFrame, &rootFrameOffsetInParent); 8015 if (rootFrameParent) { 8016 nsRect parentScreenRectAppUnits = 8017 rootFrameParent->GetScreenRectInAppUnits(); 8018 nsPresContext* parentPresContext = rootFrameParent->PresContext(); 8019 double parentScale = double(presContext->AppUnitsPerDevPixel()) / 8020 parentPresContext->AppUnitsPerDevPixel(); 8021 nsPoint rootPt = 8022 parentScreenRectAppUnits.TopLeft() + rootFrameOffsetInParent; 8023 rootScreenPos.x = NS_round(parentScale * rootPt.x); 8024 rootScreenPos.y = NS_round(parentScale * rootPt.y); 8025 } else if (nsCOMPtr<nsIWidget> rootWidget = presContext->GetRootWidget()) { 8026 LayoutDeviceIntPoint rootDevPx = rootWidget->WidgetToScreenOffset(); 8027 rootScreenPos.x = presContext->DevPixelsToAppUnits(rootDevPx.x); 8028 rootScreenPos.y = presContext->DevPixelsToAppUnits(rootDevPx.y); 8029 } 8030 8031 return nsRect(rootScreenPos + GetOffsetTo(rootFrame), GetSize()); 8032 } 8033 8034 nsIWidget* nsIFrame::GetNearestWidget() const { 8035 if (!HasAnyStateBits(NS_FRAME_IN_POPUP)) { 8036 return PresContext()->GetRootWidget(); 8037 } 8038 nsPoint unused; 8039 return GetNearestWidget(unused); 8040 } 8041 8042 nsIWidget* nsIFrame::GetNearestWidget(nsPoint& aOffset) const { 8043 aOffset.MoveTo(0, 0); 8044 nsIFrame* frame = const_cast<nsIFrame*>(this); 8045 const auto targetAPD = PresContext()->AppUnitsPerDevPixel(); 8046 auto curAPD = targetAPD; 8047 do { 8048 if (auto* widget = frame->GetOwnWidget()) { 8049 aOffset = aOffset.ScaleToOtherAppUnits(curAPD, targetAPD); 8050 return widget; 8051 } 8052 aOffset += frame->GetPosition(); 8053 nsPoint crossDocOffset; 8054 frame = 8055 nsLayoutUtils::GetCrossDocParentFrameInProcess(frame, &crossDocOffset); 8056 if (!frame) { 8057 break; 8058 } 8059 auto newAPD = frame->PresContext()->AppUnitsPerDevPixel(); 8060 aOffset = aOffset.ScaleToOtherAppUnits(curAPD, newAPD); 8061 aOffset += crossDocOffset; 8062 curAPD = newAPD; 8063 } while (true); 8064 aOffset = aOffset.ScaleToOtherAppUnits(curAPD, targetAPD); 8065 return PresContext()->GetRootWidget(); 8066 } 8067 8068 Matrix4x4Flagged nsIFrame::GetTransformMatrix(ViewportType aViewportType, 8069 RelativeTo aStopAtAncestor, 8070 nsIFrame** aOutAncestor, 8071 uint32_t aFlags) const { 8072 MOZ_ASSERT(aOutAncestor, "Need a place to put the ancestor!"); 8073 8074 /* If we're transformed, we want to hand back the combination 8075 * transform/translate matrix that will apply our current transform, then 8076 * shift us to our parent. 8077 */ 8078 const bool isTransformed = IsTransformed(); 8079 const nsIFrame* zoomedContentRoot = nullptr; 8080 if (aStopAtAncestor.mViewportType == ViewportType::Visual) { 8081 zoomedContentRoot = ViewportUtils::IsZoomedContentRoot(this); 8082 if (zoomedContentRoot) { 8083 MOZ_ASSERT(aViewportType != ViewportType::Visual); 8084 } 8085 } 8086 8087 if (isTransformed || zoomedContentRoot) { 8088 MOZ_ASSERT(GetParent()); 8089 Matrix4x4Flagged result; 8090 int32_t scaleFactor = 8091 ((aFlags & IN_CSS_UNITS) ? AppUnitsPerCSSPixel() 8092 : PresContext()->AppUnitsPerDevPixel()); 8093 8094 /* Compute the delta to the parent, which we need because we are converting 8095 * coordinates to our parent. 8096 */ 8097 if (isTransformed) { 8098 // Note: this converts from Matrix4x4 to Matrix4x4Flagged. 8099 result = nsDisplayTransform::GetResultingTransformMatrix( 8100 this, nsPoint(), scaleFactor, 8101 nsDisplayTransform::INCLUDE_PERSPECTIVE); 8102 } 8103 8104 // The offset from a zoomed content root to its parent (e.g. from 8105 // a canvas frame to a scroll frame) is in layout coordinates, so 8106 // apply it before applying any layout-to-visual transform. 8107 *aOutAncestor = GetParent(); 8108 nsPoint delta = GetPosition(); 8109 /* Combine the raw transform with a translation to our parent. */ 8110 result.PostTranslate(NSAppUnitsToFloatPixels(delta.x, scaleFactor), 8111 NSAppUnitsToFloatPixels(delta.y, scaleFactor), 0.0f); 8112 8113 if (zoomedContentRoot) { 8114 Matrix4x4Flagged layoutToVisual; 8115 if (aFlags & nsIFrame::IN_CSS_UNITS) { 8116 layoutToVisual = ViewportUtils::GetVisualToLayoutTransform( 8117 zoomedContentRoot->GetContent()) 8118 .Inverse() 8119 .ToUnknownMatrix(); 8120 } else { 8121 layoutToVisual = 8122 ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>( 8123 zoomedContentRoot->GetContent()) 8124 .Inverse() 8125 .ToUnknownMatrix(); 8126 } 8127 result = result * layoutToVisual; 8128 } 8129 8130 return result; 8131 } 8132 8133 // We are not transformed, so the returned transform is just going to be a 8134 // translation up to whatever ancestor we decide to stop at. 8135 8136 nsPoint crossdocOffset; 8137 *aOutAncestor = 8138 nsLayoutUtils::GetCrossDocParentFrameInProcess(this, &crossdocOffset); 8139 8140 /* Otherwise, we're not transformed. In that case, we'll walk up the frame 8141 * tree until we either hit the root frame or something that may be 8142 * transformed. We'll then change coordinates into that frame, since we're 8143 * guaranteed that nothing in-between can be transformed. First, however, 8144 * we have to check to see if we have a parent. If not, we'll set the 8145 * outparam to null (indicating that there's nothing left) and will hand back 8146 * the identity matrix. 8147 */ 8148 if (!*aOutAncestor) { 8149 return Matrix4x4Flagged(); 8150 } 8151 8152 /* Keep iterating while the frame can't possibly be transformed. */ 8153 const nsIFrame* current = this; 8154 auto shouldStopAt = [](const nsIFrame* aCurrent, RelativeTo& aStopAtAncestor, 8155 nsIFrame* aOutAncestor, uint32_t aFlags) { 8156 return aOutAncestor->IsTransformed() || 8157 ((aStopAtAncestor.mViewportType == ViewportType::Visual) && 8158 ViewportUtils::IsZoomedContentRoot(aOutAncestor)) || 8159 ((aFlags & STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT) && 8160 (aOutAncestor->IsStackingContext() || 8161 DisplayPortUtils::FrameHasDisplayPort(aOutAncestor, aCurrent))); 8162 }; 8163 8164 // We run the GetOffsetToCrossDoc code here as an optimization, instead of 8165 // walking the parent chain here and then asking GetOffsetToCrossDoc to walk 8166 // the same parent chain and compute the offset. 8167 const int32_t finalAPD = PresContext()->AppUnitsPerDevPixel(); 8168 // offset accumulates the offset at finalAPD. 8169 nsPoint offset = GetPosition(); 8170 8171 int32_t currAPD = (*aOutAncestor)->PresContext()->AppUnitsPerDevPixel(); 8172 // docOffset accumulates the current offset at currAPD, and then flushes to 8173 // offset at finalAPD when the APD changes or we finish. 8174 nsPoint docOffset = crossdocOffset; 8175 MOZ_ASSERT(crossdocOffset == nsPoint(0, 0) || !GetParent()); 8176 8177 while (*aOutAncestor != aStopAtAncestor.mFrame && 8178 !shouldStopAt(current, aStopAtAncestor, *aOutAncestor, aFlags)) { 8179 docOffset += (*aOutAncestor)->GetPosition(); 8180 8181 nsIFrame* parent = (*aOutAncestor)->GetParent(); 8182 if (!parent) { 8183 crossdocOffset.x = crossdocOffset.y = 0; 8184 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(*aOutAncestor, 8185 &crossdocOffset); 8186 8187 int32_t newAPD = 8188 parent ? parent->PresContext()->AppUnitsPerDevPixel() : currAPD; 8189 if (!parent || newAPD != currAPD) { 8190 // Convert docOffset to finalAPD and add it to offset. 8191 offset += docOffset.ScaleToOtherAppUnits(currAPD, finalAPD); 8192 docOffset.x = docOffset.y = 0; 8193 } 8194 currAPD = newAPD; 8195 docOffset += crossdocOffset; 8196 8197 if (!parent) { 8198 break; 8199 } 8200 } 8201 8202 current = *aOutAncestor; 8203 *aOutAncestor = parent; 8204 } 8205 offset += docOffset.ScaleToOtherAppUnits(currAPD, finalAPD); 8206 8207 NS_ASSERTION(*aOutAncestor, "Somehow ended up with a null ancestor...?"); 8208 8209 int32_t scaleFactor = 8210 ((aFlags & IN_CSS_UNITS) ? AppUnitsPerCSSPixel() 8211 : PresContext()->AppUnitsPerDevPixel()); 8212 return Matrix4x4Flagged::Translation2d( 8213 NSAppUnitsToFloatPixels(offset.x, scaleFactor), 8214 NSAppUnitsToFloatPixels(offset.y, scaleFactor)); 8215 } 8216 8217 static void InvalidateRenderingObservers(nsIFrame* aDisplayRoot, 8218 nsIFrame* aFrame, 8219 bool aFrameChanged = true) { 8220 MOZ_ASSERT(aDisplayRoot == nsLayoutUtils::GetDisplayRootFrame(aFrame)); 8221 SVGObserverUtils::InvalidateDirectRenderingObservers(aFrame); 8222 nsIFrame* parent = aFrame; 8223 while (parent != aDisplayRoot && 8224 (parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(parent)) && 8225 !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) { 8226 SVGObserverUtils::InvalidateDirectRenderingObservers(parent); 8227 } 8228 8229 if (!aFrameChanged) { 8230 return; 8231 } 8232 8233 aFrame->MarkNeedsDisplayItemRebuild(); 8234 } 8235 8236 static void SchedulePaintInternal( 8237 nsIFrame* aDisplayRoot, nsIFrame* aFrame, 8238 nsIFrame::PaintType aType = nsIFrame::PAINT_DEFAULT) { 8239 MOZ_ASSERT(aDisplayRoot == nsLayoutUtils::GetDisplayRootFrame(aFrame)); 8240 nsPresContext* pres = aDisplayRoot->PresContext()->GetRootPresContext(); 8241 8242 // No need to schedule a paint for an external document since they aren't 8243 // painted directly. 8244 if (!pres || (pres->Document() && pres->Document()->IsResourceDoc())) { 8245 return; 8246 } 8247 if (!pres->GetContainerWeak()) { 8248 NS_WARNING("Shouldn't call SchedulePaint in a detached pres context"); 8249 return; 8250 } 8251 8252 pres->PresShell()->SchedulePaint(); 8253 8254 if (aType == nsIFrame::PAINT_DEFAULT) { 8255 aDisplayRoot->AddStateBits(NS_FRAME_UPDATE_LAYER_TREE); 8256 } 8257 } 8258 8259 static void InvalidateFrameInternal(nsIFrame* aFrame, bool aHasDisplayItem, 8260 bool aRebuildDisplayItems) { 8261 if (aHasDisplayItem) { 8262 aFrame->AddStateBits(NS_FRAME_NEEDS_PAINT); 8263 } 8264 8265 if (aRebuildDisplayItems) { 8266 aFrame->MarkNeedsDisplayItemRebuild(); 8267 } 8268 SVGObserverUtils::InvalidateDirectRenderingObservers(aFrame); 8269 bool needsSchedulePaint = false; 8270 if (nsLayoutUtils::IsPopup(aFrame)) { 8271 needsSchedulePaint = true; 8272 } else { 8273 nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); 8274 while (parent && 8275 !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) { 8276 if (aHasDisplayItem && !parent->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 8277 parent->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT); 8278 } 8279 SVGObserverUtils::InvalidateDirectRenderingObservers(parent); 8280 8281 // If we're inside a popup, then we need to make sure that we 8282 // call schedule paint so that the NS_FRAME_UPDATE_LAYER_TREE 8283 // flag gets added to the popup display root frame. 8284 if (nsLayoutUtils::IsPopup(parent)) { 8285 needsSchedulePaint = true; 8286 break; 8287 } 8288 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(parent); 8289 } 8290 if (!parent) { 8291 needsSchedulePaint = true; 8292 } 8293 } 8294 if (!aHasDisplayItem) { 8295 return; 8296 } 8297 if (needsSchedulePaint) { 8298 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame); 8299 SchedulePaintInternal(displayRoot, aFrame); 8300 } 8301 if (aFrame->HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) { 8302 aFrame->RemoveProperty(nsIFrame::InvalidationRect()); 8303 aFrame->RemoveStateBits(NS_FRAME_HAS_INVALID_RECT); 8304 } 8305 } 8306 8307 void nsIFrame::InvalidateFrameSubtree(bool aRebuildDisplayItems /* = true */) { 8308 InvalidateFrame(0, aRebuildDisplayItems); 8309 8310 if (HasAnyStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT)) { 8311 return; 8312 } 8313 8314 AddStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT); 8315 8316 for (const auto& childList : CrossDocChildLists()) { 8317 for (nsIFrame* child : childList.mList) { 8318 // Don't explicitly rebuild display items for our descendants, 8319 // since we should be marked and it implicitly includes all 8320 // descendants. 8321 child->InvalidateFrameSubtree(false); 8322 } 8323 } 8324 } 8325 8326 void nsIFrame::ClearInvalidationStateBits() { 8327 if (HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) { 8328 for (const auto& childList : CrossDocChildLists()) { 8329 for (nsIFrame* child : childList.mList) { 8330 child->ClearInvalidationStateBits(); 8331 } 8332 } 8333 } 8334 8335 RemoveStateBits(NS_FRAME_NEEDS_PAINT | NS_FRAME_DESCENDANT_NEEDS_PAINT | 8336 NS_FRAME_ALL_DESCENDANTS_NEED_PAINT); 8337 } 8338 8339 bool HasRetainedDataFor(const nsIFrame* aFrame, uint32_t aDisplayItemKey) { 8340 if (RefPtr<WebRenderUserData> data = 8341 GetWebRenderUserData<WebRenderFallbackData>(aFrame, 8342 aDisplayItemKey)) { 8343 return true; 8344 } 8345 8346 return false; 8347 } 8348 8349 void nsIFrame::InvalidateFrame(uint32_t aDisplayItemKey, 8350 bool aRebuildDisplayItems /* = true */) { 8351 bool hasDisplayItem = 8352 !aDisplayItemKey || HasRetainedDataFor(this, aDisplayItemKey); 8353 InvalidateFrameInternal(this, hasDisplayItem, aRebuildDisplayItems); 8354 } 8355 8356 void nsIFrame::InvalidateFrameWithRect(const nsRect& aRect, 8357 uint32_t aDisplayItemKey, 8358 bool aRebuildDisplayItems /* = true */) { 8359 if (aRect.IsEmpty()) { 8360 return; 8361 } 8362 bool hasDisplayItem = 8363 !aDisplayItemKey || HasRetainedDataFor(this, aDisplayItemKey); 8364 bool alreadyInvalid = false; 8365 if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) { 8366 InvalidateFrameInternal(this, hasDisplayItem, aRebuildDisplayItems); 8367 } else { 8368 alreadyInvalid = true; 8369 } 8370 8371 if (!hasDisplayItem) { 8372 return; 8373 } 8374 8375 nsRect* rect; 8376 if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) { 8377 rect = GetProperty(InvalidationRect()); 8378 MOZ_ASSERT(rect); 8379 } else { 8380 if (alreadyInvalid) { 8381 return; 8382 } 8383 rect = new nsRect(); 8384 AddProperty(InvalidationRect(), rect); 8385 AddStateBits(NS_FRAME_HAS_INVALID_RECT); 8386 } 8387 8388 *rect = rect->Union(aRect); 8389 } 8390 8391 bool nsIFrame::IsInvalid(nsRect& aRect) { 8392 if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) { 8393 return false; 8394 } 8395 8396 if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) { 8397 nsRect* rect = GetProperty(InvalidationRect()); 8398 NS_ASSERTION( 8399 rect, "Must have an invalid rect if NS_FRAME_HAS_INVALID_RECT is set!"); 8400 aRect = *rect; 8401 } else { 8402 aRect.SetEmpty(); 8403 } 8404 return true; 8405 } 8406 8407 void nsIFrame::SchedulePaint(PaintType aType, bool aFrameChanged) { 8408 if (PresShell()->IsPaintingSuppressed()) { 8409 // We can't have any display items yet, and when we unsuppress we will 8410 // invalidate the root frame. 8411 return; 8412 } 8413 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this); 8414 InvalidateRenderingObservers(displayRoot, this, aFrameChanged); 8415 SchedulePaintInternal(displayRoot, this, aType); 8416 } 8417 8418 void nsIFrame::SchedulePaintWithoutInvalidatingObservers(PaintType aType) { 8419 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this); 8420 SchedulePaintInternal(displayRoot, this, aType); 8421 } 8422 8423 void nsIFrame::InvalidateLayer(DisplayItemType aDisplayItemKey, 8424 const nsIntRect* aDamageRect, 8425 const nsRect* aFrameDamageRect, 8426 uint32_t aFlags /* = 0 */) { 8427 NS_ASSERTION(aDisplayItemKey > DisplayItemType::TYPE_ZERO, "Need a key"); 8428 8429 nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this); 8430 InvalidateRenderingObservers(displayRoot, this, false); 8431 8432 // Check if frame supports WebRender's async update 8433 if ((aFlags & UPDATE_IS_ASYNC) && 8434 WebRenderUserData::SupportsAsyncUpdate(this)) { 8435 // WebRender does not use layer, then return nullptr. 8436 return; 8437 } 8438 8439 if (aFrameDamageRect && aFrameDamageRect->IsEmpty()) { 8440 return; 8441 } 8442 8443 // In the bug 930056, dialer app startup but not shown on the 8444 // screen because sometimes we don't have any retainned data 8445 // for remote type displayitem and thus Repaint event is not 8446 // triggered. So, always invalidate in this case. 8447 DisplayItemType displayItemKey = aDisplayItemKey; 8448 if (aDisplayItemKey == DisplayItemType::TYPE_REMOTE) { 8449 displayItemKey = DisplayItemType::TYPE_ZERO; 8450 } 8451 8452 if (aFrameDamageRect) { 8453 InvalidateFrameWithRect(*aFrameDamageRect, 8454 static_cast<uint32_t>(displayItemKey)); 8455 } else { 8456 InvalidateFrame(static_cast<uint32_t>(displayItemKey)); 8457 } 8458 } 8459 8460 static nsRect ComputeEffectsRect(nsIFrame* aFrame, const nsRect& aOverflowRect, 8461 const nsSize& aNewSize) { 8462 nsRect r = aOverflowRect; 8463 8464 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 8465 // For SVG frames, we only need to account for filters. 8466 // TODO: We could also take account of clipPath and mask to reduce the 8467 // ink overflow, but that's not essential. 8468 if (aFrame->StyleEffects()->HasFilters()) { 8469 aFrame->SetOrUpdateDeletableProperty(nsIFrame::PreEffectsBBoxProperty(), 8470 r); 8471 r = SVGUtils::GetPostFilterInkOverflowRect(aFrame, aOverflowRect); 8472 } 8473 return r; 8474 } 8475 8476 // box-shadow 8477 r.UnionRect(r, nsLayoutUtils::GetBoxShadowRectForFrame(aFrame, aNewSize)); 8478 8479 // border-image-outset. 8480 // We need to include border-image-outset because it can cause the 8481 // border image to be drawn beyond the border box. 8482 8483 // (1) It's important we not check whether there's a border-image 8484 // since the style hint for a change in border image doesn't cause 8485 // reflow, and that's probably more important than optimizing the 8486 // overflow areas for the silly case of border-image-outset without 8487 // border-image 8488 // (2) It's important that we not check whether the border-image 8489 // is actually loaded, since that would require us to reflow when 8490 // the image loads. 8491 const nsStyleBorder* styleBorder = aFrame->StyleBorder(); 8492 nsMargin outsetMargin = styleBorder->GetImageOutset(); 8493 8494 if (outsetMargin != nsMargin(0, 0, 0, 0)) { 8495 nsRect outsetRect(nsPoint(0, 0), aNewSize); 8496 outsetRect.Inflate(outsetMargin); 8497 r.UnionRect(r, outsetRect); 8498 } 8499 8500 // Note that we don't remove the outlineInnerRect if a frame loses outline 8501 // style. That would require an extra property lookup for every frame, 8502 // or a new frame state bit to track whether a property had been stored, 8503 // or something like that. It's not worth doing that here. At most it's 8504 // only one heap-allocated rect per frame and it will be cleaned up when 8505 // the frame dies. 8506 8507 if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame)) { 8508 aFrame->SetOrUpdateDeletableProperty(nsIFrame::PreEffectsBBoxProperty(), r); 8509 r = SVGIntegrationUtils::ComputePostEffectsInkOverflowRect(aFrame, r); 8510 } 8511 8512 return r; 8513 } 8514 8515 void nsIFrame::SetPosition(const nsPoint& aPt) { 8516 if (mRect.TopLeft() == aPt) { 8517 return; 8518 } 8519 mRect.MoveTo(aPt); 8520 MarkNeedsDisplayItemRebuild(); 8521 } 8522 8523 void nsIFrame::MovePositionBy(const nsPoint& aTranslation) { 8524 nsPoint position = GetNormalPosition() + aTranslation; 8525 8526 const nsMargin* computedOffsets = nullptr; 8527 if (IsRelativelyOrStickyPositioned()) { 8528 computedOffsets = GetProperty(nsIFrame::ComputedOffsetProperty()); 8529 } 8530 ReflowInput::ApplyRelativePositioning( 8531 this, computedOffsets ? *computedOffsets : nsMargin(), &position); 8532 SetPosition(position); 8533 } 8534 8535 nsRect nsIFrame::GetNormalRect() const { 8536 bool hasProperty; 8537 nsPoint normalPosition = GetProperty(NormalPositionProperty(), &hasProperty); 8538 if (hasProperty) { 8539 return nsRect(normalPosition, GetSize()); 8540 } 8541 return GetRect(); 8542 } 8543 8544 nsRect nsIFrame::GetBoundingClientRect() { 8545 return nsLayoutUtils::GetAllInFlowRectsUnion( 8546 this, nsLayoutUtils::GetContainingBlockForClientRect(this), 8547 nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms); 8548 } 8549 8550 nsPoint nsIFrame::GetPositionIgnoringScrolling() const { 8551 return GetParent() ? GetParent()->GetPositionOfChildIgnoringScrolling(this) 8552 : GetPosition(); 8553 } 8554 8555 nsRect nsIFrame::GetOverflowRect(OverflowType aType) const { 8556 // Note that in some cases the overflow area might not have been 8557 // updated (yet) to reflect any outline set on the frame or the area 8558 // of child frames. That's OK because any reflow that updates these 8559 // areas will invalidate the appropriate area, so any (mis)uses of 8560 // this method will be fixed up. 8561 8562 if (mOverflow.mType == OverflowStorageType::Large) { 8563 // there is an overflow rect, and it's not stored as deltas but as 8564 // a separately-allocated rect 8565 return GetOverflowAreasProperty()->Overflow(aType); 8566 } 8567 8568 if (aType == OverflowType::Ink && 8569 mOverflow.mType != OverflowStorageType::None) { 8570 return InkOverflowFromDeltas(); 8571 } 8572 8573 return GetRectRelativeToSelf(); 8574 } 8575 8576 OverflowAreas nsIFrame::GetOverflowAreas() const { 8577 if (mOverflow.mType == OverflowStorageType::Large) { 8578 // there is an overflow rect, and it's not stored as deltas but as 8579 // a separately-allocated rect 8580 return *GetOverflowAreasProperty(); 8581 } 8582 8583 return OverflowAreas(InkOverflowFromDeltas(), 8584 nsRect(nsPoint(0, 0), GetSize())); 8585 } 8586 8587 OverflowAreas nsIFrame::GetOverflowAreasRelativeToSelf() const { 8588 if (IsTransformed()) { 8589 if (OverflowAreas* preTransformOverflows = 8590 GetProperty(PreTransformOverflowAreasProperty())) { 8591 return *preTransformOverflows; 8592 } 8593 } 8594 return GetOverflowAreas(); 8595 } 8596 8597 OverflowAreas nsIFrame::GetOverflowAreasRelativeToParent() const { 8598 return GetOverflowAreas() + GetPosition(); 8599 } 8600 8601 OverflowAreas nsIFrame::GetActualAndNormalOverflowAreasRelativeToParent() 8602 const { 8603 const bool hasAnchorPosReference = HasAnchorPosReference(); 8604 if (MOZ_LIKELY(!IsRelativelyOrStickyPositioned() && !hasAnchorPosReference)) { 8605 return GetOverflowAreasRelativeToParent(); 8606 } 8607 8608 const OverflowAreas overflows = GetOverflowAreas(); 8609 OverflowAreas actualAndNormalOverflows = overflows + GetNormalPosition(); 8610 if (IsRelativelyPositioned() || hasAnchorPosReference) { 8611 actualAndNormalOverflows.UnionWith(overflows + GetPosition()); 8612 } else { 8613 // For sticky positioned elements, we only use the normal position for the 8614 // scrollable overflow. This avoids circular dependencies between sticky 8615 // positioned elements and their scroll container. (The scroll position and 8616 // the scroll container's size impact the sticky position, so we don't want 8617 // the sticky position to impact them.) 8618 MOZ_ASSERT(IsStickyPositioned()); 8619 actualAndNormalOverflows.UnionWith( 8620 OverflowAreas(overflows.InkOverflow() + GetPosition(), nsRect())); 8621 } 8622 return actualAndNormalOverflows; 8623 } 8624 8625 nsRect nsIFrame::ScrollableOverflowRectRelativeToParent() const { 8626 return ScrollableOverflowRect() + GetPosition(); 8627 } 8628 8629 nsRect nsIFrame::InkOverflowRectRelativeToParent() const { 8630 return InkOverflowRect() + GetPosition(); 8631 } 8632 8633 nsRect nsIFrame::ScrollableOverflowRectRelativeToSelf() const { 8634 if (IsTransformed()) { 8635 if (OverflowAreas* preTransformOverflows = 8636 GetProperty(PreTransformOverflowAreasProperty())) { 8637 return preTransformOverflows->ScrollableOverflow(); 8638 } 8639 } 8640 return ScrollableOverflowRect(); 8641 } 8642 8643 nsRect nsIFrame::InkOverflowRectRelativeToSelf() const { 8644 if (IsTransformed()) { 8645 if (OverflowAreas* preTransformOverflows = 8646 GetProperty(PreTransformOverflowAreasProperty())) { 8647 return preTransformOverflows->InkOverflow(); 8648 } 8649 } 8650 return InkOverflowRect(); 8651 } 8652 8653 nsRect nsIFrame::PreEffectsInkOverflowRect() const { 8654 nsRect* r = GetProperty(nsIFrame::PreEffectsBBoxProperty()); 8655 return r ? *r : InkOverflowRectRelativeToSelf(); 8656 } 8657 8658 bool nsIFrame::UpdateOverflow() { 8659 MOZ_ASSERT(FrameMaintainsOverflow(), 8660 "Non-display SVG do not maintain ink overflow rects"); 8661 8662 nsRect rect(nsPoint(0, 0), GetSize()); 8663 OverflowAreas overflowAreas(rect, rect); 8664 8665 if (!ComputeCustomOverflow(overflowAreas)) { 8666 // If updating overflow wasn't supported by this frame, then it should 8667 // have scheduled any necessary reflows. We can return false to say nothing 8668 // changed, and wait for reflow to correct it. 8669 return false; 8670 } 8671 8672 UnionChildOverflow(overflowAreas); 8673 8674 if (FinishAndStoreOverflow(overflowAreas, GetSize())) { 8675 return true; 8676 } 8677 8678 // Frames that combine their 3d transform with their ancestors 8679 // only compute a pre-transform overflow rect, and then contribute 8680 // to the normal overflow rect of the preserve-3d root. Always return 8681 // true here so that we propagate changes up to the root for final 8682 // calculation. 8683 return Combines3DTransformWithAncestors(); 8684 } 8685 8686 /* virtual */ 8687 bool nsIFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) { 8688 return true; 8689 } 8690 8691 bool nsIFrame::DoesClipChildrenInBothAxes() const { 8692 if (IsScrollContainerOrSubclass()) { 8693 return true; 8694 } 8695 const nsStyleDisplay* display = StyleDisplay(); 8696 if (display->IsContainPaint() && SupportsContainLayoutAndPaint()) { 8697 return true; 8698 } 8699 return display->mOverflowX == StyleOverflow::Clip && 8700 display->mOverflowY == StyleOverflow::Clip; 8701 } 8702 8703 /* virtual */ 8704 void nsIFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas, 8705 bool aAsIfScrolled) { 8706 if (aAsIfScrolled || !DoesClipChildrenInBothAxes()) { 8707 nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas); 8708 } 8709 } 8710 8711 // Return true if this form control element's preferred size property (but not 8712 // percentage max size property) contains a percentage value that should be 8713 // resolved against zero when calculating its min-content contribution in the 8714 // corresponding axis. 8715 // 8716 // For proper replaced elements, the percentage value in both their max size 8717 // property or preferred size property should be resolved against zero. This is 8718 // handled in IsPercentageResolvedAgainstZero(). 8719 inline static bool FormControlShrinksForPercentSize(const nsIFrame* aFrame) { 8720 if (!aFrame->IsReplaced()) { 8721 // Quick test to reject most frames. 8722 return false; 8723 } 8724 8725 switch (aFrame->Type()) { 8726 case LayoutFrameType::Progress: 8727 case LayoutFrameType::Range: 8728 case LayoutFrameType::TextInput: 8729 case LayoutFrameType::ColorControl: 8730 case LayoutFrameType::ComboboxControl: 8731 case LayoutFrameType::ListControl: 8732 case LayoutFrameType::CheckboxRadio: 8733 case LayoutFrameType::FileControl: 8734 case LayoutFrameType::ImageControl: 8735 return true; 8736 default: 8737 // True buttons (<button>, backed by block/grid/flex frame) and most 8738 // button-flavored <inputs> (those backed by InputButtonControlFrame) 8739 // don't have this shrinking behavior. But color-inputs and comboboxes do; 8740 // and both of those derive from ButtonControlFrame. So: we can't easily 8741 // use do_QueryFrame to differentiate the buttons-that-do vs. the 8742 // buttons-that-don't. So we explicitly list the buttons-that-do by 8743 // LayoutFrameType above, and the others fall into this catch-all. 8744 return false; 8745 } 8746 } 8747 8748 bool nsIFrame::IsPercentageResolvedAgainstZero( 8749 const StyleSize& aStyleSize, const StyleMaxSize& aStyleMaxSize) const { 8750 const bool sizeHasPercent = aStyleSize.HasPercent(); 8751 return ((sizeHasPercent || aStyleMaxSize.HasPercent()) && 8752 HasReplacedSizing()) || 8753 (sizeHasPercent && FormControlShrinksForPercentSize(this)); 8754 } 8755 8756 // Summary of the Cyclic-Percentage Intrinsic Size Contribution Rules: 8757 // 8758 // Element Type | Replaced | Non-replaced 8759 // Contribution Type | min-content max-content | min-content max-content 8760 // --------------------------------------------------------------------------- 8761 // min size | zero zero | zero zero 8762 // max & preferred size | zero initial | initial initial 8763 // 8764 // https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution 8765 bool nsIFrame::IsPercentageResolvedAgainstZero(const LengthPercentage& aSize, 8766 SizeProperty aProperty) const { 8767 // Early return to avoid calling the virtual function, IsFrameOfType(). 8768 if (aProperty == SizeProperty::MinSize) { 8769 return true; 8770 } 8771 8772 const bool hasPercentOnReplaced = aSize.HasPercent() && HasReplacedSizing(); 8773 if (aProperty == SizeProperty::MaxSize) { 8774 return hasPercentOnReplaced; 8775 } 8776 8777 MOZ_ASSERT(aProperty == SizeProperty::Size); 8778 return hasPercentOnReplaced || 8779 (aSize.HasPercent() && FormControlShrinksForPercentSize(this)); 8780 } 8781 8782 bool nsIFrame::IsBlockWrapper() const { 8783 auto pseudoType = Style()->GetPseudoType(); 8784 return pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper || 8785 pseudoType == PseudoStyleType::cellContent || 8786 pseudoType == PseudoStyleType::columnSpanWrapper; 8787 } 8788 8789 bool nsIFrame::IsBlockFrameOrSubclass() const { 8790 const nsBlockFrame* thisAsBlock = do_QueryFrame(this); 8791 return !!thisAsBlock; 8792 } 8793 8794 bool nsIFrame::IsImageFrameOrSubclass() const { 8795 const nsImageFrame* asImage = do_QueryFrame(this); 8796 return !!asImage; 8797 } 8798 8799 bool nsIFrame::IsScrollContainerOrSubclass() const { 8800 const bool result = IsScrollContainerFrame() || IsListControlFrame(); 8801 MOZ_ASSERT(result == !!QueryFrame(ScrollContainerFrame::kFrameIID)); 8802 return result; 8803 } 8804 8805 bool nsIFrame::IsSubgrid() const { 8806 return IsGridContainerFrame() && 8807 static_cast<const nsGridContainerFrame*>(this)->IsSubgrid(); 8808 } 8809 8810 static nsIFrame* GetNearestBlockContainer(nsIFrame* frame) { 8811 while (!frame->IsBlockContainer()) { 8812 frame = frame->GetParent(); 8813 NS_ASSERTION( 8814 frame, 8815 "How come we got to the root frame without seeing a containing block?"); 8816 } 8817 return frame; 8818 } 8819 8820 bool nsIFrame::IsBlockContainer() const { 8821 // The block wrappers we use to wrap blocks inside inlines aren't 8822 // described in the CSS spec. We need to make them not be containing 8823 // blocks. 8824 // Since the parent of such a block is either a normal block or 8825 // another such pseudo, this shouldn't cause anything bad to happen. 8826 // Also the anonymous blocks inside table cells are not containing blocks. 8827 // 8828 // If we ever start skipping table row groups from being containing blocks, 8829 // you need to remove the StickyScrollContainer hack referencing bug 1421660. 8830 // Table rows are not containing blocks either 8831 return !IsLineParticipant() && !IsBlockWrapper() && !IsTableRowFrame(); 8832 } 8833 8834 nsIFrame* nsIFrame::GetContainingBlock( 8835 uint32_t aFlags, const nsStyleDisplay* aStyleDisplay) const { 8836 MOZ_ASSERT(aStyleDisplay == StyleDisplay()); 8837 8838 // Keep this in sync with MightBeContainingBlockFor in ReflowInput.cpp. 8839 8840 if (!GetParent()) { 8841 return nullptr; 8842 } 8843 // MathML frames might have absolute positioning style, but they would 8844 // still be in-flow. So we have to check to make sure that the frame 8845 // is really out-of-flow too. 8846 nsIFrame* f; 8847 if (IsAbsolutelyPositioned(aStyleDisplay)) { 8848 f = GetParent(); // the parent is always the containing block 8849 } else { 8850 f = GetNearestBlockContainer(GetParent()); 8851 } 8852 8853 if (aFlags & SKIP_SCROLLED_FRAME && f && 8854 f->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { 8855 f = f->GetParent(); 8856 } 8857 return f; 8858 } 8859 8860 #ifdef DEBUG_FRAME_DUMP 8861 8862 Maybe<uint32_t> nsIFrame::ContentIndexInContainer(const nsIFrame* aFrame) { 8863 if (nsIContent* content = aFrame->GetContent()) { 8864 return content->ComputeIndexInParentContent(); 8865 } 8866 return Nothing(); 8867 } 8868 8869 nsAutoCString nsIFrame::ListTag(bool aListOnlyDeterministic) const { 8870 nsAutoString tmp; 8871 GetFrameName(tmp); 8872 8873 nsAutoCString tag; 8874 tag += NS_ConvertUTF16toUTF8(tmp); 8875 ListPtr(tag, aListOnlyDeterministic, this, "@"); 8876 return tag; 8877 } 8878 8879 std::string nsIFrame::ConvertToString(const LogicalRect& aRect, 8880 const WritingMode aWM, ListFlags aFlags) { 8881 if (aFlags.contains(ListFlag::DisplayInCSSPixels)) { 8882 // Abuse CSSRect to store all LogicalRect's dimensions in CSS pixels. 8883 return ToString(mozilla::CSSRect(CSSPixel::FromAppUnits(aRect.IStart(aWM)), 8884 CSSPixel::FromAppUnits(aRect.BStart(aWM)), 8885 CSSPixel::FromAppUnits(aRect.ISize(aWM)), 8886 CSSPixel::FromAppUnits(aRect.BSize(aWM)))); 8887 } 8888 return ToString(aRect); 8889 } 8890 8891 std::string nsIFrame::ConvertToString(const LogicalSize& aSize, 8892 const WritingMode aWM, ListFlags aFlags) { 8893 if (aFlags.contains(ListFlag::DisplayInCSSPixels)) { 8894 // Abuse CSSSize to store all LogicalSize's dimensions in CSS pixels. 8895 return ToString(CSSSize(CSSPixel::FromAppUnits(aSize.ISize(aWM)), 8896 CSSPixel::FromAppUnits(aSize.BSize(aWM)))); 8897 } 8898 return ToString(aSize); 8899 } 8900 8901 // Debugging 8902 void nsIFrame::ListGeneric(nsACString& aTo, const char* aPrefix, 8903 ListFlags aFlags) const { 8904 aTo += aPrefix; 8905 const bool onlyDeterministic = 8906 aFlags.contains(ListFlag::OnlyListDeterministicInfo); 8907 aTo += ListTag(onlyDeterministic); 8908 if (!onlyDeterministic) { 8909 if (GetParent()) { 8910 aTo += nsPrintfCString(" parent=%p", static_cast<void*>(GetParent())); 8911 } 8912 if (GetNextSibling()) { 8913 aTo += nsPrintfCString(" next=%p", static_cast<void*>(GetNextSibling())); 8914 } 8915 } 8916 if (GetPrevContinuation()) { 8917 bool fluid = GetPrevInFlow() == GetPrevContinuation(); 8918 aTo += nsPrintfCString(" prev-%s", fluid ? "in-flow" : "continuation"); 8919 ListPtr(aTo, aFlags, GetPrevContinuation()); 8920 } 8921 if (GetNextContinuation()) { 8922 bool fluid = GetNextInFlow() == GetNextContinuation(); 8923 aTo += nsPrintfCString(" next-%s", fluid ? "in-flow" : "continuation"); 8924 ListPtr(aTo, aFlags, GetNextContinuation()); 8925 } 8926 if (const nsAtom* const autoPageValue = 8927 GetProperty(AutoPageValueProperty())) { 8928 aTo += " AutoPage="; 8929 aTo += nsAtomCString(autoPageValue); 8930 } 8931 if (const nsIFrame::PageValues* const pageValues = 8932 GetProperty(PageValuesProperty())) { 8933 aTo += " PageValues={"; 8934 if (pageValues->mStartPageValue) { 8935 aTo += nsAtomCString(pageValues->mStartPageValue); 8936 } else { 8937 aTo += "<null>"; 8938 } 8939 aTo += ", "; 8940 if (pageValues->mEndPageValue) { 8941 aTo += nsAtomCString(pageValues->mEndPageValue); 8942 } else { 8943 aTo += "<null>"; 8944 } 8945 aTo += "}"; 8946 } 8947 void* IBsibling = GetProperty(IBSplitSibling()); 8948 if (IBsibling) { 8949 aTo += nsPrintfCString(" IBSplitSibling"); 8950 ListPtr(aTo, aFlags, IBsibling); 8951 } 8952 void* IBprevsibling = GetProperty(IBSplitPrevSibling()); 8953 if (IBprevsibling) { 8954 aTo += nsPrintfCString(" IBSplitPrevSibling"); 8955 ListPtr(aTo, aFlags, IBprevsibling); 8956 } 8957 if (nsLayoutUtils::FontSizeInflationEnabled(PresContext())) { 8958 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) { 8959 aTo += nsPrintfCString(" FFR"); 8960 if (nsFontInflationData* data = 8961 nsFontInflationData::FindFontInflationDataFor(this)) { 8962 aTo += nsPrintfCString( 8963 ",enabled=%s,UIS=%s", data->InflationEnabled() ? "yes" : "no", 8964 ConvertToString(data->UsableISize(), aFlags).c_str()); 8965 } 8966 } 8967 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) { 8968 aTo += nsPrintfCString(" FIC"); 8969 } 8970 aTo += nsPrintfCString(" FI=%f", nsLayoutUtils::FontSizeInflationFor(this)); 8971 } 8972 aTo += nsPrintfCString(" %s", ConvertToString(mRect, aFlags).c_str()); 8973 8974 mozilla::WritingMode wm = GetWritingMode(); 8975 if (wm.IsVertical() || wm.IsBidiRTL()) { 8976 aTo += 8977 nsPrintfCString(" wm=%s logical-size=(%s)", ToString(wm).c_str(), 8978 ConvertToString(GetLogicalSize(), wm, aFlags).c_str()); 8979 } 8980 8981 nsIFrame* parent = GetParent(); 8982 if (parent) { 8983 WritingMode pWM = parent->GetWritingMode(); 8984 if (pWM.IsVertical() || pWM.IsBidiRTL()) { 8985 nsSize containerSize = parent->mRect.Size(); 8986 LogicalRect lr(pWM, mRect, containerSize); 8987 aTo += nsPrintfCString(" parent-wm=%s cs=(%s) logical-rect=%s", 8988 ToString(pWM).c_str(), 8989 ConvertToString(containerSize, aFlags).c_str(), 8990 ConvertToString(lr, pWM, aFlags).c_str()); 8991 } 8992 } 8993 nsIFrame* f = const_cast<nsIFrame*>(this); 8994 if (f->HasOverflowAreas()) { 8995 nsRect io = f->InkOverflowRect(); 8996 if (!io.IsEqualEdges(mRect)) { 8997 aTo += nsPrintfCString(" ink-overflow=%s", 8998 ConvertToString(io, aFlags).c_str()); 8999 } 9000 nsRect so = f->ScrollableOverflowRect(); 9001 if (!so.IsEqualEdges(mRect)) { 9002 aTo += nsPrintfCString(" scr-overflow=%s", 9003 ConvertToString(so, aFlags).c_str()); 9004 } 9005 } 9006 if (OverflowAreas* preTransformOverflows = 9007 f->GetProperty(PreTransformOverflowAreasProperty())) { 9008 nsRect io = preTransformOverflows->InkOverflow(); 9009 if (!io.IsEqualEdges(mRect) && 9010 (!f->HasOverflowAreas() || !io.IsEqualEdges(f->InkOverflowRect()))) { 9011 aTo += nsPrintfCString(" pre-transform-ink-overflow=%s", 9012 ConvertToString(io, aFlags).c_str()); 9013 } 9014 nsRect so = preTransformOverflows->ScrollableOverflow(); 9015 if (!so.IsEqualEdges(mRect) && 9016 (!f->HasOverflowAreas() || 9017 !so.IsEqualEdges(f->ScrollableOverflowRect()))) { 9018 aTo += nsPrintfCString(" pre-transform-scr-overflow=%s", 9019 ConvertToString(so, aFlags).c_str()); 9020 } 9021 } 9022 bool hasNormalPosition; 9023 nsPoint normalPosition = GetNormalPosition(&hasNormalPosition); 9024 if (hasNormalPosition) { 9025 aTo += nsPrintfCString(" normal-position=%s", 9026 ConvertToString(normalPosition, aFlags).c_str()); 9027 } 9028 if (HasProperty(BidiDataProperty())) { 9029 FrameBidiData bidi = GetBidiData(); 9030 aTo += nsPrintfCString(" bidi(%d,%d,%d)", bidi.baseLevel.Value(), 9031 bidi.embeddingLevel.Value(), 9032 bidi.precedingControl.Value()); 9033 } 9034 if (IsTransformed()) { 9035 aTo += nsPrintfCString(" transformed"); 9036 } 9037 if (ChildrenHavePerspective()) { 9038 aTo += nsPrintfCString(" perspective"); 9039 } 9040 if (Extend3DContext()) { 9041 aTo += nsPrintfCString(" extend-3d"); 9042 } 9043 if (Combines3DTransformWithAncestors()) { 9044 aTo += nsPrintfCString(" combines-3d-transform-with-ancestors"); 9045 } 9046 if (mContent) { 9047 if (!onlyDeterministic) { 9048 aTo += nsPrintfCString(" [content=%p]", static_cast<void*>(mContent)); 9049 } 9050 if (IsPrimaryFrame() && DisplayPortUtils::HasDisplayPort(mContent)) { 9051 // Anon boxes and continuations point to the same content - Just print on 9052 // primary frame. 9053 aTo += "[displayport]"_ns; 9054 } 9055 } 9056 if (!onlyDeterministic) { 9057 aTo += nsPrintfCString("[cs=%p]", static_cast<void*>(mComputedStyle)); 9058 } 9059 if (mComputedStyle) { 9060 const auto pseudoType = mComputedStyle->GetPseudoType(); 9061 const auto pseudoTypeStr = ToString(pseudoType); 9062 if (!pseudoTypeStr.empty()) { 9063 aTo += nsPrintfCString("[%s]", pseudoTypeStr.c_str()); 9064 } 9065 } 9066 9067 auto contentVisibility = StyleDisplay()->ContentVisibility(*this); 9068 if (contentVisibility != StyleContentVisibility::Visible) { 9069 aTo += nsPrintfCString(" [content-visibility="); 9070 if (contentVisibility == StyleContentVisibility::Auto) { 9071 aTo += "auto, "_ns; 9072 } else if (contentVisibility == StyleContentVisibility::Hidden) { 9073 aTo += "hidden, "_ns; 9074 } 9075 9076 if (HidesContent()) { 9077 aTo += "HidesContent=hidden"_ns; 9078 } else { 9079 aTo += "HidesContent=visibile"_ns; 9080 } 9081 aTo += "]"; 9082 } 9083 9084 if (IsFrameModified()) { 9085 aTo += nsPrintfCString(" modified"); 9086 } 9087 9088 if (HasModifiedDescendants()) { 9089 aTo += nsPrintfCString(" has-modified-descendants"); 9090 } 9091 } 9092 9093 void nsIFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const { 9094 nsCString str; 9095 ListGeneric(str, aPrefix, aFlags); 9096 fprintf_stderr(out, "%s\n", str.get()); 9097 } 9098 9099 void nsIFrame::ListTextRuns(FILE* out) const { 9100 nsTHashSet<const void*> seen; 9101 ListTextRuns(out, seen); 9102 } 9103 9104 void nsIFrame::ListTextRuns(FILE* out, nsTHashSet<const void*>& aSeen) const { 9105 for (const auto& childList : ChildLists()) { 9106 for (const nsIFrame* kid : childList.mList) { 9107 kid->ListTextRuns(out, aSeen); 9108 } 9109 } 9110 } 9111 9112 void nsIFrame::ListMatchedRules(FILE* out, const char* aPrefix) const { 9113 AutoTArray<StyleMatchingDeclarationBlock, 8> decls; 9114 Servo_ComputedValues_GetMatchingDeclarations(Style(), &decls); 9115 for (const StyleMatchingDeclarationBlock& block : decls) { 9116 nsAutoCString ruleText; 9117 Servo_DeclarationBlock_GetCssText(block.block, &ruleText); 9118 fprintf_stderr(out, "%s%s\n", aPrefix, ruleText.get()); 9119 } 9120 } 9121 9122 void nsIFrame::ListWithMatchedRules(FILE* out, const char* aPrefix) const { 9123 fprintf_stderr(out, "%s%s\n", aPrefix, ListTag().get()); 9124 9125 nsCString rulePrefix; 9126 rulePrefix += aPrefix; 9127 rulePrefix += " "; 9128 ListMatchedRules(out, rulePrefix.get()); 9129 } 9130 9131 nsresult nsIFrame::GetFrameName(nsAString& aResult) const { 9132 return MakeFrameName(u"Frame"_ns, aResult); 9133 } 9134 9135 nsresult nsIFrame::MakeFrameName(const nsAString& aType, 9136 nsAString& aResult) const { 9137 aResult = aType; 9138 if (mContent && !mContent->IsText()) { 9139 nsAutoString buf; 9140 mContent->NodeInfo()->NameAtom()->ToString(buf); 9141 if (nsAtom* id = mContent->GetID()) { 9142 buf.AppendLiteral(" id="); 9143 buf.Append(nsDependentAtomString(id)); 9144 } 9145 if (IsSubDocumentFrame()) { 9146 nsAutoString src; 9147 mContent->AsElement()->GetAttr(nsGkAtoms::src, src); 9148 buf.AppendLiteral(" src="); 9149 buf.Append(src); 9150 } 9151 aResult.Append('('); 9152 aResult.Append(buf); 9153 aResult.Append(')'); 9154 } 9155 aResult.Append('('); 9156 Maybe<uint32_t> index = ContentIndexInContainer(this); 9157 if (index.isSome()) { 9158 aResult.AppendInt(*index); 9159 } else { 9160 aResult.AppendInt(-1); 9161 } 9162 aResult.Append(')'); 9163 return NS_OK; 9164 } 9165 9166 void nsIFrame::DumpFrameTree() const { DumpFrameTree(false); } 9167 9168 void nsIFrame::DumpFrameTree(bool aListOnlyDeterministic) const { 9169 ListFlags flags; 9170 if (aListOnlyDeterministic) { 9171 flags += ListFlag::OnlyListDeterministicInfo; 9172 } 9173 PresShell()->GetRootFrame()->List(stderr, "", flags); 9174 } 9175 9176 void nsIFrame::DumpFrameTreeInCSSPixels() const { 9177 DumpFrameTreeInCSSPixels(false); 9178 } 9179 9180 void nsIFrame::DumpFrameTreeInCSSPixels(bool aListOnlyDeterministic) const { 9181 ListFlags flags{ListFlag::DisplayInCSSPixels}; 9182 if (aListOnlyDeterministic) { 9183 flags += ListFlag::OnlyListDeterministicInfo; 9184 } 9185 PresShell()->GetRootFrame()->List(stderr, "", flags); 9186 } 9187 9188 void nsIFrame::DumpFrameTreeLimited() const { List(stderr); } 9189 void nsIFrame::DumpFrameTreeLimitedInCSSPixels() const { 9190 List(stderr, "", ListFlag::DisplayInCSSPixels); 9191 } 9192 9193 #endif 9194 9195 bool nsIFrame::IsVisibleForPainting() const { 9196 return StyleVisibility()->IsVisible(); 9197 } 9198 9199 bool nsIFrame::IsVisibleOrCollapsedForPainting() const { 9200 return StyleVisibility()->IsVisibleOrCollapsed(); 9201 } 9202 9203 /* virtual */ 9204 bool nsIFrame::IsEmpty() { 9205 return IsHiddenByContentVisibilityOfInFlowParentForLayout(); 9206 } 9207 9208 bool nsIFrame::CachedIsEmpty() { 9209 NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IS_DIRTY) || 9210 IsHiddenByContentVisibilityOfInFlowParentForLayout(), 9211 "Must only be called on reflowed lines or those hidden by " 9212 "content-visibility."); 9213 return IsEmpty(); 9214 } 9215 9216 /* virtual */ 9217 bool nsIFrame::IsSelfEmpty() { 9218 return IsHiddenByContentVisibilityOfInFlowParentForLayout(); 9219 } 9220 9221 nsISelectionController* nsIFrame::GetSelectionController() const { 9222 if (nsTextControlFrame* const tcf = GetContainingTextControlFrame()) { 9223 return tcf->ControlElement()->GetSelectionController(); 9224 } 9225 return static_cast<nsISelectionController*>(PresShell()); 9226 } 9227 9228 int16_t nsIFrame::GetDisplaySelection() const { 9229 nsISelectionController* const selCon = GetSelectionController(); 9230 if (MOZ_UNLIKELY(!selCon)) { 9231 return nsISelectionController::SELECTION_OFF; 9232 } 9233 int16_t display = nsISelectionController::SELECTION_OFF; 9234 selCon->GetDisplaySelection(&display); 9235 return display; 9236 } 9237 9238 already_AddRefed<nsFrameSelection> nsIFrame::GetFrameSelection() { 9239 RefPtr<nsFrameSelection> fs = 9240 const_cast<nsFrameSelection*>(GetConstFrameSelection()); 9241 return fs.forget(); 9242 } 9243 9244 const nsFrameSelection* nsIFrame::GetConstFrameSelection() const { 9245 if (nsTextControlFrame* tcf = GetContainingTextControlFrame()) { 9246 return tcf->GetOwnedFrameSelection(); 9247 } 9248 return PresShell()->ConstFrameSelection(); 9249 } 9250 9251 bool nsIFrame::IsFrameSelected() const { 9252 NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(), 9253 "use the public IsSelected() instead"); 9254 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 9255 if (const ShadowRoot* shadowRoot = 9256 GetContent()->GetShadowRootForSelection()) { 9257 return shadowRoot->IsSelected(0, shadowRoot->GetChildCount()); 9258 } 9259 } 9260 return GetContent()->IsSelected(0, GetContent()->GetChildCount()); 9261 } 9262 9263 nsresult nsIFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) { 9264 MOZ_ASSERT(outPoint != nullptr, "Null parameter"); 9265 nsRect contentRect = GetContentRectRelativeToSelf(); 9266 nsPoint pt = contentRect.TopLeft(); 9267 if (mContent) { 9268 nsIContent* newContent = mContent->GetParent(); 9269 if (newContent) { 9270 const int32_t newOffset = newContent->ComputeIndexOf_Deprecated(mContent); 9271 9272 // Find the direction of the frame from the EmbeddingLevelProperty, 9273 // which is the resolved bidi level set in 9274 // nsBidiPresUtils::ResolveParagraph (odd levels = right-to-left). 9275 // If the embedding level isn't set, just use the CSS direction 9276 // property. 9277 bool hasBidiData; 9278 FrameBidiData bidiData = GetProperty(BidiDataProperty(), &hasBidiData); 9279 bool isRTL = hasBidiData 9280 ? bidiData.embeddingLevel.IsRTL() 9281 : StyleVisibility()->mDirection == StyleDirection::Rtl; 9282 if ((!isRTL && inOffset > newOffset) || 9283 (isRTL && inOffset <= newOffset)) { 9284 pt = contentRect.TopRight(); 9285 } 9286 } 9287 } 9288 *outPoint = pt; 9289 return NS_OK; 9290 } 9291 9292 nsresult nsIFrame::GetCharacterRectsInRange(int32_t aInOffset, int32_t aLength, 9293 nsTArray<nsRect>& aOutRect) { 9294 /* no text */ 9295 return NS_ERROR_FAILURE; 9296 } 9297 9298 nsresult nsIFrame::GetChildFrameContainingOffset(int32_t inContentOffset, 9299 bool inHint, 9300 int32_t* outFrameContentOffset, 9301 nsIFrame** outChildFrame) { 9302 MOZ_ASSERT(outChildFrame && outFrameContentOffset, "Null parameter"); 9303 *outFrameContentOffset = (int32_t)inHint; 9304 // the best frame to reflect any given offset would be a visible frame if 9305 // possible i.e. we are looking for a valid frame to place the blinking caret 9306 nsRect rect = GetRect(); 9307 if (!rect.width || !rect.height) { 9308 // if we have a 0 width or height then lets look for another frame that 9309 // possibly has the same content. If we have no frames in flow then just 9310 // let us return 'this' frame 9311 nsIFrame* nextFlow = GetNextInFlow(); 9312 if (nextFlow) { 9313 return nextFlow->GetChildFrameContainingOffset( 9314 inContentOffset, inHint, outFrameContentOffset, outChildFrame); 9315 } 9316 } 9317 *outChildFrame = this; 9318 return NS_OK; 9319 } 9320 9321 // 9322 // What I've pieced together about this routine: 9323 // Starting with a block frame (from which a line frame can be gotten) 9324 // and a line number, drill down and get the first/last selectable 9325 // frame on that line, depending on aPos->mDirection. 9326 // aOutSideLimit != 0 means ignore aLineStart, instead work from 9327 // the end (if > 0) or beginning (if < 0). 9328 // 9329 static nsresult GetNextPrevLineFromBlockFrame(PeekOffsetStruct* aPos, 9330 nsIFrame* aBlockFrame, 9331 int32_t aLineStart, 9332 int8_t aOutSideLimit) { 9333 MOZ_ASSERT(aPos); 9334 MOZ_ASSERT(aBlockFrame); 9335 9336 nsPresContext* pc = aBlockFrame->PresContext(); 9337 9338 // magic numbers: aLineStart will be -1 for end of block, 0 will be start of 9339 // block. 9340 9341 aPos->mResultFrame = nullptr; 9342 aPos->mResultContent = nullptr; 9343 aPos->mAttach = aPos->mDirection == eDirNext ? CaretAssociationHint::After 9344 : CaretAssociationHint::Before; 9345 9346 AutoAssertNoDomMutations guard; 9347 nsILineIterator* it = aBlockFrame->GetLineIterator(); 9348 if (!it) { 9349 return NS_ERROR_FAILURE; 9350 } 9351 int32_t searchingLine = aLineStart; 9352 int32_t countLines = it->GetNumLines(); 9353 if (aOutSideLimit > 0) { // start at end 9354 searchingLine = countLines; 9355 } else if (aOutSideLimit < 0) { // start at beginning 9356 searchingLine = -1; //"next" will be 0 9357 } else if ((aPos->mDirection == eDirPrevious && searchingLine == 0) || 9358 (aPos->mDirection == eDirNext && 9359 searchingLine >= (countLines - 1))) { 9360 // Not found. 9361 return NS_ERROR_FAILURE; 9362 } 9363 nsIFrame* resultFrame = nullptr; 9364 nsIFrame* farStoppingFrame = nullptr; // we keep searching until we find a 9365 // "this" frame then we go to next line 9366 nsIFrame* nearStoppingFrame = nullptr; // if we are backing up from edge, 9367 // stop here 9368 bool isBeforeFirstFrame, isAfterLastFrame; 9369 bool found = false; 9370 9371 const bool forceInEditableRegion = 9372 aPos->mOptions.contains(PeekOffsetOption::ForceEditableRegion); 9373 while (!found) { 9374 if (aPos->mDirection == eDirPrevious) { 9375 searchingLine--; 9376 } else { 9377 searchingLine++; 9378 } 9379 if ((aPos->mDirection == eDirPrevious && searchingLine < 0) || 9380 (aPos->mDirection == eDirNext && searchingLine >= countLines)) { 9381 // we need to jump to new block frame. 9382 return NS_ERROR_FAILURE; 9383 } 9384 { 9385 auto line = it->GetLine(searchingLine).unwrap(); 9386 if (!line.mNumFramesOnLine) { 9387 continue; 9388 } 9389 nsIFrame* firstFrame = nullptr; 9390 nsIFrame* lastFrame = nullptr; 9391 nsIFrame* frame = line.mFirstFrameOnLine; 9392 int32_t i = line.mNumFramesOnLine; 9393 do { 9394 // If the caller wants a frame for a inclusive ancestor of the ancestor 9395 // limiter, ignore frames for outside the limiter. 9396 if (aPos->FrameContentIsInAncestorLimiter(frame)) { 9397 if (!firstFrame) { 9398 firstFrame = frame; 9399 } 9400 lastFrame = frame; 9401 } 9402 if (i == 1) { 9403 break; 9404 } 9405 frame = frame->GetNextSibling(); 9406 if (!frame) { 9407 NS_ERROR("GetLine promised more frames than could be found"); 9408 return NS_ERROR_FAILURE; 9409 } 9410 } while (--i); 9411 if (!lastFrame) { 9412 // If we're looking for an editable content frame, but all frames in the 9413 // line are not in the specified editing host, return error because we 9414 // must reach the editing host boundary. 9415 return NS_ERROR_FAILURE; 9416 } 9417 // Don't enter into native anonymous subtrees. 9418 if (!lastFrame->ContentIsRootOfNativeAnonymousSubtree()) { 9419 nsIFrame::GetLastLeaf(&lastFrame); 9420 } 9421 9422 if (aPos->mDirection == eDirNext) { 9423 nearStoppingFrame = firstFrame; 9424 farStoppingFrame = lastFrame; 9425 } else { 9426 nearStoppingFrame = lastFrame; 9427 farStoppingFrame = firstFrame; 9428 } 9429 } 9430 nsPoint offset = aBlockFrame->GetOffsetToRootFrame(); 9431 nsPoint newDesiredPos = 9432 aPos->mDesiredCaretPos - 9433 offset; // get desired position into blockframe coords 9434 // TODO: nsILineIterator::FindFrameAt should take optional editing host 9435 // parameter and if it's set, it should return the nearest editable frame 9436 // for the editing host when the frame at the desired position is not 9437 // editable. 9438 nsresult rv = it->FindFrameAt(searchingLine, newDesiredPos, &resultFrame, 9439 &isBeforeFirstFrame, &isAfterLastFrame); 9440 if (NS_FAILED(rv)) { 9441 continue; 9442 } 9443 9444 if (resultFrame) { 9445 // If ancestor limiter is specified and we reached outside content of it, 9446 // return error because we reached its element boundary. 9447 if (!aPos->FrameContentIsInAncestorLimiter(resultFrame)) { 9448 return NS_ERROR_FAILURE; 9449 } 9450 // Check to see if this is ANOTHER blockframe inside the other one that 9451 // we should look inside. 9452 if (resultFrame->CanProvideLineIterator() && 9453 IsRelevantBlockFrame(resultFrame)) { 9454 aPos->mResultFrame = resultFrame; 9455 return NS_OK; 9456 } 9457 // resultFrame is not a block frame 9458 Maybe<nsFrameIterator> frameIterator; 9459 frameIterator.emplace( 9460 pc, resultFrame, nsFrameIterator::Type::PostOrder, 9461 false, // aVisual 9462 aPos->mOptions.contains(PeekOffsetOption::StopAtScroller), 9463 false, // aFollowOOFs 9464 false // aSkipPopupChecks 9465 ); 9466 9467 auto FoundValidFrame = [forceInEditableRegion, aPos]( 9468 const nsIFrame::ContentOffsets& aOffsets, 9469 const nsIFrame* aFrame) { 9470 if (!aOffsets.content) { 9471 return false; 9472 } 9473 if (!aFrame->IsSelectable()) { 9474 return false; 9475 } 9476 if (aPos->mAncestorLimiter && 9477 !aOffsets.content->IsInclusiveDescendantOf( 9478 aPos->mAncestorLimiter)) { 9479 return false; 9480 } 9481 if (forceInEditableRegion && !aOffsets.content->IsEditable()) { 9482 return false; 9483 } 9484 return true; 9485 }; 9486 9487 nsIFrame* storeOldResultFrame = resultFrame; 9488 while (!found) { 9489 nsPoint point; 9490 nsRect tempRect = resultFrame->GetRect(); 9491 nsPoint offset = resultFrame->GetOffsetToRootFrame(); 9492 if (resultFrame->GetWritingMode().IsVertical()) { 9493 point.y = aPos->mDesiredCaretPos.y; 9494 point.x = tempRect.width + offset.x; 9495 } else { 9496 point.y = tempRect.height + offset.y; 9497 point.x = aPos->mDesiredCaretPos.x; 9498 } 9499 9500 if (!resultFrame->IsViewportFrame()) { 9501 nsPoint offset = resultFrame->GetOffsetToRootFrame(); 9502 nsIFrame::ContentOffsets offsets = 9503 resultFrame->GetContentOffsetsFromPoint( 9504 point - offset, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE); 9505 aPos->mResultContent = offsets.content; 9506 aPos->mContentOffset = offsets.offset; 9507 aPos->mAttach = offsets.associate; 9508 if (FoundValidFrame(offsets, resultFrame)) { 9509 found = true; 9510 break; 9511 } 9512 } 9513 9514 if (aPos->mDirection == eDirPrevious && 9515 resultFrame == farStoppingFrame) { 9516 break; 9517 } 9518 if (aPos->mDirection == eDirNext && resultFrame == nearStoppingFrame) { 9519 break; 9520 } 9521 // always try previous on THAT line if that fails go the other way 9522 resultFrame = frameIterator->Traverse(/* aForward = */ false); 9523 if (!resultFrame) { 9524 return NS_ERROR_FAILURE; 9525 } 9526 } 9527 9528 if (!found) { 9529 resultFrame = storeOldResultFrame; 9530 frameIterator.reset(); 9531 frameIterator.emplace( 9532 pc, resultFrame, nsFrameIterator::Type::Leaf, 9533 false, // aVisual 9534 aPos->mOptions.contains(PeekOffsetOption::StopAtScroller), 9535 false, // aFollowOOFs 9536 false // aSkipPopupChecks 9537 ); 9538 MOZ_ASSERT(frameIterator); 9539 } 9540 while (!found) { 9541 nsPoint point = aPos->mDesiredCaretPos; 9542 nsPoint offset = resultFrame->GetOffsetToRootFrame(); 9543 nsIFrame::ContentOffsets offsets = 9544 resultFrame->GetContentOffsetsFromPoint( 9545 point - offset, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE); 9546 aPos->mResultContent = offsets.content; 9547 aPos->mContentOffset = offsets.offset; 9548 aPos->mAttach = offsets.associate; 9549 if (FoundValidFrame(offsets, resultFrame)) { 9550 found = true; 9551 aPos->mAttach = resultFrame == farStoppingFrame 9552 ? CaretAssociationHint::Before 9553 : CaretAssociationHint::After; 9554 break; 9555 } 9556 if (aPos->mDirection == eDirPrevious && 9557 resultFrame == nearStoppingFrame) { 9558 break; 9559 } 9560 if (aPos->mDirection == eDirNext && resultFrame == farStoppingFrame) { 9561 break; 9562 } 9563 // previous didnt work now we try "next" 9564 nsIFrame* tempFrame = frameIterator->Traverse(/* aForward = */ true); 9565 if (!tempFrame) { 9566 break; 9567 } 9568 resultFrame = tempFrame; 9569 } 9570 aPos->mResultFrame = resultFrame; 9571 } else { 9572 // we need to jump to new block frame. 9573 aPos->mAmount = eSelectLine; 9574 aPos->mStartOffset = 0; 9575 aPos->mAttach = aPos->mDirection == eDirNext 9576 ? CaretAssociationHint::Before 9577 : CaretAssociationHint::After; 9578 if (aPos->mDirection == eDirPrevious) { 9579 aPos->mStartOffset = -1; // start from end 9580 } 9581 return aBlockFrame->PeekOffset(aPos); 9582 } 9583 } 9584 return NS_OK; 9585 } 9586 9587 nsIFrame::CaretPosition nsIFrame::GetExtremeCaretPosition(bool aStart) { 9588 CaretPosition result; 9589 9590 FrameTarget targetFrame = DrillDownToSelectionFrame(this, !aStart, 0); 9591 FrameContentRange range = GetRangeForFrame(targetFrame.frame); 9592 result.mResultContent = range.content; 9593 result.mContentOffset = aStart ? range.start : range.end; 9594 return result; 9595 } 9596 9597 // If this is a preformatted text frame, see if it ends with a newline 9598 static nsContentAndOffset FindLineBreakInText(nsIFrame* aFrame, 9599 nsDirection aDirection) { 9600 nsContentAndOffset result; 9601 9602 if (aFrame->IsGeneratedContentFrame() || 9603 !aFrame->HasSignificantTerminalNewline()) { 9604 return result; 9605 } 9606 9607 int32_t endOffset = aFrame->GetOffsets().second; 9608 result.mContent = aFrame->GetContent(); 9609 result.mOffset = endOffset - (aDirection == eDirPrevious ? 0 : 1); 9610 return result; 9611 } 9612 9613 // Find the first (or last) descendant of the given frame 9614 // which is either a block-level frame or a BRFrame, or some other kind of break 9615 // which stops the line. 9616 static nsContentAndOffset FindLineBreakingFrame(nsIFrame* aFrame, 9617 nsDirection aDirection) { 9618 nsContentAndOffset result; 9619 9620 if (aFrame->IsGeneratedContentFrame()) { 9621 return result; 9622 } 9623 9624 // Treat form controls and other replaced inline level elements as inline 9625 // leaves. 9626 if (aFrame->IsReplaced() && aFrame->IsInlineOutside() && 9627 !aFrame->IsBrFrame() && !aFrame->IsTextFrame()) { 9628 return result; 9629 } 9630 9631 // Check the frame itself 9632 // Fall through block-in-inline split frames because their mContent is 9633 // the content of the inline frames they were created from. The 9634 // first/last child of such frames is the real block frame we're 9635 // looking for. 9636 if ((IsRelevantBlockFrame(aFrame) && 9637 !aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) || 9638 aFrame->IsBrFrame()) { 9639 nsIContent* content = aFrame->GetContent(); 9640 result.mContent = content->GetParent(); 9641 // In some cases (bug 310589, bug 370174) we end up here with a null 9642 // content. This probably shouldn't ever happen, but since it sometimes 9643 // does, we want to avoid crashing here. 9644 NS_ASSERTION(result.mContent, "Unexpected orphan content"); 9645 if (result.mContent) { 9646 result.mOffset = result.mContent->ComputeIndexOf_Deprecated(content) + 9647 (aDirection == eDirPrevious ? 1 : 0); 9648 } 9649 return result; 9650 } 9651 9652 result = FindLineBreakInText(aFrame, aDirection); 9653 if (result.mContent) { 9654 return result; 9655 } 9656 9657 // Iterate over children and call ourselves recursively 9658 if (aDirection == eDirPrevious) { 9659 nsIFrame* child = aFrame->PrincipalChildList().LastChild(); 9660 while (child && !result.mContent) { 9661 result = FindLineBreakingFrame(child, aDirection); 9662 child = child->GetPrevSibling(); 9663 } 9664 } else { // eDirNext 9665 nsIFrame* child = aFrame->PrincipalChildList().FirstChild(); 9666 while (child && !result.mContent) { 9667 result = FindLineBreakingFrame(child, aDirection); 9668 child = child->GetNextSibling(); 9669 } 9670 } 9671 return result; 9672 } 9673 9674 nsresult nsIFrame::PeekOffsetForParagraph(PeekOffsetStruct* aPos) { 9675 nsIFrame* frame = this; 9676 nsContentAndOffset blockFrameOrBR; 9677 blockFrameOrBR.mContent = nullptr; 9678 bool reachedLimit = IsRelevantBlockFrame(frame) || IsEditingHost(frame); 9679 9680 auto traverse = [&aPos](nsIFrame* current) { 9681 return aPos->mDirection == eDirPrevious ? current->GetPrevSibling() 9682 : current->GetNextSibling(); 9683 }; 9684 9685 // Go through containing frames until reaching a block frame. 9686 // In each step, search the previous (or next) siblings for the closest 9687 // "stop frame" (a block frame or a BRFrame). 9688 // If found, set it to be the selection boundary and abort. 9689 while (!reachedLimit) { 9690 nsIFrame* parent = frame->GetParent(); 9691 // Treat a frame associated with the root content as if it were a block 9692 // frame. 9693 if (!frame->mContent || !frame->mContent->GetParent()) { 9694 reachedLimit = true; 9695 break; 9696 } 9697 9698 if (aPos->mDirection == eDirNext) { 9699 // Try to find our own line-break before looking at our siblings. 9700 blockFrameOrBR = FindLineBreakInText(frame, eDirNext); 9701 } 9702 9703 nsIFrame* sibling = traverse(frame); 9704 while (sibling && !blockFrameOrBR.mContent) { 9705 blockFrameOrBR = FindLineBreakingFrame(sibling, aPos->mDirection); 9706 sibling = traverse(sibling); 9707 } 9708 if (blockFrameOrBR.mContent) { 9709 aPos->mResultContent = blockFrameOrBR.mContent; 9710 aPos->mContentOffset = blockFrameOrBR.mOffset; 9711 break; 9712 } 9713 frame = parent; 9714 reachedLimit = 9715 frame && (IsRelevantBlockFrame(frame) || IsEditingHost(frame)); 9716 } 9717 9718 if (reachedLimit) { // no "stop frame" found 9719 aPos->mResultContent = frame->GetContent(); 9720 if (aPos->mResultContent) { 9721 if (ShadowRoot* shadowRoot = 9722 aPos->mResultContent->GetShadowRootForSelection()) { 9723 // Even if there's no children for this node, 9724 // the elements inside the shadow root is still 9725 // selectable 9726 aPos->mResultContent = shadowRoot; 9727 } 9728 } 9729 if (aPos->mDirection == eDirPrevious) { 9730 aPos->mContentOffset = 0; 9731 } else if (aPos->mResultContent) { 9732 aPos->mContentOffset = aPos->mResultContent->GetChildCount(); 9733 } 9734 } 9735 return NS_OK; 9736 } 9737 9738 // Determine movement direction relative to frame 9739 static bool IsMovingInFrameDirection(const nsIFrame* frame, 9740 nsDirection aDirection, bool aVisual) { 9741 bool isReverseDirection = 9742 aVisual && nsBidiPresUtils::IsReversedDirectionFrame(frame); 9743 return aDirection == (isReverseDirection ? eDirPrevious : eDirNext); 9744 } 9745 9746 // Determines "are we looking for a boundary between whitespace and 9747 // non-whitespace (in the direction we're moving in)". It is true when moving 9748 // forward and looking for a beginning of a word, or when moving backwards and 9749 // looking for an end of a word. 9750 static bool ShouldWordSelectionEatSpace(const PeekOffsetStruct& aPos) { 9751 if (aPos.mWordMovementType != eDefaultBehavior) { 9752 // aPos->mWordMovementType possible values: 9753 // eEndWord: eat the space if we're moving backwards 9754 // eStartWord: eat the space if we're moving forwards 9755 return (aPos.mWordMovementType == eEndWord) == 9756 (aPos.mDirection == eDirPrevious); 9757 } 9758 // Use the hidden preference which is based on operating system 9759 // behavior. This pref only affects whether moving forward by word 9760 // should go to the end of this word or start of the next word. When 9761 // going backwards, the start of the word is always used, on every 9762 // operating system. 9763 return aPos.mDirection == eDirNext && 9764 StaticPrefs::layout_word_select_eat_space_to_next_word(); 9765 } 9766 9767 enum class OffsetIsAtLineEdge : bool { No, Yes }; 9768 9769 static void SetPeekResultFromFrame(PeekOffsetStruct& aPos, nsIFrame* aFrame, 9770 int32_t aOffset, 9771 OffsetIsAtLineEdge aAtLineEdge) { 9772 FrameContentRange range = GetRangeForFrame(aFrame); 9773 aPos.mResultFrame = aFrame; 9774 aPos.mResultContent = range.content; 9775 // Output offset is relative to content, not frame 9776 aPos.mContentOffset = 9777 aOffset < 0 ? range.end + aOffset + 1 : range.start + aOffset; 9778 // Ensure we don't go past the range. This is important if aFrame is empty. 9779 aPos.mContentOffset = std::clamp(aPos.mContentOffset, range.start, range.end); 9780 if (aAtLineEdge == OffsetIsAtLineEdge::Yes) { 9781 aPos.mAttach = aPos.mContentOffset == range.start 9782 ? CaretAssociationHint::After 9783 : CaretAssociationHint::Before; 9784 } 9785 } 9786 9787 void nsIFrame::SelectablePeekReport::TransferTo(PeekOffsetStruct& aPos) const { 9788 return SetPeekResultFromFrame(aPos, mFrame, mOffset, OffsetIsAtLineEdge::No); 9789 } 9790 9791 nsIFrame::SelectablePeekReport::SelectablePeekReport( 9792 const mozilla::GenericErrorResult<nsresult>&& aErr) { 9793 MOZ_ASSERT(NS_FAILED(aErr.operator nsresult())); 9794 // Return an empty report 9795 } 9796 9797 nsresult nsIFrame::PeekOffsetForCharacter(PeekOffsetStruct* aPos, 9798 int32_t aOffset) { 9799 SelectablePeekReport current{this, aOffset}; 9800 9801 nsIFrame::FrameSearchResult peekSearchState = CONTINUE; 9802 9803 const bool forceEditableRegion = 9804 aPos->mOptions.contains(PeekOffsetOption::ForceEditableRegion); 9805 9806 while (peekSearchState != FOUND) { 9807 const bool movingInFrameDirection = IsMovingInFrameDirection( 9808 current.mFrame, aPos->mDirection, 9809 aPos->mOptions.contains(PeekOffsetOption::Visual)); 9810 9811 if (current.mJumpedLine) { 9812 // If we jumped lines, it's as if we found a character, but we still need 9813 // to eat non-renderable content on the new line. 9814 peekSearchState = current.PeekOffsetNoAmount(movingInFrameDirection); 9815 } else { 9816 PeekOffsetCharacterOptions options; 9817 options.mRespectClusters = aPos->mAmount == eSelectCluster; 9818 peekSearchState = 9819 current.PeekOffsetCharacter(movingInFrameDirection, options); 9820 if (peekSearchState == FOUND && forceEditableRegion && 9821 !current.mFrame->ContentIsEditable()) { 9822 // Treat non-editable content as unselectable. Note that we may need to 9823 // set mJumpedLine propery even if it's not editable. Therefore, we 9824 // cannot skip the above call. 9825 peekSearchState = CONTINUE_UNSELECTABLE; 9826 } 9827 } 9828 9829 current.mMovedOverNonSelectableText |= 9830 peekSearchState == CONTINUE_UNSELECTABLE; 9831 9832 if (peekSearchState != FOUND) { 9833 SelectablePeekReport next = current.mFrame->GetFrameFromDirection(*aPos); 9834 if (next.Failed()) { 9835 return NS_ERROR_FAILURE; 9836 } 9837 next.mJumpedLine |= current.mJumpedLine; 9838 next.mMovedOverNonSelectableText |= current.mMovedOverNonSelectableText; 9839 next.mHasSelectableFrame |= current.mHasSelectableFrame; 9840 current = next; 9841 } 9842 9843 // Found frame, but because we moved over non selectable text we want 9844 // the offset to be at the frame edge. Note that if we are extending the 9845 // selection, this doesn't matter. 9846 if (peekSearchState == FOUND && current.mMovedOverNonSelectableText && 9847 (!aPos->mOptions.contains(PeekOffsetOption::Extend) || 9848 current.mHasSelectableFrame)) { 9849 auto [start, end] = current.mFrame->GetOffsets(); 9850 current.mOffset = aPos->mDirection == eDirNext ? 0 : end - start; 9851 } 9852 } 9853 9854 // Set outputs 9855 current.TransferTo(*aPos); 9856 // If we're dealing with a text frame and moving backward positions us at 9857 // the end of that line, decrease the offset by one to make sure that 9858 // we're placed before the linefeed character on the previous line. 9859 if (current.mOffset < 0 && current.mJumpedLine && 9860 aPos->mDirection == eDirPrevious && 9861 current.mFrame->HasSignificantTerminalNewline() && 9862 !current.mIgnoredBrFrame) { 9863 --aPos->mContentOffset; 9864 } 9865 return NS_OK; 9866 } 9867 9868 nsresult nsIFrame::PeekOffsetForWord(PeekOffsetStruct* aPos, int32_t aOffset) { 9869 SelectablePeekReport current{this, aOffset}; 9870 bool shouldStopAtHardBreak = 9871 aPos->mWordMovementType == eDefaultBehavior && 9872 StaticPrefs::layout_word_select_eat_space_to_next_word(); 9873 bool wordSelectEatSpace = ShouldWordSelectionEatSpace(*aPos); 9874 9875 PeekWordState state; 9876 while (true) { 9877 bool movingInFrameDirection = IsMovingInFrameDirection( 9878 current.mFrame, aPos->mDirection, 9879 aPos->mOptions.contains(PeekOffsetOption::Visual)); 9880 9881 FrameSearchResult searchResult = current.mFrame->PeekOffsetWord( 9882 movingInFrameDirection, wordSelectEatSpace, 9883 aPos->mOptions.contains(PeekOffsetOption::IsKeyboardSelect), 9884 ¤t.mOffset, &state, 9885 !aPos->mOptions.contains(PeekOffsetOption::PreserveSpaces)); 9886 if (searchResult == FOUND) { 9887 break; 9888 } 9889 9890 SelectablePeekReport next = [&]() { 9891 PeekOffsetOptions options = aPos->mOptions; 9892 if (state.mSawInlineCharacter) { 9893 // If we've already found a character, we don't want to stop at 9894 // placeholder frame boundary if there is in the word. 9895 options += PeekOffsetOption::StopAtPlaceholder; 9896 } 9897 return current.mFrame->GetFrameFromDirection(aPos->mDirection, options, 9898 aPos->mAncestorLimiter); 9899 }(); 9900 if (next.Failed()) { 9901 // If we've crossed the line boundary, check to make sure that we 9902 // have not consumed a trailing newline as whitespace if it's 9903 // significant. 9904 if (next.mJumpedLine && wordSelectEatSpace && 9905 current.mFrame->HasSignificantTerminalNewline() && 9906 current.mFrame->StyleText()->mWhiteSpaceCollapse != 9907 StyleWhiteSpaceCollapse::PreserveBreaks) { 9908 current.mOffset -= 1; 9909 } 9910 break; 9911 } 9912 9913 if ((next.mJumpedLine || next.mFoundPlaceholder) && !wordSelectEatSpace && 9914 state.mSawBeforeType) { 9915 // We can't jump lines if we're looking for whitespace following 9916 // non-whitespace, and we already encountered non-whitespace. 9917 break; 9918 } 9919 9920 if (shouldStopAtHardBreak && next.mJumpedHardBreak) { 9921 /** 9922 * Prev, always: Jump and stop right there 9923 * Next, saw inline: just stop 9924 * Next, no inline: Jump and consume whitespaces 9925 */ 9926 if (aPos->mDirection == eDirPrevious) { 9927 // Try moving to the previous line if exists 9928 current.TransferTo(*aPos); 9929 current.mFrame->PeekOffsetForCharacter(aPos, current.mOffset); 9930 return NS_OK; 9931 } 9932 if (state.mSawInlineCharacter || current.mJumpedHardBreak) { 9933 if (current.mFrame->HasSignificantTerminalNewline()) { 9934 current.mOffset -= 1; 9935 } 9936 current.TransferTo(*aPos); 9937 return NS_OK; 9938 } 9939 // Mark the state as whitespace and continue 9940 state.Update(false, true); 9941 } 9942 9943 if (next.mJumpedLine) { 9944 state.mContext.Truncate(); 9945 } 9946 current = next; 9947 // Jumping a line is equivalent to encountering whitespace 9948 // This affects only when it already met an actual character 9949 if (wordSelectEatSpace && next.mJumpedLine) { 9950 state.SetSawBeforeType(); 9951 } 9952 } 9953 9954 // Set outputs 9955 current.TransferTo(*aPos); 9956 return NS_OK; 9957 } 9958 9959 static nsIFrame* GetFirstSelectableDescendantWithLineIterator( 9960 const PeekOffsetStruct& aPeekOffsetStruct, nsIFrame* aParentFrame) { 9961 const bool forceEditableRegion = aPeekOffsetStruct.mOptions.contains( 9962 PeekOffsetOption::ForceEditableRegion); 9963 auto FoundValidFrame = [aPeekOffsetStruct, 9964 forceEditableRegion](const nsIFrame* aFrame) { 9965 if (!aFrame->IsSelectable()) { 9966 return false; 9967 } 9968 if (!aPeekOffsetStruct.FrameContentIsInAncestorLimiter(aFrame)) { 9969 return false; 9970 } 9971 if (forceEditableRegion && !aFrame->ContentIsEditable()) { 9972 return false; 9973 } 9974 return true; 9975 }; 9976 9977 for (nsIFrame* child : aParentFrame->PrincipalChildList()) { 9978 // some children may not be selectable, e.g. :before / :after pseudoelements 9979 // content with user-select: none, or contenteditable="false" 9980 // we need to skip them 9981 if (child->CanProvideLineIterator() && FoundValidFrame(child)) { 9982 return child; 9983 } 9984 if (nsIFrame* nested = GetFirstSelectableDescendantWithLineIterator( 9985 aPeekOffsetStruct, child)) { 9986 return nested; 9987 } 9988 } 9989 return nullptr; 9990 } 9991 9992 nsresult nsIFrame::PeekOffsetForLine(PeekOffsetStruct* aPos) { 9993 nsIFrame* blockFrame = this; 9994 nsresult result = NS_ERROR_FAILURE; 9995 9996 // outer loop 9997 // moving to a next block when no more blocks are available in a subtree 9998 AutoAssertNoDomMutations guard; 9999 while (NS_FAILED(result)) { 10000 auto [newBlock, lineFrame] = blockFrame->GetContainingBlockForLine( 10001 aPos->mOptions.contains(PeekOffsetOption::StopAtScroller)); 10002 if (!newBlock) { 10003 return NS_ERROR_FAILURE; 10004 } 10005 // FYI: If the editing host is an inline element, the block frame content 10006 // may be either not editable or editable but belonging to different editing 10007 // host. 10008 blockFrame = newBlock; 10009 nsILineIterator* iter = blockFrame->GetLineIterator(); 10010 int32_t thisLine = iter->FindLineContaining(lineFrame); 10011 if (NS_WARN_IF(thisLine < 0)) { 10012 return NS_ERROR_FAILURE; 10013 } 10014 10015 int8_t edgeCase = 0; // no edge case. This should look at thisLine 10016 10017 // this part will find a frame or a block frame. If it's a block frame 10018 // it will "drill down" to find a viable frame or it will return an 10019 // error. 10020 nsIFrame* lastFrame = this; 10021 10022 // inner loop - crawling the frames within a specific block subtree 10023 while (true) { 10024 result = 10025 GetNextPrevLineFromBlockFrame(aPos, blockFrame, thisLine, edgeCase); 10026 // we came back to same spot! keep going 10027 if (NS_SUCCEEDED(result) && 10028 (!aPos->mResultFrame || aPos->mResultFrame == lastFrame)) { 10029 aPos->mResultFrame = nullptr; 10030 lastFrame = nullptr; 10031 if (aPos->mDirection == eDirPrevious) { 10032 thisLine--; 10033 } else { 10034 thisLine++; 10035 } 10036 continue; 10037 } 10038 10039 if (NS_FAILED(result)) { 10040 break; 10041 } 10042 10043 lastFrame = aPos->mResultFrame; // set last frame 10044 /* SPECIAL CHECK FOR NAVIGATION INTO TABLES 10045 * when we hit a frame which doesn't have line iterator, we need to 10046 * drill down and find a child with the line iterator to prevent the 10047 * crawling process to prematurely finish. Note that this is only sound if 10048 * we're guaranteed to not have multiple children implementing 10049 * LineIterator. 10050 * 10051 * So far known cases are: 10052 * 1) table wrapper (drill down into table row group) 10053 * 2) table cell (drill down into its only anon child) 10054 */ 10055 const bool shouldDrillIntoChildren = 10056 aPos->mResultFrame->IsTableWrapperFrame() || 10057 aPos->mResultFrame->IsTableCellFrame(); 10058 10059 if (shouldDrillIntoChildren) { 10060 nsIFrame* child = GetFirstSelectableDescendantWithLineIterator( 10061 *aPos, aPos->mResultFrame); 10062 if (child) { 10063 aPos->mResultFrame = child; 10064 } 10065 } 10066 10067 if (!aPos->mResultFrame->CanProvideLineIterator()) { 10068 // no more selectable content at this level 10069 break; 10070 } 10071 10072 if (aPos->mResultFrame == blockFrame) { 10073 // Make sure block element is not the same as the one we had before. 10074 break; 10075 } 10076 10077 // we've struck another block element with selectable content! 10078 if (aPos->mDirection == eDirPrevious) { 10079 edgeCase = 1; // far edge, search from end backwards 10080 } else { 10081 edgeCase = -1; // near edge search from beginning onwards 10082 } 10083 thisLine = 0; // this line means nothing now. 10084 // everything else means something so keep looking "inside" the 10085 // block 10086 blockFrame = aPos->mResultFrame; 10087 } 10088 } 10089 return result; 10090 } 10091 10092 nsresult nsIFrame::PeekOffsetForLineEdge(PeekOffsetStruct* aPos) { 10093 // Adjusted so that the caret can't get confused when content changes 10094 nsIFrame* frame = AdjustFrameForSelectionStyles(this); 10095 // FIXME: Use PeekOffsetStruct::mAncestorLimiter instead. 10096 Element* editingHost = frame->GetContent()->GetEditingHost(); 10097 10098 auto [blockFrame, lineFrame] = frame->GetContainingBlockForLine( 10099 aPos->mOptions.contains(PeekOffsetOption::StopAtScroller)); 10100 if (!blockFrame) { 10101 return NS_ERROR_FAILURE; 10102 } 10103 AutoAssertNoDomMutations guard; 10104 nsILineIterator* it = blockFrame->GetLineIterator(); 10105 int32_t thisLine = it->FindLineContaining(lineFrame); 10106 if (thisLine < 0) { 10107 return NS_ERROR_FAILURE; 10108 } 10109 10110 nsIFrame* baseFrame = nullptr; 10111 bool endOfLine = eSelectEndLine == aPos->mAmount; 10112 10113 if (aPos->mOptions.contains(PeekOffsetOption::Visual) && 10114 PresContext()->BidiEnabled()) { 10115 nsIFrame* firstFrame; 10116 bool isReordered; 10117 nsIFrame* lastFrame; 10118 MOZ_TRY( 10119 it->CheckLineOrder(thisLine, &isReordered, &firstFrame, &lastFrame)); 10120 baseFrame = endOfLine ? lastFrame : firstFrame; 10121 } else { 10122 auto line = it->GetLine(thisLine).unwrap(); 10123 10124 nsIFrame* frame = line.mFirstFrameOnLine; 10125 bool lastFrameWasEditable = false; 10126 for (int32_t count = line.mNumFramesOnLine; count; 10127 --count, frame = frame->GetNextSibling()) { 10128 if (frame->IsGeneratedContentFrame()) { 10129 continue; 10130 } 10131 // When jumping to the end of the line with the "end" key, 10132 // try to skip over brFrames 10133 if (endOfLine && line.mNumFramesOnLine > 1 && frame->IsBrFrame() && 10134 lastFrameWasEditable == frame->GetContent()->IsEditable()) { 10135 continue; 10136 } 10137 lastFrameWasEditable = 10138 frame->GetContent() && frame->GetContent()->IsEditable(); 10139 baseFrame = frame; 10140 if (!endOfLine) { 10141 break; 10142 } 10143 } 10144 } 10145 if (!baseFrame) { 10146 return NS_ERROR_FAILURE; 10147 } 10148 // Make sure we are not leaving our inline editing host if exists 10149 if (editingHost) { 10150 if (nsIFrame* frame = editingHost->GetPrimaryFrame()) { 10151 if (frame->IsInlineOutside() && 10152 !editingHost->Contains(baseFrame->GetContent())) { 10153 baseFrame = frame; 10154 if (endOfLine) { 10155 baseFrame = baseFrame->LastContinuation(); 10156 } 10157 } 10158 } 10159 } 10160 FrameTarget targetFrame = DrillDownToSelectionFrame( 10161 baseFrame, endOfLine, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE); 10162 SetPeekResultFromFrame(*aPos, targetFrame.frame, endOfLine ? -1 : 0, 10163 OffsetIsAtLineEdge::Yes); 10164 if (endOfLine && targetFrame.frame->HasSignificantTerminalNewline()) { 10165 // Do not position the caret after the terminating newline if we're 10166 // trying to move to the end of line (see bug 596506) 10167 --aPos->mContentOffset; 10168 } 10169 if (!aPos->mResultContent) { 10170 return NS_ERROR_FAILURE; 10171 } 10172 return NS_OK; 10173 } 10174 10175 nsresult nsIFrame::PeekOffset(PeekOffsetStruct* aPos) { 10176 MOZ_ASSERT(aPos); 10177 10178 if (NS_WARN_IF(HasAnyStateBits(NS_FRAME_IS_DIRTY))) { 10179 // FIXME(Bug 1654362): <caption> currently can remain dirty. 10180 return NS_ERROR_UNEXPECTED; 10181 } 10182 10183 // Translate content offset to be relative to frame 10184 int32_t offset = aPos->mStartOffset - GetRangeForFrame(this).start; 10185 10186 switch (aPos->mAmount) { 10187 case eSelectCharacter: 10188 case eSelectCluster: 10189 return PeekOffsetForCharacter(aPos, offset); 10190 case eSelectWordNoSpace: 10191 // eSelectWordNoSpace means that we should not be eating any whitespace 10192 // when moving to the adjacent word. This means that we should set aPos-> 10193 // mWordMovementType to eEndWord if we're moving forwards, and to 10194 // eStartWord if we're moving backwards. 10195 if (aPos->mDirection == eDirPrevious) { 10196 aPos->mWordMovementType = eStartWord; 10197 } else { 10198 aPos->mWordMovementType = eEndWord; 10199 } 10200 // Intentionally fall through the eSelectWord case. 10201 [[fallthrough]]; 10202 case eSelectWord: 10203 return PeekOffsetForWord(aPos, offset); 10204 case eSelectLine: 10205 return PeekOffsetForLine(aPos); 10206 case eSelectBeginLine: 10207 case eSelectEndLine: 10208 return PeekOffsetForLineEdge(aPos); 10209 case eSelectParagraph: 10210 return PeekOffsetForParagraph(aPos); 10211 default: { 10212 NS_ASSERTION(false, "Invalid amount"); 10213 return NS_ERROR_FAILURE; 10214 } 10215 } 10216 } 10217 10218 nsIFrame::FrameSearchResult nsIFrame::PeekOffsetNoAmount(bool aForward, 10219 int32_t* aOffset) { 10220 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range"); 10221 // Sure, we can stop right here. 10222 return FOUND; 10223 } 10224 10225 nsIFrame::FrameSearchResult nsIFrame::PeekOffsetCharacter( 10226 bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) { 10227 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range"); 10228 int32_t startOffset = *aOffset; 10229 // A negative offset means "end of frame", which in our case means offset 1. 10230 if (startOffset < 0) { 10231 startOffset = 1; 10232 } 10233 if (aForward == (startOffset == 0)) { 10234 // We're before the frame and moving forward, or after it and moving 10235 // backwards: skip to the other side and we're done. 10236 *aOffset = 1 - startOffset; 10237 return FOUND; 10238 } 10239 return CONTINUE; 10240 } 10241 10242 nsIFrame::FrameSearchResult nsIFrame::PeekOffsetWord( 10243 bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect, 10244 int32_t* aOffset, PeekWordState* aState, bool /*aTrimSpaces*/) { 10245 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range"); 10246 int32_t startOffset = *aOffset; 10247 // This isn't text, so truncate the context 10248 aState->mContext.Truncate(); 10249 if (startOffset < 0) { 10250 startOffset = 1; 10251 } 10252 if (aForward == (startOffset == 0)) { 10253 // We're before the frame and moving forward, or after it and moving 10254 // backwards. If we're looking for non-whitespace, we found it (without 10255 // skipping this frame). 10256 if (!aState->mAtStart) { 10257 if (aState->mLastCharWasPunctuation) { 10258 // We're not punctuation, so this is a punctuation boundary. 10259 if (BreakWordBetweenPunctuation(aState, aForward, false, false, 10260 aIsKeyboardSelect)) { 10261 return FOUND; 10262 } 10263 } else { 10264 // This is not a punctuation boundary. 10265 if (aWordSelectEatSpace && aState->mSawBeforeType) { 10266 return FOUND; 10267 } 10268 } 10269 } 10270 // Otherwise skip to the other side and note that we encountered 10271 // non-whitespace. 10272 *aOffset = 1 - startOffset; 10273 aState->Update(false, // not punctuation 10274 false // not whitespace 10275 ); 10276 if (!aWordSelectEatSpace) { 10277 aState->SetSawBeforeType(); 10278 } 10279 } 10280 return CONTINUE; 10281 } 10282 10283 // static 10284 bool nsIFrame::BreakWordBetweenPunctuation(const PeekWordState* aState, 10285 bool aForward, bool aPunctAfter, 10286 bool aWhitespaceAfter, 10287 bool aIsKeyboardSelect) { 10288 NS_ASSERTION(aPunctAfter != aState->mLastCharWasPunctuation, 10289 "Call this only at punctuation boundaries"); 10290 if (aState->mLastCharWasWhitespace) { 10291 // We always stop between whitespace and punctuation 10292 return true; 10293 } 10294 if (!StaticPrefs::layout_word_select_stop_at_punctuation()) { 10295 // When this pref is false, we never stop at a punctuation boundary unless 10296 // it's followed by whitespace (in the relevant direction). 10297 return aWhitespaceAfter; 10298 } 10299 if (!aIsKeyboardSelect) { 10300 // mouse caret movement (e.g. word selection) always stops at every 10301 // punctuation boundary 10302 return true; 10303 } 10304 bool afterPunct = aForward ? aState->mLastCharWasPunctuation : aPunctAfter; 10305 if (!afterPunct) { 10306 // keyboard caret movement only stops after punctuation (in content order) 10307 return false; 10308 } 10309 // Stop only if we've seen some non-punctuation since the last whitespace; 10310 // don't stop after punctuation that follows whitespace. 10311 return aState->mSeenNonPunctuationSinceWhitespace; 10312 } 10313 10314 std::pair<nsIFrame*, nsIFrame*> nsIFrame::GetContainingBlockForLine( 10315 bool aLockScroll) const { 10316 const nsIFrame* parentFrame = this; 10317 const nsIFrame* frame; 10318 while (parentFrame) { 10319 frame = parentFrame; 10320 if (frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 10321 // if we are searching for a frame that is not in flow we will not find 10322 // it. we must instead look for its placeholder 10323 if (frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { 10324 // abspos continuations don't have placeholders, get the fif 10325 frame = frame->FirstInFlow(); 10326 } 10327 frame = frame->GetPlaceholderFrame(); 10328 if (!frame) { 10329 return std::pair(nullptr, nullptr); 10330 } 10331 } 10332 parentFrame = frame->GetParent(); 10333 if (parentFrame) { 10334 if (aLockScroll && parentFrame->IsScrollContainerFrame()) { 10335 return std::pair(nullptr, nullptr); 10336 } 10337 if (parentFrame->CanProvideLineIterator()) { 10338 return std::pair(const_cast<nsIFrame*>(parentFrame), 10339 const_cast<nsIFrame*>(frame)); 10340 } 10341 } 10342 } 10343 return std::pair(nullptr, nullptr); 10344 } 10345 10346 Result<bool, nsresult> nsIFrame::IsVisuallyAtLineEdge( 10347 nsILineIterator* aLineIterator, int32_t aLine, nsDirection aDirection) { 10348 auto line = aLineIterator->GetLine(aLine).unwrap(); 10349 10350 const bool lineIsRTL = aLineIterator->IsLineIteratorFlowRTL(); 10351 10352 nsIFrame *firstFrame = nullptr, *lastFrame = nullptr; 10353 bool isReordered = false; 10354 MOZ_TRY(aLineIterator->CheckLineOrder(aLine, &isReordered, &firstFrame, 10355 &lastFrame)); 10356 if (!firstFrame || !lastFrame) { 10357 return true; // XXX: Why true? We check whether `this` is at the edge... 10358 } 10359 10360 nsIFrame* leftmostFrame = lineIsRTL ? lastFrame : firstFrame; 10361 nsIFrame* rightmostFrame = lineIsRTL ? firstFrame : lastFrame; 10362 auto FrameIsRTL = [](nsIFrame* aFrame) { 10363 return nsBidiPresUtils::FrameDirection(aFrame) == 10364 mozilla::intl::BidiDirection::RTL; 10365 }; 10366 if (!lineIsRTL == (aDirection == eDirPrevious)) { 10367 nsIFrame* maybeLeftmostFrame = leftmostFrame; 10368 for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) { 10369 if (maybeLeftmostFrame == this) { 10370 return true; 10371 } 10372 // If left edge of the line starts with placeholder frames, we can ignore 10373 // them and should keep checking the following frames. 10374 if (!maybeLeftmostFrame->IsPlaceholderFrame()) { 10375 if ((FrameIsRTL(maybeLeftmostFrame) == lineIsRTL) == 10376 (aDirection == eDirPrevious)) { 10377 nsIFrame::GetFirstLeaf(&maybeLeftmostFrame); 10378 } else { 10379 nsIFrame::GetLastLeaf(&maybeLeftmostFrame); 10380 } 10381 return maybeLeftmostFrame == this; 10382 } 10383 maybeLeftmostFrame = nsBidiPresUtils::GetFrameToRightOf( 10384 maybeLeftmostFrame, line.mFirstFrameOnLine, line.mNumFramesOnLine); 10385 if (!maybeLeftmostFrame) { 10386 return false; 10387 } 10388 } 10389 return false; 10390 } 10391 10392 nsIFrame* maybeRightmostFrame = rightmostFrame; 10393 for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) { 10394 if (maybeRightmostFrame == this) { 10395 return true; 10396 } 10397 // If the line ends with placehlder frames, we can ignore them and should 10398 // keep checking the preceding frames. 10399 if (!maybeRightmostFrame->IsPlaceholderFrame()) { 10400 if ((FrameIsRTL(maybeRightmostFrame) == lineIsRTL) == 10401 (aDirection == eDirPrevious)) { 10402 nsIFrame::GetFirstLeaf(&maybeRightmostFrame); 10403 } else { 10404 nsIFrame::GetLastLeaf(&maybeRightmostFrame); 10405 } 10406 return maybeRightmostFrame == this; 10407 } 10408 maybeRightmostFrame = nsBidiPresUtils::GetFrameToLeftOf( 10409 maybeRightmostFrame, line.mFirstFrameOnLine, line.mNumFramesOnLine); 10410 if (!maybeRightmostFrame) { 10411 return false; 10412 } 10413 } 10414 return false; 10415 } 10416 10417 Result<bool, nsresult> nsIFrame::IsLogicallyAtLineEdge( 10418 nsILineIterator* aLineIterator, int32_t aLine, nsDirection aDirection) { 10419 auto line = aLineIterator->GetLine(aLine).unwrap(); 10420 if (!line.mNumFramesOnLine) { 10421 return false; 10422 } 10423 MOZ_ASSERT(line.mFirstFrameOnLine); 10424 10425 if (aDirection == eDirPrevious) { 10426 nsIFrame* maybeFirstFrame = line.mFirstFrameOnLine; 10427 for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) { 10428 if (maybeFirstFrame == this) { 10429 return true; 10430 } 10431 // If the line starts with placeholder frames, we can ignore them and 10432 // should keep checking the following frames. 10433 if (!maybeFirstFrame->IsPlaceholderFrame()) { 10434 nsIFrame::GetFirstLeaf(&maybeFirstFrame); 10435 return maybeFirstFrame == this; 10436 } 10437 maybeFirstFrame = maybeFirstFrame->GetNextSibling(); 10438 if (!maybeFirstFrame) { 10439 return false; 10440 } 10441 } 10442 return false; 10443 } 10444 10445 // eDirNext 10446 nsIFrame* maybeLastFrame = line.GetLastFrameOnLine(); 10447 for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) { 10448 if (maybeLastFrame == this) { 10449 return true; 10450 } 10451 // If the line ends with placehlder frames, we can ignore them and should 10452 // keep checking the preceding frames. 10453 if (!maybeLastFrame->IsPlaceholderFrame()) { 10454 nsIFrame::GetLastLeaf(&maybeLastFrame); 10455 return maybeLastFrame == this; 10456 } 10457 maybeLastFrame = maybeLastFrame->GetPrevSibling(); 10458 } 10459 return false; 10460 } 10461 10462 nsIFrame::SelectablePeekReport nsIFrame::GetFrameFromDirection( 10463 nsDirection aDirection, const PeekOffsetOptions& aOptions, 10464 const Element* aAncestorLimiter) { 10465 SelectablePeekReport result; 10466 10467 nsPresContext* presContext = PresContext(); 10468 const bool needsVisualTraversal = 10469 aOptions.contains(PeekOffsetOption::Visual) && presContext->BidiEnabled(); 10470 const bool followOofs = 10471 !aOptions.contains(PeekOffsetOption::StopAtPlaceholder); 10472 nsFrameIterator frameIterator( 10473 presContext, this, nsFrameIterator::Type::Leaf, needsVisualTraversal, 10474 aOptions.contains(PeekOffsetOption::StopAtScroller), followOofs, 10475 false, // aSkipPopupChecks 10476 aAncestorLimiter); 10477 10478 // Find the prev/next selectable frame 10479 bool selectable = false; 10480 nsIFrame* traversedFrame = this; 10481 AutoAssertNoDomMutations guard; 10482 const nsFrameSelection* frameSelection = 10483 GetContent() ? GetContent()->GetFrameSelection() : nullptr; 10484 while (!selectable) { 10485 auto [blockFrame, lineFrame] = traversedFrame->GetContainingBlockForLine( 10486 aOptions.contains(PeekOffsetOption::StopAtScroller)); 10487 if (!blockFrame) { 10488 return result; 10489 } 10490 10491 nsILineIterator* it = blockFrame->GetLineIterator(); 10492 int32_t thisLine = it->FindLineContaining(lineFrame); 10493 if (thisLine < 0) { 10494 return result; 10495 } 10496 10497 bool atLineEdge = MOZ_TRY( 10498 needsVisualTraversal 10499 ? traversedFrame->IsVisuallyAtLineEdge(it, thisLine, aDirection) 10500 : traversedFrame->IsLogicallyAtLineEdge(it, thisLine, aDirection)); 10501 if (atLineEdge) { 10502 result.mJumpedLine = true; 10503 if (!aOptions.contains(PeekOffsetOption::JumpLines)) { 10504 return result; // we are done. cannot jump lines 10505 } 10506 int32_t lineToCheckWrap = 10507 aDirection == eDirPrevious ? thisLine - 1 : thisLine; 10508 if (lineToCheckWrap < 0 || 10509 !it->GetLine(lineToCheckWrap).unwrap().mIsWrapped) { 10510 result.mJumpedHardBreak = true; 10511 } 10512 } 10513 10514 traversedFrame = frameIterator.Traverse(aDirection == eDirNext); 10515 if (!traversedFrame) { 10516 return result; 10517 } 10518 10519 if (aOptions.contains(PeekOffsetOption::StopAtPlaceholder) && 10520 traversedFrame->IsPlaceholderFrame()) { 10521 // XXX If the placeholder frame does not have meaningful content, the user 10522 // may want to select as a word around the out-of-flow cotent. However, 10523 // non-text frame resets context in nsIFrame::PeekOffsetWord(). Therefore, 10524 // next text frame considers the new word starts from its edge. So, it's 10525 // not enough to implement such behavior with adding a check here whether 10526 // the real frame may change the word with its contents if it were not 10527 // out-of-flow. 10528 result.mFoundPlaceholder = true; 10529 return result; 10530 } 10531 10532 auto IsSelectableFrame = [aAncestorLimiter, aOptions, 10533 frameSelection](const nsIFrame* aFrame) { 10534 if (!aFrame->IsSelectable() || MOZ_UNLIKELY(!aFrame->GetContent())) { 10535 return false; 10536 } 10537 // If the found frame content is managed by different nsFrameSelection, we 10538 // cannot select the frame content with current selection. 10539 if (frameSelection != aFrame->GetContent()->GetFrameSelection()) { 10540 return false; 10541 } 10542 if (MOZ_UNLIKELY(aAncestorLimiter && 10543 !aAncestorLimiter->GetPrimaryFrame()) && 10544 !aFrame->GetContent()->IsInclusiveFlatTreeDescendantOf( 10545 aAncestorLimiter)) { 10546 return false; 10547 } 10548 return !aOptions.contains(PeekOffsetOption::ForceEditableRegion) || 10549 aFrame->GetContent()->IsEditable(); 10550 }; 10551 10552 // Skip br frames, but only if we can select something before hitting the 10553 // end of the line or a non-selectable region. 10554 if (atLineEdge && aDirection == eDirPrevious && 10555 traversedFrame->IsBrFrame()) { 10556 for (nsIFrame* current = traversedFrame->GetPrevSibling(); current; 10557 current = current->GetPrevSibling()) { 10558 if (!current->IsBlockOutside() && IsSelectableFrame(current)) { 10559 if (!current->IsBrFrame()) { 10560 result.mIgnoredBrFrame = true; 10561 } 10562 break; 10563 } 10564 } 10565 if (result.mIgnoredBrFrame) { 10566 continue; 10567 } 10568 } 10569 10570 selectable = IsSelectableFrame(traversedFrame); 10571 if (MOZ_UNLIKELY(!frameSelection) && selectable && 10572 MOZ_LIKELY(traversedFrame->GetContent())) { 10573 frameSelection = traversedFrame->GetContent()->GetFrameSelection(); 10574 } 10575 if (!selectable) { 10576 if (traversedFrame->IsSelectable()) { 10577 result.mHasSelectableFrame = true; 10578 } 10579 result.mMovedOverNonSelectableText = true; 10580 } 10581 } // while (!selectable) 10582 10583 result.mOffset = (aDirection == eDirNext) ? 0 : -1; 10584 10585 if (aOptions.contains(PeekOffsetOption::Visual) && 10586 nsBidiPresUtils::IsReversedDirectionFrame(traversedFrame)) { 10587 // The new frame is reverse-direction, go to the other end 10588 result.mOffset = -1 - result.mOffset; 10589 } 10590 result.mFrame = traversedFrame; 10591 return result; 10592 } 10593 10594 nsIFrame::SelectablePeekReport nsIFrame::GetFrameFromDirection( 10595 const PeekOffsetStruct& aPos) { 10596 return GetFrameFromDirection(aPos.mDirection, aPos.mOptions, 10597 aPos.mAncestorLimiter); 10598 } 10599 10600 /* virtual */ 10601 void nsIFrame::ChildIsDirty(nsIFrame* aChild) { 10602 MOZ_ASSERT_UNREACHABLE( 10603 "should never be called on a frame that doesn't " 10604 "inherit from nsContainerFrame"); 10605 } 10606 10607 #ifdef ACCESSIBILITY 10608 a11y::AccType nsIFrame::AccessibleType() { 10609 if (IsTableCaption() && !GetRect().IsEmpty()) { 10610 return a11y::eHTMLCaptionType; 10611 } 10612 return a11y::eNoType; 10613 } 10614 #endif 10615 10616 bool nsIFrame::ClearOverflowRects() { 10617 if (mOverflow.mType == OverflowStorageType::None) { 10618 return false; 10619 } 10620 if (mOverflow.mType == OverflowStorageType::Large) { 10621 RemoveProperty(OverflowAreasProperty()); 10622 } 10623 mOverflow.mType = OverflowStorageType::None; 10624 return true; 10625 } 10626 10627 bool nsIFrame::SetOverflowAreas(const OverflowAreas& aOverflowAreas) { 10628 if (mOverflow.mType == OverflowStorageType::Large) { 10629 OverflowAreas* overflow = GetOverflowAreasProperty(); 10630 bool changed = *overflow != aOverflowAreas; 10631 *overflow = aOverflowAreas; 10632 10633 // Don't bother with converting to the deltas form if we already 10634 // have a property. 10635 return changed; 10636 } 10637 10638 const nsRect& vis = aOverflowAreas.InkOverflow(); 10639 uint32_t l = -vis.x, // left edge: positive delta is leftwards 10640 t = -vis.y, // top: positive is upwards 10641 r = vis.XMost() - mRect.width, // right: positive is rightwards 10642 b = vis.YMost() - mRect.height; // bottom: positive is downwards 10643 if (aOverflowAreas.ScrollableOverflow().IsEqualEdges( 10644 nsRect(nsPoint(0, 0), GetSize())) && 10645 l <= InkOverflowDeltas::kMax && t <= InkOverflowDeltas::kMax && 10646 r <= InkOverflowDeltas::kMax && b <= InkOverflowDeltas::kMax && 10647 // we have to check these against zero because we *never* want to 10648 // set a frame as having no overflow in this function. This is 10649 // because FinishAndStoreOverflow calls this function prior to 10650 // SetRect based on whether the overflow areas match aNewSize. 10651 // In the case where the overflow areas exactly match mRect but 10652 // do not match aNewSize, we need to store overflow in a property 10653 // so that our eventual SetRect/SetSize will know that it has to 10654 // reset our overflow areas. 10655 (l | t | r | b) != 0) { 10656 InkOverflowDeltas oldDeltas = mOverflow.mInkOverflowDeltas; 10657 // It's a "small" overflow area so we store the deltas for each edge 10658 // directly in the frame, rather than allocating a separate rect. 10659 // If they're all zero, that's fine; we're setting things to 10660 // no-overflow. 10661 mOverflow.mInkOverflowDeltas.mLeft = l; 10662 mOverflow.mInkOverflowDeltas.mTop = t; 10663 mOverflow.mInkOverflowDeltas.mRight = r; 10664 mOverflow.mInkOverflowDeltas.mBottom = b; 10665 // There was no scrollable overflow before, and there isn't now. 10666 return oldDeltas != mOverflow.mInkOverflowDeltas; 10667 } else { 10668 bool changed = 10669 !aOverflowAreas.ScrollableOverflow().IsEqualEdges( 10670 nsRect(nsPoint(0, 0), GetSize())) || 10671 !aOverflowAreas.InkOverflow().IsEqualEdges(InkOverflowFromDeltas()); 10672 10673 // it's a large overflow area that we need to store as a property 10674 mOverflow.mType = OverflowStorageType::Large; 10675 AddProperty(OverflowAreasProperty(), new OverflowAreas(aOverflowAreas)); 10676 return changed; 10677 } 10678 } 10679 10680 enum class ApplyTransform : bool { No, Yes }; 10681 10682 /** 10683 * Compute the outline inner rect (so without outline-width and outline-offset) 10684 * of aFrame, maybe iterating over its descendants, in aFrame's coordinate space 10685 * or its post-transform coordinate space (depending on aApplyTransform). 10686 */ 10687 static nsRect ComputeOutlineInnerRect( 10688 nsIFrame* aFrame, ApplyTransform aApplyTransform, bool& aOutValid, 10689 const nsSize* aSizeOverride = nullptr, 10690 const OverflowAreas* aOverflowOverride = nullptr) { 10691 const nsRect bounds(nsPoint(0, 0), 10692 aSizeOverride ? *aSizeOverride : aFrame->GetSize()); 10693 10694 // The SVG container frames besides SVGTextFrame do not maintain 10695 // an accurate mRect. It will make the outline be larger than 10696 // we expect, we need to make them narrow to their children's outline. 10697 // aOutValid is set to false if the returned nsRect is not valid 10698 // and should not be included in the outline rectangle. 10699 aOutValid = !aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) || 10700 !aFrame->IsSVGContainerFrame() || aFrame->IsSVGTextFrame(); 10701 10702 nsRect u; 10703 10704 if (!aFrame->FrameMaintainsOverflow()) { 10705 return u; 10706 } 10707 10708 // Start from our border-box, transformed. See comment below about 10709 // transform of children. 10710 bool doTransform = 10711 aApplyTransform == ApplyTransform::Yes && aFrame->IsTransformed(); 10712 TransformReferenceBox boundsRefBox(nullptr, bounds); 10713 if (doTransform) { 10714 u = nsDisplayTransform::TransformRect(bounds, aFrame, boundsRefBox); 10715 } else { 10716 u = bounds; 10717 } 10718 10719 if (aOutValid && !StaticPrefs::layout_outline_include_overflow()) { 10720 return u; 10721 } 10722 10723 // Only iterate through the children if the overflow areas suggest 10724 // that we might need to, and if the frame doesn't clip its overflow 10725 // anyway. 10726 if (aOverflowOverride) { 10727 if (!doTransform && bounds.IsEqualEdges(aOverflowOverride->InkOverflow()) && 10728 bounds.IsEqualEdges(aOverflowOverride->ScrollableOverflow())) { 10729 return u; 10730 } 10731 } else { 10732 if (!doTransform && bounds.IsEqualEdges(aFrame->InkOverflowRect()) && 10733 bounds.IsEqualEdges(aFrame->ScrollableOverflowRect())) { 10734 return u; 10735 } 10736 } 10737 const nsStyleDisplay* disp = aFrame->StyleDisplay(); 10738 LayoutFrameType fType = aFrame->Type(); 10739 if (fType == LayoutFrameType::ScrollContainer || 10740 fType == LayoutFrameType::ListControl || 10741 fType == LayoutFrameType::SVGOuterSVG) { 10742 return u; 10743 } 10744 10745 auto overflowClipAxes = aFrame->ShouldApplyOverflowClipping(disp); 10746 auto overflowClipMargin = aFrame->OverflowClipMargin( 10747 overflowClipAxes, /* aAllowNegative = */ false); 10748 if (overflowClipAxes == kPhysicalAxesBoth && overflowClipMargin.IsAllZero()) { 10749 return u; 10750 } 10751 10752 const nsStyleEffects* effects = aFrame->StyleEffects(); 10753 Maybe<nsRect> clipPropClipRect = 10754 aFrame->GetClipPropClipRect(disp, effects, bounds.Size()); 10755 10756 // Iterate over all children except pop-up, absolutely-positioned, 10757 // float, and overflow ones. 10758 const FrameChildListIDs skip = { 10759 FrameChildListID::Absolute, FrameChildListID::Fixed, 10760 FrameChildListID::Float, FrameChildListID::Overflow}; 10761 for (const auto& [list, listID] : aFrame->ChildLists()) { 10762 if (skip.contains(listID)) { 10763 continue; 10764 } 10765 10766 for (nsIFrame* child : list) { 10767 if (child->IsPlaceholderFrame()) { 10768 continue; 10769 } 10770 10771 // Note that passing ApplyTransform::Yes when 10772 // child->Combines3DTransformWithAncestors() returns true is incorrect if 10773 // our aApplyTransform is No... but the opposite would be as well. 10774 // This is because elements within a preserve-3d scene are always 10775 // transformed up to the top of the scene. This means we don't have a 10776 // mechanism for getting a transform up to an intermediate point within 10777 // the scene. We choose to over-transform rather than under-transform 10778 // because this is consistent with other overflow areas. 10779 bool validRect = true; 10780 nsRect childRect = 10781 ComputeOutlineInnerRect(child, ApplyTransform::Yes, validRect) + 10782 child->GetPosition(); 10783 10784 if (!validRect) { 10785 continue; 10786 } 10787 10788 if (clipPropClipRect) { 10789 // Intersect with the clip before transforming. 10790 childRect.IntersectRect(childRect, *clipPropClipRect); 10791 } 10792 10793 // Note that we transform each child separately according to 10794 // aFrame's transform, and then union, which gives a different 10795 // (smaller) result from unioning and then transforming the 10796 // union. This doesn't match the way we handle overflow areas 10797 // with 2-D transforms, though it does match the way we handle 10798 // overflow areas in preserve-3d 3-D scenes. 10799 if (doTransform && !child->Combines3DTransformWithAncestors()) { 10800 childRect = 10801 nsDisplayTransform::TransformRect(childRect, aFrame, boundsRefBox); 10802 } 10803 10804 // If a SVGContainer has a non-SVGContainer child, we assign 10805 // its child's outline to this SVGContainer directly. 10806 if (!aOutValid && validRect) { 10807 u = childRect; 10808 aOutValid = true; 10809 } else { 10810 u = u.UnionEdges(childRect); 10811 } 10812 } 10813 } 10814 10815 if (!overflowClipAxes.isEmpty()) { 10816 OverflowAreas::ApplyOverflowClippingOnRect(u, bounds, overflowClipAxes, 10817 overflowClipMargin); 10818 } 10819 return u; 10820 } 10821 10822 static void ComputeAndIncludeOutlineArea(nsIFrame* aFrame, 10823 OverflowAreas& aOverflowAreas, 10824 const nsSize& aNewSize) { 10825 const nsStyleOutline* outline = aFrame->StyleOutline(); 10826 if (!outline->ShouldPaintOutline()) { 10827 return; 10828 } 10829 10830 // When the outline property is set on a :-moz-block-inside-inline-wrapper 10831 // pseudo-element, it inherited that outline from the inline that was broken 10832 // because it contained a block. In that case, we don't want a really wide 10833 // outline if the block inside the inline is narrow, so union the actual 10834 // contents of the anonymous blocks. 10835 nsIFrame* frameForArea = aFrame; 10836 do { 10837 PseudoStyleType pseudoType = frameForArea->Style()->GetPseudoType(); 10838 if (pseudoType != PseudoStyleType::mozBlockInsideInlineWrapper) { 10839 break; 10840 } 10841 // If we're done, we really want it and all its later siblings. 10842 frameForArea = frameForArea->PrincipalChildList().FirstChild(); 10843 NS_ASSERTION(frameForArea, "anonymous block with no children?"); 10844 } while (frameForArea); 10845 10846 // Find the union of the border boxes of all descendants, or in 10847 // the block-in-inline case, all descendants we care about. 10848 // 10849 // Note that the interesting perspective-related cases are taken 10850 // care of by the code that handles those issues for overflow 10851 // calling FinishAndStoreOverflow again, which in turn calls this 10852 // function again. We still need to deal with preserve-3d a bit. 10853 nsRect innerRect; 10854 bool validRect = false; 10855 if (frameForArea == aFrame) { 10856 innerRect = ComputeOutlineInnerRect(aFrame, ApplyTransform::No, validRect, 10857 &aNewSize, &aOverflowAreas); 10858 } else { 10859 for (; frameForArea; frameForArea = frameForArea->GetNextSibling()) { 10860 nsRect r = 10861 ComputeOutlineInnerRect(frameForArea, ApplyTransform::Yes, validRect); 10862 10863 // Adjust for offsets transforms up to aFrame's pre-transform 10864 // (i.e., normal) coordinate space; see comments in 10865 // UnionBorderBoxes for some of the subtlety here. 10866 for (nsIFrame *f = frameForArea, *parent = f->GetParent(); 10867 /* see middle of loop */; f = parent, parent = f->GetParent()) { 10868 r += f->GetPosition(); 10869 if (parent == aFrame) { 10870 break; 10871 } 10872 if (parent->IsTransformed() && !f->Combines3DTransformWithAncestors()) { 10873 TransformReferenceBox refBox(parent); 10874 r = nsDisplayTransform::TransformRect(r, parent, refBox); 10875 } 10876 } 10877 10878 innerRect.UnionRect(innerRect, r); 10879 } 10880 } 10881 10882 // Keep this code in sync with nsDisplayOutline::GetInnerRect. 10883 if (innerRect == aFrame->GetRectRelativeToSelf()) { 10884 aFrame->RemoveProperty(nsIFrame::OutlineInnerRectProperty()); 10885 } else { 10886 aFrame->SetOrUpdateDeletableProperty(nsIFrame::OutlineInnerRectProperty(), 10887 innerRect); 10888 } 10889 10890 nsRect outerRect(innerRect); 10891 outerRect.Inflate(outline->EffectiveOffsetFor(outerRect)); 10892 10893 if (outline->mOutlineStyle.IsAuto()) { 10894 nsPresContext* pc = aFrame->PresContext(); 10895 10896 pc->Theme()->GetWidgetOverflow(pc->DeviceContext(), aFrame, 10897 StyleAppearance::FocusOutline, &outerRect); 10898 } else { 10899 outerRect.Inflate(outline->mOutlineWidth); 10900 } 10901 10902 nsRect& vo = aOverflowAreas.InkOverflow(); 10903 vo = vo.UnionEdges(innerRect.Union(outerRect)); 10904 } 10905 10906 bool nsIFrame::FinishAndStoreOverflow(OverflowAreas& aOverflowAreas, 10907 nsSize aNewSize, nsSize* aOldSize, 10908 const nsStyleDisplay* aStyleDisplay) { 10909 MOZ_ASSERT(FrameMaintainsOverflow(), 10910 "Don't call - overflow rects not maintained on these SVG frames"); 10911 10912 const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay); 10913 bool hasTransform = IsTransformed(); 10914 10915 nsRect bounds(nsPoint(0, 0), aNewSize); 10916 // Store the passed in overflow area if we are a preserve-3d frame or we have 10917 // a transform, and it's not just the frame bounds. 10918 if (hasTransform || Combines3DTransformWithAncestors()) { 10919 if (!aOverflowAreas.InkOverflow().IsEqualEdges(bounds) || 10920 !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) { 10921 OverflowAreas* initial = GetProperty(nsIFrame::InitialOverflowProperty()); 10922 if (!initial) { 10923 AddProperty(nsIFrame::InitialOverflowProperty(), 10924 new OverflowAreas(aOverflowAreas)); 10925 } else if (initial != &aOverflowAreas) { 10926 *initial = aOverflowAreas; 10927 } 10928 } else { 10929 RemoveProperty(nsIFrame::InitialOverflowProperty()); 10930 } 10931 #ifdef DEBUG 10932 SetProperty(nsIFrame::DebugInitialOverflowPropertyApplied(), true); 10933 #endif 10934 } else { 10935 #ifdef DEBUG 10936 RemoveProperty(nsIFrame::DebugInitialOverflowPropertyApplied()); 10937 #endif 10938 } 10939 10940 nsSize oldSize = mRect.Size(); 10941 bool sizeChanged = ((aOldSize ? *aOldSize : oldSize) != aNewSize); 10942 10943 // Our frame size may not have been computed and set yet, but code under 10944 // functions such as ComputeEffectsRect (which we're about to call) use the 10945 // values that are stored in our frame rect to compute their results. We 10946 // need the results from those functions to be based on the frame size that 10947 // we *will* have, so we temporarily set our frame size here before calling 10948 // those functions. 10949 // 10950 // XXX Someone should document here why we revert the frame size before we 10951 // return rather than just leaving it set. 10952 // 10953 // We pass false here to avoid invalidating display items for this temporary 10954 // change. We sometimes reflow frames multiple times, with the final size 10955 // being the same as the initial. The single call to SetSize after reflow is 10956 // done will take care of invalidating display items if the size has actually 10957 // changed. 10958 SetSize(aNewSize, false); 10959 10960 const auto overflowClipAxes = ShouldApplyOverflowClipping(disp); 10961 10962 if (ChildrenHavePerspective(disp) && sizeChanged) { 10963 RecomputePerspectiveChildrenOverflow(this); 10964 10965 if (overflowClipAxes != kPhysicalAxesBoth) { 10966 aOverflowAreas.SetAllTo(bounds); 10967 DebugOnly<bool> ok = ComputeCustomOverflow(aOverflowAreas); 10968 10969 // ComputeCustomOverflow() should not return false, when 10970 // FrameMaintainsOverflow() returns true. 10971 MOZ_ASSERT(ok, "FrameMaintainsOverflow() != ComputeCustomOverflow()"); 10972 10973 UnionChildOverflow(aOverflowAreas); 10974 } 10975 } 10976 10977 // This is now called FinishAndStoreOverflow() instead of 10978 // StoreOverflow() because frame-generic ways of adding overflow 10979 // can happen here, e.g. CSS2 outline and native theme. 10980 // If the overflow area width or height is nscoord_MAX, then a saturating 10981 // union may have encountered an overflow, so the overflow may not contain the 10982 // frame border-box. Don't warn in that case. 10983 // Don't warn for SVG either, since SVG doesn't need the overflow area 10984 // to contain the frame bounds. 10985 #ifdef DEBUG 10986 for (const auto otype : AllOverflowTypes()) { 10987 const nsRect& r = aOverflowAreas.Overflow(otype); 10988 NS_ASSERTION(aNewSize.width == 0 || aNewSize.height == 0 || 10989 r.width == nscoord_MAX || r.height == nscoord_MAX || 10990 HasAnyStateBits(NS_FRAME_SVG_LAYOUT) || 10991 r.Contains(nsRect(nsPoint(), aNewSize)), 10992 "Computed overflow area must contain frame bounds"); 10993 } 10994 #endif 10995 10996 // Overflow area must always include the frame's top-left and bottom-right, 10997 // even if the frame rect is empty (so we can scroll to those positions). 10998 const bool shouldIncludeBounds = [&] { 10999 if (aNewSize.width == 0 && IsInlineFrame()) { 11000 // Pending a real fix for bug 426879, don't do this for inline frames with 11001 // zero width. 11002 return false; 11003 } 11004 if (HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 11005 // Do not do this for SVG either, since it will usually massively increase 11006 // the area unnecessarily (except for SVG that applies clipping, since 11007 // that's the pre-existing behavior, and breaks pre-rendering otherwise). 11008 // FIXME(bug 1770704): This check most likely wants to be removed or check 11009 // for specific frame types at least. 11010 return !overflowClipAxes.isEmpty(); 11011 } 11012 return true; 11013 }(); 11014 11015 if (shouldIncludeBounds) { 11016 for (const auto otype : AllOverflowTypes()) { 11017 nsRect& o = aOverflowAreas.Overflow(otype); 11018 o = o.UnionEdges(bounds); 11019 } 11020 } 11021 11022 // If we clip our children, clear accumulated overflow area in the affected 11023 // dimension(s). The children are actually clipped to the padding-box, but 11024 // since the overflow area should include the entire border-box, just set it 11025 // to the border-box size here. 11026 if (!overflowClipAxes.isEmpty()) { 11027 aOverflowAreas.ApplyClipping( 11028 bounds, overflowClipAxes, 11029 OverflowClipMargin(overflowClipAxes, /* aAllowNegative = */ false)); 11030 } 11031 11032 ComputeAndIncludeOutlineArea(this, aOverflowAreas, aNewSize); 11033 11034 // Nothing in here should affect scrollable overflow. 11035 aOverflowAreas.InkOverflow() = 11036 ComputeEffectsRect(this, aOverflowAreas.InkOverflow(), aNewSize); 11037 11038 // Absolute position clipping 11039 const nsStyleEffects* effects = StyleEffects(); 11040 Maybe<nsRect> clipPropClipRect = GetClipPropClipRect(disp, effects, aNewSize); 11041 if (clipPropClipRect) { 11042 for (const auto otype : AllOverflowTypes()) { 11043 nsRect& o = aOverflowAreas.Overflow(otype); 11044 o.IntersectRect(o, *clipPropClipRect); 11045 } 11046 } 11047 11048 /* If we're transformed, transform the overflow rect by the current 11049 * transformation. */ 11050 if (hasTransform) { 11051 SetProperty(nsIFrame::PreTransformOverflowAreasProperty(), 11052 new OverflowAreas(aOverflowAreas)); 11053 11054 if (Combines3DTransformWithAncestors()) { 11055 /* If we're a preserve-3d leaf frame, then our pre-transform overflow 11056 * should be correct. Our post-transform overflow is empty though, because 11057 * we only contribute to the overflow area of the preserve-3d root frame. 11058 * If we're an intermediate frame then the pre-transform overflow should 11059 * contain all our non-preserve-3d children, which is what we want. Again 11060 * we have no post-transform overflow. 11061 */ 11062 aOverflowAreas.SetAllTo(nsRect()); 11063 } else { 11064 TransformReferenceBox refBox(this); 11065 for (const auto otype : AllOverflowTypes()) { 11066 nsRect& o = aOverflowAreas.Overflow(otype); 11067 // If the overflow is empty, it can still have a non-zero length in one 11068 // axis. Transforming such axis-bound rect can cause the resulting rect 11069 // to be non-empty, e.g. by rotating the rect. 11070 if (!o.IsEmpty()) { 11071 o = nsDisplayTransform::TransformRect(o, this, refBox); 11072 } 11073 } 11074 11075 /* If we're the root of the 3d context, then we want to include the 11076 * overflow areas of all the participants. This won't have happened yet as 11077 * the code above set their overflow area to empty. Manually collect these 11078 * overflow areas now. 11079 */ 11080 if (Extend3DContext(disp, effects)) { 11081 ComputePreserve3DChildrenOverflow(aOverflowAreas); 11082 } 11083 } 11084 } else { 11085 RemoveProperty(nsIFrame::PreTransformOverflowAreasProperty()); 11086 } 11087 11088 /* Revert the size change in case some caller is depending on this. */ 11089 SetSize(oldSize, false); 11090 11091 bool anyOverflowChanged; 11092 if (aOverflowAreas != OverflowAreas(bounds, bounds)) { 11093 anyOverflowChanged = SetOverflowAreas(aOverflowAreas); 11094 } else { 11095 anyOverflowChanged = ClearOverflowRects(); 11096 } 11097 11098 if (anyOverflowChanged) { 11099 SVGObserverUtils::InvalidateDirectRenderingObservers(this); 11100 if (nsBlockFrame* block = do_QueryFrame(this)) { 11101 // NOTE(emilio): we need to use BeforeReflow::Yes, because we want to 11102 // invalidate in cases where we _used_ to have an overflow marker and no 11103 // longer do. 11104 if (TextOverflow::CanHaveOverflowMarkers( 11105 block, TextOverflow::BeforeReflow::Yes)) { 11106 DiscardDisplayItems(this, [](nsDisplayItem* aItem) { 11107 return aItem->GetType() == DisplayItemType::TYPE_TEXT_OVERFLOW; 11108 }); 11109 SchedulePaint(PAINT_DEFAULT); 11110 } 11111 } 11112 } 11113 return anyOverflowChanged; 11114 } 11115 11116 void nsIFrame::RecomputePerspectiveChildrenOverflow( 11117 const nsIFrame* aStartFrame) { 11118 for (const auto& childList : ChildLists()) { 11119 for (nsIFrame* child : childList.mList) { 11120 if (!child->FrameMaintainsOverflow()) { 11121 continue; // frame does not maintain overflow rects 11122 } 11123 if (child->HasPerspective()) { 11124 OverflowAreas* overflow = 11125 child->GetProperty(nsIFrame::InitialOverflowProperty()); 11126 nsRect bounds(nsPoint(0, 0), child->GetSize()); 11127 if (overflow) { 11128 OverflowAreas overflowCopy = *overflow; 11129 child->FinishAndStoreOverflow(overflowCopy, bounds.Size()); 11130 } else { 11131 OverflowAreas boundsOverflow; 11132 boundsOverflow.SetAllTo(bounds); 11133 child->FinishAndStoreOverflow(boundsOverflow, bounds.Size()); 11134 } 11135 } else if (child->GetContent() == aStartFrame->GetContent() || 11136 child->GetClosestFlattenedTreeAncestorPrimaryFrame() == 11137 aStartFrame) { 11138 // If a frame is using perspective, then the size used to compute 11139 // perspective-origin is the size of the frame belonging to its parent 11140 // style. We must find any descendant frames using our size 11141 // (by recursing into frames that have the same containing block) 11142 // to update their overflow rects too. 11143 child->RecomputePerspectiveChildrenOverflow(aStartFrame); 11144 } 11145 } 11146 } 11147 } 11148 11149 void nsIFrame::ComputePreserve3DChildrenOverflow( 11150 OverflowAreas& aOverflowAreas) { 11151 // Find all descendants that participate in the 3d context, and include their 11152 // overflow. These descendants have an empty overflow, so won't have been 11153 // included in the normal overflow calculation. Any children that don't 11154 // participate have normal overflow, so will have been included already. 11155 11156 nsRect childVisual; 11157 nsRect childScrollable; 11158 for (const auto& childList : ChildLists()) { 11159 for (nsIFrame* child : childList.mList) { 11160 // If this child participates in the 3d context, then take the 11161 // pre-transform region (which contains all descendants that aren't 11162 // participating in the 3d context) and transform it into the 3d context 11163 // root coordinate space. 11164 if (child->Combines3DTransformWithAncestors()) { 11165 OverflowAreas childOverflow = child->GetOverflowAreasRelativeToSelf(); 11166 TransformReferenceBox refBox(child); 11167 for (const auto otype : AllOverflowTypes()) { 11168 nsRect& o = childOverflow.Overflow(otype); 11169 o = nsDisplayTransform::TransformRect(o, child, refBox); 11170 } 11171 11172 aOverflowAreas.UnionWith(childOverflow); 11173 11174 // If this child also extends the 3d context, then recurse into it 11175 // looking for more participants. 11176 if (child->Extend3DContext()) { 11177 child->ComputePreserve3DChildrenOverflow(aOverflowAreas); 11178 } 11179 } 11180 } 11181 } 11182 } 11183 11184 bool nsIFrame::ZIndexApplies() const { 11185 return StyleDisplay()->IsPositionedStyle() || IsFlexOrGridItem() || 11186 IsMenuPopupFrame(); 11187 } 11188 11189 Maybe<int32_t> nsIFrame::ZIndex() const { 11190 if (!ZIndexApplies()) { 11191 return Nothing(); 11192 } 11193 const auto& zIndex = StylePosition()->mZIndex; 11194 if (zIndex.IsAuto()) { 11195 return Nothing(); 11196 } 11197 return Some(zIndex.AsInteger()); 11198 } 11199 11200 bool nsIFrame::IsScrollAnchor(ScrollAnchorContainer** aOutContainer) { 11201 if (!mInScrollAnchorChain) { 11202 return false; 11203 } 11204 11205 nsIFrame* f = this; 11206 11207 // FIXME(emilio, bug 1629280): We should find a non-null anchor if we have the 11208 // flag set, but bug 1629280 makes it so that we cannot really assert it / 11209 // make this just a `while (true)`, and uncomment the below assertion. 11210 while (auto* container = ScrollAnchorContainer::FindFor(f)) { 11211 // MOZ_ASSERT(f->IsInScrollAnchorChain()); 11212 if (nsIFrame* anchor = container->AnchorNode()) { 11213 if (anchor != this) { 11214 return false; 11215 } 11216 if (aOutContainer) { 11217 *aOutContainer = container; 11218 } 11219 return true; 11220 } 11221 11222 f = container->Frame(); 11223 } 11224 11225 return false; 11226 } 11227 11228 bool nsIFrame::IsInScrollAnchorChain() const { return mInScrollAnchorChain; } 11229 11230 void nsIFrame::SetInScrollAnchorChain(bool aInChain) { 11231 mInScrollAnchorChain = aInChain; 11232 } 11233 11234 uint32_t nsIFrame::GetDepthInFrameTree() const { 11235 uint32_t result = 0; 11236 for (nsContainerFrame* ancestor = GetParent(); ancestor; 11237 ancestor = ancestor->GetParent()) { 11238 result++; 11239 } 11240 return result; 11241 } 11242 11243 /** 11244 * This function takes a frame that is part of a block-in-inline split, 11245 * and _if_ that frame is an anonymous block created by an ib split it 11246 * returns the block's preceding inline. This is needed because the 11247 * split inline's style is the parent of the anonymous block's style. 11248 * 11249 * If aFrame is not an anonymous block, null is returned. 11250 */ 11251 static nsIFrame* GetIBSplitSiblingForAnonymousBlock(const nsIFrame* aFrame) { 11252 MOZ_ASSERT(aFrame, "Must have a non-null frame!"); 11253 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT), 11254 "GetIBSplitSibling should only be called on ib-split frames"); 11255 11256 if (aFrame->Style()->GetPseudoType() != 11257 PseudoStyleType::mozBlockInsideInlineWrapper) { 11258 // it's not an anonymous block 11259 return nullptr; 11260 } 11261 11262 // Find the first continuation of the frame. (Ugh. This ends up 11263 // being O(N^2) when it is called O(N) times.) 11264 aFrame = aFrame->FirstContinuation(); 11265 11266 /* 11267 * Now look up the nsGkAtoms::IBSplitPrevSibling 11268 * property. 11269 */ 11270 nsIFrame* ibSplitSibling = 11271 aFrame->GetProperty(nsIFrame::IBSplitPrevSibling()); 11272 NS_ASSERTION(ibSplitSibling, "Broken frame tree?"); 11273 return ibSplitSibling; 11274 } 11275 11276 /** 11277 * Get the parent, corrected for the mangled frame tree resulting from 11278 * having a block within an inline. The result only differs from the 11279 * result of |GetParent| when |GetParent| returns an anonymous block 11280 * that was created for an element that was 'display: inline' because 11281 * that element contained a block. 11282 * 11283 * Also skip anonymous scrolled-content parents; inherit directly from the 11284 * outer scroll frame. 11285 * 11286 * Also skip NAC parents if the child frame is NAC. 11287 */ 11288 static nsIFrame* GetCorrectedParent(const nsIFrame* aFrame) { 11289 nsIFrame* parent = aFrame->GetParent(); 11290 if (!parent) { 11291 return nullptr; 11292 } 11293 11294 // For a table caption we want the _inner_ table frame (unless it's anonymous) 11295 // as the style parent. 11296 if (aFrame->IsTableCaption()) { 11297 MOZ_ASSERT(parent->IsTableWrapperFrame()); 11298 nsTableFrame* innerTable = 11299 static_cast<const nsTableWrapperFrame*>(parent)->InnerTableFrame(); 11300 if (!innerTable->Style()->IsAnonBox()) { 11301 return innerTable; 11302 } 11303 } 11304 11305 // Table wrappers are always anon boxes; if we're in here for an outer 11306 // table, that actually means its the _inner_ table that wants to 11307 // know its parent. So get the pseudo of the inner in that case. 11308 auto pseudo = aFrame->Style()->GetPseudoType(); 11309 if (pseudo == PseudoStyleType::tableWrapper) { 11310 MOZ_ASSERT(aFrame->IsTableWrapperFrame()); 11311 nsTableFrame* innerTable = 11312 static_cast<const nsTableWrapperFrame*>(aFrame)->InnerTableFrame(); 11313 pseudo = innerTable->Style()->GetPseudoType(); 11314 } 11315 11316 // Prevent a NAC pseudo-element from inheriting from its NAC parent, and 11317 // inherit from the NAC generator element instead. (We exclude element-backed 11318 // pseudos from this check, since they're not NAC.) 11319 if (pseudo != PseudoStyleType::NotPseudo && 11320 !PseudoStyle::IsElementBackedPseudo(pseudo)) { 11321 MOZ_ASSERT(aFrame->GetContent()); 11322 Element* element = Element::FromNode(aFrame->GetContent()); 11323 // Make sure to only do the fixup for anonymous content pseudos (i.e. avoid 11324 // fixup for ::first-line and such). 11325 if (element && !element->IsRootOfNativeAnonymousSubtree() && 11326 element->GetPseudoElementType() == aFrame->Style()->GetPseudoType()) { 11327 while (parent->GetContent() && 11328 !parent->GetContent()->IsRootOfNativeAnonymousSubtree()) { 11329 parent = parent->GetInFlowParent(); 11330 } 11331 parent = parent->GetInFlowParent(); 11332 } 11333 } 11334 11335 return nsIFrame::CorrectStyleParentFrame(parent, pseudo); 11336 } 11337 11338 /* static */ 11339 nsIFrame* nsIFrame::CorrectStyleParentFrame(nsIFrame* aProspectiveParent, 11340 PseudoStyleType aChildPseudo) { 11341 MOZ_ASSERT(aProspectiveParent, "Must have a prospective parent"); 11342 11343 if (aChildPseudo != PseudoStyleType::NotPseudo) { 11344 // Non-inheriting anon boxes have no style parent frame at all. 11345 if (PseudoStyle::IsNonInheritingAnonBox(aChildPseudo)) { 11346 return nullptr; 11347 } 11348 11349 // Other anon boxes are parented to their actual parent already, except 11350 // for non-elements. Those should not be treated as an anon box. 11351 if (PseudoStyle::IsAnonBox(aChildPseudo) && 11352 !nsCSSAnonBoxes::IsNonElement(aChildPseudo)) { 11353 NS_ASSERTION(aChildPseudo != PseudoStyleType::mozBlockInsideInlineWrapper, 11354 "Should have dealt with kids that have " 11355 "NS_FRAME_PART_OF_IBSPLIT elsewhere"); 11356 return aProspectiveParent; 11357 } 11358 } 11359 11360 // Otherwise, walk up out of all anon boxes. For placeholder frames, walk out 11361 // of all pseudo-elements as well. Otherwise ReparentComputedStyle could 11362 // cause style data to be out of sync with the frame tree. 11363 nsIFrame* parent = aProspectiveParent; 11364 do { 11365 if (parent->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { 11366 nsIFrame* sibling = GetIBSplitSiblingForAnonymousBlock(parent); 11367 11368 if (sibling) { 11369 // |parent| was a block in an {ib} split; use the inline as 11370 // |the style parent. 11371 parent = sibling; 11372 } 11373 } 11374 11375 if (!parent->Style()->IsPseudoOrAnonBox()) { 11376 return parent; 11377 } 11378 11379 if (!parent->Style()->IsAnonBox() && aChildPseudo != PseudoStyleType::MAX) { 11380 // nsPlaceholderFrame passes in PseudoStyleType::MAX for 11381 // aChildPseudo (even though that's not a valid pseudo-type) just to 11382 // trigger this behavior of walking up to the nearest non-pseudo 11383 // ancestor. 11384 return parent; 11385 } 11386 11387 parent = parent->GetInFlowParent(); 11388 } while (parent); 11389 11390 if (aProspectiveParent->Style()->GetPseudoType() == 11391 PseudoStyleType::viewportScroll) { 11392 // aProspectiveParent is the scrollframe for a viewport 11393 // and the kids are the anonymous scrollbars 11394 return aProspectiveParent; 11395 } 11396 11397 // We can get here if the root element is absolutely positioned. 11398 // We can't test for this very accurately, but it can only happen 11399 // when the prospective parent is a canvas frame. 11400 NS_ASSERTION(aProspectiveParent->IsCanvasFrame(), 11401 "Should have found a parent before this"); 11402 return nullptr; 11403 } 11404 11405 ComputedStyle* nsIFrame::DoGetParentComputedStyle( 11406 nsIFrame** aProviderFrame) const { 11407 *aProviderFrame = nullptr; 11408 11409 // Handle display:contents and the root frame, when there's no parent frame 11410 // to inherit from. 11411 if (MOZ_LIKELY(mContent)) { 11412 Element* parentElement = mContent->GetFlattenedTreeParentElement(); 11413 if (MOZ_LIKELY(parentElement)) { 11414 auto pseudo = Style()->GetPseudoType(); 11415 if (pseudo == PseudoStyleType::NotPseudo || !mContent->IsElement() || 11416 (!PseudoStyle::IsAnonBox(pseudo) && 11417 // Ensure that we don't return the display:contents style 11418 // of the parent content for pseudos that have the same content 11419 // as their primary frame (like -moz-list-bullets do): 11420 IsPrimaryFrame()) || 11421 /* if next is true then it's really a request for the table frame's 11422 parent context, see nsTable[Outer]Frame::GetParentComputedStyle. */ 11423 pseudo == PseudoStyleType::tableWrapper) { 11424 // In some edge cases involving display: contents, we may end up here 11425 // for something that's pending to be reframed. In this case we return 11426 // the wrong style from here (because we've already lost track of it!), 11427 // but it's not a big deal as we're going to be reframed anyway. 11428 if (MOZ_LIKELY(parentElement->HasServoData()) && 11429 Servo_Element_IsDisplayContents(parentElement)) { 11430 RefPtr<ComputedStyle> style = 11431 ServoStyleSet::ResolveServoStyle(*parentElement); 11432 // NOTE(emilio): we return a weak reference because the element also 11433 // holds the style context alive. This is a bit silly (we could've 11434 // returned a weak ref directly), but it's probably not worth 11435 // optimizing, given this function has just one caller which is rare, 11436 // and this path is rare itself. 11437 return style; 11438 } 11439 } 11440 } else { 11441 if (Style()->GetPseudoType() == PseudoStyleType::NotPseudo) { 11442 // We're a frame for the root. We have no style parent. 11443 return nullptr; 11444 } 11445 } 11446 } 11447 11448 if (!HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 11449 /* 11450 * If this frame is an anonymous block created when an inline with a block 11451 * inside it got split, then the parent style is on its preceding inline. We 11452 * can get to it using GetIBSplitSiblingForAnonymousBlock. 11453 */ 11454 if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { 11455 nsIFrame* ibSplitSibling = GetIBSplitSiblingForAnonymousBlock(this); 11456 if (ibSplitSibling) { 11457 return (*aProviderFrame = ibSplitSibling)->Style(); 11458 } 11459 } 11460 11461 // If this frame is one of the blocks that split an inline, we must 11462 // return the "special" inline parent, i.e., the parent that this 11463 // frame would have if we didn't mangle the frame structure. 11464 *aProviderFrame = GetCorrectedParent(this); 11465 return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr; 11466 } 11467 11468 // We're an out-of-flow frame. For out-of-flow frames, we must 11469 // resolve underneath the placeholder's parent. The placeholder is 11470 // reached from the first-in-flow. 11471 nsPlaceholderFrame* placeholder = FirstInFlow()->GetPlaceholderFrame(); 11472 if (!placeholder) { 11473 MOZ_ASSERT_UNREACHABLE("no placeholder frame for out-of-flow frame"); 11474 *aProviderFrame = GetCorrectedParent(this); 11475 return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr; 11476 } 11477 return placeholder->GetParentComputedStyleForOutOfFlow(aProviderFrame); 11478 } 11479 11480 void nsIFrame::GetLastLeaf(nsIFrame** aFrame) { 11481 if (!aFrame || !*aFrame) { 11482 return; 11483 } 11484 for (nsIFrame* maybeLastLeaf = (*aFrame)->PrincipalChildList().LastChild(); 11485 maybeLastLeaf;) { 11486 nsIFrame* lastChildNotInSubTree = nullptr; 11487 for (nsIFrame* child = maybeLastLeaf; child; 11488 child = child->GetPrevSibling()) { 11489 // ignore anonymous elements, e.g. mozTableAdd* mozTableRemove* 11490 // see bug 278197 comment #12 #13 for details 11491 if (!child->ContentIsRootOfNativeAnonymousSubtree()) { 11492 lastChildNotInSubTree = child; 11493 break; 11494 } 11495 } 11496 if (!lastChildNotInSubTree) { 11497 return; 11498 } 11499 *aFrame = lastChildNotInSubTree; 11500 maybeLastLeaf = lastChildNotInSubTree->PrincipalChildList().LastChild(); 11501 } 11502 } 11503 11504 void nsIFrame::GetFirstLeaf(nsIFrame** aFrame) { 11505 if (!aFrame || !*aFrame) { 11506 return; 11507 } 11508 nsIFrame* child = *aFrame; 11509 while (true) { 11510 child = child->PrincipalChildList().FirstChild(); 11511 if (!child) { 11512 return; // nothing to do 11513 } 11514 *aFrame = child; 11515 } 11516 } 11517 11518 bool nsIFrame::IsFocusableDueToScrollFrame() { 11519 if (!IsScrollContainerFrame()) { 11520 if (nsFieldSetFrame* fieldset = do_QueryFrame(this)) { 11521 // TODO: Do we have similar special-cases like this where we can have 11522 // anonymous scrollable boxes hanging off a primary frame? 11523 if (nsIFrame* inner = fieldset->GetInner()) { 11524 return inner->IsFocusableDueToScrollFrame(); 11525 } 11526 } 11527 return false; 11528 } 11529 if (!mContent->IsHTMLElement()) { 11530 return false; 11531 } 11532 if (mContent->IsRootOfNativeAnonymousSubtree()) { 11533 return false; 11534 } 11535 if (!mContent->GetParent()) { 11536 return false; 11537 } 11538 if (mContent->AsElement()->HasAttr(nsGkAtoms::tabindex)) { 11539 return false; 11540 } 11541 // Elements with scrollable view are focusable with script & tabbable 11542 // Otherwise you couldn't scroll them with keyboard, which is an accessibility 11543 // issue (e.g. Section 508 rules) However, we don't make them to be focusable 11544 // with the mouse, because the extra focus outlines are considered 11545 // unnecessarily ugly. When clicked on, the selection position within the 11546 // element will be enough to make them keyboard scrollable. 11547 auto* scrollContainer = static_cast<ScrollContainerFrame*>(this); 11548 if (scrollContainer->GetScrollStyles().IsHiddenInBothDirections()) { 11549 return false; 11550 } 11551 if (scrollContainer->GetScrollRange().IsEqualEdges(nsRect())) { 11552 return false; 11553 } 11554 return true; 11555 } 11556 11557 Focusable nsIFrame::IsFocusable(IsFocusableFlags aFlags) { 11558 // cannot focus content in print preview mode. Only the root can be focused, 11559 // but that's handled elsewhere. 11560 if (PresContext()->Type() == nsPresContext::eContext_PrintPreview) { 11561 return {}; 11562 } 11563 11564 if (!mContent || !mContent->IsElement()) { 11565 return {}; 11566 } 11567 11568 if (!(aFlags & IsFocusableFlags::IgnoreVisibility) && 11569 !IsVisibleConsideringAncestors()) { 11570 return {}; 11571 } 11572 11573 const StyleUserFocus uf = StyleUI()->UserFocus(); 11574 if (uf == StyleUserFocus::None) { 11575 return {}; 11576 } 11577 MOZ_ASSERT(!StyleUI()->IsInert(), "inert implies -moz-user-focus: none"); 11578 11579 const PseudoStyleType pseudo = Style()->GetPseudoType(); 11580 if (pseudo == PseudoStyleType::anonymousItem) { 11581 return {}; 11582 } 11583 11584 Focusable focusable; 11585 if (auto* xul = nsXULElement::FromNode(mContent)) { 11586 // As a legacy special-case, -moz-user-focus controls focusability and 11587 // tabability of XUL elements in some circumstances (which default to 11588 // -moz-user-focus: ignore). 11589 auto focusability = xul->GetXULFocusability(aFlags); 11590 focusable.mFocusable = 11591 focusability.mForcedFocusable.valueOr(uf == StyleUserFocus::Normal); 11592 if (focusable) { 11593 focusable.mTabIndex = focusability.mForcedTabIndexIfFocusable.valueOr(0); 11594 } 11595 } else { 11596 focusable = mContent->IsFocusableWithoutStyle(aFlags); 11597 } 11598 11599 if (focusable) { 11600 return focusable; 11601 } 11602 11603 // If we're focusing with the mouse we never focus scroll areas. 11604 if (!(aFlags & IsFocusableFlags::WithMouse) && 11605 IsFocusableDueToScrollFrame()) { 11606 return {true, 0}; 11607 } 11608 11609 // FIXME(emilio): some callers rely on somewhat broken return values 11610 // (focusable = false, but non-negative tab-index) from 11611 // IsFocusableWithoutStyle (for image maps in particular). 11612 return focusable; 11613 } 11614 11615 /** 11616 * @return true if this text frame ends with a newline character which is 11617 * treated as preformatted. It should return false if this is not a text frame. 11618 */ 11619 bool nsIFrame::HasSignificantTerminalNewline() const { return false; } 11620 11621 static StyleVerticalAlignKeyword ConvertSVGDominantBaselineToVerticalAlign( 11622 StyleDominantBaseline aDominantBaseline) { 11623 // Most of these are approximate mappings. 11624 switch (aDominantBaseline) { 11625 case StyleDominantBaseline::Hanging: 11626 case StyleDominantBaseline::TextBeforeEdge: 11627 return StyleVerticalAlignKeyword::TextTop; 11628 case StyleDominantBaseline::TextAfterEdge: 11629 case StyleDominantBaseline::Ideographic: 11630 return StyleVerticalAlignKeyword::TextBottom; 11631 case StyleDominantBaseline::Central: 11632 case StyleDominantBaseline::Middle: 11633 case StyleDominantBaseline::Mathematical: 11634 return StyleVerticalAlignKeyword::Middle; 11635 case StyleDominantBaseline::Auto: 11636 case StyleDominantBaseline::Alphabetic: 11637 return StyleVerticalAlignKeyword::Baseline; 11638 default: 11639 MOZ_ASSERT_UNREACHABLE("unexpected aDominantBaseline value"); 11640 return StyleVerticalAlignKeyword::Baseline; 11641 } 11642 } 11643 11644 Maybe<StyleVerticalAlignKeyword> nsIFrame::VerticalAlignEnum() const { 11645 if (IsInSVGTextSubtree()) { 11646 StyleDominantBaseline dominantBaseline = StyleSVG()->mDominantBaseline; 11647 return Some(ConvertSVGDominantBaselineToVerticalAlign(dominantBaseline)); 11648 } 11649 11650 const auto& verticalAlign = StyleDisplay()->mVerticalAlign; 11651 if (verticalAlign.IsKeyword()) { 11652 return Some(verticalAlign.AsKeyword()); 11653 } 11654 11655 return Nothing(); 11656 } 11657 11658 void nsIFrame::UpdateStyleOfChildAnonBox(nsIFrame* aChildFrame, 11659 ServoRestyleState& aRestyleState) { 11660 #ifdef DEBUG 11661 nsIFrame* parent = aChildFrame->GetInFlowParent(); 11662 if (aChildFrame->IsTableFrame()) { 11663 parent = parent->GetParent(); 11664 } 11665 if (parent->IsLineFrame()) { 11666 parent = parent->GetParent(); 11667 } 11668 MOZ_ASSERT(nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent) == this, 11669 "This should only be used for children!"); 11670 #endif // DEBUG 11671 MOZ_ASSERT(!GetContent() || !aChildFrame->GetContent() || 11672 aChildFrame->GetContent() == GetContent(), 11673 "What content node is it a frame for?"); 11674 MOZ_ASSERT(!aChildFrame->GetPrevContinuation(), 11675 "Only first continuations should end up here"); 11676 11677 // We could force the caller to pass in the pseudo, since some callers know it 11678 // statically... But this API is a bit nicer. 11679 auto pseudo = aChildFrame->Style()->GetPseudoType(); 11680 MOZ_ASSERT(PseudoStyle::IsAnonBox(pseudo), "Child is not an anon box?"); 11681 MOZ_ASSERT(!PseudoStyle::IsNonInheritingAnonBox(pseudo), 11682 "Why did the caller bother calling us?"); 11683 11684 // Anon boxes inherit from their parent; that's us. 11685 RefPtr<ComputedStyle> newContext = 11686 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(pseudo, 11687 Style()); 11688 11689 nsChangeHint childHint = 11690 UpdateStyleOfOwnedChildFrame(aChildFrame, newContext, aRestyleState); 11691 11692 // Now that we've updated the style on aChildFrame, check whether it itself 11693 // has anon boxes to deal with. 11694 ServoRestyleState childrenState(*aChildFrame, aRestyleState, childHint, 11695 ServoRestyleState::CanUseHandledHints::Yes); 11696 aChildFrame->UpdateStyleOfOwnedAnonBoxes(childrenState); 11697 11698 // Assuming anon boxes don't have ::backdrop associated with them... if that 11699 // ever changes, we'd need to handle that here, like we do in 11700 // RestyleManager::ProcessPostTraversal 11701 11702 // We do need to handle block pseudo-elements here, though. Especially list 11703 // bullets. 11704 if (nsBlockFrame* block = do_QueryFrame(aChildFrame)) { 11705 block->UpdatePseudoElementStyles(childrenState); 11706 } 11707 } 11708 11709 /* static */ 11710 nsChangeHint nsIFrame::UpdateStyleOfOwnedChildFrame( 11711 nsIFrame* aChildFrame, ComputedStyle* aNewComputedStyle, 11712 ServoRestyleState& aRestyleState, 11713 const Maybe<ComputedStyle*>& aContinuationComputedStyle) { 11714 MOZ_ASSERT(!aChildFrame->GetAdditionalComputedStyle(0), 11715 "We don't handle additional styles here"); 11716 11717 // Figure out whether we have an actual change. It's important that we do 11718 // this, for several reasons: 11719 // 11720 // 1) Even if all the child's changes are due to properties it inherits from 11721 // us, it's possible that no one ever asked us for those style structs and 11722 // hence changes to them aren't reflected in the changes handled at all. 11723 // 11724 // 2) Content can change stylesheets that change the styles of pseudos, and 11725 // extensions can add/remove stylesheets that change the styles of 11726 // anonymous boxes directly. 11727 uint32_t equalStructs; // Not used, actually. 11728 nsChangeHint childHint = aChildFrame->Style()->CalcStyleDifference( 11729 *aNewComputedStyle, &equalStructs); 11730 11731 // If aChildFrame is out of flow, then aRestyleState's "changes handled by the 11732 // parent" doesn't apply to it, because it may have some other parent in the 11733 // frame tree. 11734 if (!aChildFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 11735 childHint = NS_RemoveSubsumedHints( 11736 childHint, aRestyleState.ChangesHandledFor(aChildFrame)); 11737 } 11738 if (childHint) { 11739 if (childHint & nsChangeHint_ReconstructFrame) { 11740 // If we generate a reconstruct here, remove any non-reconstruct hints we 11741 // may have already generated for this content. 11742 aRestyleState.ChangeList().PopChangesForContent( 11743 aChildFrame->GetContent()); 11744 } 11745 aRestyleState.ChangeList().AppendChange( 11746 aChildFrame, aChildFrame->GetContent(), childHint); 11747 } 11748 11749 aChildFrame->SetComputedStyle(aNewComputedStyle); 11750 ComputedStyle* continuationStyle = aContinuationComputedStyle 11751 ? *aContinuationComputedStyle 11752 : aNewComputedStyle; 11753 for (nsIFrame* kid = aChildFrame->GetNextContinuation(); kid; 11754 kid = kid->GetNextContinuation()) { 11755 MOZ_ASSERT(!kid->GetAdditionalComputedStyle(0)); 11756 kid->SetComputedStyle(continuationStyle); 11757 } 11758 11759 return childHint; 11760 } 11761 11762 /* static */ 11763 void nsIFrame::AddInPopupStateBitToDescendants(nsIFrame* aFrame) { 11764 if (!aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP) && 11765 aFrame->TrackingVisibility()) { 11766 // Assume all frames in popups are visible. 11767 aFrame->IncApproximateVisibleCount(); 11768 } 11769 11770 aFrame->AddStateBits(NS_FRAME_IN_POPUP); 11771 11772 for (const auto& childList : aFrame->CrossDocChildLists()) { 11773 for (nsIFrame* child : childList.mList) { 11774 AddInPopupStateBitToDescendants(child); 11775 } 11776 } 11777 } 11778 11779 /* static */ 11780 void nsIFrame::RemoveInPopupStateBitFromDescendants(nsIFrame* aFrame) { 11781 if (!aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP) || 11782 nsLayoutUtils::IsPopup(aFrame)) { 11783 return; 11784 } 11785 11786 aFrame->RemoveStateBits(NS_FRAME_IN_POPUP); 11787 11788 if (aFrame->TrackingVisibility()) { 11789 // We assume all frames in popups are visible, so this decrement balances 11790 // out the increment in AddInPopupStateBitToDescendants above. 11791 aFrame->DecApproximateVisibleCount(); 11792 } 11793 for (const auto& childList : aFrame->CrossDocChildLists()) { 11794 for (nsIFrame* child : childList.mList) { 11795 RemoveInPopupStateBitFromDescendants(child); 11796 } 11797 } 11798 } 11799 11800 void nsIFrame::SetParent(nsContainerFrame* aParent) { 11801 // If our parent is a wrapper anon box, our new parent should be too. We 11802 // _can_ change parent if our parent is a wrapper anon box, because some 11803 // wrapper anon boxes can have continuations. 11804 MOZ_ASSERT_IF(ParentIsWrapperAnonBox(), 11805 aParent->Style()->IsInheritingAnonBox()); 11806 11807 // Note that the current mParent may already be destroyed at this point. 11808 mParent = aParent; 11809 MOZ_ASSERT(!mParent || PresShell() == mParent->PresShell()); 11810 11811 nsFrameState flagsToPropagateSameDoc = 11812 GetStateBits() & (NS_FRAME_CONTAINS_RELATIVE_BSIZE | 11813 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE); 11814 if (flagsToPropagateSameDoc) { 11815 for (nsIFrame* f = aParent; f; f = f->GetParent()) { 11816 if (f->HasAllStateBits(flagsToPropagateSameDoc)) { 11817 break; 11818 } 11819 f->AddStateBits(flagsToPropagateSameDoc); 11820 } 11821 } 11822 11823 if (HasInvalidFrameInSubtree()) { 11824 for (nsIFrame* f = aParent; 11825 f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT | 11826 NS_FRAME_IS_NONDISPLAY); 11827 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) { 11828 f->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT); 11829 } 11830 } 11831 11832 if (aParent->HasAnyStateBits(NS_FRAME_IN_POPUP)) { 11833 AddInPopupStateBitToDescendants(this); 11834 } else { 11835 RemoveInPopupStateBitFromDescendants(this); 11836 } 11837 11838 // If our new parent only has invalid children, then we just invalidate 11839 // ourselves too. This is probably faster than clearing the flag all 11840 // the way up the frame tree. 11841 if (aParent->HasAnyStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT)) { 11842 InvalidateFrame(); 11843 } else { 11844 SchedulePaint(); 11845 } 11846 } 11847 11848 bool nsIFrame::IsStackingContext(const nsStyleDisplay* aStyleDisplay, 11849 const nsStyleEffects* aStyleEffects) { 11850 // Properties that influence the output of this function should be handled in 11851 // change_bits_for_longhand as well. 11852 if (HasOpacity(aStyleDisplay, aStyleEffects, nullptr)) { 11853 return true; 11854 } 11855 if (IsTransformed()) { 11856 return true; 11857 } 11858 auto willChange = aStyleDisplay->mWillChange.bits; 11859 if (aStyleDisplay->IsContainPaint() || aStyleDisplay->IsContainLayout() || 11860 willChange & StyleWillChangeBits::CONTAIN) { 11861 if (SupportsContainLayoutAndPaint()) { 11862 return true; 11863 } 11864 } 11865 11866 if (ForcesStackingContextForViewTransition()) { 11867 return true; 11868 } 11869 11870 // strictly speaking, 'perspective' doesn't require visual atomicity, 11871 // but the spec says it acts like the rest of these 11872 if (aStyleDisplay->HasPerspectiveStyle() || 11873 willChange & StyleWillChangeBits::PERSPECTIVE) { 11874 if (SupportsCSSTransforms()) { 11875 return true; 11876 } 11877 } 11878 if (!StylePosition()->mZIndex.IsAuto() || 11879 willChange & StyleWillChangeBits::Z_INDEX) { 11880 if (ZIndexApplies()) { 11881 return true; 11882 } 11883 } 11884 // Elements captured in a view transition during a view transition or whose 11885 // view-transition-name computed value is not none (at any time) form a s 11886 // https://drafts.csswg.org/css-view-transitions-1/#named-and-transitioning 11887 return aStyleEffects->mMixBlendMode != StyleBlend::Normal || 11888 SVGIntegrationUtils::UsingEffectsForFrame(this) || 11889 aStyleDisplay->IsPositionForcingStackingContext() || 11890 aStyleDisplay->mIsolation != StyleIsolation::Auto || 11891 willChange & StyleWillChangeBits::STACKING_CONTEXT_UNCONDITIONAL; 11892 } 11893 11894 bool nsIFrame::IsStackingContext() { 11895 return IsStackingContext(StyleDisplay(), StyleEffects()); 11896 } 11897 11898 static bool IsFrameRectScrolledOutOfView(const nsIFrame* aTarget, 11899 const nsRect& aTargetRect, 11900 const nsIFrame* aParent) { 11901 // The ancestor frame we are checking if it clips out aTargetRect relative to 11902 // aTarget. 11903 nsIFrame* clipParent = nullptr; 11904 11905 // find the first scrollable frame or root frame if we are in a fixed pos 11906 // subtree 11907 for (nsIFrame* f = const_cast<nsIFrame*>(aParent); f; 11908 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) { 11909 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(f); 11910 if (scrollContainerFrame) { 11911 clipParent = f; 11912 break; 11913 } 11914 if (f->StyleDisplay()->mPosition == StylePositionProperty::Fixed && 11915 nsLayoutUtils::IsReallyFixedPos(f)) { 11916 clipParent = f->GetParent(); 11917 break; 11918 } 11919 } 11920 11921 if (!clipParent) { 11922 // Even if we couldn't find the nearest scrollable frame, it might mean we 11923 // are in an out-of-process iframe, try to see if |aTarget| frame is 11924 // scrolled out of view in an scrollable frame in a cross-process ancestor 11925 // document. 11926 return nsLayoutUtils::FrameRectIsScrolledOutOfViewInCrossProcess( 11927 aTarget, aTargetRect); 11928 } 11929 11930 nsRect clipRect = clipParent->InkOverflowRectRelativeToSelf(); 11931 // We consider that the target is scrolled out if the scrollable (or root) 11932 // frame is empty. 11933 if (clipRect.IsEmpty()) { 11934 return true; 11935 } 11936 11937 nsRect transformedRect = nsLayoutUtils::TransformFrameRectToAncestor( 11938 aTarget, aTargetRect, clipParent); 11939 11940 if (transformedRect.IsEmpty()) { 11941 // If the transformed rect is empty it represents a line or a point that we 11942 // should check is outside the the scrollable rect. 11943 if (transformedRect.x > clipRect.XMost() || 11944 transformedRect.y > clipRect.YMost() || 11945 clipRect.x > transformedRect.XMost() || 11946 clipRect.y > transformedRect.YMost()) { 11947 return true; 11948 } 11949 } else if (!transformedRect.Intersects(clipRect)) { 11950 return true; 11951 } 11952 11953 nsIFrame* parent = clipParent->GetParent(); 11954 if (!parent) { 11955 return false; 11956 } 11957 11958 return IsFrameRectScrolledOutOfView(clipParent, transformedRect, parent); 11959 } 11960 11961 bool nsIFrame::IsScrolledOutOfView() const { 11962 nsRect rect = InkOverflowRectRelativeToSelf(); 11963 return IsFrameRectScrolledOutOfView(this, rect, this); 11964 } 11965 11966 gfx::Matrix nsIFrame::ComputeWidgetTransform() const { 11967 const nsStyleUIReset* uiReset = StyleUIReset(); 11968 if (uiReset->mMozWindowTransform.IsNone()) { 11969 return gfx::Matrix(); 11970 } 11971 11972 TransformReferenceBox refBox(nullptr, nsRect(nsPoint(), GetSize())); 11973 11974 int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); 11975 gfx::Matrix4x4 matrix = nsStyleTransformMatrix::ReadTransforms( 11976 uiReset->mMozWindowTransform, refBox, float(appUnitsPerDevPixel)); 11977 11978 gfx::Matrix result2d; 11979 if (!matrix.CanDraw2D(&result2d)) { 11980 // FIXME: It would be preferable to reject non-2D transforms at parse time. 11981 NS_WARNING( 11982 "-moz-window-transform does not describe a 2D transform, " 11983 "but only 2d transforms are supported"); 11984 return gfx::Matrix(); 11985 } 11986 11987 return result2d; 11988 } 11989 11990 void nsIFrame::DoUpdateStyleOfOwnedAnonBoxes(ServoRestyleState& aRestyleState) { 11991 // As a special case, we check for {ib}-split block frames here, rather 11992 // than have an nsInlineFrame::AppendDirectlyOwnedAnonBoxes implementation 11993 // that returns them. 11994 // 11995 // (If we did handle them in AppendDirectlyOwnedAnonBoxes, we would have to 11996 // return *all* of the in-flow {ib}-split block frames, not just the first 11997 // one. For restyling, we really just need the first in flow, and the other 11998 // user of the AppendOwnedAnonBoxes API, AllChildIterator, doesn't need to 11999 // know about them at all, since these block frames never create NAC. So we 12000 // avoid any unncessary hashtable lookups for the {ib}-split frames by calling 12001 // UpdateStyleOfOwnedAnonBoxesForIBSplit directly here.) 12002 if (IsInlineFrame()) { 12003 if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { 12004 static_cast<nsInlineFrame*>(this)->UpdateStyleOfOwnedAnonBoxesForIBSplit( 12005 aRestyleState); 12006 } 12007 return; 12008 } 12009 12010 AutoTArray<OwnedAnonBox, 4> frames; 12011 AppendDirectlyOwnedAnonBoxes(frames); 12012 for (OwnedAnonBox& box : frames) { 12013 if (box.mUpdateStyleFn) { 12014 box.mUpdateStyleFn(this, box.mAnonBoxFrame, aRestyleState); 12015 } else { 12016 UpdateStyleOfChildAnonBox(box.mAnonBoxFrame, aRestyleState); 12017 } 12018 } 12019 } 12020 12021 /* virtual */ 12022 void nsIFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) { 12023 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)); 12024 MOZ_ASSERT_UNREACHABLE( 12025 "Subclasses that have directly owned anonymous boxes should override " 12026 "this method!"); 12027 } 12028 12029 void nsIFrame::DoAppendOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) { 12030 size_t i = aResult.Length(); 12031 AppendDirectlyOwnedAnonBoxes(aResult); 12032 12033 // After appending the directly owned anonymous boxes of this frame to 12034 // aResult above, we need to check each of them to see if they own 12035 // any anonymous boxes themselves. Note that we keep progressing 12036 // through aResult, looking for additional entries in aResult from these 12037 // subsequent AppendDirectlyOwnedAnonBoxes calls. (Thus we can't 12038 // use a ranged for loop here.) 12039 12040 while (i < aResult.Length()) { 12041 nsIFrame* f = aResult[i].mAnonBoxFrame; 12042 if (f->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) { 12043 f->AppendDirectlyOwnedAnonBoxes(aResult); 12044 } 12045 ++i; 12046 } 12047 } 12048 12049 nsIFrame::CaretPosition::CaretPosition() : mContentOffset(0) {} 12050 12051 nsIFrame::CaretPosition::~CaretPosition() = default; 12052 12053 bool nsIFrame::HasCSSAnimations() { 12054 auto* collection = AnimationCollection<CSSAnimation>::Get(this); 12055 return collection && !collection->mAnimations.IsEmpty(); 12056 } 12057 12058 bool nsIFrame::HasCSSTransitions() { 12059 auto* collection = AnimationCollection<CSSTransition>::Get(this); 12060 return collection && !collection->mAnimations.IsEmpty(); 12061 } 12062 12063 void nsIFrame::AddSizeOfExcludingThisForTree(nsWindowSizes& aSizes) const { 12064 aSizes.mLayoutFramePropertiesSize += 12065 mProperties.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); 12066 12067 // We don't do this for Gecko because this stuff is stored in the nsPresArena 12068 // and so measured elsewhere. 12069 if (!aSizes.mState.HaveSeenPtr(mComputedStyle)) { 12070 mComputedStyle->AddSizeOfIncludingThis(aSizes, 12071 &aSizes.mLayoutComputedValuesNonDom); 12072 } 12073 12074 // And our additional styles. 12075 int32_t index = 0; 12076 while (auto* extra = GetAdditionalComputedStyle(index++)) { 12077 if (!aSizes.mState.HaveSeenPtr(extra)) { 12078 extra->AddSizeOfIncludingThis(aSizes, 12079 &aSizes.mLayoutComputedValuesNonDom); 12080 } 12081 } 12082 12083 for (const auto& childList : ChildLists()) { 12084 for (const nsIFrame* f : childList.mList) { 12085 f->AddSizeOfExcludingThisForTree(aSizes); 12086 } 12087 } 12088 } 12089 12090 nsRect nsIFrame::GetCompositorHitTestArea(nsDisplayListBuilder* aBuilder) { 12091 nsRect area; 12092 12093 ScrollContainerFrame* scrollContainerFrame = 12094 nsLayoutUtils::GetScrollContainerFrameFor(this); 12095 if (scrollContainerFrame) { 12096 // If this frame is the scrolled frame of a scroll container frame, then we 12097 // need to pick up the area corresponding to the overflow rect as well. 12098 // Otherwise the parts of the overflow that are not occupied by descendants 12099 // get skipped and the APZ code sends touch events to the content underneath 12100 // instead. See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15. 12101 area = ScrollableOverflowRect(); 12102 } else { 12103 area = GetRectRelativeToSelf(); 12104 } 12105 12106 if (!area.IsEmpty()) { 12107 return area + aBuilder->ToReferenceFrame(this); 12108 } 12109 12110 return area; 12111 } 12112 12113 CompositorHitTestInfo nsIFrame::GetCompositorHitTestInfo( 12114 nsDisplayListBuilder* aBuilder) { 12115 CompositorHitTestInfo result = CompositorHitTestInvisibleToHit; 12116 if (Style()->PointerEvents() == StylePointerEvents::None) { 12117 return result; 12118 } 12119 return GetCompositorHitTestInfoWithoutPointerEvents(aBuilder); 12120 } 12121 12122 CompositorHitTestInfo nsIFrame::GetCompositorHitTestInfoWithoutPointerEvents( 12123 nsDisplayListBuilder* aBuilder) { 12124 CompositorHitTestInfo result = CompositorHitTestInvisibleToHit; 12125 12126 if (aBuilder->IsInsidePointerEventsNoneDoc() || 12127 aBuilder->IsInViewTransitionCapture()) { 12128 // Somewhere up the parent document chain is a subdocument with pointer- 12129 // events:none set on it, or we're getting captured in a view transition. 12130 return result; 12131 } 12132 if (!GetParent()) { 12133 MOZ_ASSERT(IsViewportFrame()); 12134 // Viewport frames are never event targets, other frames, like canvas 12135 // frames, are the event targets for any regions viewport frames may cover. 12136 return result; 12137 } 12138 if (!StyleVisibility()->IsVisible()) { 12139 return result; 12140 } 12141 12142 // Anything that didn't match the above conditions is visible to hit-testing. 12143 result = CompositorHitTestFlags::eVisibleToHitTest; 12144 SVGUtils::MaskUsage maskUsage = SVGUtils::DetermineMaskUsage(this, false); 12145 if (maskUsage.UsingMaskOrClipPath()) { 12146 // If WebRender is enabled, simple clip-paths can be converted into WR 12147 // clips that WR knows how to hit-test against, so we don't need to mark 12148 // it as an irregular area. 12149 if (!maskUsage.IsSimpleClipShape()) { 12150 result += CompositorHitTestFlags::eIrregularArea; 12151 } 12152 } 12153 12154 if (aBuilder->IsBuildingNonLayerizedScrollbar()) { 12155 // Scrollbars may be painted into a layer below the actual layer they will 12156 // scroll, and therefore wheel events may be dispatched to the outer frame 12157 // instead of the intended scrollframe. To address this, we force a d-t-c 12158 // region on scrollbar frames that won't be placed in their own layer. See 12159 // bug 1213324 for details. 12160 result += CompositorHitTestFlags::eInactiveScrollframe; 12161 } else if (aBuilder->GetAncestorHasApzAwareEventHandler()) { 12162 result += CompositorHitTestFlags::eApzAwareListeners; 12163 } else if (IsRangeFrame()) { 12164 // Range frames handle touch events directly without having a touch listener 12165 // so we need to let APZ know that this area cares about events. 12166 result += CompositorHitTestFlags::eApzAwareListeners; 12167 } 12168 12169 if (aBuilder->IsTouchEventPrefEnabledDoc()) { 12170 // Inherit the touch-action flags from the parent, if there is one. We do 12171 // this because of how the touch-action on a frame combines the touch-action 12172 // from ancestor DOM elements. Refer to the documentation in 12173 // TouchActionHelper.cpp for details; this code is meant to be equivalent to 12174 // that code, but woven into the top-down recursive display list building 12175 // process. 12176 CompositorHitTestInfo inheritedTouchAction = 12177 aBuilder->GetInheritedCompositorHitTestInfo() & 12178 CompositorHitTestTouchActionMask; 12179 12180 nsIFrame* touchActionFrame = this; 12181 if (ScrollContainerFrame* scrollContainerFrame = 12182 nsLayoutUtils::GetScrollContainerFrameFor(this)) { 12183 ScrollStyles ss = scrollContainerFrame->GetScrollStyles(); 12184 if (ss.mVertical != StyleOverflow::Hidden || 12185 ss.mHorizontal != StyleOverflow::Hidden) { 12186 touchActionFrame = scrollContainerFrame; 12187 // On scrollframes, stop inheriting the pan-x and pan-y flags; instead, 12188 // reset them back to zero to allow panning on the scrollframe unless we 12189 // encounter an element that disables it that's inside the scrollframe. 12190 // This is equivalent to the |considerPanning| variable in 12191 // TouchActionHelper.cpp, but for a top-down traversal. 12192 CompositorHitTestInfo panMask( 12193 CompositorHitTestFlags::eTouchActionPanXDisabled, 12194 CompositorHitTestFlags::eTouchActionPanYDisabled); 12195 inheritedTouchAction -= panMask; 12196 } 12197 } 12198 12199 result += inheritedTouchAction; 12200 12201 const StyleTouchAction touchAction = touchActionFrame->UsedTouchAction(); 12202 // The CSS allows the syntax auto | none | [pan-x || pan-y] | manipulation 12203 // so we can eliminate some combinations of things. 12204 if (touchAction == StyleTouchAction::AUTO) { 12205 // nothing to do 12206 } else if (touchAction & StyleTouchAction::MANIPULATION) { 12207 result += CompositorHitTestFlags::eTouchActionAnimatingZoomDisabled; 12208 } else { 12209 // This path handles the cases none | [pan-x || pan-y || pinch-zoom] so 12210 // double-tap is disabled in here. 12211 if (!(touchAction & StyleTouchAction::PINCH_ZOOM)) { 12212 result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled; 12213 } 12214 12215 result += CompositorHitTestFlags::eTouchActionAnimatingZoomDisabled; 12216 12217 if (!(touchAction & StyleTouchAction::PAN_X)) { 12218 result += CompositorHitTestFlags::eTouchActionPanXDisabled; 12219 } 12220 if (!(touchAction & StyleTouchAction::PAN_Y)) { 12221 result += CompositorHitTestFlags::eTouchActionPanYDisabled; 12222 } 12223 if (touchAction & StyleTouchAction::NONE) { 12224 // all the touch-action disabling flags will already have been set above 12225 MOZ_ASSERT(result.contains(CompositorHitTestTouchActionMask)); 12226 } 12227 } 12228 } 12229 12230 const Maybe<ScrollDirection> scrollDirection = 12231 aBuilder->GetCurrentScrollbarDirection(); 12232 if (scrollDirection.isSome()) { 12233 if (GetContent()->IsXULElement(nsGkAtoms::thumb)) { 12234 const bool thumbGetsLayer = aBuilder->GetCurrentScrollbarTarget() != 12235 layers::ScrollableLayerGuid::NULL_SCROLL_ID; 12236 if (thumbGetsLayer) { 12237 result += CompositorHitTestFlags::eScrollbarThumb; 12238 } else { 12239 result += CompositorHitTestFlags::eInactiveScrollframe; 12240 } 12241 } 12242 12243 if (*scrollDirection == ScrollDirection::eVertical) { 12244 result += CompositorHitTestFlags::eScrollbarVertical; 12245 } 12246 12247 // includes the ScrollbarFrame, SliderFrame, anything else that 12248 // might be inside the xul:scrollbar 12249 result += CompositorHitTestFlags::eScrollbar; 12250 } 12251 12252 return result; 12253 } 12254 12255 // Returns true if we can guarantee there is no visible descendants. 12256 static bool HasNoVisibleDescendants(const nsIFrame* aFrame) { 12257 for (const auto& childList : aFrame->ChildLists()) { 12258 for (nsIFrame* f : childList.mList) { 12259 if (nsPlaceholderFrame::GetRealFrameFor(f) 12260 ->IsVisibleOrMayHaveVisibleDescendants()) { 12261 return false; 12262 } 12263 } 12264 } 12265 return true; 12266 } 12267 12268 void nsIFrame::UpdateVisibleDescendantsState() { 12269 if (StyleVisibility()->IsVisible()) { 12270 // Notify invisible ancestors that a visible descendant exists now. 12271 nsIFrame* ancestor; 12272 for (ancestor = GetInFlowParent(); 12273 ancestor && !ancestor->StyleVisibility()->IsVisible(); 12274 ancestor = ancestor->GetInFlowParent()) { 12275 ancestor->mAllDescendantsAreInvisible = false; 12276 } 12277 } else { 12278 mAllDescendantsAreInvisible = HasNoVisibleDescendants(this); 12279 } 12280 } 12281 12282 PhysicalAxes nsIFrame::ShouldApplyOverflowClipping( 12283 const nsStyleDisplay* aDisp) const { 12284 MOZ_ASSERT(aDisp == StyleDisplay(), "Wrong display struct"); 12285 12286 if (IsScrollContainerOrSubclass()) { 12287 // Scrollers deal with overflow on their own. 12288 return {}; 12289 } 12290 12291 // 'contain:paint', which we handle as 'overflow:clip' here. 'contain:paint' 12292 // should prevent all means of escaping that clipping (e.g. because it forms a 12293 // fixed-pos containing block). 12294 if (aDisp->IsContainPaint() && SupportsContainLayoutAndPaint()) { 12295 return kPhysicalAxesBoth; 12296 } 12297 12298 // and overflow: scrollable that we should interpret as clip 12299 if (aDisp->IsScrollableOverflow()) { 12300 // REVIEW: these are the frame types that set up clipping. 12301 LayoutFrameType type = Type(); 12302 switch (type) { 12303 case LayoutFrameType::CheckboxRadio: 12304 case LayoutFrameType::ComboboxControl: 12305 case LayoutFrameType::Progress: 12306 case LayoutFrameType::Range: 12307 case LayoutFrameType::SubDocument: 12308 case LayoutFrameType::SVGForeignObject: 12309 case LayoutFrameType::SVGInnerSVG: 12310 case LayoutFrameType::SVGOuterSVG: 12311 case LayoutFrameType::SVGSymbol: 12312 case LayoutFrameType::Image: 12313 case LayoutFrameType::TableCell: 12314 return kPhysicalAxesBoth; 12315 case LayoutFrameType::Table: 12316 // Tables, for legacy reasons only clip when hidden in both directions, 12317 // and treat all other scrollable overflow values as `visible`. 12318 // This is (somewhat, since other browsers make this change at 12319 // computed-value time, see bug 1918789) interoperable css2-era 12320 // behavior. We might be able to change this, but not today. 12321 // See layout/reftests/table-overflow/bug785684-x.html 12322 return aDisp->mOverflowX == StyleOverflow::Hidden && 12323 aDisp->mOverflowY == StyleOverflow::Hidden 12324 ? kPhysicalAxesBoth 12325 : PhysicalAxes(); 12326 case LayoutFrameType::TextInput: 12327 // It has an anonymous scroll container frame that handles any overflow. 12328 return PhysicalAxes(); 12329 default: 12330 break; 12331 } 12332 if (IsSuppressedScrollableBlockForPrint()) { 12333 return kPhysicalAxesBoth; 12334 } 12335 } 12336 12337 if (aDisp->mOverflowX == StyleOverflow::Clip || 12338 aDisp->mOverflowY == StyleOverflow::Clip) { 12339 // FIXME: we could use GetViewportScrollStylesOverrideElement() here instead 12340 // if that worked correctly in a print context. (see bug 1654667) 12341 const auto* element = Element::FromNodeOrNull(GetContent()); 12342 if (!element || 12343 !PresContext()->ElementWouldPropagateScrollStyles(*element)) { 12344 PhysicalAxes axes; 12345 if (aDisp->mOverflowX == StyleOverflow::Clip) { 12346 axes += PhysicalAxis::Horizontal; 12347 } 12348 if (aDisp->mOverflowY == StyleOverflow::Clip) { 12349 axes += PhysicalAxis::Vertical; 12350 } 12351 return axes; 12352 } 12353 } 12354 12355 return PhysicalAxes(); 12356 } 12357 12358 bool nsIFrame::IsSuppressedScrollableBlockForPrint() const { 12359 // This condition needs to match the suppressScrollFrame logic in the frame 12360 // constructor. 12361 if (!PresContext()->IsPaginated() || !IsBlockFrame() || 12362 !StyleDisplay()->IsScrollableOverflow() || 12363 !StyleDisplay()->IsBlockOutsideStyle() || 12364 mContent->IsInNativeAnonymousSubtree()) { 12365 return false; 12366 } 12367 if (auto* element = Element::FromNode(mContent); 12368 element && PresContext()->ElementWouldPropagateScrollStyles(*element)) { 12369 return false; 12370 } 12371 return true; 12372 } 12373 12374 PhysicalAxes nsIFrame::GetAnchorPosCompensatingForScroll() const { 12375 if (!HasAnchorPosReference()) { 12376 return {}; 12377 } 12378 const auto* prop = GetProperty(AnchorPosReferences()); 12379 if (!prop) { 12380 return {}; 12381 } 12382 12383 return prop->CompensatingForScrollAxes(); 12384 } 12385 12386 bool nsIFrame::HasUnreflowedContainerQueryAncestor() const { 12387 // If this frame has done the first reflow, its ancestors are guaranteed to 12388 // have as well. 12389 if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || 12390 !PresContext()->HasContainerQueryFrames()) { 12391 return false; 12392 } 12393 for (nsIFrame* cur = GetInFlowParent(); cur; cur = cur->GetInFlowParent()) { 12394 if (!cur->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 12395 // Done first reflow from this ancestor up, including query containers. 12396 return false; 12397 } 12398 if (cur->StyleDisplay()->IsQueryContainer()) { 12399 return true; 12400 } 12401 } 12402 // No query container from this frame up to root. 12403 return false; 12404 } 12405 12406 bool nsIFrame::ShouldBreakBefore( 12407 const ReflowInput::BreakType aBreakType) const { 12408 const auto* display = StyleDisplay(); 12409 return ShouldBreakBetween(display, display->mBreakBefore, aBreakType); 12410 } 12411 12412 bool nsIFrame::ShouldBreakAfter(const ReflowInput::BreakType aBreakType) const { 12413 const auto* display = StyleDisplay(); 12414 return ShouldBreakBetween(display, display->mBreakAfter, aBreakType); 12415 } 12416 12417 bool nsIFrame::ShouldBreakBetween( 12418 const nsStyleDisplay* aDisplay, const StyleBreakBetween aBreakBetween, 12419 const ReflowInput::BreakType aBreakType) const { 12420 const bool shouldBreakBetween = [&] { 12421 switch (aBreakBetween) { 12422 case StyleBreakBetween::Always: 12423 return true; 12424 case StyleBreakBetween::Auto: 12425 case StyleBreakBetween::Avoid: 12426 return false; 12427 case StyleBreakBetween::Page: 12428 case StyleBreakBetween::Left: 12429 case StyleBreakBetween::Right: 12430 return aBreakType == ReflowInput::BreakType::Page; 12431 } 12432 MOZ_ASSERT_UNREACHABLE("Unknown break-between value!"); 12433 return false; 12434 }(); 12435 12436 if (!shouldBreakBetween) { 12437 return false; 12438 } 12439 if (IsAbsolutelyPositioned(aDisplay)) { 12440 // 'break-before' and 'break-after' properties does not apply to 12441 // absolutely-positioned boxes. 12442 return false; 12443 } 12444 return true; 12445 } 12446 12447 #ifdef DEBUG 12448 static void GetTagName(nsIFrame* aFrame, nsIContent* aContent, int aResultSize, 12449 char* aResult) { 12450 if (aContent) { 12451 snprintf(aResult, aResultSize, "%s@%p", 12452 nsAtomCString(aContent->NodeInfo()->NameAtom()).get(), aFrame); 12453 } else { 12454 snprintf(aResult, aResultSize, "@%p", aFrame); 12455 } 12456 } 12457 12458 void nsIFrame::Trace(const char* aMethod, bool aEnter) { 12459 if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) { 12460 char tagbuf[40]; 12461 GetTagName(this, mContent, sizeof(tagbuf), tagbuf); 12462 printf_stderr("%s: %s %s", tagbuf, aEnter ? "enter" : "exit", aMethod); 12463 } 12464 } 12465 12466 void nsIFrame::Trace(const char* aMethod, bool aEnter, 12467 const nsReflowStatus& aStatus) { 12468 if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) { 12469 char tagbuf[40]; 12470 GetTagName(this, mContent, sizeof(tagbuf), tagbuf); 12471 printf_stderr("%s: %s %s, status=%scomplete%s", tagbuf, 12472 aEnter ? "enter" : "exit", aMethod, 12473 aStatus.IsIncomplete() ? "not" : "", 12474 (aStatus.NextInFlowNeedsReflow()) ? "+reflow" : ""); 12475 } 12476 } 12477 12478 void nsIFrame::TraceMsg(const char* aFormatString, ...) { 12479 if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) { 12480 // Format arguments into a buffer 12481 char argbuf[200]; 12482 va_list ap; 12483 va_start(ap, aFormatString); 12484 VsprintfLiteral(argbuf, aFormatString, ap); 12485 va_end(ap); 12486 12487 char tagbuf[40]; 12488 GetTagName(this, mContent, sizeof(tagbuf), tagbuf); 12489 printf_stderr("%s: %s", tagbuf, argbuf); 12490 } 12491 } 12492 12493 void nsIFrame::VerifyDirtyBitSet(const nsFrameList& aFrameList) { 12494 for (nsIFrame* f : aFrameList) { 12495 NS_ASSERTION(f->HasAnyStateBits(NS_FRAME_IS_DIRTY), "dirty bit not set"); 12496 } 12497 } 12498 12499 // Validation of SideIsVertical. 12500 # define CASE(side, result) \ 12501 static_assert(SideIsVertical(side) == result, "SideIsVertical is wrong") 12502 CASE(eSideTop, false); 12503 CASE(eSideRight, true); 12504 CASE(eSideBottom, false); 12505 CASE(eSideLeft, true); 12506 # undef CASE 12507 12508 // Validation of HalfCornerIsX. 12509 # define CASE(corner, result) \ 12510 static_assert(HalfCornerIsX(corner) == result, "HalfCornerIsX is wrong") 12511 CASE(eCornerTopLeftX, true); 12512 CASE(eCornerTopLeftY, false); 12513 CASE(eCornerTopRightX, true); 12514 CASE(eCornerTopRightY, false); 12515 CASE(eCornerBottomRightX, true); 12516 CASE(eCornerBottomRightY, false); 12517 CASE(eCornerBottomLeftX, true); 12518 CASE(eCornerBottomLeftY, false); 12519 # undef CASE 12520 12521 // Validation of HalfToFullCorner. 12522 # define CASE(corner, result) \ 12523 static_assert(HalfToFullCorner(corner) == result, \ 12524 "HalfToFullCorner is " \ 12525 "wrong") 12526 CASE(eCornerTopLeftX, eCornerTopLeft); 12527 CASE(eCornerTopLeftY, eCornerTopLeft); 12528 CASE(eCornerTopRightX, eCornerTopRight); 12529 CASE(eCornerTopRightY, eCornerTopRight); 12530 CASE(eCornerBottomRightX, eCornerBottomRight); 12531 CASE(eCornerBottomRightY, eCornerBottomRight); 12532 CASE(eCornerBottomLeftX, eCornerBottomLeft); 12533 CASE(eCornerBottomLeftY, eCornerBottomLeft); 12534 # undef CASE 12535 12536 // Validation of FullToHalfCorner. 12537 # define CASE(corner, vert, result) \ 12538 static_assert(FullToHalfCorner(corner, vert) == result, \ 12539 "FullToHalfCorner is wrong") 12540 CASE(eCornerTopLeft, false, eCornerTopLeftX); 12541 CASE(eCornerTopLeft, true, eCornerTopLeftY); 12542 CASE(eCornerTopRight, false, eCornerTopRightX); 12543 CASE(eCornerTopRight, true, eCornerTopRightY); 12544 CASE(eCornerBottomRight, false, eCornerBottomRightX); 12545 CASE(eCornerBottomRight, true, eCornerBottomRightY); 12546 CASE(eCornerBottomLeft, false, eCornerBottomLeftX); 12547 CASE(eCornerBottomLeft, true, eCornerBottomLeftY); 12548 # undef CASE 12549 12550 // Validation of SideToFullCorner. 12551 # define CASE(side, second, result) \ 12552 static_assert(SideToFullCorner(side, second) == result, \ 12553 "SideToFullCorner is wrong") 12554 CASE(eSideTop, false, eCornerTopLeft); 12555 CASE(eSideTop, true, eCornerTopRight); 12556 12557 CASE(eSideRight, false, eCornerTopRight); 12558 CASE(eSideRight, true, eCornerBottomRight); 12559 12560 CASE(eSideBottom, false, eCornerBottomRight); 12561 CASE(eSideBottom, true, eCornerBottomLeft); 12562 12563 CASE(eSideLeft, false, eCornerBottomLeft); 12564 CASE(eSideLeft, true, eCornerTopLeft); 12565 # undef CASE 12566 12567 // Validation of SideToHalfCorner. 12568 # define CASE(side, second, parallel, result) \ 12569 static_assert(SideToHalfCorner(side, second, parallel) == result, \ 12570 "SideToHalfCorner is wrong") 12571 CASE(eSideTop, false, true, eCornerTopLeftX); 12572 CASE(eSideTop, false, false, eCornerTopLeftY); 12573 CASE(eSideTop, true, true, eCornerTopRightX); 12574 CASE(eSideTop, true, false, eCornerTopRightY); 12575 12576 CASE(eSideRight, false, false, eCornerTopRightX); 12577 CASE(eSideRight, false, true, eCornerTopRightY); 12578 CASE(eSideRight, true, false, eCornerBottomRightX); 12579 CASE(eSideRight, true, true, eCornerBottomRightY); 12580 12581 CASE(eSideBottom, false, true, eCornerBottomRightX); 12582 CASE(eSideBottom, false, false, eCornerBottomRightY); 12583 CASE(eSideBottom, true, true, eCornerBottomLeftX); 12584 CASE(eSideBottom, true, false, eCornerBottomLeftY); 12585 12586 CASE(eSideLeft, false, false, eCornerBottomLeftX); 12587 CASE(eSideLeft, false, true, eCornerBottomLeftY); 12588 CASE(eSideLeft, true, false, eCornerTopLeftX); 12589 CASE(eSideLeft, true, true, eCornerTopLeftY); 12590 # undef CASE 12591 12592 #endif