Build font data structure by walking the necessary text. (Bug 706193, patch 3) r=roc
authorL. David Baron <dbaron@dbaron.org>
Mon, 16 Apr 2012 15:32:12 -0700
changeset 95105 9cf58850cf26befc69b189d831ae6d8e5bff8a7d
parent 95104 9499f6b28addcbcd9c480eb80cfe6c4c63a4a3a1
child 95106 0c18caf33991da536864054f0dbaa7037332eab3
push idunknown
push userunknown
push dateunknown
reviewersroc
bugs706193
milestone14.0a1
Build font data structure by walking the necessary text. (Bug 706193, patch 3) r=roc Compute the amount of text in the scope of an nsFontInflationData object. This walks the text that's inside of the block formatting context at which this object is rooted, excluding the text that's inside any nested BFC. Using the amount of text, the font sizes of the text, and the line threshold preference, we compute whether to enable font size inflation within that block formatting context.
layout/generic/nsFontInflationData.cpp
layout/generic/nsFontInflationData.h
layout/generic/nsFrame.cpp
layout/generic/nsHTMLReflowState.cpp
layout/generic/nsTextFrameUtils.cpp
layout/generic/nsTextFrameUtils.h
--- a/layout/generic/nsFontInflationData.cpp
+++ b/layout/generic/nsFontInflationData.cpp
@@ -34,28 +34,309 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /* Per-block-formatting-context manager of font size inflation for pan and zoom UI. */
 
 #include "nsFontInflationData.h"
 #include "FramePropertyTable.h"
+#include "nsTextFragment.h"
+#include "nsIFormControlFrame.h"
+#include "nsHTMLReflowState.h"
+#include "nsTextFrameUtils.h"
 
 using namespace mozilla;
