Bumping manifests a=b2g-bump
[gecko.git] / layout / generic / nsFontInflationData.cpp
blob0873e1bc7f530115ace44526647e89821eff5f8f
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;
19 static void
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()));
39 /* static */ bool
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;
49 nscoord oldNCAWidth;
50 if (data) {
51 oldNCAWidth = data->mNCAWidth;
52 oldInflationEnabled = data->mInflationEnabled;
53 } else {
54 data = new nsFontInflationData(bfc);
55 bfcProps.Set(FontInflationDataProperty(), data);
56 oldNCAWidth = -1;
57 oldInflationEnabled = true; /* not relevant */
60 data->UpdateWidth(aReflowState);
62 if (oldInflationEnabled != data->mInflationEnabled)
63 return true;
65 return oldInflationEnabled &&
66 oldNCAWidth != data->mNCAWidth;
69 /* static */ void
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()));
78 if (data) {
79 data->MarkTextDirty();
83 nsFontInflationData::nsFontInflationData(nsIFrame *aBFCFrame)
84 : mBFCFrame(aBFCFrame)
85 , mNCAWidth(0)
86 , mTextAmount(0)
87 , mTextThreshold(0)
88 , mInflationEnabled(false)
89 , mTextDirty(true)
93 /**
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.
100 static nsIFrame*
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]) {
123 break;
125 result = ancestors1[i1];
128 return result;
131 static nscoord
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,
166 frame, availSize);
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);
178 return result;
181 void
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) {
191 mTextAmount = 0;
192 mTextThreshold = 0; // doesn't matter
193 mTextDirty = false;
194 mInflationEnabled = false;
195 return;
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,
207 bfc);
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
222 // need to rescan.
223 mTextDirty = true;
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
238 // be inflated!
240 nsIFormControlFrame* fcf = do_QueryFrame(aFrame);
241 if (fcf) {
242 return 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()
252 : list.LastChild();
253 kid;
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.
258 continue;
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());
267 if (len != 0) {
268 return kid;
271 } else {
272 nsIFrame *kidResult =
273 FindEdgeInflatableFrameIn(kid, aDirection);
274 if (kidResult) {
275 return kidResult;
281 return nullptr;
284 void
285 nsFontInflationData::ScanText()
287 mTextDirty = false;
288 mTextAmount = 0;
289 ScanTextIn(mBFCFrame);
290 mInflationEnabled = mTextAmount >= mTextThreshold;
293 static uint32_t
294 DoCharCountOfLargestOption(nsIFrame *aContainer)
296 uint32_t result = 0;
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);
302 } else {
303 // REVIEW: Check the frame structure for this!
304 optionResult = 0;
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;
318 return result;
321 static uint32_t
322 CharCountOfLargestOption(nsIFrame *aListControlFrame)
324 return DoCharCountOfLargestOption(
325 static_cast<nsListControlFrame*>(aListControlFrame)->GetOptionsContainer());
328 void
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
334 // be inflated!
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.
343 continue;
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());
353 if (len != 0) {
354 nscoord fontSize = kid->StyleFont()->mFont.size;
355 if (fontSize > 0) {
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;
379 } else {
380 // recursive step
381 ScanTextIn(kid);
384 if (mTextAmount >= mTextThreshold) {
385 return;