|
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/. */ |
|
5 |
|
6 /* Per-block-formatting-context manager of font size inflation for pan and zoom UI. */ |
|
7 |
|
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" |
|
15 |
|
16 using namespace mozilla; |
|
17 using namespace mozilla::layout; |
|
18 |
|
19 static void |
|
20 DestroyFontInflationData(void *aPropertyValue) |
|
21 { |
|
22 delete static_cast<nsFontInflationData*>(aPropertyValue); |
|
23 } |
|
24 |
|
25 NS_DECLARE_FRAME_PROPERTY(FontInflationDataProperty, DestroyFontInflationData) |
|
26 |
|
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"); |
|
34 |
|
35 return static_cast<nsFontInflationData*>( |
|
36 bfc->Properties().Get(FontInflationDataProperty())); |
|
37 } |
|
38 |
|
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 } |
|
59 |
|
60 data->UpdateWidth(aReflowState); |
|
61 |
|
62 if (oldInflationEnabled != data->mInflationEnabled) |
|
63 return true; |
|
64 |
|
65 return oldInflationEnabled && |
|
66 oldNCAWidth != data->mNCAWidth; |
|
67 } |
|
68 |
|
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"); |
|
74 |
|
75 FrameProperties bfcProps(aBFCFrame->Properties()); |
|
76 nsFontInflationData *data = static_cast<nsFontInflationData*>( |
|
77 bfcProps.Get(FontInflationDataProperty())); |
|
78 if (data) { |
|
79 data->MarkTextDirty(); |
|
80 } |
|
81 } |
|
82 |
|
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 } |
|
92 |
|
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(); |
|
107 |
|
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 } |
|
117 |
|
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 } |
|
127 |
|
128 return result; |
|
129 } |
|
130 |
|
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 } |
|
139 |
|
140 AutoInfallibleTArray<nsIFrame*, 16> frames; |
|
141 for (nsIFrame *f = aDescendantFrame; f != ancestorFrame; |
|
142 f = f->GetParent()->FirstInFlow()) { |
|
143 frames.AppendElement(f); |
|
144 } |
|
145 |
|
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. |
|
150 |
|
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 } |
|
166 |
|
167 NS_ABORT_IF_FALSE(reflowStates[len - 1].frame == aDescendantFrame, |
|
168 "bad logic in this function"); |
|
169 nscoord result = reflowStates[len - 1].ComputedWidth(); |
|
170 |
|
171 for (uint32_t i = len; i-- != 0; ) { |
|
172 reflowStates[i].~nsHTMLReflowState(); |
|
173 } |
|
174 moz_free(reflowStates); |
|
175 |
|
176 return result; |
|
177 } |
|
178 |
|
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"); |
|
185 |
|
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"); |
|
200 |
|
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 } |
|
209 |
|
210 nscoord newNCAWidth = ComputeDescendantWidth(aReflowState, nca); |
|
211 |
|
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; |
|
217 |
|
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 } |
|
223 |
|
224 mNCAWidth = newNCAWidth; |
|
225 mTextThreshold = newTextThreshold; |
|
226 mInflationEnabled = mTextAmount >= mTextThreshold; |
|
227 } |
|
228 |
|
229 /* static */ nsIFrame* |
|
230 nsFontInflationData::FindEdgeInflatableFrameIn(nsIFrame* aFrame, |
|
231 SearchDirection aDirection) |
|
232 { |
|
233 // NOTE: This function has a similar structure to ScanTextIn! |
|
234 |
|
235 // FIXME: Should probably only scan the text that's actually going to |
|
236 // be inflated! |
|
237 |
|
238 nsIFormControlFrame* fcf = do_QueryFrame(aFrame); |
|
239 if (fcf) { |
|
240 return aFrame; |
|
241 } |
|
242 |
|
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 } |
|
258 |
|
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 } |
|
278 |
|
279 return nullptr; |
|
280 } |
|
281 |
|
282 void |
|
283 nsFontInflationData::ScanText() |
|
284 { |
|
285 mTextDirty = false; |
|
286 mTextAmount = 0; |
|
287 ScanTextIn(mBFCFrame); |
|
288 mInflationEnabled = mTextAmount >= mTextThreshold; |
|
289 } |
|
290 |
|
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 } |
|
318 |
|
319 static uint32_t |
|
320 CharCountOfLargestOption(nsIFrame *aListControlFrame) |
|
321 { |
|
322 return DoCharCountOfLargestOption( |
|
323 static_cast<nsListControlFrame*>(aListControlFrame)->GetOptionsContainer()); |
|
324 } |
|
325 |
|
326 void |
|
327 nsFontInflationData::ScanTextIn(nsIFrame *aFrame) |
|
328 { |
|
329 // NOTE: This function has a similar structure to FindEdgeInflatableFrameIn! |
|
330 |
|
331 // FIXME: Should probably only scan the text that's actually going to |
|
332 // be inflated! |
|
333 |
|
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 } |
|
343 |
|
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 } |
|
381 |
|
382 if (mTextAmount >= mTextThreshold) { |
|
383 return; |
|
384 } |
|
385 } |
|
386 } |
|
387 } |