+using namespace mozilla::layout;
 
 static void
 DestroyFontInflationData(void *aPropertyValue)
 {
   delete static_cast<nsFontInflationData*>(aPropertyValue);
 }
 
 NS_DECLARE_FRAME_PROPERTY(FontInflationDataProperty, DestroyFontInflationData);
 
 /* 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->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
+               "should have found a flow root");
 
   return static_cast<nsFontInflationData*>(
              bfc->Properties().Get(FontInflationDataProperty()));
 }
+
+/* static */ void
+nsFontInflationData::UpdateFontInflationDataWidthFor(const nsHTMLReflowState& aReflowState)
+{
+  nsIFrame *bfc = aReflowState.frame;
+  NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
+               "should have been given a flow root");
+  FrameProperties bfcProps(bfc->Properties());
+  nsFontInflationData *data = static_cast<nsFontInflationData*>(
+                                bfcProps.Get(FontInflationDataProperty()));
+  if (!data) {
+    data = new nsFontInflationData(bfc);
+    bfcProps.Set(FontInflationDataProperty(), data);
+  }
+
+  data->UpdateWidth(aReflowState);
+}
+
+/* static */ void
+nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame *aBFCFrame)
+{
+  NS_ASSERTION(aBFCFrame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
+               "should have been given a flow root");
+
+  FrameProperties bfcProps(aBFCFrame->Properties());
+  nsFontInflationData *data = static_cast<nsFontInflationData*>(
+                                bfcProps.Get(FontInflationDataProperty()));
+  if (data) {
+    data->MarkTextDirty();
+  }
+}
+
+nsFontInflationData::nsFontInflationData(nsIFrame *aBFCFrame)
+  : mBFCFrame(aBFCFrame)
+  , 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->GetFirstInFlow();
+  aFrame2 = aFrame2->GetFirstInFlow();
+  aKnownCommonAncestor = aKnownCommonAncestor->GetFirstInFlow();
+
+  nsAutoTArray<nsIFrame*, 32> ancestors1, ancestors2;
+  for (nsIFrame *f = aFrame1; f != aKnownCommonAncestor;
+       (f = f->GetParent()) && (f = f->GetFirstInFlow())) {
+    ancestors1.AppendElement(f);
+  }
+  for (nsIFrame *f = aFrame2; f != aKnownCommonAncestor;
+       (f = f->GetParent()) && (f = f->GetFirstInFlow())) {
+    ancestors2.AppendElement(f);
+  }
+
+  nsIFrame *result = aKnownCommonAncestor;
+  PRUint32 i1 = ancestors1.Length(),
+           i2 = ancestors2.Length();
+  while (i1-- != 0 && i2-- != 0) {
+    if (ancestors1[i1] != ancestors2[i2]) {
+      break;
+    }
+    result = ancestors1[i1];
+  }
+
+  return result;
+}
+
+static nscoord
+ComputeDescendantWidth(const nsHTMLReflowState& aAncestorReflowState,
+                       nsIFrame *aDescendantFrame)
+{
+  nsIFrame *ancestorFrame = aAncestorReflowState.frame->GetFirstInFlow();
+  if (aDescendantFrame == ancestorFrame) {
+    return aAncestorReflowState.ComputedWidth();
+  }
+
+  AutoInfallibleTArray<nsIFrame*, 16> frames;
+  for (nsIFrame *f = aDescendantFrame; f != ancestorFrame;
+       f = f->GetParent()) {
+    frames.AppendElement(f);
+  }
+
+  PRUint32 len = frames.Length();
+  nsHTMLReflowState *reflowStates = static_cast<nsHTMLReflowState*>
+                                (moz_xmalloc(sizeof(nsHTMLReflowState) * len));
+  nsPresContext *presContext = aDescendantFrame->PresContext();
+  for (PRUint32 i = 0; i < len; ++i) {
+    const nsHTMLReflowState &parentReflowState =
+      (i == 0) ? aAncestorReflowState : reflowStates[i - 1];
+    nsSize availSize(parentReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE);
+    nsIFrame *frame = frames[len - i - 1];
+    NS_ABORT_IF_FALSE(frame->GetParent()->GetFirstInFlow() ==
+                        parentReflowState.frame->GetFirstInFlow(),
+                      "bad logic in this function");
+    new (reflowStates + i) nsHTMLReflowState(presContext, parentReflowState,
+                                             frame, availSize);
+  }
+
+  NS_ABORT_IF_FALSE(reflowStates[len - 1].frame == aDescendantFrame,
+                    "bad logic in this function");
+  nscoord result = reflowStates[len - 1].ComputedWidth();
+
+  for (PRUint32 i = len; i-- != 0; ) {
+    reflowStates[i].~nsHTMLReflowState();
+  }
+  moz_free(reflowStates);
+
+  return result;
+}
+
+void
+nsFontInflationData::UpdateWidth(const nsHTMLReflowState &aReflowState)
+{
+  nsIFrame *bfc = aReflowState.frame;
+  NS_ASSERTION(bfc->GetStateBits() & 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);
+  NS_ABORT_IF_FALSE(!firstInflatableDescendant == !lastInflatableDescendant,
+                    "null-ness should match; NearestCommonAncestorFirstInFlow"
+                    " will crash when passed null");
+
+  // Particularly when we're computing for the root BFC, the width of
+  // nca might differ significantly for the width of bfc.
+  nsIFrame *nca = NearestCommonAncestorFirstInFlow(firstInflatableDescendant,
+                                                   lastInflatableDescendant,
+                                                   bfc);
+  while (!nsLayoutUtils::IsContainerForFontSizeInflation(nca)) {
+    nca = nca->GetParent();
+  }
+
+  nscoord newNCAWidth = ComputeDescendantWidth(aReflowState, nca);
+
+  // See comment above "font.size.inflation.lineThreshold" in
+  // modules/libpref/src/init/all.js .
+  PRUint32 lineThreshold = nsLayoutUtils::FontSizeInflationLineThreshold();
+  nscoord newTextThreshold = (newNCAWidth * lineThreshold) / 100;
+
+  if (mTextThreshold <= mTextAmount && mTextAmount < newTextThreshold) {
+    // Because we truncate our scan when we hit sufficient text, we now
+    // need to rescan.
+    mTextDirty = true;
+  }
+
+  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!
+  nsAutoTArray<FrameChildList, 4> lists;
+  aFrame->GetChildLists(&lists);
+  for (PRUint32 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->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
+        // Goes in a different set of inflation data.
+        continue;
+      }
+
+      if (kid->GetType() == nsGkAtoms::textFrame) {
+        nsIContent *content = kid->GetContent();
+        if (content && kid == content->GetPrimaryFrame()) {
+          PRUint32 len = nsTextFrameUtils::
+            ComputeApproximateLengthWithWhitespaceCompression(
+              content, kid->GetStyleText());
+          if (len != 0) {
+            return kid;
+          }
+        }
+      } else {
+        nsIFrame *kidResult =
+          FindEdgeInflatableFrameIn(kid, aDirection);
+        if (kidResult) {
+          return kidResult;
+        }
+      }
+    }
+  }
+
+  return nsnull;
+}
+
+void
+nsFontInflationData::ScanText()
+{
+  mTextDirty = false;
+  mTextAmount = 0;
+  ScanTextIn(mBFCFrame);
+  mInflationEnabled = mTextAmount >= mTextThreshold;
+}
+
+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!
+
+  nsIFrame::ChildListIterator lists(aFrame);
+  for (; !lists.IsDone(); lists.Next()) {
+    nsFrameList::Enumerator kids(lists.CurrentList());
+    for (; !kids.AtEnd(); kids.Next()) {
+      nsIFrame *kid = kids.get();
+      if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
+        // Goes in a different set of inflation data.
+        continue;
+      }
+
+      if (kid->GetType() == nsGkAtoms::textFrame) {
+        nsIContent *content = kid->GetContent();
+        if (content && kid == content->GetPrimaryFrame()) {
+          PRUint32 len = nsTextFrameUtils::
+            ComputeApproximateLengthWithWhitespaceCompression(
+              content, kid->GetStyleText());
+          if (len != 0) {
+            nscoord fontSize = kid->GetStyleFont()->mFont.size;
+            if (fontSize > 0) {
+              mTextAmount += fontSize * len;
+            }
+          }
+        }
+      } else {
+        // recursive step
+        ScanTextIn(kid);
+      }
+
+      if (mTextAmount >= mTextThreshold) {
+        return;
+      }
+    }
+  }
+}
--- a/layout/generic/nsFontInflationData.h
+++ b/layout/generic/nsFontInflationData.h
@@ -39,27 +39,64 @@
 
 #ifndef nsFontInflationData_h_
 #define nsFontInflationData_h_
 
 #include "nsIFrame.h"
 #include "nsLayoutUtils.h"
 #include "nsBlockFrame.h"
 
