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/. */
7 /* Per-block-formatting-context manager of font size inflation for pan and zoom
10 #include "nsFontInflationData.h"
11 #include "FrameProperties.h"
12 #include "nsTextControlFrame.h"
13 #include "nsListControlFrame.h"
14 #include "nsComboboxControlFrame.h"
15 #include "mozilla/dom/Text.h" // for inline nsINode::AsText() definition
16 #include "mozilla/PresShell.h"
17 #include "mozilla/ReflowInput.h"
18 #include "nsTextFrameUtils.h"
20 using namespace mozilla
;
21 using namespace mozilla::layout
;
23 NS_DECLARE_FRAME_PROPERTY_DELETABLE(FontInflationDataProperty
,
26 /* static */ nsFontInflationData
* nsFontInflationData::FindFontInflationDataFor(
27 const nsIFrame
* aFrame
) {
28 // We have one set of font inflation data per block formatting context.
29 const nsIFrame
* bfc
= FlowRootFor(aFrame
);
30 NS_ASSERTION(bfc
->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT
),
31 "should have found a flow root");
32 MOZ_ASSERT(aFrame
->GetWritingMode().IsVertical() ==
33 bfc
->GetWritingMode().IsVertical(),
34 "current writing mode should match that of our flow root");
36 return bfc
->GetProperty(FontInflationDataProperty());
40 bool nsFontInflationData::UpdateFontInflationDataISizeFor(
41 const ReflowInput
& aReflowInput
) {
42 nsIFrame
* bfc
= aReflowInput
.mFrame
;
43 NS_ASSERTION(bfc
->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT
),
44 "should have been given a flow root");
45 nsFontInflationData
* data
= bfc
->GetProperty(FontInflationDataProperty());
46 bool oldInflationEnabled
;
47 nscoord oldUsableISize
;
49 oldUsableISize
= data
->mUsableISize
;
50 oldInflationEnabled
= data
->mInflationEnabled
;
52 data
= new nsFontInflationData(bfc
);
53 bfc
->SetProperty(FontInflationDataProperty(), data
);
55 oldInflationEnabled
= true; /* not relevant */
58 data
->UpdateISize(aReflowInput
);
60 if (oldInflationEnabled
!= data
->mInflationEnabled
) return true;
62 return oldInflationEnabled
&& oldUsableISize
!= data
->mUsableISize
;
66 void nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame
* aBFCFrame
) {
67 NS_ASSERTION(aBFCFrame
->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT
),
68 "should have been given a flow root");
70 nsFontInflationData
* data
=
71 aBFCFrame
->GetProperty(FontInflationDataProperty());
73 data
->MarkTextDirty();
77 nsFontInflationData::nsFontInflationData(nsIFrame
* aBFCFrame
)
78 : mBFCFrame(aBFCFrame
),
82 mInflationEnabled(false),
86 * Find the closest common ancestor between aFrame1 and aFrame2, except
87 * treating the parent of a frame as the first-in-flow of its parent (so
88 * the result doesn't change when breaking changes).
90 * aKnownCommonAncestor is a known common ancestor of both.
92 static nsIFrame
* NearestCommonAncestorFirstInFlow(
93 nsIFrame
* aFrame1
, nsIFrame
* aFrame2
, nsIFrame
* aKnownCommonAncestor
) {
94 aFrame1
= aFrame1
->FirstInFlow();
95 aFrame2
= aFrame2
->FirstInFlow();
96 aKnownCommonAncestor
= aKnownCommonAncestor
->FirstInFlow();
98 AutoTArray
<nsIFrame
*, 32> ancestors1
, ancestors2
;
99 for (nsIFrame
* f
= aFrame1
; f
!= aKnownCommonAncestor
;
100 (f
= f
->GetParent()) && (f
= f
->FirstInFlow())) {
101 ancestors1
.AppendElement(f
);
103 for (nsIFrame
* f
= aFrame2
; f
!= aKnownCommonAncestor
;
104 (f
= f
->GetParent()) && (f
= f
->FirstInFlow())) {
105 ancestors2
.AppendElement(f
);
108 nsIFrame
* result
= aKnownCommonAncestor
;
109 uint32_t i1
= ancestors1
.Length(), i2
= ancestors2
.Length();
110 while (i1
-- != 0 && i2
-- != 0) {
111 if (ancestors1
[i1
] != ancestors2
[i2
]) {
114 result
= ancestors1
[i1
];
120 static nscoord
ComputeDescendantISize(const ReflowInput
& aAncestorReflowInput
,
121 nsIFrame
* aDescendantFrame
) {
122 nsIFrame
* ancestorFrame
= aAncestorReflowInput
.mFrame
->FirstInFlow();
123 if (aDescendantFrame
== ancestorFrame
) {
124 return aAncestorReflowInput
.ComputedISize();
127 AutoTArray
<nsIFrame
*, 16> frames
;
128 for (nsIFrame
* f
= aDescendantFrame
; f
!= ancestorFrame
;
129 f
= f
->GetParent()->FirstInFlow()) {
130 frames
.AppendElement(f
);
133 // This ignores the inline-size contributions made by scrollbars, though in
134 // reality we don't have any scrollbars on the sorts of devices on
135 // which we use font inflation, so it's not a problem. But it may
136 // occasionally cause problems when writing tests on desktop.
138 uint32_t len
= frames
.Length();
139 ReflowInput
* reflowInputs
=
140 static_cast<ReflowInput
*>(moz_xmalloc(sizeof(ReflowInput
) * len
));
141 nsPresContext
* presContext
= aDescendantFrame
->PresContext();
142 for (uint32_t i
= 0; i
< len
; ++i
) {
143 const ReflowInput
& parentReflowInput
=
144 (i
== 0) ? aAncestorReflowInput
: reflowInputs
[i
- 1];
145 nsIFrame
* frame
= frames
[len
- i
- 1];
146 WritingMode wm
= frame
->GetWritingMode();
147 LogicalSize availSize
= parentReflowInput
.ComputedSize(wm
);
148 availSize
.BSize(wm
) = NS_UNCONSTRAINEDSIZE
;
149 MOZ_ASSERT(frame
->GetParent()->FirstInFlow() ==
150 parentReflowInput
.mFrame
->FirstInFlow(),
151 "bad logic in this function");
152 new (reflowInputs
+ i
)
153 ReflowInput(presContext
, parentReflowInput
, frame
, availSize
);
156 MOZ_ASSERT(reflowInputs
[len
- 1].mFrame
== aDescendantFrame
,
157 "bad logic in this function");
158 nscoord result
= reflowInputs
[len
- 1].ComputedISize();
160 for (uint32_t i
= len
; i
-- != 0;) {
161 reflowInputs
[i
].~ReflowInput();
168 void nsFontInflationData::UpdateISize(const ReflowInput
& aReflowInput
) {
169 nsIFrame
* bfc
= aReflowInput
.mFrame
;
170 NS_ASSERTION(bfc
->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT
),
171 "must be block formatting context");
173 nsIFrame
* firstInflatableDescendant
=
174 FindEdgeInflatableFrameIn(bfc
, eFromStart
);
175 if (!firstInflatableDescendant
) {
177 mTextThreshold
= 0; // doesn't matter
179 mInflationEnabled
= false;
182 nsIFrame
* lastInflatableDescendant
= FindEdgeInflatableFrameIn(bfc
, eFromEnd
);
183 MOZ_ASSERT(!firstInflatableDescendant
== !lastInflatableDescendant
,
184 "null-ness should match; NearestCommonAncestorFirstInFlow"
185 " will crash when passed null");
187 // Particularly when we're computing for the root BFC, the inline-size of
188 // nca might differ significantly for the inline-size of bfc.
189 nsIFrame
* nca
= NearestCommonAncestorFirstInFlow(
190 firstInflatableDescendant
, lastInflatableDescendant
, bfc
);
191 while (!nca
->IsContainerForFontSizeInflation()) {
192 nca
= nca
->GetParent()->FirstInFlow();
195 nscoord newNCAISize
= ComputeDescendantISize(aReflowInput
, nca
);
197 // See comment above "font.size.inflation.lineThreshold" in
198 // modules/libpref/src/init/StaticPrefList.yaml .
199 PresShell
* presShell
= bfc
->PresShell();
200 uint32_t lineThreshold
= presShell
->FontSizeInflationLineThreshold();
201 nscoord newTextThreshold
= (newNCAISize
* lineThreshold
) / 100;
203 if (mTextThreshold
<= mTextAmount
&& mTextAmount
< newTextThreshold
) {
204 // Because we truncate our scan when we hit sufficient text, we now
209 // Font inflation increases the font size for a given flow root so that the
210 // text is legible when we've zoomed such that the respective nearest common
211 // ancestor's (NCA) full inline-size (ISize) fills the screen. We assume how-
212 // ever that we don't want to zoom out further than the root iframe's ISize
213 // (i.e. the viewport for a top-level document, or the containing iframe
214 // otherwise), since in some cases zooming out further might not even be
215 // possible or make sense.
216 // Hence the ISize assumed to be usable for displaying text is limited to the
218 nsPresContext
* presContext
= bfc
->PresContext();
220 bfc
->GetWritingMode().IsVertical() == nca
->GetWritingMode().IsVertical(),
221 "writing mode of NCA should match that of its flow root");
222 nscoord iFrameISize
= bfc
->GetWritingMode().IsVertical()
223 ? presContext
->GetVisibleArea().height
224 : presContext
->GetVisibleArea().width
;
225 mUsableISize
= std::min(iFrameISize
, newNCAISize
);
226 mTextThreshold
= newTextThreshold
;
227 mInflationEnabled
= mTextAmount
>= mTextThreshold
;
230 /* static */ nsIFrame
* nsFontInflationData::FindEdgeInflatableFrameIn(
231 nsIFrame
* aFrame
, SearchDirection aDirection
) {
232 // NOTE: This function has a similar structure to ScanTextIn!
234 // FIXME: Should probably only scan the text that's actually going to
237 nsIFormControlFrame
* fcf
= do_QueryFrame(aFrame
);
242 // FIXME: aDirection!
243 AutoTArray
<FrameChildList
, 4> lists
;
244 aFrame
->GetChildLists(&lists
);
245 for (uint32_t i
= 0, len
= lists
.Length(); i
< len
; ++i
) {
246 const nsFrameList
& list
=
247 lists
[(aDirection
== eFromStart
) ? i
: len
- i
- 1].mList
;
248 for (nsIFrame
* kid
= (aDirection
== eFromStart
) ? list
.FirstChild()
250 kid
; kid
= (aDirection
== eFromStart
) ? kid
->GetNextSibling()
251 : kid
->GetPrevSibling()) {
252 if (kid
->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT
)) {
253 // Goes in a different set of inflation data.
257 if (kid
->IsTextFrame()) {
258 nsIContent
* content
= kid
->GetContent();
259 if (content
&& kid
== content
->GetPrimaryFrame()) {
260 uint32_t len
= nsTextFrameUtils::
261 ComputeApproximateLengthWithWhitespaceCompression(
262 content
->AsText(), kid
->StyleText());
268 nsIFrame
* kidResult
= FindEdgeInflatableFrameIn(kid
, aDirection
);
279 void nsFontInflationData::ScanText() {
282 ScanTextIn(mBFCFrame
);
283 mInflationEnabled
= mTextAmount
>= mTextThreshold
;
286 static uint32_t DoCharCountOfLargestOption(nsIFrame
* aContainer
) {
288 for (nsIFrame
* option
: aContainer
->PrincipalChildList()) {
289 uint32_t optionResult
;
290 if (option
->GetContent()->IsHTMLElement(nsGkAtoms::optgroup
)) {
291 optionResult
= DoCharCountOfLargestOption(option
);
293 // REVIEW: Check the frame structure for this!
295 for (nsIFrame
* optionChild
: option
->PrincipalChildList()) {
296 if (optionChild
->IsTextFrame()) {
297 optionResult
+= nsTextFrameUtils::
298 ComputeApproximateLengthWithWhitespaceCompression(
299 optionChild
->GetContent()->AsText(),
300 optionChild
->StyleText());
304 if (optionResult
> result
) {
305 result
= optionResult
;
311 static uint32_t CharCountOfLargestOption(nsIFrame
* aListControlFrame
) {
312 return DoCharCountOfLargestOption(
313 static_cast<nsListControlFrame
*>(aListControlFrame
)
314 ->GetOptionsContainer());
317 void nsFontInflationData::ScanTextIn(nsIFrame
* aFrame
) {
318 // NOTE: This function has a similar structure to FindEdgeInflatableFrameIn!
320 // FIXME: Should probably only scan the text that's actually going to
323 for (const auto& childList
: aFrame
->ChildLists()) {
324 for (nsIFrame
* kid
: childList
.mList
) {
325 if (kid
->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT
)) {
326 // Goes in a different set of inflation data.
330 LayoutFrameType fType
= kid
->Type();
331 if (fType
== LayoutFrameType::Text
) {
332 nsIContent
* content
= kid
->GetContent();
333 if (content
&& kid
== content
->GetPrimaryFrame()) {
334 uint32_t len
= nsTextFrameUtils::
335 ComputeApproximateLengthWithWhitespaceCompression(
336 content
->AsText(), kid
->StyleText());
338 nscoord fontSize
= kid
->StyleFont()->mFont
.size
.ToAppUnits();
340 mTextAmount
+= fontSize
* len
;
344 } else if (fType
== LayoutFrameType::TextInput
) {
345 // We don't want changes to the amount of text in a text input
346 // to change what we count towards inflation.
347 nscoord fontSize
= kid
->StyleFont()->mFont
.size
.ToAppUnits();
348 int32_t charCount
= static_cast<nsTextControlFrame
*>(kid
)->GetCols();
349 mTextAmount
+= charCount
* fontSize
;
350 } else if (fType
== LayoutFrameType::ComboboxControl
) {
351 // See textInputFrame above (with s/amount of text/selected option/).
352 // Don't just recurse down to the list control inside, since we
353 // need to exclude the display frame.
354 nscoord fontSize
= kid
->StyleFont()->mFont
.size
.ToAppUnits();
355 int32_t charCount
= static_cast<nsComboboxControlFrame
*>(kid
)
356 ->CharCountOfLargestOptionForInflation();
357 mTextAmount
+= charCount
* fontSize
;
358 } else if (fType
== LayoutFrameType::ListControl
) {
359 // See textInputFrame above (with s/amount of text/selected option/).
360 nscoord fontSize
= kid
->StyleFont()->mFont
.size
.ToAppUnits();
361 int32_t charCount
= CharCountOfLargestOption(kid
);
362 mTextAmount
+= charCount
* fontSize
;
368 if (mTextAmount
>= mTextThreshold
) {