tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsFontInflationData.cpp (14014B)


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