Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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)
21 {
22 delete static_cast<nsFontInflationData*>(aPropertyValue);
23 }
25 NS_DECLARE_FRAME_PROPERTY(FontInflationDataProperty, DestroyFontInflationData)
27 /* static */ nsFontInflationData*
28 nsFontInflationData::FindFontInflationDataFor(const nsIFrame *aFrame)
29 {
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()));
37 }
39 /* static */ bool
40 nsFontInflationData::UpdateFontInflationDataWidthFor(const nsHTMLReflowState& aReflowState)
41 {
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 */
58 }
60 data->UpdateWidth(aReflowState);
62 if (oldInflationEnabled != data->mInflationEnabled)
63 return true;
65 return oldInflationEnabled &&
66 oldNCAWidth != data->mNCAWidth;
67 }
69 /* static */ void
70 nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame *aBFCFrame)
71 {
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();
80 }
81 }
83 nsFontInflationData::nsFontInflationData(nsIFrame *aBFCFrame)
84 : mBFCFrame(aBFCFrame)
85 , mNCAWidth(0)
86 , mTextAmount(0)
87 , mTextThreshold(0)
88 , mInflationEnabled(false)
89 , mTextDirty(true)
90 {
91 }
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).
97 *
98 * aKnownCommonAncestor is a known common ancestor of both.
99 */
100 static nsIFrame*
101 NearestCommonAncestorFirstInFlow(nsIFrame *aFrame1, nsIFrame *aFrame2,
102 nsIFrame *aKnownCommonAncestor)
103 {
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);
112 }
113 for (nsIFrame *f = aFrame2; f != aKnownCommonAncestor;
114 (f = f->GetParent()) && (f = f->FirstInFlow())) {
115 ancestors2.AppendElement(f);
116 }
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;
124 }
125 result = ancestors1[i1];
126 }
128 return result;
129 }
131 static nscoord
132 ComputeDescendantWidth(const nsHTMLReflowState& aAncestorReflowState,
133 nsIFrame *aDescendantFrame)
134 {
135 nsIFrame *ancestorFrame = aAncestorReflowState.frame->FirstInFlow();
136 if (aDescendantFrame == ancestorFrame) {
137 return aAncestorReflowState.ComputedWidth();
138 }
140 AutoInfallibleTArray<nsIFrame*, 16> frames;
141 for (nsIFrame *f = aDescendantFrame; f != ancestorFrame;
142 f = f->GetParent()->FirstInFlow()) {
143 frames.AppendElement(f);
144 }
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 nsSize availSize(parentReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE);
159 nsIFrame *frame = frames[len - i - 1];
160 NS_ABORT_IF_FALSE(frame->GetParent()->FirstInFlow() ==
161 parentReflowState.frame->FirstInFlow(),
162 "bad logic in this function");
163 new (reflowStates + i) nsHTMLReflowState(presContext, parentReflowState,
164 frame, availSize);
165 }
167 NS_ABORT_IF_FALSE(reflowStates[len - 1].frame == aDescendantFrame,
168 "bad logic in this function");
169 nscoord result = reflowStates[len - 1].ComputedWidth();
171 for (uint32_t i = len; i-- != 0; ) {
172 reflowStates[i].~nsHTMLReflowState();
173 }
174 moz_free(reflowStates);
176 return result;
177 }
179 void
180 nsFontInflationData::UpdateWidth(const nsHTMLReflowState &aReflowState)
181 {
182 nsIFrame *bfc = aReflowState.frame;
183 NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
184 "must be block formatting context");
186 nsIFrame *firstInflatableDescendant =
187 FindEdgeInflatableFrameIn(bfc, eFromStart);
188 if (!firstInflatableDescendant) {
189 mTextAmount = 0;
190 mTextThreshold = 0; // doesn't matter
191 mTextDirty = false;
192 mInflationEnabled = false;
193 return;
194 }
195 nsIFrame *lastInflatableDescendant =
196 FindEdgeInflatableFrameIn(bfc, eFromEnd);
197 NS_ABORT_IF_FALSE(!firstInflatableDescendant == !lastInflatableDescendant,
198 "null-ness should match; NearestCommonAncestorFirstInFlow"
199 " will crash when passed null");
201 // Particularly when we're computing for the root BFC, the width of
202 // nca might differ significantly for the width of bfc.
203 nsIFrame *nca = NearestCommonAncestorFirstInFlow(firstInflatableDescendant,
204 lastInflatableDescendant,
205 bfc);
206 while (!nca->IsContainerForFontSizeInflation()) {
207 nca = nca->GetParent()->FirstInFlow();
208 }
210 nscoord newNCAWidth = ComputeDescendantWidth(aReflowState, nca);
212 // See comment above "font.size.inflation.lineThreshold" in
213 // modules/libpref/src/init/all.js .
214 nsIPresShell* presShell = bfc->PresContext()->PresShell();
215 uint32_t lineThreshold = presShell->FontSizeInflationLineThreshold();
216 nscoord newTextThreshold = (newNCAWidth * lineThreshold) / 100;
218 if (mTextThreshold <= mTextAmount && mTextAmount < newTextThreshold) {
219 // Because we truncate our scan when we hit sufficient text, we now
220 // need to rescan.
221 mTextDirty = true;
222 }
224 mNCAWidth = newNCAWidth;
225 mTextThreshold = newTextThreshold;
226 mInflationEnabled = mTextAmount >= mTextThreshold;
227 }
229 /* static */ nsIFrame*
230 nsFontInflationData::FindEdgeInflatableFrameIn(nsIFrame* aFrame,
231 SearchDirection aDirection)
232 {
233 // NOTE: This function has a similar structure to ScanTextIn!
235 // FIXME: Should probably only scan the text that's actually going to
236 // be inflated!
238 nsIFormControlFrame* fcf = do_QueryFrame(aFrame);
239 if (fcf) {
240 return aFrame;
241 }
243 // FIXME: aDirection!
244 nsAutoTArray<FrameChildList, 4> lists;
245 aFrame->GetChildLists(&lists);
246 for (uint32_t i = 0, len = lists.Length(); i < len; ++i) {
247 const nsFrameList& list =
248 lists[(aDirection == eFromStart) ? i : len - i - 1].mList;
249 for (nsIFrame *kid = (aDirection == eFromStart) ? list.FirstChild()
250 : list.LastChild();
251 kid;
252 kid = (aDirection == eFromStart) ? kid->GetNextSibling()
253 : kid->GetPrevSibling()) {
254 if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
255 // Goes in a different set of inflation data.
256 continue;
257 }
259 if (kid->GetType() == nsGkAtoms::textFrame) {
260 nsIContent *content = kid->GetContent();
261 if (content && kid == content->GetPrimaryFrame()) {
262 uint32_t len = nsTextFrameUtils::
263 ComputeApproximateLengthWithWhitespaceCompression(
264 content, kid->StyleText());
265 if (len != 0) {
266 return kid;
267 }
268 }
269 } else {
270 nsIFrame *kidResult =
271 FindEdgeInflatableFrameIn(kid, aDirection);
272 if (kidResult) {
273 return kidResult;
274 }
275 }
276 }
277 }
279 return nullptr;
280 }
282 void
283 nsFontInflationData::ScanText()
284 {
285 mTextDirty = false;
286 mTextAmount = 0;
287 ScanTextIn(mBFCFrame);
288 mInflationEnabled = mTextAmount >= mTextThreshold;
289 }
291 static uint32_t
292 DoCharCountOfLargestOption(nsIFrame *aContainer)
293 {
294 uint32_t result = 0;
295 for (nsIFrame* option = aContainer->GetFirstPrincipalChild();
296 option; option = option->GetNextSibling()) {
297 uint32_t optionResult;
298 if (option->GetContent()->IsHTML(nsGkAtoms::optgroup)) {
299 optionResult = DoCharCountOfLargestOption(option);
300 } else {
301 // REVIEW: Check the frame structure for this!
302 optionResult = 0;
303 for (nsIFrame *optionChild = option->GetFirstPrincipalChild();
304 optionChild; optionChild = optionChild->GetNextSibling()) {
305 if (optionChild->GetType() == nsGkAtoms::textFrame) {
306 optionResult += nsTextFrameUtils::
307 ComputeApproximateLengthWithWhitespaceCompression(
308 optionChild->GetContent(), optionChild->StyleText());
309 }
310 }
311 }
312 if (optionResult > result) {
313 result = optionResult;
314 }
315 }
316 return result;
317 }
319 static uint32_t
320 CharCountOfLargestOption(nsIFrame *aListControlFrame)
321 {
322 return DoCharCountOfLargestOption(
323 static_cast<nsListControlFrame*>(aListControlFrame)->GetOptionsContainer());
324 }
326 void
327 nsFontInflationData::ScanTextIn(nsIFrame *aFrame)
328 {
329 // NOTE: This function has a similar structure to FindEdgeInflatableFrameIn!
331 // FIXME: Should probably only scan the text that's actually going to
332 // be inflated!
334 nsIFrame::ChildListIterator lists(aFrame);
335 for (; !lists.IsDone(); lists.Next()) {
336 nsFrameList::Enumerator kids(lists.CurrentList());
337 for (; !kids.AtEnd(); kids.Next()) {
338 nsIFrame *kid = kids.get();
339 if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
340 // Goes in a different set of inflation data.
341 continue;
342 }
344 nsIAtom *fType = kid->GetType();
345 if (fType == nsGkAtoms::textFrame) {
346 nsIContent *content = kid->GetContent();
347 if (content && kid == content->GetPrimaryFrame()) {
348 uint32_t len = nsTextFrameUtils::
349 ComputeApproximateLengthWithWhitespaceCompression(
350 content, kid->StyleText());
351 if (len != 0) {
352 nscoord fontSize = kid->StyleFont()->mFont.size;
353 if (fontSize > 0) {
354 mTextAmount += fontSize * len;
355 }
356 }
357 }
358 } else if (fType == nsGkAtoms::textInputFrame) {
359 // We don't want changes to the amount of text in a text input
360 // to change what we count towards inflation.
361 nscoord fontSize = kid->StyleFont()->mFont.size;
362 int32_t charCount = static_cast<nsTextControlFrame*>(kid)->GetCols();
363 mTextAmount += charCount * fontSize;
364 } else if (fType == nsGkAtoms::comboboxControlFrame) {
365 // See textInputFrame above (with s/amount of text/selected option/).
366 // Don't just recurse down to the list control inside, since we
367 // need to exclude the display frame.
368 nscoord fontSize = kid->StyleFont()->mFont.size;
369 int32_t charCount = CharCountOfLargestOption(
370 static_cast<nsComboboxControlFrame*>(kid)->GetDropDown());
371 mTextAmount += charCount * fontSize;
372 } else if (fType == nsGkAtoms::listControlFrame) {
373 // See textInputFrame above (with s/amount of text/selected option/).
374 nscoord fontSize = kid->StyleFont()->mFont.size;
375 int32_t charCount = CharCountOfLargestOption(kid);
376 mTextAmount += charCount * fontSize;
377 } else {
378 // recursive step
379 ScanTextIn(kid);
380 }
382 if (mTextAmount >= mTextThreshold) {
383 return;
384 }
385 }
386 }
387 }