summaryrefslogtreecommitdiffstats
path: root/layout/generic/nsFontInflationData.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/generic/nsFontInflationData.cpp')
-rw-r--r--layout/generic/nsFontInflationData.cpp373
1 files changed, 373 insertions, 0 deletions
diff --git a/layout/generic/nsFontInflationData.cpp b/layout/generic/nsFontInflationData.cpp
new file mode 100644
index 0000000000..943b4109ad
--- /dev/null
+++ b/layout/generic/nsFontInflationData.cpp
@@ -0,0 +1,373 @@
+/* -*- 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!
+
+ nsIFormControlFrame* fcf = do_QueryFrame(aFrame);
+ if (fcf) {
+ 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)->GetCols();
+ 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 = CharCountOfLargestOption(
+ static_cast<nsComboboxControlFrame*>(kid)->GetDropDown());
+ 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;
+ }
+ }
+ }
+}