Coverage Report

Created: 2018-09-25 14:53

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