layout/generic/nsFontInflationData.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/layout/generic/nsFontInflationData.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,387 @@
     1.4 +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +/* Per-block-formatting-context manager of font size inflation for pan and zoom UI. */
    1.10 +
    1.11 +#include "nsFontInflationData.h"
    1.12 +#include "FramePropertyTable.h"
    1.13 +#include "nsTextControlFrame.h"
    1.14 +#include "nsListControlFrame.h"
    1.15 +#include "nsComboboxControlFrame.h"
    1.16 +#include "nsHTMLReflowState.h"
    1.17 +#include "nsTextFrameUtils.h"
    1.18 +
    1.19 +using namespace mozilla;
    1.20 +using namespace mozilla::layout;
    1.21 +
    1.22 +static void
    1.23 +DestroyFontInflationData(void *aPropertyValue)
    1.24 +{
    1.25 +  delete static_cast<nsFontInflationData*>(aPropertyValue);
    1.26 +}
    1.27 +
    1.28 +NS_DECLARE_FRAME_PROPERTY(FontInflationDataProperty, DestroyFontInflationData)
    1.29 +
    1.30 +/* static */ nsFontInflationData*
    1.31 +nsFontInflationData::FindFontInflationDataFor(const nsIFrame *aFrame)
    1.32 +{
    1.33 +  // We have one set of font inflation data per block formatting context.
    1.34 +  const nsIFrame *bfc = FlowRootFor(aFrame);
    1.35 +  NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
    1.36 +               "should have found a flow root");
    1.37 +
    1.38 +  return static_cast<nsFontInflationData*>(
    1.39 +             bfc->Properties().Get(FontInflationDataProperty()));
    1.40 +}
    1.41 +
    1.42 +/* static */ bool
    1.43 +nsFontInflationData::UpdateFontInflationDataWidthFor(const nsHTMLReflowState& aReflowState)
    1.44 +{
    1.45 +  nsIFrame *bfc = aReflowState.frame;
    1.46 +  NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
    1.47 +               "should have been given a flow root");
    1.48 +  FrameProperties bfcProps(bfc->Properties());
    1.49 +  nsFontInflationData *data = static_cast<nsFontInflationData*>(
    1.50 +                                bfcProps.Get(FontInflationDataProperty()));
    1.51 +  bool oldInflationEnabled;
    1.52 +  nscoord oldNCAWidth;
    1.53 +  if (data) {
    1.54 +    oldNCAWidth = data->mNCAWidth;
    1.55 +    oldInflationEnabled = data->mInflationEnabled;
    1.56 +  } else {
    1.57 +    data = new nsFontInflationData(bfc);
    1.58 +    bfcProps.Set(FontInflationDataProperty(), data);
    1.59 +    oldNCAWidth = -1;
    1.60 +    oldInflationEnabled = true; /* not relevant */
    1.61 +  }
    1.62 +
    1.63 +  data->UpdateWidth(aReflowState);
    1.64 +
    1.65 +  if (oldInflationEnabled != data->mInflationEnabled)
    1.66 +    return true;
    1.67 +
    1.68 +  return oldInflationEnabled &&
    1.69 +         oldNCAWidth != data->mNCAWidth;
    1.70 +}
    1.71 +
    1.72 +/* static */ void
    1.73 +nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame *aBFCFrame)
    1.74 +{
    1.75 +  NS_ASSERTION(aBFCFrame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
    1.76 +               "should have been given a flow root");
    1.77 +
    1.78 +  FrameProperties bfcProps(aBFCFrame->Properties());
    1.79 +  nsFontInflationData *data = static_cast<nsFontInflationData*>(
    1.80 +                                bfcProps.Get(FontInflationDataProperty()));
    1.81 +  if (data) {
    1.82 +    data->MarkTextDirty();
    1.83 +  }
    1.84 +}
    1.85 +
    1.86 +nsFontInflationData::nsFontInflationData(nsIFrame *aBFCFrame)
    1.87 +  : mBFCFrame(aBFCFrame)
    1.88 +  , mNCAWidth(0)
    1.89 +  , mTextAmount(0)
    1.90 +  , mTextThreshold(0)
    1.91 +  , mInflationEnabled(false)
    1.92 +  , mTextDirty(true)
    1.93 +{
    1.94 +}
    1.95 +
    1.96 +/**
    1.97 + * Find the closest common ancestor between aFrame1 and aFrame2, except
    1.98 + * treating the parent of a frame as the first-in-flow of its parent (so
    1.99 + * the result doesn't change when breaking changes).
   1.100 + *
   1.101 + * aKnownCommonAncestor is a known common ancestor of both.
   1.102 + */
   1.103 +static nsIFrame*
   1.104 +NearestCommonAncestorFirstInFlow(nsIFrame *aFrame1, nsIFrame *aFrame2,
   1.105 +                                 nsIFrame *aKnownCommonAncestor)
   1.106 +{
   1.107 +  aFrame1 = aFrame1->FirstInFlow();
   1.108 +  aFrame2 = aFrame2->FirstInFlow();
   1.109 +  aKnownCommonAncestor = aKnownCommonAncestor->FirstInFlow();
   1.110 +
   1.111 +  nsAutoTArray<nsIFrame*, 32> ancestors1, ancestors2;
   1.112 +  for (nsIFrame *f = aFrame1; f != aKnownCommonAncestor;
   1.113 +       (f = f->GetParent()) && (f = f->FirstInFlow())) {
   1.114 +    ancestors1.AppendElement(f);
   1.115 +  }
   1.116 +  for (nsIFrame *f = aFrame2; f != aKnownCommonAncestor;
   1.117 +       (f = f->GetParent()) && (f = f->FirstInFlow())) {
   1.118 +    ancestors2.AppendElement(f);
   1.119 +  }
   1.120 +
   1.121 +  nsIFrame *result = aKnownCommonAncestor;
   1.122 +  uint32_t i1 = ancestors1.Length(),
   1.123 +           i2 = ancestors2.Length();
   1.124 +  while (i1-- != 0 && i2-- != 0) {
   1.125 +    if (ancestors1[i1] != ancestors2[i2]) {
   1.126 +      break;
   1.127 +    }
   1.128 +    result = ancestors1[i1];
   1.129 +  }
   1.130 +
   1.131 +  return result;
   1.132 +}
   1.133 +
   1.134 +static nscoord
   1.135 +ComputeDescendantWidth(const nsHTMLReflowState& aAncestorReflowState,
   1.136 +                       nsIFrame *aDescendantFrame)
   1.137 +{
   1.138 +  nsIFrame *ancestorFrame = aAncestorReflowState.frame->FirstInFlow();
   1.139 +  if (aDescendantFrame == ancestorFrame) {
   1.140 +    return aAncestorReflowState.ComputedWidth();
   1.141 +  }
   1.142 +
   1.143 +  AutoInfallibleTArray<nsIFrame*, 16> frames;
   1.144 +  for (nsIFrame *f = aDescendantFrame; f != ancestorFrame;
   1.145 +       f = f->GetParent()->FirstInFlow()) {
   1.146 +    frames.AppendElement(f);
   1.147 +  }
   1.148 +
   1.149 +  // This ignores the width contributions made by scrollbars, though in
   1.150 +  // reality we don't have any scrollbars on the sorts of devices on
   1.151 +  // which we use font inflation, so it's not a problem.  But it may
   1.152 +  // occasionally cause problems when writing tests on desktop.
   1.153 +
   1.154 +  uint32_t len = frames.Length();
   1.155 +  nsHTMLReflowState *reflowStates = static_cast<nsHTMLReflowState*>
   1.156 +                                (moz_xmalloc(sizeof(nsHTMLReflowState) * len));
   1.157 +  nsPresContext *presContext = aDescendantFrame->PresContext();
   1.158 +  for (uint32_t i = 0; i < len; ++i) {
   1.159 +    const nsHTMLReflowState &parentReflowState =
   1.160 +      (i == 0) ? aAncestorReflowState : reflowStates[i - 1];
   1.161 +    nsSize availSize(parentReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE);
   1.162 +    nsIFrame *frame = frames[len - i - 1];
   1.163 +    NS_ABORT_IF_FALSE(frame->GetParent()->FirstInFlow() ==
   1.164 +                        parentReflowState.frame->FirstInFlow(),
   1.165 +                      "bad logic in this function");
   1.166 +    new (reflowStates + i) nsHTMLReflowState(presContext, parentReflowState,
   1.167 +                                             frame, availSize);
   1.168 +  }
   1.169 +
   1.170 +  NS_ABORT_IF_FALSE(reflowStates[len - 1].frame == aDescendantFrame,
   1.171 +                    "bad logic in this function");
   1.172 +  nscoord result = reflowStates[len - 1].ComputedWidth();
   1.173 +
   1.174 +  for (uint32_t i = len; i-- != 0; ) {
   1.175 +    reflowStates[i].~nsHTMLReflowState();
   1.176 +  }
   1.177 +  moz_free(reflowStates);
   1.178 +
   1.179 +  return result;
   1.180 +}
   1.181 +
   1.182 +void
   1.183 +nsFontInflationData::UpdateWidth(const nsHTMLReflowState &aReflowState)
   1.184 +{
   1.185 +  nsIFrame *bfc = aReflowState.frame;
   1.186 +  NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
   1.187 +               "must be block formatting context");
   1.188 +
   1.189 +  nsIFrame *firstInflatableDescendant =
   1.190 +             FindEdgeInflatableFrameIn(bfc, eFromStart);
   1.191 +  if (!firstInflatableDescendant) {
   1.192 +    mTextAmount = 0;
   1.193 +    mTextThreshold = 0; // doesn't matter
   1.194 +    mTextDirty = false;
   1.195 +    mInflationEnabled = false;
   1.196 +    return;
   1.197 +  }
   1.198 +  nsIFrame *lastInflatableDescendant =
   1.199 +             FindEdgeInflatableFrameIn(bfc, eFromEnd);
   1.200 +  NS_ABORT_IF_FALSE(!firstInflatableDescendant == !lastInflatableDescendant,
   1.201 +                    "null-ness should match; NearestCommonAncestorFirstInFlow"
   1.202 +                    " will crash when passed null");
   1.203 +
   1.204 +  // Particularly when we're computing for the root BFC, the width of
   1.205 +  // nca might differ significantly for the width of bfc.
   1.206 +  nsIFrame *nca = NearestCommonAncestorFirstInFlow(firstInflatableDescendant,
   1.207 +                                                   lastInflatableDescendant,
   1.208 +                                                   bfc);
   1.209 +  while (!nca->IsContainerForFontSizeInflation()) {
   1.210 +    nca = nca->GetParent()->FirstInFlow();
   1.211 +  }
   1.212 +
   1.213 +  nscoord newNCAWidth = ComputeDescendantWidth(aReflowState, nca);
   1.214 +
   1.215 +  // See comment above "font.size.inflation.lineThreshold" in
   1.216 +  // modules/libpref/src/init/all.js .
   1.217 +  nsIPresShell* presShell = bfc->PresContext()->PresShell();
   1.218 +  uint32_t lineThreshold = presShell->FontSizeInflationLineThreshold();
   1.219 +  nscoord newTextThreshold = (newNCAWidth * lineThreshold) / 100;
   1.220 +
   1.221 +  if (mTextThreshold <= mTextAmount && mTextAmount < newTextThreshold) {
   1.222 +    // Because we truncate our scan when we hit sufficient text, we now
   1.223 +    // need to rescan.
   1.224 +    mTextDirty = true;
   1.225 +  }
   1.226 +
   1.227 +  mNCAWidth = newNCAWidth;
   1.228 +  mTextThreshold = newTextThreshold;
   1.229 +  mInflationEnabled = mTextAmount >= mTextThreshold;
   1.230 +}
   1.231 +
   1.232 +/* static */ nsIFrame*
   1.233 +nsFontInflationData::FindEdgeInflatableFrameIn(nsIFrame* aFrame,
   1.234 +                                               SearchDirection aDirection)
   1.235 +{
   1.236 +  // NOTE: This function has a similar structure to ScanTextIn!
   1.237 +
   1.238 +  // FIXME: Should probably only scan the text that's actually going to
   1.239 +  // be inflated!
   1.240 +
   1.241 +  nsIFormControlFrame* fcf = do_QueryFrame(aFrame);
   1.242 +  if (fcf) {
   1.243 +    return aFrame;
   1.244 +  }
   1.245 +
   1.246 +  // FIXME: aDirection!
   1.247 +  nsAutoTArray<FrameChildList, 4> lists;
   1.248 +  aFrame->GetChildLists(&lists);
   1.249 +  for (uint32_t i = 0, len = lists.Length(); i < len; ++i) {
   1.250 +    const nsFrameList& list =
   1.251 +      lists[(aDirection == eFromStart) ? i : len - i - 1].mList;
   1.252 +    for (nsIFrame *kid = (aDirection == eFromStart) ? list.FirstChild()
   1.253 +                                                    : list.LastChild();
   1.254 +         kid;
   1.255 +         kid = (aDirection == eFromStart) ? kid->GetNextSibling()
   1.256 +                                          : kid->GetPrevSibling()) {
   1.257 +      if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
   1.258 +        // Goes in a different set of inflation data.
   1.259 +        continue;
   1.260 +      }
   1.261 +
   1.262 +      if (kid->GetType() == nsGkAtoms::textFrame) {
   1.263 +        nsIContent *content = kid->GetContent();
   1.264 +        if (content && kid == content->GetPrimaryFrame()) {
   1.265 +          uint32_t len = nsTextFrameUtils::
   1.266 +            ComputeApproximateLengthWithWhitespaceCompression(
   1.267 +              content, kid->StyleText());
   1.268 +          if (len != 0) {
   1.269 +            return kid;
   1.270 +          }
   1.271 +        }
   1.272 +      } else {
   1.273 +        nsIFrame *kidResult =
   1.274 +          FindEdgeInflatableFrameIn(kid, aDirection);
   1.275 +        if (kidResult) {
   1.276 +          return kidResult;
   1.277 +        }
   1.278 +      }
   1.279 +    }
   1.280 +  }
   1.281 +
   1.282 +  return nullptr;
   1.283 +}
   1.284 +
   1.285 +void
   1.286 +nsFontInflationData::ScanText()
   1.287 +{
   1.288 +  mTextDirty = false;
   1.289 +  mTextAmount = 0;
   1.290 +  ScanTextIn(mBFCFrame);
   1.291 +  mInflationEnabled = mTextAmount >= mTextThreshold;
   1.292 +}
   1.293 +
   1.294 +static uint32_t
   1.295 +DoCharCountOfLargestOption(nsIFrame *aContainer)
   1.296 +{
   1.297 +  uint32_t result = 0;
   1.298 +  for (nsIFrame* option = aContainer->GetFirstPrincipalChild();
   1.299 +       option; option = option->GetNextSibling()) {
   1.300 +    uint32_t optionResult;
   1.301 +    if (option->GetContent()->IsHTML(nsGkAtoms::optgroup)) {
   1.302 +      optionResult = DoCharCountOfLargestOption(option);
   1.303 +    } else {
   1.304 +      // REVIEW: Check the frame structure for this!
   1.305 +      optionResult = 0;
   1.306 +      for (nsIFrame *optionChild = option->GetFirstPrincipalChild();
   1.307 +           optionChild; optionChild = optionChild->GetNextSibling()) {
   1.308 +        if (optionChild->GetType() == nsGkAtoms::textFrame) {
   1.309 +          optionResult += nsTextFrameUtils::
   1.310 +            ComputeApproximateLengthWithWhitespaceCompression(
   1.311 +              optionChild->GetContent(), optionChild->StyleText());
   1.312 +        }
   1.313 +      }
   1.314 +    }
   1.315 +    if (optionResult > result) {
   1.316 +      result = optionResult;
   1.317 +    }
   1.318 +  }
   1.319 +  return result;
   1.320 +}
   1.321 +
   1.322 +static uint32_t
   1.323 +CharCountOfLargestOption(nsIFrame *aListControlFrame)
   1.324 +{
   1.325 +  return DoCharCountOfLargestOption(
   1.326 +    static_cast<nsListControlFrame*>(aListControlFrame)->GetOptionsContainer());
   1.327 +}
   1.328 +
   1.329 +void
   1.330 +nsFontInflationData::ScanTextIn(nsIFrame *aFrame)
   1.331 +{
   1.332 +  // NOTE: This function has a similar structure to FindEdgeInflatableFrameIn!
   1.333 +
   1.334 +  // FIXME: Should probably only scan the text that's actually going to
   1.335 +  // be inflated!
   1.336 +
   1.337 +  nsIFrame::ChildListIterator lists(aFrame);
   1.338 +  for (; !lists.IsDone(); lists.Next()) {
   1.339 +    nsFrameList::Enumerator kids(lists.CurrentList());
   1.340 +    for (; !kids.AtEnd(); kids.Next()) {
   1.341 +      nsIFrame *kid = kids.get();
   1.342 +      if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
   1.343 +        // Goes in a different set of inflation data.
   1.344 +        continue;
   1.345 +      }
   1.346 +
   1.347 +      nsIAtom *fType = kid->GetType();
   1.348 +      if (fType == nsGkAtoms::textFrame) {
   1.349 +        nsIContent *content = kid->GetContent();
   1.350 +        if (content && kid == content->GetPrimaryFrame()) {
   1.351 +          uint32_t len = nsTextFrameUtils::
   1.352 +            ComputeApproximateLengthWithWhitespaceCompression(
   1.353 +              content, kid->StyleText());
   1.354 +          if (len != 0) {
   1.355 +            nscoord fontSize = kid->StyleFont()->mFont.size;
   1.356 +            if (fontSize > 0) {
   1.357 +              mTextAmount += fontSize * len;
   1.358 +            }
   1.359 +          }
   1.360 +        }
   1.361 +      } else if (fType == nsGkAtoms::textInputFrame) {
   1.362 +        // We don't want changes to the amount of text in a text input
   1.363 +        // to change what we count towards inflation.
   1.364 +        nscoord fontSize = kid->StyleFont()->mFont.size;
   1.365 +        int32_t charCount = static_cast<nsTextControlFrame*>(kid)->GetCols();
   1.366 +        mTextAmount += charCount * fontSize;
   1.367 +      } else if (fType == nsGkAtoms::comboboxControlFrame) {
   1.368 +        // See textInputFrame above (with s/amount of text/selected option/).
   1.369 +        // Don't just recurse down to the list control inside, since we
   1.370 +        // need to exclude the display frame.
   1.371 +        nscoord fontSize = kid->StyleFont()->mFont.size;
   1.372 +        int32_t charCount = CharCountOfLargestOption(
   1.373 +          static_cast<nsComboboxControlFrame*>(kid)->GetDropDown());
   1.374 +        mTextAmount += charCount * fontSize;
   1.375 +      } else if (fType == nsGkAtoms::listControlFrame) {
   1.376 +        // See textInputFrame above (with s/amount of text/selected option/).
   1.377 +        nscoord fontSize = kid->StyleFont()->mFont.size;
   1.378 +        int32_t charCount = CharCountOfLargestOption(kid);
   1.379 +        mTextAmount += charCount * fontSize;
   1.380 +      } else {
   1.381 +        // recursive step
   1.382 +        ScanTextIn(kid);
   1.383 +      }
   1.384 +
   1.385 +      if (mTextAmount >= mTextThreshold) {
   1.386 +        return;
   1.387 +      }
   1.388 +    }
   1.389 +  }
   1.390 +}

mercurial