mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	Depends on D229247 Differential Revision: https://phabricator.services.mozilla.com/D229248
		
			
				
	
	
		
			375 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			375 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
/* Per-block-formatting-context manager of font size inflation for pan and zoom
 | 
						|
 * UI. */
 | 
						|
 | 
						|
#include "nsFontInflationData.h"
 | 
						|
#include "FrameProperties.h"
 | 
						|
#include "nsTextControlFrame.h"
 | 
						|
#include "nsListControlFrame.h"
 | 
						|
#include "nsComboboxControlFrame.h"
 | 
						|
#include "mozilla/dom/Text.h"  // for inline nsINode::AsText() definition
 | 
						|
#include "mozilla/PresShell.h"
 | 
						|
#include "mozilla/ReflowInput.h"
 | 
						|
#include "nsTextFrameUtils.h"
 | 
						|
 | 
						|
using namespace mozilla;
 | 
						|
using namespace mozilla::layout;
 | 
						|
 | 
						|
NS_DECLARE_FRAME_PROPERTY_DELETABLE(FontInflationDataProperty,
 | 
						|
                                    nsFontInflationData)
 | 
						|
 | 
						|
/* static */ nsFontInflationData* nsFontInflationData::FindFontInflationDataFor(
 | 
						|
    const nsIFrame* aFrame) {
 | 
						|
  // We have one set of font inflation data per block formatting context.
 | 
						|
  const nsIFrame* bfc = FlowRootFor(aFrame);
 | 
						|
  NS_ASSERTION(bfc->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT),
 | 
						|
               "should have found a flow root");
 | 
						|
  MOZ_ASSERT(aFrame->GetWritingMode().IsVertical() ==
 | 
						|
                 bfc->GetWritingMode().IsVertical(),
 | 
						|
             "current writing mode should match that of our flow root");
 | 
						|
 | 
						|
  return bfc->GetProperty(FontInflationDataProperty());
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool nsFontInflationData::UpdateFontInflationDataISizeFor(
 | 
						|
    const ReflowInput& aReflowInput) {
 | 
						|
  nsIFrame* bfc = aReflowInput.mFrame;
 | 
						|
  NS_ASSERTION(bfc->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT),
 | 
						|
               "should have been given a flow root");
 | 
						|
  nsFontInflationData* data = bfc->GetProperty(FontInflationDataProperty());
 | 
						|
  bool oldInflationEnabled;
 | 
						|
  nscoord oldUsableISize;
 | 
						|
  if (data) {
 | 
						|
    oldUsableISize = data->mUsableISize;
 | 
						|
    oldInflationEnabled = data->mInflationEnabled;
 | 
						|
  } else {
 | 
						|
    data = new nsFontInflationData(bfc);
 | 
						|
    bfc->SetProperty(FontInflationDataProperty(), data);
 | 
						|
    oldUsableISize = -1;
 | 
						|
    oldInflationEnabled = true; /* not relevant */
 | 
						|
  }
 | 
						|
 | 
						|
  data->UpdateISize(aReflowInput);
 | 
						|
 | 
						|
  if (oldInflationEnabled != data->mInflationEnabled) {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  return oldInflationEnabled && oldUsableISize != data->mUsableISize;
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
void nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame* aBFCFrame) {
 | 
						|
  NS_ASSERTION(aBFCFrame->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT),
 | 
						|
               "should have been given a flow root");
 | 
						|
 | 
						|
  nsFontInflationData* data =
 | 
						|
      aBFCFrame->GetProperty(FontInflationDataProperty());
 | 
						|
  if (data) {
 | 
						|
    data->MarkTextDirty();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
nsFontInflationData::nsFontInflationData(nsIFrame* aBFCFrame)
 | 
						|
    : mBFCFrame(aBFCFrame),
 | 
						|
      mUsableISize(0),
 | 
						|
      mTextAmount(0),
 | 
						|
      mTextThreshold(0),
 | 
						|
      mInflationEnabled(false),
 | 
						|
      mTextDirty(true) {}
 | 
						|
 | 
						|
/**
 | 
						|
 * Find the closest common ancestor between aFrame1 and aFrame2, except
 | 
						|
 * treating the parent of a frame as the first-in-flow of its parent (so
 | 
						|
 * the result doesn't change when breaking changes).
 | 
						|
 *
 | 
						|
 * aKnownCommonAncestor is a known common ancestor of both.
 | 
						|
 */
 | 
						|
static nsIFrame* NearestCommonAncestorFirstInFlow(
 | 
						|
    nsIFrame* aFrame1, nsIFrame* aFrame2, nsIFrame* aKnownCommonAncestor) {
 | 
						|
  aFrame1 = aFrame1->FirstInFlow();
 | 
						|
  aFrame2 = aFrame2->FirstInFlow();
 | 
						|
  aKnownCommonAncestor = aKnownCommonAncestor->FirstInFlow();
 | 
						|
 | 
						|
  AutoTArray<nsIFrame*, 32> ancestors1, ancestors2;
 | 
						|
  for (nsIFrame* f = aFrame1; f != aKnownCommonAncestor;
 | 
						|
       (f = f->GetParent()) && (f = f->FirstInFlow())) {
 | 
						|
    ancestors1.AppendElement(f);
 | 
						|
  }
 | 
						|
  for (nsIFrame* f = aFrame2; f != aKnownCommonAncestor;
 | 
						|
       (f = f->GetParent()) && (f = f->FirstInFlow())) {
 | 
						|
    ancestors2.AppendElement(f);
 | 
						|
  }
 | 
						|
 | 
						|
  nsIFrame* result = aKnownCommonAncestor;
 | 
						|
  uint32_t i1 = ancestors1.Length(), i2 = ancestors2.Length();
 | 
						|
  while (i1-- != 0 && i2-- != 0) {
 | 
						|
    if (ancestors1[i1] != ancestors2[i2]) {
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    result = ancestors1[i1];
 | 
						|
  }
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
static nscoord ComputeDescendantISize(const ReflowInput& aAncestorReflowInput,
 | 
						|
                                      nsIFrame* aDescendantFrame) {
 | 
						|
  nsIFrame* ancestorFrame = aAncestorReflowInput.mFrame->FirstInFlow();
 | 
						|
  if (aDescendantFrame == ancestorFrame) {
 | 
						|
    return aAncestorReflowInput.ComputedISize();
 | 
						|
  }
 | 
						|
 | 
						|
  AutoTArray<nsIFrame*, 16> frames;
 | 
						|
  for (nsIFrame* f = aDescendantFrame; f != ancestorFrame;
 | 
						|
       f = f->GetParent()->FirstInFlow()) {
 | 
						|
    frames.AppendElement(f);
 | 
						|
  }
 | 
						|
 | 
						|
  // This ignores the inline-size contributions made by scrollbars, though in
 | 
						|
  // reality we don't have any scrollbars on the sorts of devices on
 | 
						|
  // which we use font inflation, so it's not a problem.  But it may
 | 
						|
  // occasionally cause problems when writing tests on desktop.
 | 
						|
 | 
						|
  uint32_t len = frames.Length();
 | 
						|
  ReflowInput* reflowInputs =
 | 
						|
      static_cast<ReflowInput*>(moz_xmalloc(sizeof(ReflowInput) * len));
 | 
						|
  nsPresContext* presContext = aDescendantFrame->PresContext();
 | 
						|
  for (uint32_t i = 0; i < len; ++i) {
 | 
						|
    const ReflowInput& parentReflowInput =
 | 
						|
        (i == 0) ? aAncestorReflowInput : reflowInputs[i - 1];
 | 
						|
    nsIFrame* frame = frames[len - i - 1];
 | 
						|
    WritingMode wm = frame->GetWritingMode();
 | 
						|
    LogicalSize availSize = parentReflowInput.ComputedSize(wm);
 | 
						|
    availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
 | 
						|
    MOZ_ASSERT(frame->GetParent()->FirstInFlow() ==
 | 
						|
                   parentReflowInput.mFrame->FirstInFlow(),
 | 
						|
               "bad logic in this function");
 | 
						|
    new (reflowInputs + i)
 | 
						|
        ReflowInput(presContext, parentReflowInput, frame, availSize);
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(reflowInputs[len - 1].mFrame == aDescendantFrame,
 | 
						|
             "bad logic in this function");
 | 
						|
  nscoord result = reflowInputs[len - 1].ComputedISize();
 | 
						|
 | 
						|
  for (uint32_t i = len; i-- != 0;) {
 | 
						|
    reflowInputs[i].~ReflowInput();
 | 
						|
  }
 | 
						|
  free(reflowInputs);
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
void nsFontInflationData::UpdateISize(const ReflowInput& aReflowInput) {
 | 
						|
  nsIFrame* bfc = aReflowInput.mFrame;
 | 
						|
  NS_ASSERTION(bfc->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT),
 | 
						|
               "must be block formatting context");
 | 
						|
 | 
						|
  nsIFrame* firstInflatableDescendant =
 | 
						|
      FindEdgeInflatableFrameIn(bfc, eFromStart);
 | 
						|
  if (!firstInflatableDescendant) {
 | 
						|
    mTextAmount = 0;
 | 
						|
    mTextThreshold = 0;  // doesn't matter
 | 
						|
    mTextDirty = false;
 | 
						|
    mInflationEnabled = false;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  nsIFrame* lastInflatableDescendant = FindEdgeInflatableFrameIn(bfc, eFromEnd);
 | 
						|
  MOZ_ASSERT(!firstInflatableDescendant == !lastInflatableDescendant,
 | 
						|
             "null-ness should match; NearestCommonAncestorFirstInFlow"
 | 
						|
             " will crash when passed null");
 | 
						|
 | 
						|
  // Particularly when we're computing for the root BFC, the inline-size of
 | 
						|
  // nca might differ significantly for the inline-size of bfc.
 | 
						|
  nsIFrame* nca = NearestCommonAncestorFirstInFlow(
 | 
						|
      firstInflatableDescendant, lastInflatableDescendant, bfc);
 | 
						|
  while (!nca->IsContainerForFontSizeInflation()) {
 | 
						|
    nca = nca->GetParent()->FirstInFlow();
 | 
						|
  }
 | 
						|
 | 
						|
  nscoord newNCAISize = ComputeDescendantISize(aReflowInput, nca);
 | 
						|
 | 
						|
  // See comment above "font.size.inflation.lineThreshold" in
 | 
						|
  // modules/libpref/src/init/StaticPrefList.yaml .
 | 
						|
  PresShell* presShell = bfc->PresShell();
 | 
						|
  uint32_t lineThreshold = presShell->FontSizeInflationLineThreshold();
 | 
						|
  nscoord newTextThreshold = (newNCAISize * lineThreshold) / 100;
 | 
						|
 | 
						|
  if (mTextThreshold <= mTextAmount && mTextAmount < newTextThreshold) {
 | 
						|
    // Because we truncate our scan when we hit sufficient text, we now
 | 
						|
    // need to rescan.
 | 
						|
    mTextDirty = true;
 | 
						|
  }
 | 
						|
 | 
						|
  // Font inflation increases the font size for a given flow root so that the
 | 
						|
  // text is legible when we've zoomed such that the respective nearest common
 | 
						|
  // ancestor's (NCA) full inline-size (ISize) fills the screen. We assume how-
 | 
						|
  // ever that we don't want to zoom out further than the root iframe's ISize
 | 
						|
  // (i.e. the viewport for a top-level document, or the containing iframe
 | 
						|
  // otherwise), since in some cases zooming out further might not even be
 | 
						|
  // possible or make sense.
 | 
						|
  // Hence the ISize assumed to be usable for displaying text is limited to the
 | 
						|
  // visible area.
 | 
						|
  nsPresContext* presContext = bfc->PresContext();
 | 
						|
  MOZ_ASSERT(
 | 
						|
      bfc->GetWritingMode().IsVertical() == nca->GetWritingMode().IsVertical(),
 | 
						|
      "writing mode of NCA should match that of its flow root");
 | 
						|
  nscoord iFrameISize = bfc->GetWritingMode().IsVertical()
 | 
						|
                            ? presContext->GetVisibleArea().height
 | 
						|
                            : presContext->GetVisibleArea().width;
 | 
						|
  mUsableISize = std::min(iFrameISize, newNCAISize);
 | 
						|
  mTextThreshold = newTextThreshold;
 | 
						|
  mInflationEnabled = mTextAmount >= mTextThreshold;
 | 
						|
}
 | 
						|
 | 
						|
/* static */ nsIFrame* nsFontInflationData::FindEdgeInflatableFrameIn(
 | 
						|
    nsIFrame* aFrame, SearchDirection aDirection) {
 | 
						|
  // NOTE: This function has a similar structure to ScanTextIn!
 | 
						|
 | 
						|
  // FIXME: Should probably only scan the text that's actually going to
 | 
						|
  // be inflated!
 | 
						|
 | 
						|
  if (aFrame->IsTextInputFrame()) {
 | 
						|
    return aFrame;
 | 
						|
  }
 | 
						|
 | 
						|
  // FIXME: aDirection!
 | 
						|
  AutoTArray<FrameChildList, 4> lists;
 | 
						|
  aFrame->GetChildLists(&lists);
 | 
						|
  for (uint32_t i = 0, len = lists.Length(); i < len; ++i) {
 | 
						|
    const nsFrameList& list =
 | 
						|
        lists[(aDirection == eFromStart) ? i : len - i - 1].mList;
 | 
						|
    for (nsIFrame* kid = (aDirection == eFromStart) ? list.FirstChild()
 | 
						|
                                                    : list.LastChild();
 | 
						|
         kid; kid = (aDirection == eFromStart) ? kid->GetNextSibling()
 | 
						|
                                               : kid->GetPrevSibling()) {
 | 
						|
      if (kid->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
 | 
						|
        // Goes in a different set of inflation data.
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      if (kid->IsTextFrame()) {
 | 
						|
        nsIContent* content = kid->GetContent();
 | 
						|
        if (content && kid == content->GetPrimaryFrame()) {
 | 
						|
          uint32_t len = nsTextFrameUtils::
 | 
						|
              ComputeApproximateLengthWithWhitespaceCompression(
 | 
						|
                  content->AsText(), kid->StyleText());
 | 
						|
          if (len != 0) {
 | 
						|
            return kid;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        nsIFrame* kidResult = FindEdgeInflatableFrameIn(kid, aDirection);
 | 
						|
        if (kidResult) {
 | 
						|
          return kidResult;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
void nsFontInflationData::ScanText() {
 | 
						|
  mTextDirty = false;
 | 
						|
  mTextAmount = 0;
 | 
						|
  ScanTextIn(mBFCFrame);
 | 
						|
  mInflationEnabled = mTextAmount >= mTextThreshold;
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t DoCharCountOfLargestOption(nsIFrame* aContainer) {
 | 
						|
  uint32_t result = 0;
 | 
						|
  for (nsIFrame* option : aContainer->PrincipalChildList()) {
 | 
						|
    uint32_t optionResult;
 | 
						|
    if (option->GetContent()->IsHTMLElement(nsGkAtoms::optgroup)) {
 | 
						|
      optionResult = DoCharCountOfLargestOption(option);
 | 
						|
    } else {
 | 
						|
      // REVIEW: Check the frame structure for this!
 | 
						|
      optionResult = 0;
 | 
						|
      for (nsIFrame* optionChild : option->PrincipalChildList()) {
 | 
						|
        if (optionChild->IsTextFrame()) {
 | 
						|
          optionResult += nsTextFrameUtils::
 | 
						|
              ComputeApproximateLengthWithWhitespaceCompression(
 | 
						|
                  optionChild->GetContent()->AsText(),
 | 
						|
                  optionChild->StyleText());
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (optionResult > result) {
 | 
						|
      result = optionResult;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t CharCountOfLargestOption(nsIFrame* aListControlFrame) {
 | 
						|
  return DoCharCountOfLargestOption(
 | 
						|
      static_cast<nsListControlFrame*>(aListControlFrame)
 | 
						|
          ->GetOptionsContainer());
 | 
						|
}
 | 
						|
 | 
						|
void nsFontInflationData::ScanTextIn(nsIFrame* aFrame) {
 | 
						|
  // NOTE: This function has a similar structure to FindEdgeInflatableFrameIn!
 | 
						|
 | 
						|
  // FIXME: Should probably only scan the text that's actually going to
 | 
						|
  // be inflated!
 | 
						|
 | 
						|
  for (const auto& childList : aFrame->ChildLists()) {
 | 
						|
    for (nsIFrame* kid : childList.mList) {
 | 
						|
      if (kid->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
 | 
						|
        // Goes in a different set of inflation data.
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      LayoutFrameType fType = kid->Type();
 | 
						|
      if (fType == LayoutFrameType::Text) {
 | 
						|
        nsIContent* content = kid->GetContent();
 | 
						|
        if (content && kid == content->GetPrimaryFrame()) {
 | 
						|
          uint32_t len = nsTextFrameUtils::
 | 
						|
              ComputeApproximateLengthWithWhitespaceCompression(
 | 
						|
                  content->AsText(), kid->StyleText());
 | 
						|
          if (len != 0) {
 | 
						|
            nscoord fontSize = kid->StyleFont()->mFont.size.ToAppUnits();
 | 
						|
            if (fontSize > 0) {
 | 
						|
              mTextAmount += fontSize * len;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      } else if (fType == LayoutFrameType::TextInput) {
 | 
						|
        // We don't want changes to the amount of text in a text input
 | 
						|
        // to change what we count towards inflation.
 | 
						|
        nscoord fontSize = kid->StyleFont()->mFont.size.ToAppUnits();
 | 
						|
        int32_t charCount =
 | 
						|
            static_cast<nsTextControlFrame*>(kid)->GetColsOrDefault();
 | 
						|
        mTextAmount += charCount * fontSize;
 | 
						|
      } else if (fType == LayoutFrameType::ComboboxControl) {
 | 
						|
        // See textInputFrame above (with s/amount of text/selected option/).
 | 
						|
        // Don't just recurse down to the list control inside, since we
 | 
						|
        // need to exclude the display frame.
 | 
						|
        nscoord fontSize = kid->StyleFont()->mFont.size.ToAppUnits();
 | 
						|
        int32_t charCount = static_cast<nsComboboxControlFrame*>(kid)
 | 
						|
                                ->CharCountOfLargestOptionForInflation();
 | 
						|
        mTextAmount += charCount * fontSize;
 | 
						|
      } else if (fType == LayoutFrameType::ListControl) {
 | 
						|
        // See textInputFrame above (with s/amount of text/selected option/).
 | 
						|
        nscoord fontSize = kid->StyleFont()->mFont.size.ToAppUnits();
 | 
						|
        int32_t charCount = CharCountOfLargestOption(kid);
 | 
						|
        mTextAmount += charCount * fontSize;
 | 
						|
      } else {
 | 
						|
        // recursive step
 | 
						|
        ScanTextIn(kid);
 | 
						|
      }
 | 
						|
 | 
						|
      if (mTextAmount >= mTextThreshold) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 |