layout/generic/nsFontInflationData.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 /* Per-block-formatting-context manager of font size inflation for pan and zoom UI. */
     8 #include "nsFontInflationData.h"
     9 #include "FramePropertyTable.h"
    10 #include "nsTextControlFrame.h"
    11 #include "nsListControlFrame.h"
    12 #include "nsComboboxControlFrame.h"
    13 #include "nsHTMLReflowState.h"
    14 #include "nsTextFrameUtils.h"
    16 using namespace mozilla;
    17 using namespace mozilla::layout;
    19 static void
    20 DestroyFontInflationData(void *aPropertyValue)
    21 {
    22   delete static_cast<nsFontInflationData*>(aPropertyValue);
    23 }
    25 NS_DECLARE_FRAME_PROPERTY(FontInflationDataProperty, DestroyFontInflationData)
    27 /* static */ nsFontInflationData*
    28 nsFontInflationData::FindFontInflationDataFor(const nsIFrame *aFrame)
    29 {
    30   // We have one set of font inflation data per block formatting context.
    31   const nsIFrame *bfc = FlowRootFor(aFrame);
    32   NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
    33                "should have found a flow root");
    35   return static_cast<nsFontInflationData*>(
    36              bfc->Properties().Get(FontInflationDataProperty()));
    37 }
    39 /* static */ bool
    40 nsFontInflationData::UpdateFontInflationDataWidthFor(const nsHTMLReflowState& aReflowState)
    41 {
    42   nsIFrame *bfc = aReflowState.frame;
    43   NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
    44                "should have been given a flow root");
    45   FrameProperties bfcProps(bfc->Properties());
    46   nsFontInflationData *data = static_cast<nsFontInflationData*>(
    47                                 bfcProps.Get(FontInflationDataProperty()));
    48   bool oldInflationEnabled;
    49   nscoord oldNCAWidth;
    50   if (data) {
    51     oldNCAWidth = data->mNCAWidth;
    52     oldInflationEnabled = data->mInflationEnabled;
    53   } else {
    54     data = new nsFontInflationData(bfc);
    55     bfcProps.Set(FontInflationDataProperty(), data);
    56     oldNCAWidth = -1;
    57     oldInflationEnabled = true; /* not relevant */
    58   }
    60   data->UpdateWidth(aReflowState);
    62   if (oldInflationEnabled != data->mInflationEnabled)
    63     return true;
    65   return oldInflationEnabled &&
    66          oldNCAWidth != data->mNCAWidth;
    67 }
    69 /* static */ void
    70 nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame *aBFCFrame)
    71 {
    72   NS_ASSERTION(aBFCFrame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
    73                "should have been given a flow root");
    75   FrameProperties bfcProps(aBFCFrame->Properties());
    76   nsFontInflationData *data = static_cast<nsFontInflationData*>(
    77                                 bfcProps.Get(FontInflationDataProperty()));
    78   if (data) {
    79     data->MarkTextDirty();
    80   }
    81 }
    83 nsFontInflationData::nsFontInflationData(nsIFrame *aBFCFrame)
    84   : mBFCFrame(aBFCFrame)
    85   , mNCAWidth(0)
    86   , mTextAmount(0)
    87   , mTextThreshold(0)
    88   , mInflationEnabled(false)
    89   , mTextDirty(true)
    90 {
    91 }
    93 /**
    94  * Find the closest common ancestor between aFrame1 and aFrame2, except
    95  * treating the parent of a frame as the first-in-flow of its parent (so
    96  * the result doesn't change when breaking changes).
    97  *
    98  * aKnownCommonAncestor is a known common ancestor of both.
    99  */
   100 static nsIFrame*
   101 NearestCommonAncestorFirstInFlow(nsIFrame *aFrame1, nsIFrame *aFrame2,
   102                                  nsIFrame *aKnownCommonAncestor)
   103 {
   104   aFrame1 = aFrame1->FirstInFlow();
   105   aFrame2 = aFrame2->FirstInFlow();
   106   aKnownCommonAncestor = aKnownCommonAncestor->FirstInFlow();
   108   nsAutoTArray<nsIFrame*, 32> ancestors1, ancestors2;
   109   for (nsIFrame *f = aFrame1; f != aKnownCommonAncestor;
   110        (f = f->GetParent()) && (f = f->FirstInFlow())) {
   111     ancestors1.AppendElement(f);
   112   }
   113   for (nsIFrame *f = aFrame2; f != aKnownCommonAncestor;
   114        (f = f->GetParent()) && (f = f->FirstInFlow())) {
   115     ancestors2.AppendElement(f);
   116   }
   118   nsIFrame *result = aKnownCommonAncestor;
   119   uint32_t i1 = ancestors1.Length(),
   120            i2 = ancestors2.Length();
   121   while (i1-- != 0 && i2-- != 0) {
   122     if (ancestors1[i1] != ancestors2[i2]) {
   123       break;
   124     }
   125     result = ancestors1[i1];
   126   }
   128   return result;
   129 }
   131 static nscoord
   132 ComputeDescendantWidth(const nsHTMLReflowState& aAncestorReflowState,
   133                        nsIFrame *aDescendantFrame)
   134 {
   135   nsIFrame *ancestorFrame = aAncestorReflowState.frame->FirstInFlow();
   136   if (aDescendantFrame == ancestorFrame) {
   137     return aAncestorReflowState.ComputedWidth();
   138   }
   140   AutoInfallibleTArray<nsIFrame*, 16> frames;
   141   for (nsIFrame *f = aDescendantFrame; f != ancestorFrame;
   142        f = f->GetParent()->FirstInFlow()) {
   143     frames.AppendElement(f);
   144   }
   146   // This ignores the width contributions made by scrollbars, though in
   147   // reality we don't have any scrollbars on the sorts of devices on
   148   // which we use font inflation, so it's not a problem.  But it may
   149   // occasionally cause problems when writing tests on desktop.
   151   uint32_t len = frames.Length();
   152   nsHTMLReflowState *reflowStates = static_cast<nsHTMLReflowState*>
   153                                 (moz_xmalloc(sizeof(nsHTMLReflowState) * len));
   154   nsPresContext *presContext = aDescendantFrame->PresContext();
   155   for (uint32_t i = 0; i < len; ++i) {
   156     const nsHTMLReflowState &parentReflowState =
   157       (i == 0) ? aAncestorReflowState : reflowStates[i - 1];
   158     nsSize availSize(parentReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE);
   159     nsIFrame *frame = frames[len - i - 1];
   160     NS_ABORT_IF_FALSE(frame->GetParent()->FirstInFlow() ==
   161                         parentReflowState.frame->FirstInFlow(),
   162                       "bad logic in this function");
   163     new (reflowStates + i) nsHTMLReflowState(presContext, parentReflowState,
   164                                              frame, availSize);
   165   }
   167   NS_ABORT_IF_FALSE(reflowStates[len - 1].frame == aDescendantFrame,
   168                     "bad logic in this function");
   169   nscoord result = reflowStates[len - 1].ComputedWidth();
   171   for (uint32_t i = len; i-- != 0; ) {
   172     reflowStates[i].~nsHTMLReflowState();
   173   }
   174   moz_free(reflowStates);
   176   return result;
   177 }
   179 void
   180 nsFontInflationData::UpdateWidth(const nsHTMLReflowState &aReflowState)
   181 {
   182   nsIFrame *bfc = aReflowState.frame;
   183   NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
   184                "must be block formatting context");
   186   nsIFrame *firstInflatableDescendant =
   187              FindEdgeInflatableFrameIn(bfc, eFromStart);
   188   if (!firstInflatableDescendant) {
   189     mTextAmount = 0;
   190     mTextThreshold = 0; // doesn't matter
   191     mTextDirty = false;
   192     mInflationEnabled = false;
   193     return;
   194   }
   195   nsIFrame *lastInflatableDescendant =
   196              FindEdgeInflatableFrameIn(bfc, eFromEnd);
   197   NS_ABORT_IF_FALSE(!firstInflatableDescendant == !lastInflatableDescendant,
   198                     "null-ness should match; NearestCommonAncestorFirstInFlow"
   199                     " will crash when passed null");
   201   // Particularly when we're computing for the root BFC, the width of
   202   // nca might differ significantly for the width of bfc.
   203   nsIFrame *nca = NearestCommonAncestorFirstInFlow(firstInflatableDescendant,
   204                                                    lastInflatableDescendant,
   205                                                    bfc);
   206   while (!nca->IsContainerForFontSizeInflation()) {
   207     nca = nca->GetParent()->FirstInFlow();
   208   }
   210   nscoord newNCAWidth = ComputeDescendantWidth(aReflowState, nca);
   212   // See comment above "font.size.inflation.lineThreshold" in
   213   // modules/libpref/src/init/all.js .
   214   nsIPresShell* presShell = bfc->PresContext()->PresShell();
   215   uint32_t lineThreshold = presShell->FontSizeInflationLineThreshold();
   216   nscoord newTextThreshold = (newNCAWidth * lineThreshold) / 100;
   218   if (mTextThreshold <= mTextAmount && mTextAmount < newTextThreshold) {
   219     // Because we truncate our scan when we hit sufficient text, we now
   220     // need to rescan.
   221     mTextDirty = true;
   222   }
   224   mNCAWidth = newNCAWidth;
   225   mTextThreshold = newTextThreshold;
   226   mInflationEnabled = mTextAmount >= mTextThreshold;
   227 }
   229 /* static */ nsIFrame*
   230 nsFontInflationData::FindEdgeInflatableFrameIn(nsIFrame* aFrame,
   231                                                SearchDirection aDirection)
   232 {
   233   // NOTE: This function has a similar structure to ScanTextIn!
   235   // FIXME: Should probably only scan the text that's actually going to
   236   // be inflated!
   238   nsIFormControlFrame* fcf = do_QueryFrame(aFrame);
   239   if (fcf) {
   240     return aFrame;
   241   }
   243   // FIXME: aDirection!
   244   nsAutoTArray<FrameChildList, 4> lists;
   245   aFrame->GetChildLists(&lists);
   246   for (uint32_t i = 0, len = lists.Length(); i < len; ++i) {
   247     const nsFrameList& list =
   248       lists[(aDirection == eFromStart) ? i : len - i - 1].mList;
   249     for (nsIFrame *kid = (aDirection == eFromStart) ? list.FirstChild()
   250                                                     : list.LastChild();
   251          kid;
   252          kid = (aDirection == eFromStart) ? kid->GetNextSibling()
   253                                           : kid->GetPrevSibling()) {
   254       if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
   255         // Goes in a different set of inflation data.
   256         continue;
   257       }
   259       if (kid->GetType() == nsGkAtoms::textFrame) {
   260         nsIContent *content = kid->GetContent();
   261         if (content && kid == content->GetPrimaryFrame()) {
   262           uint32_t len = nsTextFrameUtils::
   263             ComputeApproximateLengthWithWhitespaceCompression(
   264               content, kid->StyleText());
   265           if (len != 0) {
   266             return kid;
   267           }
   268         }
   269       } else {
   270         nsIFrame *kidResult =
   271           FindEdgeInflatableFrameIn(kid, aDirection);
   272         if (kidResult) {
   273           return kidResult;
   274         }
   275       }
   276     }
   277   }
   279   return nullptr;
   280 }
   282 void
   283 nsFontInflationData::ScanText()
   284 {
   285   mTextDirty = false;
   286   mTextAmount = 0;
   287   ScanTextIn(mBFCFrame);
   288   mInflationEnabled = mTextAmount >= mTextThreshold;
   289 }
   291 static uint32_t
   292 DoCharCountOfLargestOption(nsIFrame *aContainer)
   293 {
   294   uint32_t result = 0;
   295   for (nsIFrame* option = aContainer->GetFirstPrincipalChild();
   296        option; option = option->GetNextSibling()) {
   297     uint32_t optionResult;
   298     if (option->GetContent()->IsHTML(nsGkAtoms::optgroup)) {
   299       optionResult = DoCharCountOfLargestOption(option);
   300     } else {
   301       // REVIEW: Check the frame structure for this!
   302       optionResult = 0;
   303       for (nsIFrame *optionChild = option->GetFirstPrincipalChild();
   304            optionChild; optionChild = optionChild->GetNextSibling()) {
   305         if (optionChild->GetType() == nsGkAtoms::textFrame) {
   306           optionResult += nsTextFrameUtils::
   307             ComputeApproximateLengthWithWhitespaceCompression(
   308               optionChild->GetContent(), optionChild->StyleText());
   309         }
   310       }
   311     }
   312     if (optionResult > result) {
   313       result = optionResult;
   314     }
   315   }
   316   return result;
   317 }
   319 static uint32_t
   320 CharCountOfLargestOption(nsIFrame *aListControlFrame)
   321 {
   322   return DoCharCountOfLargestOption(
   323     static_cast<nsListControlFrame*>(aListControlFrame)->GetOptionsContainer());
   324 }
   326 void
   327 nsFontInflationData::ScanTextIn(nsIFrame *aFrame)
   328 {
   329   // NOTE: This function has a similar structure to FindEdgeInflatableFrameIn!
   331   // FIXME: Should probably only scan the text that's actually going to
   332   // be inflated!
   334   nsIFrame::ChildListIterator lists(aFrame);
   335   for (; !lists.IsDone(); lists.Next()) {
   336     nsFrameList::Enumerator kids(lists.CurrentList());
   337     for (; !kids.AtEnd(); kids.Next()) {
   338       nsIFrame *kid = kids.get();
   339       if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
   340         // Goes in a different set of inflation data.
   341         continue;
   342       }
   344       nsIAtom *fType = kid->GetType();
   345       if (fType == nsGkAtoms::textFrame) {
   346         nsIContent *content = kid->GetContent();
   347         if (content && kid == content->GetPrimaryFrame()) {
   348           uint32_t len = nsTextFrameUtils::
   349             ComputeApproximateLengthWithWhitespaceCompression(
   350               content, kid->StyleText());
   351           if (len != 0) {
   352             nscoord fontSize = kid->StyleFont()->mFont.size;
   353             if (fontSize > 0) {
   354               mTextAmount += fontSize * len;
   355             }
   356           }
   357         }
   358       } else if (fType == nsGkAtoms::textInputFrame) {
   359         // We don't want changes to the amount of text in a text input
   360         // to change what we count towards inflation.
   361         nscoord fontSize = kid->StyleFont()->mFont.size;
   362         int32_t charCount = static_cast<nsTextControlFrame*>(kid)->GetCols();
   363         mTextAmount += charCount * fontSize;
   364       } else if (fType == nsGkAtoms::comboboxControlFrame) {
   365         // See textInputFrame above (with s/amount of text/selected option/).
   366         // Don't just recurse down to the list control inside, since we
   367         // need to exclude the display frame.
   368         nscoord fontSize = kid->StyleFont()->mFont.size;
   369         int32_t charCount = CharCountOfLargestOption(
   370           static_cast<nsComboboxControlFrame*>(kid)->GetDropDown());
   371         mTextAmount += charCount * fontSize;
   372       } else if (fType == nsGkAtoms::listControlFrame) {
   373         // See textInputFrame above (with s/amount of text/selected option/).
   374         nscoord fontSize = kid->StyleFont()->mFont.size;
   375         int32_t charCount = CharCountOfLargestOption(kid);
   376         mTextAmount += charCount * fontSize;
   377       } else {
   378         // recursive step
   379         ScanTextIn(kid);
   380       }
   382       if (mTextAmount >= mTextThreshold) {
   383         return;
   384       }
   385     }
   386   }
   387 }

mercurial