1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /* Per-block-formatting-context manager of font size inflation for pan and zoom UI. */
8 #include "nsFontInflationData.h"
9 #include "FramePropertyTable.h"
10 #include "nsTextControlFrame.h"
11 #include "nsListControlFrame.h"
12 #include "nsComboboxControlFrame.h"
13 #include "nsHTMLReflowState.h"
14 #include "nsTextFrameUtils.h"
16 using namespace mozilla
;
17 using namespace mozilla::layout
;
20 DestroyFontInflationData(void *aPropertyValue
)
22 delete static_cast<nsFontInflationData
*>(aPropertyValue
);
25 NS_DECLARE_FRAME_PROPERTY(FontInflationDataProperty
, DestroyFontInflationData
)
27 /* static */ nsFontInflationData
*
28 nsFontInflationData::FindFontInflationDataFor(const nsIFrame
*aFrame
)
30 // We have one set of font inflation data per block formatting context.
31 const nsIFrame
*bfc
= FlowRootFor(aFrame
);
32 NS_ASSERTION(bfc
->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT
,
33 "should have found a flow root");
35 return static_cast<nsFontInflationData
*>(
36 bfc
->Properties().Get(FontInflationDataProperty()));
40 nsFontInflationData::UpdateFontInflationDataWidthFor(const nsHTMLReflowState
& aReflowState
)
42 nsIFrame
*bfc
= aReflowState
.frame
;
43 NS_ASSERTION(bfc
->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT
,
44 "should have been given a flow root");
45 FrameProperties
bfcProps(bfc
->Properties());
46 nsFontInflationData
*data
= static_cast<nsFontInflationData
*>(
47 bfcProps
.Get(FontInflationDataProperty()));
48 bool oldInflationEnabled
;
51 oldNCAWidth
= data
->mNCAWidth
;
52 oldInflationEnabled
= data
->mInflationEnabled
;
54 data
= new nsFontInflationData(bfc
);
55 bfcProps
.Set(FontInflationDataProperty(), data
);
57 oldInflationEnabled
= true; /* not relevant */
60 data
->UpdateWidth(aReflowState
);
62 if (oldInflationEnabled
!= data
->mInflationEnabled
)
65 return oldInflationEnabled
&&
66 oldNCAWidth
!= data
->mNCAWidth
;
70 nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame
*aBFCFrame
)
72 NS_ASSERTION(aBFCFrame
->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT
,
73 "should have been given a flow root");
75 FrameProperties
bfcProps(aBFCFrame
->Properties());
76 nsFontInflationData
*data
= static_cast<nsFontInflationData
*>(
77 bfcProps
.Get(FontInflationDataProperty()));
79 data
->MarkTextDirty();
83 nsFontInflationData::nsFontInflationData(nsIFrame
*aBFCFrame
)
84 : mBFCFrame(aBFCFrame
)
88 , mInflationEnabled(false)
94 * Find the closest common ancestor between aFrame1 and aFrame2, except
95 * treating the parent of a frame as the first-in-flow of its parent (so
96 * the result doesn't change when breaking changes).
98 * aKnownCommonAncestor is a known common ancestor of both.
101 NearestCommonAncestorFirstInFlow(nsIFrame
*aFrame1
, nsIFrame
*aFrame2
,
102 nsIFrame
*aKnownCommonAncestor
)
104 aFrame1
= aFrame1
->FirstInFlow();
105 aFrame2
= aFrame2
->FirstInFlow();
106 aKnownCommonAncestor
= aKnownCommonAncestor
->FirstInFlow();
108 nsAutoTArray
<nsIFrame
*, 32> ancestors1
, ancestors2
;
109 for (nsIFrame
*f
= aFrame1
; f
!= aKnownCommonAncestor
;
110 (f
= f
->GetParent()) && (f
= f
->FirstInFlow())) {
111 ancestors1
.AppendElement(f
);
113 for (nsIFrame
*f
= aFrame2
; f
!= aKnownCommonAncestor
;
114 (f
= f
->GetParent()) && (f
= f
->FirstInFlow())) {
115 ancestors2
.AppendElement(f
);
118 nsIFrame
*result
= aKnownCommonAncestor
;
119 uint32_t i1
= ancestors1
.Length(),
120 i2
= ancestors2
.Length();
121 while (i1
-- != 0 && i2
-- != 0) {
122 if (ancestors1
[i1
] != ancestors2
[i2
]) {
125 result
= ancestors1
[i1
];
132 ComputeDescendantWidth(const nsHTMLReflowState
& aAncestorReflowState
,
133 nsIFrame
*aDescendantFrame
)
135 nsIFrame
*ancestorFrame
= aAncestorReflowState
.frame
->FirstInFlow();
136 if (aDescendantFrame
== ancestorFrame
) {
137 return aAncestorReflowState
.ComputedWidth();
140 AutoInfallibleTArray
<nsIFrame
*, 16> frames
;
141 for (nsIFrame
*f
= aDescendantFrame
; f
!= ancestorFrame
;
142 f
= f
->GetParent()->FirstInFlow()) {
143 frames
.AppendElement(f
);
146 // This ignores the width contributions made by scrollbars, though in
147 // reality we don't have any scrollbars on the sorts of devices on
148 // which we use font inflation, so it's not a problem. But it may
149 // occasionally cause problems when writing tests on desktop.
151 uint32_t len
= frames
.Length();
152 nsHTMLReflowState
*reflowStates
= static_cast<nsHTMLReflowState
*>
153 (moz_xmalloc(sizeof(nsHTMLReflowState
) * len
));
154 nsPresContext
*presContext
= aDescendantFrame
->PresContext();
155 for (uint32_t i
= 0; i
< len
; ++i
) {
156 const nsHTMLReflowState
&parentReflowState
=
157 (i
== 0) ? aAncestorReflowState
: reflowStates
[i
- 1];
158 nsIFrame
*frame
= frames
[len
- i
- 1];
159 WritingMode wm
= frame
->GetWritingMode();
160 LogicalSize availSize
= parentReflowState
.ComputedSize(wm
);
161 availSize
.BSize(wm
) = NS_UNCONSTRAINEDSIZE
;
162 NS_ABORT_IF_FALSE(frame
->GetParent()->FirstInFlow() ==
163 parentReflowState
.frame
->FirstInFlow(),
164 "bad logic in this function");
165 new (reflowStates
+ i
) nsHTMLReflowState(presContext
, parentReflowState
,
169 NS_ABORT_IF_FALSE(reflowStates
[len
- 1].frame
== aDescendantFrame
,
170 "bad logic in this function");
171 nscoord result
= reflowStates
[len
- 1].ComputedWidth();
173 for (uint32_t i
= len
; i
-- != 0; ) {
174 reflowStates
[i
].~nsHTMLReflowState();
176 moz_free(reflowStates
);
182 nsFontInflationData::UpdateWidth(const nsHTMLReflowState
&aReflowState
)
184 nsIFrame
*bfc
= aReflowState
.frame
;
185 NS_ASSERTION(bfc
->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT
,
186 "must be block formatting context");
188 nsIFrame
*firstInflatableDescendant
=
189 FindEdgeInflatableFrameIn(bfc
, eFromStart
);
190 if (!firstInflatableDescendant
) {
192 mTextThreshold
= 0; // doesn't matter
194 mInflationEnabled
= false;
197 nsIFrame
*lastInflatableDescendant
=
198 FindEdgeInflatableFrameIn(bfc
, eFromEnd
);
199 NS_ABORT_IF_FALSE(!firstInflatableDescendant
== !lastInflatableDescendant
,
200 "null-ness should match; NearestCommonAncestorFirstInFlow"
201 " will crash when passed null");
203 // Particularly when we're computing for the root BFC, the width of
204 // nca might differ significantly for the width of bfc.
205 nsIFrame
*nca
= NearestCommonAncestorFirstInFlow(firstInflatableDescendant
,
206 lastInflatableDescendant
,
208 while (!nca
->IsContainerForFontSizeInflation()) {
209 nca
= nca
->GetParent()->FirstInFlow();
212 nscoord newNCAWidth
= ComputeDescendantWidth(aReflowState
, nca
);
214 // See comment above "font.size.inflation.lineThreshold" in
215 // modules/libpref/src/init/all.js .
216 nsIPresShell
* presShell
= bfc
->PresContext()->PresShell();
217 uint32_t lineThreshold
= presShell
->FontSizeInflationLineThreshold();
218 nscoord newTextThreshold
= (newNCAWidth
* lineThreshold
) / 100;
220 if (mTextThreshold
<= mTextAmount
&& mTextAmount
< newTextThreshold
) {
221 // Because we truncate our scan when we hit sufficient text, we now
226 mNCAWidth
= newNCAWidth
;
227 mTextThreshold
= newTextThreshold
;
228 mInflationEnabled
= mTextAmount
>= mTextThreshold
;
231 /* static */ nsIFrame
*
232 nsFontInflationData::FindEdgeInflatableFrameIn(nsIFrame
* aFrame
,
233 SearchDirection aDirection
)
235 // NOTE: This function has a similar structure to ScanTextIn!
237 // FIXME: Should probably only scan the text that's actually going to
240 nsIFormControlFrame
* fcf
= do_QueryFrame(aFrame
);
245 // FIXME: aDirection!
246 nsAutoTArray
<FrameChildList
, 4> lists
;
247 aFrame
->GetChildLists(&lists
);
248 for (uint32_t i
= 0, len
= lists
.Length(); i
< len
; ++i
) {
249 const nsFrameList
& list
=
250 lists
[(aDirection
== eFromStart
) ? i
: len
- i
- 1].mList
;
251 for (nsIFrame
*kid
= (aDirection
== eFromStart
) ? list
.FirstChild()
254 kid
= (aDirection
== eFromStart
) ? kid
->GetNextSibling()
255 : kid
->GetPrevSibling()) {
256 if (kid
->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT
) {
257 // Goes in a different set of inflation data.
261 if (kid
->GetType() == nsGkAtoms::textFrame
) {
262 nsIContent
*content
= kid
->GetContent();
263 if (content
&& kid
== content
->GetPrimaryFrame()) {
264 uint32_t len
= nsTextFrameUtils::
265 ComputeApproximateLengthWithWhitespaceCompression(
266 content
, kid
->StyleText());
272 nsIFrame
*kidResult
=
273 FindEdgeInflatableFrameIn(kid
, aDirection
);
285 nsFontInflationData::ScanText()
289 ScanTextIn(mBFCFrame
);
290 mInflationEnabled
= mTextAmount
>= mTextThreshold
;
294 DoCharCountOfLargestOption(nsIFrame
*aContainer
)
297 for (nsIFrame
* option
= aContainer
->GetFirstPrincipalChild();
298 option
; option
= option
->GetNextSibling()) {
299 uint32_t optionResult
;
300 if (option
->GetContent()->IsHTML(nsGkAtoms::optgroup
)) {
301 optionResult
= DoCharCountOfLargestOption(option
);
303 // REVIEW: Check the frame structure for this!
305 for (nsIFrame
*optionChild
= option
->GetFirstPrincipalChild();
306 optionChild
; optionChild
= optionChild
->GetNextSibling()) {
307 if (optionChild
->GetType() == nsGkAtoms::textFrame
) {
308 optionResult
+= nsTextFrameUtils::
309 ComputeApproximateLengthWithWhitespaceCompression(
310 optionChild
->GetContent(), optionChild
->StyleText());
314 if (optionResult
> result
) {
315 result
= optionResult
;
322 CharCountOfLargestOption(nsIFrame
*aListControlFrame
)
324 return DoCharCountOfLargestOption(
325 static_cast<nsListControlFrame
*>(aListControlFrame
)->GetOptionsContainer());
329 nsFontInflationData::ScanTextIn(nsIFrame
*aFrame
)
331 // NOTE: This function has a similar structure to FindEdgeInflatableFrameIn!
333 // FIXME: Should probably only scan the text that's actually going to
336 nsIFrame::ChildListIterator
lists(aFrame
);
337 for (; !lists
.IsDone(); lists
.Next()) {
338 nsFrameList::Enumerator
kids(lists
.CurrentList());
339 for (; !kids
.AtEnd(); kids
.Next()) {
340 nsIFrame
*kid
= kids
.get();
341 if (kid
->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT
) {
342 // Goes in a different set of inflation data.
346 nsIAtom
*fType
= kid
->GetType();
347 if (fType
== nsGkAtoms::textFrame
) {
348 nsIContent
*content
= kid
->GetContent();
349 if (content
&& kid
== content
->GetPrimaryFrame()) {
350 uint32_t len
= nsTextFrameUtils::
351 ComputeApproximateLengthWithWhitespaceCompression(
352 content
, kid
->StyleText());
354 nscoord fontSize
= kid
->StyleFont()->mFont
.size
;
356 mTextAmount
+= fontSize
* len
;
360 } else if (fType
== nsGkAtoms::textInputFrame
) {
361 // We don't want changes to the amount of text in a text input
362 // to change what we count towards inflation.
363 nscoord fontSize
= kid
->StyleFont()->mFont
.size
;
364 int32_t charCount
= static_cast<nsTextControlFrame
*>(kid
)->GetCols();
365 mTextAmount
+= charCount
* fontSize
;
366 } else if (fType
== nsGkAtoms::comboboxControlFrame
) {
367 // See textInputFrame above (with s/amount of text/selected option/).
368 // Don't just recurse down to the list control inside, since we
369 // need to exclude the display frame.
370 nscoord fontSize
= kid
->StyleFont()->mFont
.size
;
371 int32_t charCount
= CharCountOfLargestOption(
372 static_cast<nsComboboxControlFrame
*>(kid
)->GetDropDown());
373 mTextAmount
+= charCount
* fontSize
;
374 } else if (fType
== nsGkAtoms::listControlFrame
) {
375 // See textInputFrame above (with s/amount of text/selected option/).
376 nscoord fontSize
= kid
->StyleFont()->mFont
.size
;
377 int32_t charCount
= CharCountOfLargestOption(kid
);
378 mTextAmount
+= charCount
* fontSize
;
384 if (mTextAmount
>= mTextThreshold
) {