+struct nsHTMLReflowState;
+
 class nsFontInflationData
 {
 public:
 
   static nsFontInflationData* FindFontInflationDataFor(const nsIFrame *aFrame);
 
+  static void
+    UpdateFontInflationDataWidthFor(const nsHTMLReflowState& aReflowState);
+
+  static void MarkFontInflationDataTextDirty(nsIFrame *aFrame);
+
+  bool InflationEnabled() {
+    if (mTextDirty) {
+      ScanText();
+    }
+    return mInflationEnabled;
+  }
+
 private:
 
+  nsFontInflationData(nsIFrame* aBFCFrame);
+
+  nsFontInflationData(const nsFontInflationData&) MOZ_DELETE;
+  void operator=(const nsFontInflationData&) MOZ_DELETE;
+
+  void UpdateWidth(const nsHTMLReflowState &aReflowState);
+  enum SearchDirection { eFromStart, eFromEnd };
+  static nsIFrame* FindEdgeInflatableFrameIn(nsIFrame *aFrame,
+                                             SearchDirection aDirection);
+
+  void MarkTextDirty() { mTextDirty = true; }
+  void ScanText();
+  // Scan text in the subtree rooted at aFrame.  Increment mTextAmount
+  // by multiplying the number of characters found by the font size
+  // (yielding the width that would be occupied by the characters if
+  // they were all em squares).  But stop scanning if mTextAmount
+  // crosses mTextThreshold.
+  void ScanTextIn(nsIFrame *aFrame);
+
   static const nsIFrame* FlowRootFor(const nsIFrame *aFrame)
   {
     while (!(aFrame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
       aFrame = aFrame->GetParent();
     }
     return aFrame;
   }
 
+  nsIFrame *mBFCFrame;
+  nscoord mTextAmount, mTextThreshold;
+  bool mInflationEnabled; // for this BFC
+  bool mTextDirty;
 };
 
 #endif /* !defined(nsFontInflationData_h_) */
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -122,16 +122,17 @@
 #include "nsChangeHint.h"
 #include "nsDeckFrame.h"
 #include "nsTableFrame.h"
 
 #include "gfxContext.h"
 #include "nsRenderingContext.h"
 #include "CSSCalc.h"
 #include "nsAbsoluteContainingBlock.h"
+#include "nsFontInflationData.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 
@@ -3513,16 +3514,20 @@ nsFrame::MarkIntrinsicWidthsDirty()
     SizeNeedsRecalc(metrics->mPrefSize);
     SizeNeedsRecalc(metrics->mMinSize);
     SizeNeedsRecalc(metrics->mMaxSize);
     SizeNeedsRecalc(metrics->mBlockPrefSize);
     SizeNeedsRecalc(metrics->mBlockMinSize);
     CoordNeedsRecalc(metrics->mFlex);
     CoordNeedsRecalc(metrics->mAscent);
   }
