BRFrame.cpp (11367B)
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 /* rendering object for HTML <br> elements */ 8 9 #include "gfxContext.h" 10 #include "mozilla/CaretAssociationHint.h" 11 #include "mozilla/PresShell.h" 12 #include "mozilla/TextControlElement.h" 13 #include "mozilla/dom/HTMLBRElement.h" 14 #include "nsBlockFrame.h" 15 #include "nsCOMPtr.h" 16 #include "nsComputedDOMStyle.h" 17 #include "nsContainerFrame.h" 18 #include "nsFontMetrics.h" 19 #include "nsGkAtoms.h" 20 #include "nsHTMLParts.h" 21 #include "nsIFrame.h" 22 #include "nsLayoutUtils.h" 23 #include "nsLineLayout.h" 24 #include "nsPresContext.h" 25 #include "nsStyleConsts.h" 26 #include "nsTextFrame.h" 27 28 // FOR SELECTION 29 #include "nsIContent.h" 30 // END INCLUDES FOR SELECTION 31 32 using namespace mozilla; 33 34 namespace mozilla { 35 36 class BRFrame final : public nsIFrame { 37 public: 38 NS_DECL_FRAMEARENA_HELPERS(BRFrame) 39 40 friend nsIFrame* ::NS_NewBRFrame(mozilla::PresShell* aPresShell, 41 ComputedStyle* aStyle); 42 43 ContentOffsets CalcContentOffsetsFromFramePoint( 44 const nsPoint& aPoint) override; 45 46 FrameSearchResult PeekOffsetNoAmount(bool aForward, 47 int32_t* aOffset) override; 48 FrameSearchResult PeekOffsetCharacter( 49 bool aForward, int32_t* aOffset, 50 PeekOffsetCharacterOptions aOptions = 51 PeekOffsetCharacterOptions()) override; 52 FrameSearchResult PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, 53 bool aIsKeyboardSelect, int32_t* aOffset, 54 PeekWordState* aState, 55 bool aTrimSpaces) override; 56 57 void Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, 58 const ReflowInput& aReflowInput, 59 nsReflowStatus& aStatus) override; 60 void AddInlineMinISize(const IntrinsicSizeInput& aInput, 61 InlineMinISizeData* aData) override; 62 void AddInlinePrefISize(const IntrinsicSizeInput& aInput, 63 InlinePrefISizeData* aData) override; 64 65 Maybe<nscoord> GetNaturalBaselineBOffset( 66 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 67 BaselineExportContext) const override; 68 69 #ifdef ACCESSIBILITY 70 mozilla::a11y::AccType AccessibleType() override; 71 #endif 72 73 #ifdef DEBUG_FRAME_DUMP 74 nsresult GetFrameName(nsAString& aResult) const override { 75 return MakeFrameName(u"BR"_ns, aResult); 76 } 77 #endif 78 79 protected: 80 BRFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) 81 : nsIFrame(aStyle, aPresContext, kClassID), 82 mAscent(NS_INTRINSIC_ISIZE_UNKNOWN) {} 83 84 virtual ~BRFrame(); 85 86 nscoord mAscent; 87 }; 88 89 } // namespace mozilla 90 91 nsIFrame* NS_NewBRFrame(mozilla::PresShell* aPresShell, ComputedStyle* aStyle) { 92 return new (aPresShell) BRFrame(aStyle, aPresShell->GetPresContext()); 93 } 94 95 NS_IMPL_FRAMEARENA_HELPERS(BRFrame) 96 97 BRFrame::~BRFrame() = default; 98 99 void BRFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, 100 const ReflowInput& aReflowInput, nsReflowStatus& aStatus) { 101 MarkInReflow(); 102 DO_GLOBAL_REFLOW_COUNT("BRFrame"); 103 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 104 105 WritingMode wm = aReflowInput.GetWritingMode(); 106 LogicalSize finalSize(wm); 107 finalSize.BSize(wm) = 0; // BR frames with block size 0 are ignored in quirks 108 // mode by nsLineLayout::VerticalAlignFrames . 109 // However, it's not always 0. See below. 110 finalSize.ISize(wm) = 0; 111 aMetrics.SetBlockStartAscent(0); 112 113 // Only when the BR is operating in a line-layout situation will it 114 // behave like a BR. Additionally, we suppress breaks from BR inside 115 // of ruby frames. To determine if we're inside ruby, we have to rely 116 // on the *parent's* ShouldSuppressLineBreak() method, instead of our 117 // own, because we may have custom "display" value that makes our 118 // ShouldSuppressLineBreak() return false. 119 nsLineLayout* ll = aReflowInput.mLineLayout; 120 if (ll && !GetParent()->Style()->ShouldSuppressLineBreak()) { 121 // Note that the compatibility mode check excludes AlmostStandards 122 // mode, since this is the inline box model. See bug 161691. 123 if (ll->LineIsEmpty() || 124 aPresContext->CompatibilityMode() == eCompatibility_FullStandards) { 125 // The line is logically empty; any whitespace is trimmed away. 126 // 127 // If this frame is going to terminate the line we know 128 // that nothing else will go on the line. Therefore, in this 129 // case, we provide some height for the BR frame so that it 130 // creates some vertical whitespace. It's necessary to use the 131 // line-height rather than the font size because the 132 // quirks-mode fix that doesn't apply the block's min 133 // line-height makes this necessary to make BR cause a line 134 // of the full line-height 135 136 // We also do this in strict mode because BR should act like a 137 // normal inline frame. That line-height is used is important 138 // here for cases where the line-height is less than 1. 139 RefPtr<nsFontMetrics> fm = 140 nsLayoutUtils::GetInflatedFontMetricsForFrame(this); 141 if (fm) { 142 nscoord logicalHeight = aReflowInput.GetLineHeight(); 143 finalSize.BSize(wm) = logicalHeight; 144 aMetrics.SetBlockStartAscent(nsLayoutUtils::GetCenteredFontBaseline( 145 fm, logicalHeight, wm.IsLineInverted())); 146 } else { 147 aMetrics.SetBlockStartAscent(aMetrics.BSize(wm) = 0); 148 } 149 } 150 151 // Return our reflow status 152 aStatus.SetInlineLineBreakAfter( 153 aReflowInput.mStyleDisplay->UsedClear(aReflowInput.GetCBWritingMode())); 154 ll->SetLineEndsInBR(true); 155 } 156 157 aMetrics.SetSize(wm, finalSize); 158 aMetrics.SetOverflowAreasToDesiredBounds(); 159 160 mAscent = aMetrics.BlockStartAscent(); 161 } 162 163 /* virtual */ 164 void BRFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput, 165 InlineMinISizeData* aData) { 166 if (!GetParent()->Style()->ShouldSuppressLineBreak()) { 167 aData->ForceBreak(); 168 } 169 } 170 171 /* virtual */ 172 void BRFrame::AddInlinePrefISize(const IntrinsicSizeInput& aInput, 173 InlinePrefISizeData* aData) { 174 if (!GetParent()->Style()->ShouldSuppressLineBreak()) { 175 aData->ForceBreak(); 176 } 177 } 178 179 Maybe<nscoord> BRFrame::GetNaturalBaselineBOffset( 180 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 181 BaselineExportContext) const { 182 if (aBaselineGroup == BaselineSharingGroup::Last) { 183 return Nothing{}; 184 } 185 return Some(mAscent); 186 } 187 188 nsIFrame::ContentOffsets BRFrame::CalcContentOffsetsFromFramePoint( 189 const nsPoint& aPoint) { 190 ContentOffsets offsets; 191 offsets.content = mContent->GetParent(); 192 if (offsets.content) { 193 offsets.offset = offsets.content->ComputeIndexOf_Deprecated(mContent); 194 offsets.secondaryOffset = offsets.offset; 195 offsets.associate = CaretAssociationHint::After; 196 } 197 return offsets; 198 } 199 200 nsIFrame::FrameSearchResult BRFrame::PeekOffsetNoAmount(bool aForward, 201 int32_t* aOffset) { 202 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range"); 203 int32_t startOffset = *aOffset; 204 // If we hit the end of a BR going backwards, go to its beginning and stay 205 // there. 206 if (!aForward && startOffset != 0) { 207 *aOffset = 0; 208 return FOUND; 209 } 210 // Otherwise, stop if we hit the beginning, continue (forward) if we hit the 211 // end. 212 return (startOffset == 0) ? FOUND : CONTINUE; 213 } 214 215 nsIFrame::FrameSearchResult BRFrame::PeekOffsetCharacter( 216 bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) { 217 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range"); 218 // Keep going. The actual line jumping will stop us. 219 return CONTINUE; 220 } 221 222 nsIFrame::FrameSearchResult BRFrame::PeekOffsetWord( 223 bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect, 224 int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) { 225 NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range"); 226 // Keep going. The actual line jumping will stop us. 227 return CONTINUE; 228 } 229 230 #ifdef ACCESSIBILITY 231 a11y::AccType BRFrame::AccessibleType() { 232 dom::HTMLBRElement* brElement = dom::HTMLBRElement::FromNode(mContent); 233 234 if (!brElement->IsPaddingForEmptyLastLine()) { 235 // Even if this <br> is a "padding <br> element" used when there is no text 236 // in an editor, it may be surrounded by before/after pseudo element 237 // content. Therefore, we need to treat it as a normal <br>. 238 return a11y::eHTMLBRType; 239 } 240 241 // If it's a padding <br> element used in the anonymous subtree of <textarea>, 242 // we don't need to expose it as a line break because of in an replaced 243 // content. 244 if (brElement->IsInNativeAnonymousSubtree()) { 245 const auto* textControlElement = TextControlElement::FromNodeOrNull( 246 brElement->GetClosestNativeAnonymousSubtreeRootParentOrHost()); 247 if (textControlElement && 248 textControlElement->IsSingleLineTextControlOrTextArea()) { 249 return a11y::eNoType; 250 } 251 } 252 253 // If this <br> is a "padding <br> element" used when there is an empty last 254 // line before a block boundary in an HTML editor, this is required only for 255 // the empty last line visible in the CSS layout world. Therefore, this is 256 // meaningless so that this should not appear in the flattened text. On the 257 // other hand, if this is a padding <br> element used when there is no 258 // visible things in the parent block in an editor, this is required to give 259 // one-line height to the block. So, basically, this is meaningless, but 260 // this may be surrounded by before/after pseudo content. Then, they appear 261 // in different lines because of this line break. So, this is not meaningless 262 // in such case. For now, we should treat this is meaningless only in the 263 // former case. We can assume that if this is a padding <br>, it directly 264 // follows a block boundary because our editor does not keep empty nodes at 265 // least intentionally. 266 // XXX This does not treat complicated layout cases. However, our editor 267 // must not work well with such layout. So, this should be okay for the 268 // web apps in the wild. 269 nsIFrame* const parentFrame = GetParent(); 270 if (HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) || !parentFrame) { 271 return a11y::eHTMLBRType; 272 } 273 nsIFrame* const currentBlock = 274 nsBlockFrame::GetNearestAncestorBlock(parentFrame); 275 nsIContent* const currentBlockContent = 276 currentBlock ? currentBlock->GetContent() : nullptr; 277 for (nsIContent* previousContent = 278 brElement->GetPrevNode(currentBlockContent); 279 previousContent; 280 previousContent = previousContent->GetPrevNode(currentBlockContent)) { 281 nsIFrame* const precedingContentFrame = previousContent->GetPrimaryFrame(); 282 if (!precedingContentFrame || precedingContentFrame->IsEmpty()) { 283 continue; 284 } 285 if (precedingContentFrame->IsBlockFrameOrSubclass()) { 286 break; // Reached a child block. 287 } 288 // FIXME: Oh, this should be a11y::eNoType because it's a meaningless <br>. 289 return a11y::eHTMLBRType; 290 } 291 return a11y::eHTMLBRType; 292 } 293 294 #endif