1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/generic/nsFontInflationData.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,387 @@ 1.4 +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +/* Per-block-formatting-context manager of font size inflation for pan and zoom UI. */ 1.10 + 1.11 +#include "nsFontInflationData.h" 1.12 +#include "FramePropertyTable.h" 1.13 +#include "nsTextControlFrame.h" 1.14 +#include "nsListControlFrame.h" 1.15 +#include "nsComboboxControlFrame.h" 1.16 +#include "nsHTMLReflowState.h" 1.17 +#include "nsTextFrameUtils.h" 1.18 + 1.19 +using namespace mozilla; 1.20 +using namespace mozilla::layout; 1.21 + 1.22 +static void 1.23 +DestroyFontInflationData(void *aPropertyValue) 1.24 +{ 1.25 + delete static_cast<nsFontInflationData*>(aPropertyValue); 1.26 +} 1.27 + 1.28 +NS_DECLARE_FRAME_PROPERTY(FontInflationDataProperty, DestroyFontInflationData) 1.29 + 1.30 +/* static */ nsFontInflationData* 1.31 +nsFontInflationData::FindFontInflationDataFor(const nsIFrame *aFrame) 1.32 +{ 1.33 + // We have one set of font inflation data per block formatting context. 1.34 + const nsIFrame *bfc = FlowRootFor(aFrame); 1.35 + NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT, 1.36 + "should have found a flow root"); 1.37 + 1.38 + return static_cast<nsFontInflationData*>( 1.39 + bfc->Properties().Get(FontInflationDataProperty())); 1.40 +} 1.41 + 1.42 +/* static */ bool 1.43 +nsFontInflationData::UpdateFontInflationDataWidthFor(const nsHTMLReflowState& aReflowState) 1.44 +{ 1.45 + nsIFrame *bfc = aReflowState.frame; 1.46 + NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT, 1.47 + "should have been given a flow root"); 1.48 + FrameProperties bfcProps(bfc->Properties()); 1.49 + nsFontInflationData *data = static_cast<nsFontInflationData*>( 1.50 + bfcProps.Get(FontInflationDataProperty())); 1.51 + bool oldInflationEnabled; 1.52 + nscoord oldNCAWidth; 1.53 + if (data) { 1.54 + oldNCAWidth = data->mNCAWidth; 1.55 + oldInflationEnabled = data->mInflationEnabled; 1.56 + } else { 1.57 + data = new nsFontInflationData(bfc); 1.58 + bfcProps.Set(FontInflationDataProperty(), data); 1.59 + oldNCAWidth = -1; 1.60 + oldInflationEnabled = true; /* not relevant */ 1.61 + } 1.62 + 1.63 + data->UpdateWidth(aReflowState); 1.64 + 1.65 + if (oldInflationEnabled != data->mInflationEnabled) 1.66 + return true; 1.67 + 1.68 + return oldInflationEnabled && 1.69 + oldNCAWidth != data->mNCAWidth; 1.70 +} 1.71 + 1.72 +/* static */ void 1.73 +nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame *aBFCFrame) 1.74 +{ 1.75 + NS_ASSERTION(aBFCFrame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT, 1.76 + "should have been given a flow root"); 1.77 + 1.78 + FrameProperties bfcProps(aBFCFrame->Properties()); 1.79 + nsFontInflationData *data = static_cast<nsFontInflationData*>( 1.80 + bfcProps.Get(FontInflationDataProperty())); 1.81 + if (data) { 1.82 + data->MarkTextDirty(); 1.83 + } 1.84 +} 1.85 + 1.86 +nsFontInflationData::nsFontInflationData(nsIFrame *aBFCFrame) 1.87 + : mBFCFrame(aBFCFrame) 1.88 + , mNCAWidth(0) 1.89 + , mTextAmount(0) 1.90 + , mTextThreshold(0) 1.91 + , mInflationEnabled(false) 1.92 + , mTextDirty(true) 1.93 +{ 1.94 +} 1.95 + 1.96 +/** 1.97 + * Find the closest common ancestor between aFrame1 and aFrame2, except 1.98 + * treating the parent of a frame as the first-in-flow of its parent (so 1.99 + * the result doesn't change when breaking changes). 1.100 + * 1.101 + * aKnownCommonAncestor is a known common ancestor of both. 1.102 + */ 1.103 +static nsIFrame* 1.104 +NearestCommonAncestorFirstInFlow(nsIFrame *aFrame1, nsIFrame *aFrame2, 1.105 + nsIFrame *aKnownCommonAncestor) 1.106 +{ 1.107 + aFrame1 = aFrame1->FirstInFlow(); 1.108 + aFrame2 = aFrame2->FirstInFlow(); 1.109 + aKnownCommonAncestor = aKnownCommonAncestor->FirstInFlow(); 1.110 + 1.111 + nsAutoTArray<nsIFrame*, 32> ancestors1, ancestors2; 1.112 + for (nsIFrame *f = aFrame1; f != aKnownCommonAncestor; 1.113 + (f = f->GetParent()) && (f = f->FirstInFlow())) { 1.114 + ancestors1.AppendElement(f); 1.115 + } 1.116 + for (nsIFrame *f = aFrame2; f != aKnownCommonAncestor; 1.117 + (f = f->GetParent()) && (f = f->FirstInFlow())) { 1.118 + ancestors2.AppendElement(f); 1.119 + } 1.120 + 1.121 + nsIFrame *result = aKnownCommonAncestor; 1.122 + uint32_t i1 = ancestors1.Length(), 1.123 + i2 = ancestors2.Length(); 1.124 + while (i1-- != 0 && i2-- != 0) { 1.125 + if (ancestors1[i1] != ancestors2[i2]) { 1.126 + break; 1.127 + } 1.128 + result = ancestors1[i1]; 1.129 + } 1.130 + 1.131 + return result; 1.132 +} 1.133 + 1.134 +static nscoord 1.135 +ComputeDescendantWidth(const nsHTMLReflowState& aAncestorReflowState, 1.136 + nsIFrame *aDescendantFrame) 1.137 +{ 1.138 + nsIFrame *ancestorFrame = aAncestorReflowState.frame->FirstInFlow(); 1.139 + if (aDescendantFrame == ancestorFrame) { 1.140 + return aAncestorReflowState.ComputedWidth(); 1.141 + } 1.142 + 1.143 + AutoInfallibleTArray<nsIFrame*, 16> frames; 1.144 + for (nsIFrame *f = aDescendantFrame; f != ancestorFrame; 1.145 + f = f->GetParent()->FirstInFlow()) { 1.146 + frames.AppendElement(f); 1.147 + } 1.148 + 1.149 + // This ignores the width contributions made by scrollbars, though in 1.150 + // reality we don't have any scrollbars on the sorts of devices on 1.151 + // which we use font inflation, so it's not a problem. But it may 1.152 + // occasionally cause problems when writing tests on desktop. 1.153 + 1.154 + uint32_t len = frames.Length(); 1.155 + nsHTMLReflowState *reflowStates = static_cast<nsHTMLReflowState*> 1.156 + (moz_xmalloc(sizeof(nsHTMLReflowState) * len)); 1.157 + nsPresContext *presContext = aDescendantFrame->PresContext(); 1.158 + for (uint32_t i = 0; i < len; ++i) { 1.159 + const nsHTMLReflowState &parentReflowState = 1.160 + (i == 0) ? aAncestorReflowState : reflowStates[i - 1]; 1.161 + nsSize availSize(parentReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE); 1.162 + nsIFrame *frame = frames[len - i - 1]; 1.163 + NS_ABORT_IF_FALSE(frame->GetParent()->FirstInFlow() == 1.164 + parentReflowState.frame->FirstInFlow(), 1.165 + "bad logic in this function"); 1.166 + new (reflowStates + i) nsHTMLReflowState(presContext, parentReflowState, 1.167 + frame, availSize); 1.168 + } 1.169 + 1.170 + NS_ABORT_IF_FALSE(reflowStates[len - 1].frame == aDescendantFrame, 1.171 + "bad logic in this function"); 1.172 + nscoord result = reflowStates[len - 1].ComputedWidth(); 1.173 + 1.174 + for (uint32_t i = len; i-- != 0; ) { 1.175 + reflowStates[i].~nsHTMLReflowState(); 1.176 + } 1.177 + moz_free(reflowStates); 1.178 + 1.179 + return result; 1.180 +} 1.181 + 1.182 +void 1.183 +nsFontInflationData::UpdateWidth(const nsHTMLReflowState &aReflowState) 1.184 +{ 1.185 + nsIFrame *bfc = aReflowState.frame; 1.186 + NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT, 1.187 + "must be block formatting context"); 1.188 + 1.189 + nsIFrame *firstInflatableDescendant = 1.190 + FindEdgeInflatableFrameIn(bfc, eFromStart); 1.191 + if (!firstInflatableDescendant) { 1.192 + mTextAmount = 0; 1.193 + mTextThreshold = 0; // doesn't matter 1.194 + mTextDirty = false; 1.195 + mInflationEnabled = false; 1.196 + return; 1.197 + } 1.198 + nsIFrame *lastInflatableDescendant = 1.199 + FindEdgeInflatableFrameIn(bfc, eFromEnd); 1.200 + NS_ABORT_IF_FALSE(!firstInflatableDescendant == !lastInflatableDescendant, 1.201 + "null-ness should match; NearestCommonAncestorFirstInFlow" 1.202 + " will crash when passed null"); 1.203 + 1.204 + // Particularly when we're computing for the root BFC, the width of 1.205 + // nca might differ significantly for the width of bfc. 1.206 + nsIFrame *nca = NearestCommonAncestorFirstInFlow(firstInflatableDescendant, 1.207 + lastInflatableDescendant, 1.208 + bfc); 1.209 + while (!nca->IsContainerForFontSizeInflation()) { 1.210 + nca = nca->GetParent()->FirstInFlow(); 1.211 + } 1.212 + 1.213 + nscoord newNCAWidth = ComputeDescendantWidth(aReflowState, nca); 1.214 + 1.215 + // See comment above "font.size.inflation.lineThreshold" in 1.216 + // modules/libpref/src/init/all.js . 1.217 + nsIPresShell* presShell = bfc->PresContext()->PresShell(); 1.218 + uint32_t lineThreshold = presShell->FontSizeInflationLineThreshold(); 1.219 + nscoord newTextThreshold = (newNCAWidth * lineThreshold) / 100; 1.220 + 1.221 + if (mTextThreshold <= mTextAmount && mTextAmount < newTextThreshold) { 1.222 + // Because we truncate our scan when we hit sufficient text, we now 1.223 + // need to rescan. 1.224 + mTextDirty = true; 1.225 + } 1.226 + 1.227 + mNCAWidth = newNCAWidth; 1.228 + mTextThreshold = newTextThreshold; 1.229 + mInflationEnabled = mTextAmount >= mTextThreshold; 1.230 +} 1.231 + 1.232 +/* static */ nsIFrame* 1.233 +nsFontInflationData::FindEdgeInflatableFrameIn(nsIFrame* aFrame, 1.234 + SearchDirection aDirection) 1.235 +{ 1.236 + // NOTE: This function has a similar structure to ScanTextIn! 1.237 + 1.238 + // FIXME: Should probably only scan the text that's actually going to 1.239 + // be inflated! 1.240 + 1.241 + nsIFormControlFrame* fcf = do_QueryFrame(aFrame); 1.242 + if (fcf) { 1.243 + return aFrame; 1.244 + } 1.245 + 1.246 + // FIXME: aDirection! 1.247 + nsAutoTArray<FrameChildList, 4> lists; 1.248 + aFrame->GetChildLists(&lists); 1.249 + for (uint32_t i = 0, len = lists.Length(); i < len; ++i) { 1.250 + const nsFrameList& list = 1.251 + lists[(aDirection == eFromStart) ? i : len - i - 1].mList; 1.252 + for (nsIFrame *kid = (aDirection == eFromStart) ? list.FirstChild() 1.253 + : list.LastChild(); 1.254 + kid; 1.255 + kid = (aDirection == eFromStart) ? kid->GetNextSibling() 1.256 + : kid->GetPrevSibling()) { 1.257 + if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) { 1.258 + // Goes in a different set of inflation data. 1.259 + continue; 1.260 + } 1.261 + 1.262 + if (kid->GetType() == nsGkAtoms::textFrame) { 1.263 + nsIContent *content = kid->GetContent(); 1.264 + if (content && kid == content->GetPrimaryFrame()) { 1.265 + uint32_t len = nsTextFrameUtils:: 1.266 + ComputeApproximateLengthWithWhitespaceCompression( 1.267 + content, kid->StyleText()); 1.268 + if (len != 0) { 1.269 + return kid; 1.270 + } 1.271 + } 1.272 + } else { 1.273 + nsIFrame *kidResult = 1.274 + FindEdgeInflatableFrameIn(kid, aDirection); 1.275 + if (kidResult) { 1.276 + return kidResult; 1.277 + } 1.278 + } 1.279 + } 1.280 + } 1.281 + 1.282 + return nullptr; 1.283 +} 1.284 + 1.285 +void 1.286 +nsFontInflationData::ScanText() 1.287 +{ 1.288 + mTextDirty = false; 1.289 + mTextAmount = 0; 1.290 + ScanTextIn(mBFCFrame); 1.291 + mInflationEnabled = mTextAmount >= mTextThreshold; 1.292 +} 1.293 + 1.294 +static uint32_t 1.295 +DoCharCountOfLargestOption(nsIFrame *aContainer) 1.296 +{ 1.297 + uint32_t result = 0; 1.298 + for (nsIFrame* option = aContainer->GetFirstPrincipalChild(); 1.299 + option; option = option->GetNextSibling()) { 1.300 + uint32_t optionResult; 1.301 + if (option->GetContent()->IsHTML(nsGkAtoms::optgroup)) { 1.302 + optionResult = DoCharCountOfLargestOption(option); 1.303 + } else { 1.304 + // REVIEW: Check the frame structure for this! 1.305 + optionResult = 0; 1.306 + for (nsIFrame *optionChild = option->GetFirstPrincipalChild(); 1.307 + optionChild; optionChild = optionChild->GetNextSibling()) { 1.308 + if (optionChild->GetType() == nsGkAtoms::textFrame) { 1.309 + optionResult += nsTextFrameUtils:: 1.310 + ComputeApproximateLengthWithWhitespaceCompression( 1.311 + optionChild->GetContent(), optionChild->StyleText()); 1.312 + } 1.313 + } 1.314 + } 1.315 + if (optionResult > result) { 1.316 + result = optionResult; 1.317 + } 1.318 + } 1.319 + return result; 1.320 +} 1.321 + 1.322 +static uint32_t 1.323 +CharCountOfLargestOption(nsIFrame *aListControlFrame) 1.324 +{ 1.325 + return DoCharCountOfLargestOption( 1.326 + static_cast<nsListControlFrame*>(aListControlFrame)->GetOptionsContainer()); 1.327 +} 1.328 + 1.329 +void 1.330 +nsFontInflationData::ScanTextIn(nsIFrame *aFrame) 1.331 +{ 1.332 + // NOTE: This function has a similar structure to FindEdgeInflatableFrameIn! 1.333 + 1.334 + // FIXME: Should probably only scan the text that's actually going to 1.335 + // be inflated! 1.336 + 1.337 + nsIFrame::ChildListIterator lists(aFrame); 1.338 + for (; !lists.IsDone(); lists.Next()) { 1.339 + nsFrameList::Enumerator kids(lists.CurrentList()); 1.340 + for (; !kids.AtEnd(); kids.Next()) { 1.341 + nsIFrame *kid = kids.get(); 1.342 + if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) { 1.343 + // Goes in a different set of inflation data. 1.344 + continue; 1.345 + } 1.346 + 1.347 + nsIAtom *fType = kid->GetType(); 1.348 + if (fType == nsGkAtoms::textFrame) { 1.349 + nsIContent *content = kid->GetContent(); 1.350 + if (content && kid == content->GetPrimaryFrame()) { 1.351 + uint32_t len = nsTextFrameUtils:: 1.352 + ComputeApproximateLengthWithWhitespaceCompression( 1.353 + content, kid->StyleText()); 1.354 + if (len != 0) { 1.355 + nscoord fontSize = kid->StyleFont()->mFont.size; 1.356 + if (fontSize > 0) { 1.357 + mTextAmount += fontSize * len; 1.358 + } 1.359 + } 1.360 + } 1.361 + } else if (fType == nsGkAtoms::textInputFrame) { 1.362 + // We don't want changes to the amount of text in a text input 1.363 + // to change what we count towards inflation. 1.364 + nscoord fontSize = kid->StyleFont()->mFont.size; 1.365 + int32_t charCount = static_cast<nsTextControlFrame*>(kid)->GetCols(); 1.366 + mTextAmount += charCount * fontSize; 1.367 + } else if (fType == nsGkAtoms::comboboxControlFrame) { 1.368 + // See textInputFrame above (with s/amount of text/selected option/). 1.369 + // Don't just recurse down to the list control inside, since we 1.370 + // need to exclude the display frame. 1.371 + nscoord fontSize = kid->StyleFont()->mFont.size; 1.372 + int32_t charCount = CharCountOfLargestOption( 1.373 + static_cast<nsComboboxControlFrame*>(kid)->GetDropDown()); 1.374 + mTextAmount += charCount * fontSize; 1.375 + } else if (fType == nsGkAtoms::listControlFrame) { 1.376 + // See textInputFrame above (with s/amount of text/selected option/). 1.377 + nscoord fontSize = kid->StyleFont()->mFont.size; 1.378 + int32_t charCount = CharCountOfLargestOption(kid); 1.379 + mTextAmount += charCount * fontSize; 1.380 + } else { 1.381 + // recursive step 1.382 + ScanTextIn(kid); 1.383 + } 1.384 + 1.385 + if (mTextAmount >= mTextThreshold) { 1.386 + return; 1.387 + } 1.388 + } 1.389 + } 1.390 +}