+
+  if (GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
+    nsFontInflationData::MarkFontInflationDataTextDirty(this);
+  }
 }
 
 /* virtual */ nscoord
 nsFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
 {
   nscoord result = 0;
   DISPLAY_MIN_WIDTH(this, result);
   return result;
--- a/layout/generic/nsHTMLReflowState.cpp
+++ b/layout/generic/nsHTMLReflowState.cpp
@@ -51,19 +51,18 @@
 #include "nsLineBox.h"
 #include "nsImageFrame.h"
 #include "nsTableFrame.h"
 #include "nsTableCellFrame.h"
 #include "nsIServiceManager.h"
 #include "nsIPercentHeightObserver.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/Preferences.h"
-#ifdef IBMBIDI
 #include "nsBidiUtils.h"
-#endif
+#include "nsFontInflationData.h"
 
 #ifdef NS_DEBUG
 #undef NOISY_VERTICAL_ALIGN
 #else
 #undef NOISY_VERTICAL_ALIGN
 #endif
 
 using namespace mozilla;
@@ -311,16 +310,22 @@ nsHTMLReflowState::Init(nsPresContext* a
 
   NS_WARN_IF_FALSE((mFrameType == NS_CSS_FRAME_TYPE_INLINE &&
                     !frame->IsFrameOfType(nsIFrame::eReplaced)) ||
                    type == nsGkAtoms::textFrame ||
                    mComputedWidth != NS_UNCONSTRAINEDSIZE,
                    "have unconstrained width; this should only result from "
                    "very large sizes, not attempts at intrinsic width "
                    "calculation");
+
+  if (frame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
+    // Create our font inflation data if we don't have it already, and
+    // give it our current width information.
+    nsFontInflationData::UpdateFontInflationDataWidthFor(*this);
+  }
 }
 
 void nsHTMLReflowState::InitCBReflowState()
 {
   if (!parentReflowState) {
     mCBReflowState = nsnull;
     return;
   }
--- a/layout/generic/nsTextFrameUtils.cpp
+++ b/layout/generic/nsTextFrameUtils.cpp
@@ -38,16 +38,18 @@
 
 #include "nsTextFrameUtils.h"
 
 #include "nsContentUtils.h"
 #include "nsIWordBreaker.h"
 #include "gfxFont.h"
 #include "nsUnicharUtils.h"
 #include "nsBidiUtils.h"
+#include "nsIContent.h"
+#include "nsStyleStruct.h"
 
 // XXX TODO implement transform of backslash to yen that nsTextTransform does
 // when requested by PresContext->LanguageSpecificTransformType(). Do it with
 // a new factory type that just munges the input stream. But first, check
 // that we really still need this, it's only enabled via a hidden pref
 // which defaults false...
 
 #define UNICODE_ZWSP 0x200B
@@ -243,16 +245,57 @@ nsTextFrameUtils::TransformText(const PR
 
   if (outputStart + aLength != aOutput) {
     flags |= TEXT_WAS_TRANSFORMED;
   }
   *aAnalysisFlags = flags;
   return aOutput;
 }
 
+PRUint32
+nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
+                    nsIContent *aContent, const nsStyleText *aStyleText)
+{
+  const nsTextFragment *frag = aContent->GetText();
+  // This is an approximation so we don't really need anything
+  // too fancy here.
+  PRUint32 len;
+  if (aStyleText->WhiteSpaceIsSignificant()) {
+    len = frag->GetLength();
+  } else {
+    bool is2b = frag->Is2b();
+    union {
+      const char *s1b;
+      const PRUnichar *s2b;
+    } u;
+    if (is2b) {
+      u.s2b = frag->Get2b();
+    } else {
+      u.s1b = frag->Get1b();
+    }
+    bool prevWS = true; // more important to ignore blocks with
+                        // only whitespace than get inline boundaries
+                        // exactly right
+    len = 0;
+    for (PRUint32 i = 0, i_end = frag->GetLength(); i < i_end; ++i) {
+      PRUnichar c = is2b ? u.s2b[i] : u.s1b[i];
+      if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
+        if (!prevWS) {
+          ++len;
+        }
+        prevWS = true;
+      } else {
+        ++len;
+        prevWS = false;
+      }
+    }
+  }
+  return len;
+}
+
 bool nsSkipCharsRunIterator::NextRun() {
   do {
     if (mRunLength) {
       mIterator.AdvanceOriginal(mRunLength);
       NS_ASSERTION(mRunLength > 0, "No characters in run (initial length too large?)");
       if (!mSkipped || mLengthIncludesSkipped) {
         mRemainingLength -= mRunLength;
       }
--- a/layout/generic/nsTextFrameUtils.h
+++ b/layout/generic/nsTextFrameUtils.h
@@ -38,16 +38,19 @@
 
 #ifndef NSTEXTFRAMEUTILS_H_
 #define NSTEXTFRAMEUTILS_H_
 
 #include "gfxFont.h"
 #include "gfxSkipChars.h"
 #include "nsTextFragment.h"
 
+class nsIContent;
+struct nsStyleText;
+
 #define BIG_TEXT_NODE_SIZE 4096
 
 #define CH_NBSP   160
 #define CH_SHY    173
 #define CH_CJKSP  12288 // U+3000 IDEOGRAPHIC SPACE (CJK Full-Width Space)
 
 #define CH_LRM  8206  //<!ENTITY lrm     CDATA "&#8206;" -- left-to-right mark, U+200E NEW RFC 2070 -->
 #define CH_RLM  8207  //<!ENTITY rlm     CDATA "&#8207;" -- right-to-left mark, U+200F NEW RFC 2070 -->
@@ -142,16 +145,20 @@ public:
   static void
   AppendLineBreakOffset(nsTArray<PRUint32>* aArray, PRUint32 aOffset)
   {
     if (aArray->Length() > 0 && (*aArray)[aArray->Length() - 1] == aOffset)
       return;
     aArray->AppendElement(aOffset);
   }
 
+  static PRUint32
+  ComputeApproximateLengthWithWhitespaceCompression(nsIContent *aContent,
+                                                    const nsStyleText
+                                                      *aStyleText);
 };
 
 class nsSkipCharsRunIterator {
 public:
   enum LengthMode {
     LENGTH_UNSKIPPED_ONLY   = false,
     LENGTH_INCLUDES_SKIPPED = true
   };