Line data Source code
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 "FrameProperties.h"
10 : #include "nsTextControlFrame.h"
11 : #include "nsListControlFrame.h"
12 : #include "nsComboboxControlFrame.h"
13 : #include "mozilla/ReflowInput.h"
14 : #include "nsTextFrameUtils.h"
15 :
16 : using namespace mozilla;
17 : using namespace mozilla::layout;
18 :
19 145 : NS_DECLARE_FRAME_PROPERTY_DELETABLE(FontInflationDataProperty,
20 : nsFontInflationData)
21 :
22 : /* static */ nsFontInflationData*
23 0 : nsFontInflationData::FindFontInflationDataFor(const nsIFrame *aFrame)
24 : {
25 : // We have one set of font inflation data per block formatting context.
26 0 : const nsIFrame *bfc = FlowRootFor(aFrame);
27 0 : NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
28 : "should have found a flow root");
29 :
30 0 : return bfc->GetProperty(FontInflationDataProperty());
31 : }
32 :
33 : /* static */ bool
34 0 : nsFontInflationData::UpdateFontInflationDataISizeFor(const ReflowInput& aReflowInput)
35 : {
36 0 : nsIFrame *bfc = aReflowInput.mFrame;
37 0 : NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
38 : "should have been given a flow root");
39 0 : nsFontInflationData *data = bfc->GetProperty(FontInflationDataProperty());
40 : bool oldInflationEnabled;
41 : nscoord oldNCAISize;
42 0 : if (data) {
43 0 : oldNCAISize = data->mNCAISize;
44 0 : oldInflationEnabled = data->mInflationEnabled;
45 : } else {
46 0 : data = new nsFontInflationData(bfc);
47 0 : bfc->SetProperty(FontInflationDataProperty(), data);
48 0 : oldNCAISize = -1;
49 0 : oldInflationEnabled = true; /* not relevant */
50 : }
51 :
52 0 : data->UpdateISize(aReflowInput);
53 :
54 0 : if (oldInflationEnabled != data->mInflationEnabled)
55 0 : return true;
56 :
57 0 : return oldInflationEnabled &&
58 0 : oldNCAISize != data->mNCAISize;
59 : }
60 :
61 : /* static */ void
62 145 : nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame *aBFCFrame)
63 : {
64 145 : NS_ASSERTION(aBFCFrame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
65 : "should have been given a flow root");
66 :
67 145 : nsFontInflationData *data = aBFCFrame->GetProperty(FontInflationDataProperty());
68 145 : if (data) {
69 0 : data->MarkTextDirty();
70 : }
71 145 : }
72 :
73 0 : nsFontInflationData::nsFontInflationData(nsIFrame *aBFCFrame)
74 : : mBFCFrame(aBFCFrame)
75 : , mNCAISize(0)
76 : , mTextAmount(0)
77 : , mTextThreshold(0)
78 : , mInflationEnabled(false)
79 0 : , mTextDirty(true)
80 : {
81 0 : }
82 :
83 : /**
84 : * Find the closest common ancestor between aFrame1 and aFrame2, except
85 : * treating the parent of a frame as the first-in-flow of its parent (so
86 : * the result doesn't change when breaking changes).
87 : *
88 : * aKnownCommonAncestor is a known common ancestor of both.
89 : */
90 : static nsIFrame*
91 0 : NearestCommonAncestorFirstInFlow(nsIFrame *aFrame1, nsIFrame *aFrame2,
92 : nsIFrame *aKnownCommonAncestor)
93 : {
94 0 : aFrame1 = aFrame1->FirstInFlow();
95 0 : aFrame2 = aFrame2->FirstInFlow();
96 0 : aKnownCommonAncestor = aKnownCommonAncestor->FirstInFlow();
97 :
98 0 : AutoTArray<nsIFrame*, 32> ancestors1, ancestors2;
99 0 : for (nsIFrame *f = aFrame1; f != aKnownCommonAncestor;
100 0 : (f = f->GetParent()) && (f = f->FirstInFlow())) {
101 0 : ancestors1.AppendElement(f);
102 : }
103 0 : for (nsIFrame *f = aFrame2; f != aKnownCommonAncestor;
104 0 : (f = f->GetParent()) && (f = f->FirstInFlow())) {
105 0 : ancestors2.AppendElement(f);
106 : }
107 :
108 0 : nsIFrame *result = aKnownCommonAncestor;
109 0 : uint32_t i1 = ancestors1.Length(),
110 0 : i2 = ancestors2.Length();
111 0 : while (i1-- != 0 && i2-- != 0) {
112 0 : if (ancestors1[i1] != ancestors2[i2]) {
113 0 : break;
114 : }
115 0 : result = ancestors1[i1];
116 : }
117 :
118 0 : return result;
119 : }
120 :
121 : static nscoord
122 0 : ComputeDescendantISize(const ReflowInput& aAncestorReflowInput,
123 : nsIFrame *aDescendantFrame)
124 : {
125 0 : nsIFrame *ancestorFrame = aAncestorReflowInput.mFrame->FirstInFlow();
126 0 : if (aDescendantFrame == ancestorFrame) {
127 0 : return aAncestorReflowInput.ComputedISize();
128 : }
129 :
130 0 : AutoTArray<nsIFrame*, 16> frames;
131 0 : for (nsIFrame *f = aDescendantFrame; f != ancestorFrame;
132 0 : f = f->GetParent()->FirstInFlow()) {
133 0 : frames.AppendElement(f);
134 : }
135 :
136 : // This ignores the inline-size contributions made by scrollbars, though in
137 : // reality we don't have any scrollbars on the sorts of devices on
138 : // which we use font inflation, so it's not a problem. But it may
139 : // occasionally cause problems when writing tests on desktop.
140 :
141 0 : uint32_t len = frames.Length();
142 : ReflowInput *reflowInputs = static_cast<ReflowInput*>
143 0 : (moz_xmalloc(sizeof(ReflowInput) * len));
144 0 : nsPresContext *presContext = aDescendantFrame->PresContext();
145 0 : for (uint32_t i = 0; i < len; ++i) {
146 : const ReflowInput &parentReflowInput =
147 0 : (i == 0) ? aAncestorReflowInput : reflowInputs[i - 1];
148 0 : nsIFrame *frame = frames[len - i - 1];
149 0 : WritingMode wm = frame->GetWritingMode();
150 0 : LogicalSize availSize = parentReflowInput.ComputedSize(wm);
151 0 : availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
152 0 : MOZ_ASSERT(frame->GetParent()->FirstInFlow() ==
153 : parentReflowInput.mFrame->FirstInFlow(),
154 : "bad logic in this function");
155 0 : new (reflowInputs + i) ReflowInput(presContext, parentReflowInput,
156 0 : frame, availSize);
157 : }
158 :
159 0 : MOZ_ASSERT(reflowInputs[len - 1].mFrame == aDescendantFrame,
160 : "bad logic in this function");
161 0 : nscoord result = reflowInputs[len - 1].ComputedISize();
162 :
163 0 : for (uint32_t i = len; i-- != 0; ) {
164 : reflowInputs[i].~ReflowInput();
165 : }
166 0 : free(reflowInputs);
167 :
168 0 : return result;
169 : }
170 :
171 : void
172 0 : nsFontInflationData::UpdateISize(const ReflowInput &aReflowInput)
173 : {
174 0 : nsIFrame *bfc = aReflowInput.mFrame;
175 0 : NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
176 : "must be block formatting context");
177 :
178 : nsIFrame *firstInflatableDescendant =
179 0 : FindEdgeInflatableFrameIn(bfc, eFromStart);
180 0 : if (!firstInflatableDescendant) {
181 0 : mTextAmount = 0;
182 0 : mTextThreshold = 0; // doesn't matter
183 0 : mTextDirty = false;
184 0 : mInflationEnabled = false;
185 0 : return;
186 : }
187 : nsIFrame *lastInflatableDescendant =
188 0 : FindEdgeInflatableFrameIn(bfc, eFromEnd);
189 0 : MOZ_ASSERT(!firstInflatableDescendant == !lastInflatableDescendant,
190 : "null-ness should match; NearestCommonAncestorFirstInFlow"
191 : " will crash when passed null");
192 :
193 : // Particularly when we're computing for the root BFC, the inline-size of
194 : // nca might differ significantly for the inline-size of bfc.
195 : nsIFrame *nca = NearestCommonAncestorFirstInFlow(firstInflatableDescendant,
196 : lastInflatableDescendant,
197 0 : bfc);
198 0 : while (!nca->IsContainerForFontSizeInflation()) {
199 0 : nca = nca->GetParent()->FirstInFlow();
200 : }
201 :
202 0 : nscoord newNCAISize = ComputeDescendantISize(aReflowInput, nca);
203 :
204 : // See comment above "font.size.inflation.lineThreshold" in
205 : // modules/libpref/src/init/all.js .
206 0 : nsIPresShell* presShell = bfc->PresContext()->PresShell();
207 0 : uint32_t lineThreshold = presShell->FontSizeInflationLineThreshold();
208 0 : nscoord newTextThreshold = (newNCAISize * lineThreshold) / 100;
209 :
210 0 : if (mTextThreshold <= mTextAmount && mTextAmount < newTextThreshold) {
211 : // Because we truncate our scan when we hit sufficient text, we now
212 : // need to rescan.
213 0 : mTextDirty = true;
214 : }
215 :
216 0 : mNCAISize = newNCAISize;
217 0 : mTextThreshold = newTextThreshold;
218 0 : mInflationEnabled = mTextAmount >= mTextThreshold;
219 : }
220 :
221 : /* static */ nsIFrame*
222 0 : nsFontInflationData::FindEdgeInflatableFrameIn(nsIFrame* aFrame,
223 : SearchDirection aDirection)
224 : {
225 : // NOTE: This function has a similar structure to ScanTextIn!
226 :
227 : // FIXME: Should probably only scan the text that's actually going to
228 : // be inflated!
229 :
230 0 : nsIFormControlFrame* fcf = do_QueryFrame(aFrame);
231 0 : if (fcf) {
232 0 : return aFrame;
233 : }
234 :
235 : // FIXME: aDirection!
236 0 : AutoTArray<FrameChildList, 4> lists;
237 0 : aFrame->GetChildLists(&lists);
238 0 : for (uint32_t i = 0, len = lists.Length(); i < len; ++i) {
239 : const nsFrameList& list =
240 0 : lists[(aDirection == eFromStart) ? i : len - i - 1].mList;
241 0 : for (nsIFrame *kid = (aDirection == eFromStart) ? list.FirstChild()
242 0 : : list.LastChild();
243 0 : kid;
244 0 : kid = (aDirection == eFromStart) ? kid->GetNextSibling()
245 : : kid->GetPrevSibling()) {
246 0 : if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
247 : // Goes in a different set of inflation data.
248 0 : continue;
249 : }
250 :
251 0 : if (kid->IsTextFrame()) {
252 0 : nsIContent *content = kid->GetContent();
253 0 : if (content && kid == content->GetPrimaryFrame()) {
254 : uint32_t len = nsTextFrameUtils::
255 0 : ComputeApproximateLengthWithWhitespaceCompression(
256 0 : content, kid->StyleText());
257 0 : if (len != 0) {
258 0 : return kid;
259 : }
260 : }
261 : } else {
262 : nsIFrame *kidResult =
263 0 : FindEdgeInflatableFrameIn(kid, aDirection);
264 0 : if (kidResult) {
265 0 : return kidResult;
266 : }
267 : }
268 : }
269 : }
270 :
271 0 : return nullptr;
272 : }
273 :
274 : void
275 0 : nsFontInflationData::ScanText()
276 : {
277 0 : mTextDirty = false;
278 0 : mTextAmount = 0;
279 0 : ScanTextIn(mBFCFrame);
280 0 : mInflationEnabled = mTextAmount >= mTextThreshold;
281 0 : }
282 :
283 : static uint32_t
284 0 : DoCharCountOfLargestOption(nsIFrame *aContainer)
285 : {
286 0 : uint32_t result = 0;
287 0 : for (nsIFrame* option : aContainer->PrincipalChildList()) {
288 : uint32_t optionResult;
289 0 : if (option->GetContent()->IsHTMLElement(nsGkAtoms::optgroup)) {
290 0 : optionResult = DoCharCountOfLargestOption(option);
291 : } else {
292 : // REVIEW: Check the frame structure for this!
293 0 : optionResult = 0;
294 0 : for (nsIFrame* optionChild : option->PrincipalChildList()) {
295 0 : if (optionChild->IsTextFrame()) {
296 0 : optionResult += nsTextFrameUtils::
297 0 : ComputeApproximateLengthWithWhitespaceCompression(
298 : optionChild->GetContent(), optionChild->StyleText());
299 : }
300 : }
301 : }
302 0 : if (optionResult > result) {
303 0 : result = optionResult;
304 : }
305 : }
306 0 : return result;
307 : }
308 :
309 : static uint32_t
310 0 : CharCountOfLargestOption(nsIFrame *aListControlFrame)
311 : {
312 : return DoCharCountOfLargestOption(
313 0 : static_cast<nsListControlFrame*>(aListControlFrame)->GetOptionsContainer());
314 : }
315 :
316 : void
317 0 : nsFontInflationData::ScanTextIn(nsIFrame *aFrame)
318 : {
319 : // NOTE: This function has a similar structure to FindEdgeInflatableFrameIn!
320 :
321 : // FIXME: Should probably only scan the text that's actually going to
322 : // be inflated!
323 :
324 0 : nsIFrame::ChildListIterator lists(aFrame);
325 0 : for (; !lists.IsDone(); lists.Next()) {
326 0 : nsFrameList::Enumerator kids(lists.CurrentList());
327 0 : for (; !kids.AtEnd(); kids.Next()) {
328 0 : nsIFrame *kid = kids.get();
329 0 : if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
330 : // Goes in a different set of inflation data.
331 0 : continue;
332 : }
333 :
334 0 : LayoutFrameType fType = kid->Type();
335 0 : if (fType == LayoutFrameType::Text) {
336 0 : nsIContent *content = kid->GetContent();
337 0 : if (content && kid == content->GetPrimaryFrame()) {
338 : uint32_t len = nsTextFrameUtils::
339 0 : ComputeApproximateLengthWithWhitespaceCompression(
340 0 : content, kid->StyleText());
341 0 : if (len != 0) {
342 0 : nscoord fontSize = kid->StyleFont()->mFont.size;
343 0 : if (fontSize > 0) {
344 0 : mTextAmount += fontSize * len;
345 : }
346 : }
347 : }
348 0 : } else if (fType == LayoutFrameType::TextInput) {
349 : // We don't want changes to the amount of text in a text input
350 : // to change what we count towards inflation.
351 0 : nscoord fontSize = kid->StyleFont()->mFont.size;
352 0 : int32_t charCount = static_cast<nsTextControlFrame*>(kid)->GetCols();
353 0 : mTextAmount += charCount * fontSize;
354 0 : } else if (fType == LayoutFrameType::ComboboxControl) {
355 : // See textInputFrame above (with s/amount of text/selected option/).
356 : // Don't just recurse down to the list control inside, since we
357 : // need to exclude the display frame.
358 0 : nscoord fontSize = kid->StyleFont()->mFont.size;
359 0 : int32_t charCount = CharCountOfLargestOption(
360 0 : static_cast<nsComboboxControlFrame*>(kid)->GetDropDown());
361 0 : mTextAmount += charCount * fontSize;
362 0 : } else if (fType == LayoutFrameType::ListControl) {
363 : // See textInputFrame above (with s/amount of text/selected option/).
364 0 : nscoord fontSize = kid->StyleFont()->mFont.size;
365 0 : int32_t charCount = CharCountOfLargestOption(kid);
366 0 : mTextAmount += charCount * fontSize;
367 : } else {
368 : // recursive step
369 0 : ScanTextIn(kid);
370 : }
371 :
372 0 : if (mTextAmount >= mTextThreshold) {
373 0 : return;
374 : }
375 : }
376 : }
377 : }
|