diff options
Diffstat (limited to 'layout/style/nsRuleNode.cpp')
-rw-r--r-- | layout/style/nsRuleNode.cpp | 10736 |
1 files changed, 10736 insertions, 0 deletions
diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp new file mode 100644 index 000000000..b22002d87 --- /dev/null +++ b/layout/style/nsRuleNode.cpp @@ -0,0 +1,10736 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=78: */ +/* 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/. */ + +/* + * a node in the lexicographic tree of rules that match an element, + * responsible for converting the rules' information into computed style + */ + +#include <algorithm> + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Function.h" +#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for PlaybackDirection +#include "mozilla/Likely.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/Unused.h" + +#include "mozilla/css/Declaration.h" + +#include "nsAlgorithm.h" // for clamped() +#include "nsRuleNode.h" +#include "nscore.h" +#include "nsIWidget.h" +#include "nsIPresShell.h" +#include "nsFontMetrics.h" +#include "gfxFont.h" +#include "nsCSSAnonBoxes.h" +#include "nsCSSPseudoElements.h" +#include "nsThemeConstants.h" +#include "PLDHashTable.h" +#include "nsStyleContext.h" +#include "nsStyleSet.h" +#include "nsStyleStruct.h" +#include "nsSize.h" +#include "nsRuleData.h" +#include "nsIStyleRule.h" +#include "nsBidiUtils.h" +#include "nsStyleStructInlines.h" +#include "nsCSSProps.h" +#include "nsTArray.h" +#include "nsContentUtils.h" +#include "CSSCalc.h" +#include "nsPrintfCString.h" +#include "nsRenderingContext.h" +#include "nsStyleUtil.h" +#include "nsIDocument.h" +#include "prtime.h" +#include "CSSVariableResolver.h" +#include "nsCSSParser.h" +#include "CounterStyleManager.h" +#include "nsCSSPropertyIDSet.h" +#include "mozilla/RuleNodeCacheConditions.h" +#include "nsDeviceContext.h" +#include "nsQueryObject.h" +#include "nsUnicodeProperties.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include <malloc.h> +#ifdef _MSC_VER +#define alloca _alloca +#endif +#endif +#ifdef SOLARIS +#include <alloca.h> +#endif + +using std::max; +using std::min; +using namespace mozilla; +using namespace mozilla::dom; + +namespace mozilla { + +enum UnsetAction +{ + eUnsetInitial, + eUnsetInherit +}; + +} // namespace mozilla + +void* +nsConditionalResetStyleData::GetConditionalStyleData(nsStyleStructID aSID, + nsStyleContext* aStyleContext) const +{ + Entry* e = static_cast<Entry*>(mEntries[aSID]); + MOZ_ASSERT(e, "if mConditionalBits bit is set, we must have at least one " + "conditional style struct"); + do { + if (e->mConditions.Matches(aStyleContext)) { + void* data = e->mStyleStruct; + + // For reset structs with conditions, we cache the data on the + // style context. + // Tell the style context that it doesn't own the data + aStyleContext->AddStyleBit(GetBitForSID(aSID)); + aStyleContext->SetStyle(aSID, data); + + return data; + } + e = e->mNext; + } while (e); + return nullptr; +} + +// Creates and returns an imgRequestProxy based on the specified +// value in aValue. +static imgRequestProxy* +GetImageRequest(nsPresContext* aPresContext, const nsCSSValue& aValue) +{ + return aValue.GetImageValue(aPresContext->Document()); +} + +// Creates an imgRequestProxy based on the specified value in +// aValue and calls aCallback with it. If the nsPresContext +// is static (e.g. for printing), then a static request (i.e. +// showing the first frame, without animation) will be created. +// (The expectation is then that aCallback will set the resulting +// imgRequestProxy in a style struct somewhere.) +static void +SetImageRequest(function<void(imgRequestProxy*)> aCallback, + nsPresContext* aPresContext, + const nsCSSValue& aValue) +{ + RefPtr<imgRequestProxy> req = + aValue.GetPossiblyStaticImageValue(aPresContext->Document(), + aPresContext); + aCallback(req); +} + +static void +SetStyleImageRequest(function<void(nsStyleImageRequest*)> aCallback, + nsPresContext* aPresContext, + const nsCSSValue& aValue, + nsStyleImageRequest::Mode aModeFlags = + nsStyleImageRequest::Mode::Track) +{ + SetImageRequest([&](imgRequestProxy* aProxy) { + RefPtr<nsStyleImageRequest> request; + if (aProxy) { + css::ImageValue* imageValue = aValue.GetImageStructValue(); + ImageTracker* imageTracker = + (aModeFlags & nsStyleImageRequest::Mode::Track) + ? aPresContext->Document()->ImageTracker() + : nullptr; + request = + new nsStyleImageRequest(aModeFlags, aProxy, imageValue, imageTracker); + } + aCallback(request); + }, aPresContext, aValue); +} + +template<typename ReferenceBox> +static void +SetStyleShapeSourceToCSSValue(StyleShapeSource<ReferenceBox>* aShapeSource, + const nsCSSValue* aValue, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions); + +/* Helper function to convert a CSS <position> specified value into its + * computed-style form. */ +static void +ComputePositionValue(nsStyleContext* aStyleContext, + const nsCSSValue& aValue, + Position& aComputedValue, + RuleNodeCacheConditions& aConditions); + +/* + * For storage of an |nsRuleNode|'s children in a PLDHashTable. + */ + +struct ChildrenHashEntry : public PLDHashEntryHdr { + // key is |mRuleNode->GetKey()| + nsRuleNode *mRuleNode; +}; + +/* static */ PLDHashNumber +nsRuleNode::ChildrenHashHashKey(const void *aKey) +{ + const nsRuleNode::Key *key = + static_cast<const nsRuleNode::Key*>(aKey); + // Disagreement on importance and level for the same rule is extremely + // rare, so hash just on the rule. + return PLDHashTable::HashVoidPtrKeyStub(key->mRule); +} + +/* static */ bool +nsRuleNode::ChildrenHashMatchEntry(const PLDHashEntryHdr *aHdr, + const void *aKey) +{ + const ChildrenHashEntry *entry = + static_cast<const ChildrenHashEntry*>(aHdr); + const nsRuleNode::Key *key = + static_cast<const nsRuleNode::Key*>(aKey); + return entry->mRuleNode->GetKey() == *key; +} + +/* static */ const PLDHashTableOps +nsRuleNode::ChildrenHashOps = { + // It's probably better to allocate the table itself using malloc and + // free rather than the pres shell's arena because the table doesn't + // grow very often and the pres shell's arena doesn't recycle very + // large size allocations. + ChildrenHashHashKey, + ChildrenHashMatchEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr +}; + + +// EnsureBlockDisplay: +// Never change display:none or display:contents *ever*, otherwise: +// - if the display value (argument) is not a block-type +// then we set it to a valid block display value +// - For enforcing the floated/positioned element CSS2 rules +// - We allow the behavior of "list-item" to be customized. +// CSS21 says that position/float do not convert 'list-item' to 'block', +// but it explicitly does not define whether 'list-item' should be +// converted to block *on the root node*. To allow for flexibility +// (so that we don't have to support a list-item root node), this method +// lets the caller pick either behavior, using the 'aConvertListItem' arg. +// Reference: http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo +/* static */ +void +nsRuleNode::EnsureBlockDisplay(StyleDisplay& display, + bool aConvertListItem /* = false */) +{ + // see if the display value is already a block + switch (display) { + case StyleDisplay::ListItem: + if (aConvertListItem) { + display = StyleDisplay::Block; + break; + } // else, fall through to share the 'break' for non-changing display vals + MOZ_FALLTHROUGH; + case StyleDisplay::None: + case StyleDisplay::Contents: + // never change display:none or display:contents *ever* + case StyleDisplay::Table: + case StyleDisplay::Block: + case StyleDisplay::Flex: + case StyleDisplay::WebkitBox: + case StyleDisplay::Grid: + // do not muck with these at all - already blocks + // This is equivalent to nsStyleDisplay::IsBlockOutside. (XXX Maybe we + // should just call that?) + // This needs to match the check done in + // nsCSSFrameConstructor::FindMathMLData for <math>. + break; + + case StyleDisplay::InlineTable: + // make inline tables into tables + display = StyleDisplay::Table; + break; + + case StyleDisplay::InlineFlex: + // make inline flex containers into flex containers + display = StyleDisplay::Flex; + break; + + case StyleDisplay::WebkitInlineBox: + // make -webkit-inline-box containers into -webkit-box containers + display = StyleDisplay::WebkitBox; + break; + + case StyleDisplay::InlineGrid: + // make inline grid containers into grid containers + display = StyleDisplay::Grid; + break; + + default: + // make it a block + display = StyleDisplay::Block; + } +} + +// EnsureInlineDisplay: +// - if the display value (argument) is not an inline type +// then we set it to a valid inline display value +/* static */ +void +nsRuleNode::EnsureInlineDisplay(StyleDisplay& display) +{ + // see if the display value is already inline + switch (display) { + case StyleDisplay::Block: + display = StyleDisplay::InlineBlock; + break; + case StyleDisplay::Table: + display = StyleDisplay::InlineTable; + break; + case StyleDisplay::Flex: + display = StyleDisplay::InlineFlex; + break; + case StyleDisplay::WebkitBox: + display = StyleDisplay::WebkitInlineBox; + break; + case StyleDisplay::Grid: + display = StyleDisplay::InlineGrid; + break; + case StyleDisplay::Box: + display = StyleDisplay::InlineBox; + break; + case StyleDisplay::Stack: + display = StyleDisplay::InlineStack; + break; + default: + break; // Do nothing + } +} + +static nscoord CalcLengthWith(const nsCSSValue& aValue, + nscoord aFontSize, + const nsStyleFont* aStyleFont, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + bool aUseProvidedRootEmSize, + bool aUseUserFontSet, + RuleNodeCacheConditions& aConditions); + +struct CalcLengthCalcOps : public css::BasicCoordCalcOps, + public css::NumbersAlreadyNormalizedOps +{ + // All of the parameters to CalcLengthWith except aValue. + const nscoord mFontSize; + const nsStyleFont* const mStyleFont; + nsStyleContext* const mStyleContext; + nsPresContext* const mPresContext; + const bool mUseProvidedRootEmSize; + const bool mUseUserFontSet; + RuleNodeCacheConditions& mConditions; + + CalcLengthCalcOps(nscoord aFontSize, const nsStyleFont* aStyleFont, + nsStyleContext* aStyleContext, nsPresContext* aPresContext, + bool aUseProvidedRootEmSize, bool aUseUserFontSet, + RuleNodeCacheConditions& aConditions) + : mFontSize(aFontSize), + mStyleFont(aStyleFont), + mStyleContext(aStyleContext), + mPresContext(aPresContext), + mUseProvidedRootEmSize(aUseProvidedRootEmSize), + mUseUserFontSet(aUseUserFontSet), + mConditions(aConditions) + { + } + + result_type ComputeLeafValue(const nsCSSValue& aValue) + { + return CalcLengthWith(aValue, mFontSize, mStyleFont, + mStyleContext, mPresContext, mUseProvidedRootEmSize, + mUseUserFontSet, mConditions); + } +}; + +static inline nscoord ScaleCoordRound(const nsCSSValue& aValue, float aFactor) +{ + return NSToCoordRoundWithClamp(aValue.GetFloatValue() * aFactor); +} + +static inline nscoord ScaleViewportCoordTrunc(const nsCSSValue& aValue, + nscoord aViewportSize) +{ + // For units (like percentages and viewport units) where authors might + // repeatedly use a value and expect some multiple of the value to be + // smaller than a container, we need to use floor rather than round. + // We need to use division by 100.0 rather than multiplication by 0.1f + // to avoid introducing error. + return NSToCoordTruncClamped(aValue.GetFloatValue() * + aViewportSize / 100.0f); +} + +already_AddRefed<nsFontMetrics> +GetMetricsFor(nsPresContext* aPresContext, + nsStyleContext* aStyleContext, + const nsStyleFont* aStyleFont, + nscoord aFontSize, // overrides value from aStyleFont + bool aUseUserFontSet) +{ + nsFont font = aStyleFont->mFont; + font.size = aFontSize; + gfxFont::Orientation orientation = gfxFont::eHorizontal; + if (aStyleContext) { + WritingMode wm(aStyleContext); + if (wm.IsVertical() && !wm.IsSideways()) { + orientation = gfxFont::eVertical; + } + } + nsFontMetrics::Params params; + params.language = aStyleFont->mLanguage; + params.explicitLanguage = aStyleFont->mExplicitLanguage; + params.orientation = orientation; + params.userFontSet = + aUseUserFontSet ? aPresContext->GetUserFontSet() : nullptr; + params.textPerf = aPresContext->GetTextPerfMetrics(); + return aPresContext->DeviceContext()->GetMetricsFor(font, params); +} + + +static nsSize CalcViewportUnitsScale(nsPresContext* aPresContext) +{ + // The caller is making use of viewport units, so notify the pres context + // that it will need to rebuild the rule tree if the size of the viewport + // changes. + aPresContext->SetUsesViewportUnits(true); + + // The default (when we have 'overflow: auto' on the root element, or + // trivially for 'overflow: hidden' since we never have scrollbars in that + // case) is to define the scale of the viewport units without considering + // scrollbars. + nsSize viewportSize(aPresContext->GetVisibleArea().Size()); + + // Check for 'overflow: scroll' styles on the root scroll frame. If we find + // any, the standard requires us to take scrollbars into account. + nsIScrollableFrame* scrollFrame = + aPresContext->PresShell()->GetRootScrollFrameAsScrollable(); + if (scrollFrame) { + ScrollbarStyles styles(scrollFrame->GetScrollbarStyles()); + + if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL || + styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) { + // Gather scrollbar size information. + nsRenderingContext context( + aPresContext->PresShell()->CreateReferenceRenderingContext()); + nsMargin sizes(scrollFrame->GetDesiredScrollbarSizes(aPresContext, &context)); + + if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) { + // 'overflow-x: scroll' means we must consider the horizontal scrollbar, + // which affects the scale of viewport height units. + viewportSize.height -= sizes.TopBottom(); + } + + if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) { + // 'overflow-y: scroll' means we must consider the vertical scrollbar, + // which affects the scale of viewport width units. + viewportSize.width -= sizes.LeftRight(); + } + } + } + + return viewportSize; +} + +// If |aStyleFont| is nullptr, aStyleContext->StyleFont() is used. +// +// In case that |aValue| is rem unit, if |aStyleContext| is null, callers must +// specify a valid |aStyleFont| and |aUseProvidedRootEmSize| must be true so +// that we can get the length from |aStyleFont|. +static nscoord CalcLengthWith(const nsCSSValue& aValue, + nscoord aFontSize, + const nsStyleFont* aStyleFont, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + bool aUseProvidedRootEmSize, + // aUseUserFontSet should always be true + // except when called from + // CalcLengthWithInitialFont. + bool aUseUserFontSet, + RuleNodeCacheConditions& aConditions) +{ + NS_ASSERTION(aValue.IsLengthUnit() || aValue.IsCalcUnit(), + "not a length or calc unit"); + NS_ASSERTION(aStyleFont || aStyleContext, + "Must have style data"); + NS_ASSERTION(aStyleContext || aUseProvidedRootEmSize, + "Must have style context or specify aUseProvidedRootEmSize"); + NS_ASSERTION(aPresContext, "Must have prescontext"); + + if (aValue.IsFixedLengthUnit()) { + return aValue.GetFixedLength(aPresContext); + } + if (aValue.IsPixelLengthUnit()) { + return aValue.GetPixelLength(); + } + if (aValue.IsCalcUnit()) { + // For properties for which lengths are the *only* units accepted in + // calc(), we can handle calc() here and just compute a final + // result. We ensure that we don't get to this code for other + // properties by not calling CalcLength in those cases: SetCoord + // only calls CalcLength for a calc when it is appropriate to do so. + CalcLengthCalcOps ops(aFontSize, aStyleFont, + aStyleContext, aPresContext, + aUseProvidedRootEmSize, aUseUserFontSet, + aConditions); + return css::ComputeCalc(aValue, ops); + } + switch (aValue.GetUnit()) { + // nsPresContext::SetVisibleArea and + // nsPresContext::MediaFeatureValuesChanged handle dynamic changes + // of the basis for viewport units by rebuilding the rule tree and + // style context tree. Not caching them in the rule tree wouldn't + // be sufficient to handle these changes because we also need a way + // to get rid of cached values in the style context tree without any + // changes in specified style. We can either do this by not caching + // in the rule tree and then throwing away the style context tree + // for dynamic viewport size changes, or by allowing caching in the + // rule tree and using the existing rebuild style data path that + // throws away the style context and the rule tree. + // Thus we do cache viewport units in the rule tree. This allows us + // to benefit from the performance advantages of the rule tree + // (e.g., faster dynamic changes on other things, like transforms) + // and allows us not to need an additional code path, in exchange + // for an increased cost to dynamic changes to the viewport size + // when viewport units are in use. + case eCSSUnit_ViewportWidth: { + nscoord viewportWidth = CalcViewportUnitsScale(aPresContext).width; + return ScaleViewportCoordTrunc(aValue, viewportWidth); + } + case eCSSUnit_ViewportHeight: { + nscoord viewportHeight = CalcViewportUnitsScale(aPresContext).height; + return ScaleViewportCoordTrunc(aValue, viewportHeight); + } + case eCSSUnit_ViewportMin: { + nsSize vuScale(CalcViewportUnitsScale(aPresContext)); + nscoord viewportMin = min(vuScale.width, vuScale.height); + return ScaleViewportCoordTrunc(aValue, viewportMin); + } + case eCSSUnit_ViewportMax: { + nsSize vuScale(CalcViewportUnitsScale(aPresContext)); + nscoord viewportMax = max(vuScale.width, vuScale.height); + return ScaleViewportCoordTrunc(aValue, viewportMax); + } + // While we could deal with 'rem' units correctly by simply not + // caching any data that uses them in the rule tree, it's valuable + // to store them in the rule tree (for faster dynamic changes of + // other things). And since the font size of the root element + // changes rarely, we instead handle dynamic changes to the root + // element's font size by rebuilding all style data in + // nsCSSFrameConstructor::RestyleElement. + case eCSSUnit_RootEM: { + aPresContext->SetUsesRootEMUnits(true); + nscoord rootFontSize; + + // NOTE: Be very careful with |styleFont|, since we haven't added any + // conditions to aConditions or set it to uncacheable yet, so we don't + // want to introduce any dependencies on aStyleContext's data here. + const nsStyleFont *styleFont = + aStyleFont ? aStyleFont : aStyleContext->StyleFont(); + + if (aUseProvidedRootEmSize) { + // We should use the provided aFontSize as the reference length to + // scale. This only happens when we are calculating font-size or + // an equivalent (scriptminsize or CalcLengthWithInitialFont) on + // the root element, in which case aFontSize is already the + // value we want. + if (aFontSize == -1) { + // XXX Should this be styleFont->mSize instead to avoid taking + // minfontsize prefs into account? + aFontSize = styleFont->mFont.size; + } + rootFontSize = aFontSize; + } else if (aStyleContext && !aStyleContext->GetParent()) { + // This is the root element (XXX we don't really know this, but + // nsRuleNode::SetFont makes the same assumption!), so we should + // use StyleFont on this context to get the root element's + // font size. + rootFontSize = styleFont->mFont.size; + } else { + // This is not the root element or we are calculating something other + // than font size, so rem is relative to the root element's font size. + // Find the root style context by walking up the style context tree. + nsStyleContext* rootStyle = aStyleContext; + while (rootStyle->GetParent()) { + rootStyle = rootStyle->GetParent(); + } + + const nsStyleFont *rootStyleFont = rootStyle->StyleFont(); + rootFontSize = rootStyleFont->mFont.size; + } + + return ScaleCoordRound(aValue, float(rootFontSize)); + } + default: + // Fall through to the code for units that can't be stored in the + // rule tree because they depend on font data. + break; + } + // Common code for units that depend on the element's font data and + // thus can't be stored in the rule tree: + const nsStyleFont *styleFont = + aStyleFont ? aStyleFont : aStyleContext->StyleFont(); + if (aFontSize == -1) { + // XXX Should this be styleFont->mSize instead to avoid taking minfontsize + // prefs into account? + aFontSize = styleFont->mFont.size; + } + switch (aValue.GetUnit()) { + case eCSSUnit_EM: { + if (aValue.GetFloatValue() == 0.0f) { + // Don't call SetFontSizeDependency for '0em'. + return 0; + } + // CSS2.1 specifies that this unit scales to the computed font + // size, not the em-width in the font metrics, despite the name. + aConditions.SetFontSizeDependency(aFontSize); + return ScaleCoordRound(aValue, float(aFontSize)); + } + case eCSSUnit_XHeight: { + aPresContext->SetUsesExChUnits(true); + RefPtr<nsFontMetrics> fm = + GetMetricsFor(aPresContext, aStyleContext, styleFont, + aFontSize, aUseUserFontSet); + aConditions.SetUncacheable(); + return ScaleCoordRound(aValue, float(fm->XHeight())); + } + case eCSSUnit_Char: { + aPresContext->SetUsesExChUnits(true); + RefPtr<nsFontMetrics> fm = + GetMetricsFor(aPresContext, aStyleContext, styleFont, + aFontSize, aUseUserFontSet); + gfxFloat zeroWidth = + fm->GetThebesFontGroup()->GetFirstValidFont()-> + GetMetrics(fm->Orientation()).zeroOrAveCharWidth; + + aConditions.SetUncacheable(); + return ScaleCoordRound(aValue, ceil(aPresContext->AppUnitsPerDevPixel() * + zeroWidth)); + } + default: + NS_NOTREACHED("unexpected unit"); + break; + } + return 0; +} + +/* static */ nscoord +nsRuleNode::CalcLength(const nsCSSValue& aValue, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + NS_ASSERTION(aStyleContext, "Must have style data"); + + return CalcLengthWith(aValue, -1, nullptr, + aStyleContext, aPresContext, + false, true, aConditions); +} + +/* Inline helper function to redirect requests to CalcLength. */ +static inline nscoord CalcLength(const nsCSSValue& aValue, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + return nsRuleNode::CalcLength(aValue, aStyleContext, + aPresContext, aConditions); +} + +/* static */ nscoord +nsRuleNode::CalcLengthWithInitialFont(nsPresContext* aPresContext, + const nsCSSValue& aValue) +{ + nsStyleFont defaultFont(aPresContext); // FIXME: best language? + RuleNodeCacheConditions conditions; + return CalcLengthWith(aValue, -1, &defaultFont, + nullptr, aPresContext, + true, false, conditions); +} + +struct LengthPercentPairCalcOps : public css::NumbersAlreadyNormalizedOps +{ + typedef nsRuleNode::ComputedCalc result_type; + + LengthPercentPairCalcOps(nsStyleContext* aContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) + : mContext(aContext), + mPresContext(aPresContext), + mConditions(aConditions), + mHasPercent(false) {} + + nsStyleContext* mContext; + nsPresContext* mPresContext; + RuleNodeCacheConditions& mConditions; + bool mHasPercent; + + result_type ComputeLeafValue(const nsCSSValue& aValue) + { + if (aValue.GetUnit() == eCSSUnit_Percent) { + mHasPercent = true; + return result_type(0, aValue.GetPercentValue()); + } + return result_type(CalcLength(aValue, mContext, mPresContext, + mConditions), + 0.0f); + } + + result_type + MergeAdditive(nsCSSUnit aCalcFunction, + result_type aValue1, result_type aValue2) + { + if (aCalcFunction == eCSSUnit_Calc_Plus) { + return result_type(NSCoordSaturatingAdd(aValue1.mLength, + aValue2.mLength), + aValue1.mPercent + aValue2.mPercent); + } + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus, + "min() and max() are not allowed in calc() on transform"); + return result_type(NSCoordSaturatingSubtract(aValue1.mLength, + aValue2.mLength, 0), + aValue1.mPercent - aValue2.mPercent); + } + + result_type + MergeMultiplicativeL(nsCSSUnit aCalcFunction, + float aValue1, result_type aValue2) + { + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L, + "unexpected unit"); + return result_type(NSCoordSaturatingMultiply(aValue2.mLength, aValue1), + aValue1 * aValue2.mPercent); + } + + result_type + MergeMultiplicativeR(nsCSSUnit aCalcFunction, + result_type aValue1, float aValue2) + { + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_R || + aCalcFunction == eCSSUnit_Calc_Divided, + "unexpected unit"); + if (aCalcFunction == eCSSUnit_Calc_Divided) { + aValue2 = 1.0f / aValue2; + } + return result_type(NSCoordSaturatingMultiply(aValue1.mLength, aValue2), + aValue1.mPercent * aValue2); + } + +}; + +static void +SpecifiedCalcToComputedCalc(const nsCSSValue& aValue, nsStyleCoord& aCoord, + nsStyleContext* aStyleContext, + RuleNodeCacheConditions& aConditions) +{ + LengthPercentPairCalcOps ops(aStyleContext, aStyleContext->PresContext(), + aConditions); + nsRuleNode::ComputedCalc vals = ComputeCalc(aValue, ops); + + nsStyleCoord::Calc* calcObj = new nsStyleCoord::Calc; + + calcObj->mLength = vals.mLength; + calcObj->mPercent = vals.mPercent; + calcObj->mHasPercent = ops.mHasPercent; + + aCoord.SetCalcValue(calcObj); +} + +/* static */ nsRuleNode::ComputedCalc +nsRuleNode::SpecifiedCalcToComputedCalc(const nsCSSValue& aValue, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + LengthPercentPairCalcOps ops(aStyleContext, aPresContext, + aConditions); + return ComputeCalc(aValue, ops); +} + +// This is our public API for handling calc() expressions that involve +// percentages. +/* static */ nscoord +nsRuleNode::ComputeComputedCalc(const nsStyleCoord& aValue, + nscoord aPercentageBasis) +{ + nsStyleCoord::Calc* calc = aValue.GetCalcValue(); + return calc->mLength + + NSToCoordFloorClamped(aPercentageBasis * calc->mPercent); +} + +/* static */ nscoord +nsRuleNode::ComputeCoordPercentCalc(const nsStyleCoord& aCoord, + nscoord aPercentageBasis) +{ + switch (aCoord.GetUnit()) { + case eStyleUnit_Coord: + return aCoord.GetCoordValue(); + case eStyleUnit_Percent: + return NSToCoordFloorClamped(aPercentageBasis * aCoord.GetPercentValue()); + case eStyleUnit_Calc: + return ComputeComputedCalc(aCoord, aPercentageBasis); + default: + MOZ_ASSERT(false, "unexpected unit"); + return 0; + } +} + +/* Given an enumerated value that represents a box position, converts it to + * a float representing the percentage of the box it corresponds to. For + * example, "center" becomes 0.5f. + * + * @param aEnumValue The enumerated value. + * @return The float percent it corresponds to. + */ +static float +GetFloatFromBoxPosition(int32_t aEnumValue) +{ + switch (aEnumValue) { + case NS_STYLE_IMAGELAYER_POSITION_LEFT: + case NS_STYLE_IMAGELAYER_POSITION_TOP: + return 0.0f; + case NS_STYLE_IMAGELAYER_POSITION_RIGHT: + case NS_STYLE_IMAGELAYER_POSITION_BOTTOM: + return 1.0f; + default: + MOZ_FALLTHROUGH_ASSERT("unexpected box position value"); + case NS_STYLE_IMAGELAYER_POSITION_CENTER: + return 0.5f; + } +} + +#define SETCOORD_NORMAL 0x01 // N +#define SETCOORD_AUTO 0x02 // A +#define SETCOORD_INHERIT 0x04 // H +#define SETCOORD_PERCENT 0x08 // P +#define SETCOORD_FACTOR 0x10 // F +#define SETCOORD_LENGTH 0x20 // L +#define SETCOORD_INTEGER 0x40 // I +#define SETCOORD_ENUMERATED 0x80 // E +#define SETCOORD_NONE 0x100 // O +#define SETCOORD_INITIAL_ZERO 0x200 +#define SETCOORD_INITIAL_AUTO 0x400 +#define SETCOORD_INITIAL_NONE 0x800 +#define SETCOORD_INITIAL_NORMAL 0x1000 +#define SETCOORD_INITIAL_HALF 0x2000 +#define SETCOORD_INITIAL_HUNDRED_PCT 0x00004000 +#define SETCOORD_INITIAL_FACTOR_ONE 0x00008000 +#define SETCOORD_INITIAL_FACTOR_ZERO 0x00010000 +#define SETCOORD_CALC_LENGTH_ONLY 0x00020000 +#define SETCOORD_CALC_CLAMP_NONNEGATIVE 0x00040000 // modifier for CALC_LENGTH_ONLY +#define SETCOORD_STORE_CALC 0x00080000 +#define SETCOORD_BOX_POSITION 0x00100000 // exclusive with _ENUMERATED +#define SETCOORD_ANGLE 0x00200000 +#define SETCOORD_UNSET_INHERIT 0x00400000 +#define SETCOORD_UNSET_INITIAL 0x00800000 + +#define SETCOORD_LP (SETCOORD_LENGTH | SETCOORD_PERCENT) +#define SETCOORD_LH (SETCOORD_LENGTH | SETCOORD_INHERIT) +#define SETCOORD_AH (SETCOORD_AUTO | SETCOORD_INHERIT) +#define SETCOORD_LAH (SETCOORD_AUTO | SETCOORD_LENGTH | SETCOORD_INHERIT) +#define SETCOORD_LPH (SETCOORD_LP | SETCOORD_INHERIT) +#define SETCOORD_LPAH (SETCOORD_LP | SETCOORD_AH) +#define SETCOORD_LPE (SETCOORD_LP | SETCOORD_ENUMERATED) +#define SETCOORD_LPEH (SETCOORD_LPE | SETCOORD_INHERIT) +#define SETCOORD_LPAEH (SETCOORD_LPAH | SETCOORD_ENUMERATED) +#define SETCOORD_LPO (SETCOORD_LP | SETCOORD_NONE) +#define SETCOORD_LPOH (SETCOORD_LPH | SETCOORD_NONE) +#define SETCOORD_LPOEH (SETCOORD_LPOH | SETCOORD_ENUMERATED) +#define SETCOORD_LE (SETCOORD_LENGTH | SETCOORD_ENUMERATED) +#define SETCOORD_LEH (SETCOORD_LE | SETCOORD_INHERIT) +#define SETCOORD_IA (SETCOORD_INTEGER | SETCOORD_AUTO) +#define SETCOORD_LAE (SETCOORD_LENGTH | SETCOORD_AUTO | SETCOORD_ENUMERATED) + +// changes aCoord iff it returns true +static bool SetCoord(const nsCSSValue& aValue, nsStyleCoord& aCoord, + const nsStyleCoord& aParentCoord, + int32_t aMask, nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + bool result = true; + if (aValue.GetUnit() == eCSSUnit_Null) { + result = false; + } + else if ((((aMask & SETCOORD_LENGTH) != 0) && + aValue.IsLengthUnit()) || + (((aMask & SETCOORD_CALC_LENGTH_ONLY) != 0) && + aValue.IsCalcUnit())) { + nscoord len = CalcLength(aValue, aStyleContext, aPresContext, + aConditions); + if ((aMask & SETCOORD_CALC_CLAMP_NONNEGATIVE) && len < 0) { + NS_ASSERTION(aValue.IsCalcUnit(), + "parser should have ensured no nonnegative lengths"); + len = 0; + } + aCoord.SetCoordValue(len); + } + else if (((aMask & SETCOORD_PERCENT) != 0) && + (aValue.GetUnit() == eCSSUnit_Percent)) { + aCoord.SetPercentValue(aValue.GetPercentValue()); + } + else if (((aMask & SETCOORD_INTEGER) != 0) && + (aValue.GetUnit() == eCSSUnit_Integer)) { + aCoord.SetIntValue(aValue.GetIntValue(), eStyleUnit_Integer); + } + else if (((aMask & SETCOORD_ENUMERATED) != 0) && + (aValue.GetUnit() == eCSSUnit_Enumerated)) { + aCoord.SetIntValue(aValue.GetIntValue(), eStyleUnit_Enumerated); + } + else if (((aMask & SETCOORD_BOX_POSITION) != 0) && + (aValue.GetUnit() == eCSSUnit_Enumerated)) { + aCoord.SetPercentValue(GetFloatFromBoxPosition(aValue.GetIntValue())); + } + else if (((aMask & SETCOORD_AUTO) != 0) && + (aValue.GetUnit() == eCSSUnit_Auto)) { + aCoord.SetAutoValue(); + } + else if ((((aMask & SETCOORD_INHERIT) != 0) && + aValue.GetUnit() == eCSSUnit_Inherit) || + (((aMask & SETCOORD_UNSET_INHERIT) != 0) && + aValue.GetUnit() == eCSSUnit_Unset)) { + aCoord = aParentCoord; // just inherit value from parent + aConditions.SetUncacheable(); + } + else if (((aMask & SETCOORD_NORMAL) != 0) && + (aValue.GetUnit() == eCSSUnit_Normal)) { + aCoord.SetNormalValue(); + } + else if (((aMask & SETCOORD_NONE) != 0) && + (aValue.GetUnit() == eCSSUnit_None)) { + aCoord.SetNoneValue(); + } + else if (((aMask & SETCOORD_FACTOR) != 0) && + (aValue.GetUnit() == eCSSUnit_Number)) { + aCoord.SetFactorValue(aValue.GetFloatValue()); + } + else if (((aMask & SETCOORD_STORE_CALC) != 0) && + (aValue.IsCalcUnit())) { + SpecifiedCalcToComputedCalc(aValue, aCoord, aStyleContext, + aConditions); + } + else if (aValue.GetUnit() == eCSSUnit_Initial || + (aValue.GetUnit() == eCSSUnit_Unset && + ((aMask & SETCOORD_UNSET_INITIAL) != 0))) { + if ((aMask & SETCOORD_INITIAL_AUTO) != 0) { + aCoord.SetAutoValue(); + } + else if ((aMask & SETCOORD_INITIAL_ZERO) != 0) { + aCoord.SetCoordValue(0); + } + else if ((aMask & SETCOORD_INITIAL_FACTOR_ZERO) != 0) { + aCoord.SetFactorValue(0.0f); + } + else if ((aMask & SETCOORD_INITIAL_NONE) != 0) { + aCoord.SetNoneValue(); + } + else if ((aMask & SETCOORD_INITIAL_NORMAL) != 0) { + aCoord.SetNormalValue(); + } + else if ((aMask & SETCOORD_INITIAL_HALF) != 0) { + aCoord.SetPercentValue(0.5f); + } + else if ((aMask & SETCOORD_INITIAL_HUNDRED_PCT) != 0) { + aCoord.SetPercentValue(1.0f); + } + else if ((aMask & SETCOORD_INITIAL_FACTOR_ONE) != 0) { + aCoord.SetFactorValue(1.0f); + } + else { + result = false; // didn't set anything + } + } + else if ((aMask & SETCOORD_ANGLE) != 0 && + (aValue.IsAngularUnit())) { + nsStyleUnit unit; + switch (aValue.GetUnit()) { + case eCSSUnit_Degree: unit = eStyleUnit_Degree; break; + case eCSSUnit_Grad: unit = eStyleUnit_Grad; break; + case eCSSUnit_Radian: unit = eStyleUnit_Radian; break; + case eCSSUnit_Turn: unit = eStyleUnit_Turn; break; + default: NS_NOTREACHED("unrecognized angular unit"); + unit = eStyleUnit_Degree; + } + aCoord.SetAngleValue(aValue.GetAngleValue(), unit); + } + else { + result = false; // didn't set anything + } + return result; +} + +// This inline function offers a shortcut for SetCoord() by refusing to accept +// SETCOORD_LENGTH, SETCOORD_INHERIT and SETCOORD_UNSET_* masks. +static inline bool SetAbsCoord(const nsCSSValue& aValue, + nsStyleCoord& aCoord, + int32_t aMask) +{ + MOZ_ASSERT((aMask & (SETCOORD_LH | SETCOORD_UNSET_INHERIT | + SETCOORD_UNSET_INITIAL)) == 0, + "does not handle SETCOORD_LENGTH, SETCOORD_INHERIT and " + "SETCOORD_UNSET_*"); + + // The values of the following variables will never be used; so it does not + // matter what to set. + const nsStyleCoord dummyParentCoord; + nsStyleContext* dummyStyleContext = nullptr; + nsPresContext* dummyPresContext = nullptr; + RuleNodeCacheConditions dummyCacheKey; + + bool rv = SetCoord(aValue, aCoord, dummyParentCoord, aMask, + dummyStyleContext, dummyPresContext, + dummyCacheKey); + MOZ_ASSERT(dummyCacheKey.CacheableWithoutDependencies(), + "SetCoord() should not modify dummyCacheKey."); + + return rv; +} + +/* Given a specified value that might be a pair value, call SetCoord twice, + * either using each member of the pair, or using the unpaired value twice. + */ +static bool +SetPairCoords(const nsCSSValue& aValue, + nsStyleCoord& aCoordX, nsStyleCoord& aCoordY, + const nsStyleCoord& aParentX, const nsStyleCoord& aParentY, + int32_t aMask, nsStyleContext* aStyleContext, + nsPresContext* aPresContext, RuleNodeCacheConditions& aConditions) +{ + const nsCSSValue& valX = + aValue.GetUnit() == eCSSUnit_Pair ? aValue.GetPairValue().mXValue : aValue; + const nsCSSValue& valY = + aValue.GetUnit() == eCSSUnit_Pair ? aValue.GetPairValue().mYValue : aValue; + + bool cX = SetCoord(valX, aCoordX, aParentX, aMask, aStyleContext, + aPresContext, aConditions); + mozilla::DebugOnly<bool> cY = SetCoord(valY, aCoordY, aParentY, aMask, + aStyleContext, aPresContext, aConditions); + MOZ_ASSERT(cX == cY, "changed one but not the other"); + return cX; +} + +static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, + nsPresContext* aPresContext, nsStyleContext *aContext, + nscolor& aResult, RuleNodeCacheConditions& aConditions) +{ + bool result = false; + nsCSSUnit unit = aValue.GetUnit(); + + if (aValue.IsNumericColorUnit()) { + aResult = aValue.GetColorValue(); + result = true; + } + else if (eCSSUnit_Ident == unit) { + nsAutoString value; + aValue.GetStringValue(value); + nscolor rgba; + if (NS_ColorNameToRGB(value, &rgba)) { + aResult = rgba; + result = true; + } + } + else if (eCSSUnit_EnumColor == unit) { + int32_t intValue = aValue.GetIntValue(); + if (0 <= intValue) { + LookAndFeel::ColorID colorID = (LookAndFeel::ColorID) intValue; + bool useStandinsForNativeColors = aPresContext && + !aPresContext->IsChrome(); + if (NS_SUCCEEDED(LookAndFeel::GetColor(colorID, + useStandinsForNativeColors, &aResult))) { + result = true; + } + } + else { + aResult = NS_RGB(0, 0, 0); + result = false; + switch (intValue) { + case NS_COLOR_MOZ_HYPERLINKTEXT: + if (aPresContext) { + aResult = aPresContext->DefaultLinkColor(); + result = true; + } + break; + case NS_COLOR_MOZ_VISITEDHYPERLINKTEXT: + if (aPresContext) { + aResult = aPresContext->DefaultVisitedLinkColor(); + result = true; + } + break; + case NS_COLOR_MOZ_ACTIVEHYPERLINKTEXT: + if (aPresContext) { + aResult = aPresContext->DefaultActiveLinkColor(); + result = true; + } + break; + case NS_COLOR_CURRENTCOLOR: + // The data computed from this can't be shared in the rule tree + // because they could be used on a node with a different color + aConditions.SetUncacheable(); + if (aContext) { + aResult = aContext->StyleColor()->mColor; + result = true; + } + break; + case NS_COLOR_MOZ_DEFAULT_COLOR: + if (aPresContext) { + aResult = aPresContext->DefaultColor(); + result = true; + } + break; + case NS_COLOR_MOZ_DEFAULT_BACKGROUND_COLOR: + if (aPresContext) { + aResult = aPresContext->DefaultBackgroundColor(); + result = true; + } + break; + default: + NS_NOTREACHED("Should never have an unknown negative colorID."); + break; + } + } + } + else if (eCSSUnit_Inherit == unit) { + aResult = aParentColor; + result = true; + aConditions.SetUncacheable(); + } + else if (eCSSUnit_Enumerated == unit && + aValue.GetIntValue() == NS_STYLE_COLOR_INHERIT_FROM_BODY) { + NS_ASSERTION(aPresContext->CompatibilityMode() == eCompatibility_NavQuirks, + "Should only get this value in quirks mode"); + // We just grab the color from the prescontext, and rely on the fact that + // if the body color ever changes all its descendants will get new style + // contexts (but NOT necessarily new rulenodes). + aResult = aPresContext->BodyTextColor(); + result = true; + aConditions.SetUncacheable(); + } + return result; +} + +template<UnsetAction UnsetTo> +static void +SetComplexColor(const nsCSSValue& aValue, + const StyleComplexColor& aParentColor, + const StyleComplexColor& aInitialColor, + nsPresContext* aPresContext, + StyleComplexColor& aResult, + RuleNodeCacheConditions& aConditions) +{ + nsCSSUnit unit = aValue.GetUnit(); + if (unit == eCSSUnit_Null) { + return; + } + if (unit == eCSSUnit_Initial || + (UnsetTo == eUnsetInitial && unit == eCSSUnit_Unset)) { + aResult = aInitialColor; + } else if (unit == eCSSUnit_Inherit || + (UnsetTo == eUnsetInherit && unit == eCSSUnit_Unset)) { + aConditions.SetUncacheable(); + aResult = aParentColor; + } else if (unit == eCSSUnit_EnumColor && + aValue.GetIntValue() == NS_COLOR_CURRENTCOLOR) { + aResult = StyleComplexColor::CurrentColor(); + } else if (unit == eCSSUnit_ComplexColor) { + aResult = aValue.GetStyleComplexColorValue(); + } else { + if (!SetColor(aValue, aParentColor.mColor, aPresContext, + nullptr, aResult.mColor, aConditions)) { + MOZ_ASSERT_UNREACHABLE("Unknown color value"); + return; + } + aResult.mForegroundRatio = 0; + } +} + +static void SetGradientCoord(const nsCSSValue& aValue, nsPresContext* aPresContext, + nsStyleContext* aContext, nsStyleCoord& aResult, + RuleNodeCacheConditions& aConditions) +{ + // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT + if (!SetCoord(aValue, aResult, nsStyleCoord(), + SETCOORD_LPO | SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC, + aContext, aPresContext, aConditions)) { + NS_NOTREACHED("unexpected unit for gradient anchor point"); + aResult.SetNoneValue(); + } +} + +static void SetGradient(const nsCSSValue& aValue, nsPresContext* aPresContext, + nsStyleContext* aContext, nsStyleGradient& aResult, + RuleNodeCacheConditions& aConditions) +{ + MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Gradient, + "The given data is not a gradient"); + + const nsCSSValueGradient* gradient = aValue.GetGradientValue(); + + if (gradient->mIsExplicitSize) { + SetCoord(gradient->GetRadiusX(), aResult.mRadiusX, nsStyleCoord(), + SETCOORD_LP | SETCOORD_STORE_CALC, + aContext, aPresContext, aConditions); + if (gradient->GetRadiusY().GetUnit() != eCSSUnit_None) { + SetCoord(gradient->GetRadiusY(), aResult.mRadiusY, nsStyleCoord(), + SETCOORD_LP | SETCOORD_STORE_CALC, + aContext, aPresContext, aConditions); + aResult.mShape = NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL; + } else { + aResult.mRadiusY = aResult.mRadiusX; + aResult.mShape = NS_STYLE_GRADIENT_SHAPE_CIRCULAR; + } + aResult.mSize = NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE; + } else if (gradient->mIsRadial) { + if (gradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated) { + aResult.mShape = gradient->GetRadialShape().GetIntValue(); + } else { + NS_ASSERTION(gradient->GetRadialShape().GetUnit() == eCSSUnit_None, + "bad unit for radial shape"); + aResult.mShape = NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL; + } + if (gradient->GetRadialSize().GetUnit() == eCSSUnit_Enumerated) { + aResult.mSize = gradient->GetRadialSize().GetIntValue(); + } else { + NS_ASSERTION(gradient->GetRadialSize().GetUnit() == eCSSUnit_None, + "bad unit for radial shape"); + aResult.mSize = NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER; + } + } else { + NS_ASSERTION(gradient->GetRadialShape().GetUnit() == eCSSUnit_None, + "bad unit for linear shape"); + NS_ASSERTION(gradient->GetRadialSize().GetUnit() == eCSSUnit_None, + "bad unit for linear size"); + aResult.mShape = NS_STYLE_GRADIENT_SHAPE_LINEAR; + aResult.mSize = NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER; + } + + aResult.mLegacySyntax = gradient->mIsLegacySyntax; + + // bg-position + SetGradientCoord(gradient->mBgPos.mXValue, aPresContext, aContext, + aResult.mBgPosX, aConditions); + + SetGradientCoord(gradient->mBgPos.mYValue, aPresContext, aContext, + aResult.mBgPosY, aConditions); + + aResult.mRepeating = gradient->mIsRepeating; + + // angle + const nsStyleCoord dummyParentCoord; + if (!SetCoord(gradient->mAngle, aResult.mAngle, dummyParentCoord, SETCOORD_ANGLE, + aContext, aPresContext, aConditions)) { + NS_ASSERTION(gradient->mAngle.GetUnit() == eCSSUnit_None, + "bad unit for gradient angle"); + aResult.mAngle.SetNoneValue(); + } + + // stops + for (uint32_t i = 0; i < gradient->mStops.Length(); i++) { + nsStyleGradientStop stop; + const nsCSSValueGradientStop &valueStop = gradient->mStops[i]; + + if (!SetCoord(valueStop.mLocation, stop.mLocation, + nsStyleCoord(), SETCOORD_LPO | SETCOORD_STORE_CALC, + aContext, aPresContext, aConditions)) { + NS_NOTREACHED("unexpected unit for gradient stop location"); + } + + stop.mIsInterpolationHint = valueStop.mIsInterpolationHint; + + // inherit is not a valid color for stops, so we pass in a dummy + // parent color + NS_ASSERTION(valueStop.mColor.GetUnit() != eCSSUnit_Inherit, + "inherit is not a valid color for gradient stops"); + if (!valueStop.mIsInterpolationHint) { + SetColor(valueStop.mColor, NS_RGB(0, 0, 0), aPresContext, + aContext, stop.mColor, aConditions); + } else { + // Always initialize to the same color so we don't need to worry + // about comparisons. + stop.mColor = NS_RGB(0, 0, 0); + } + + aResult.mStops.AppendElement(stop); + } +} + +// -moz-image-rect(<uri>, <top>, <right>, <bottom>, <left>) +static void SetStyleImageToImageRect(nsStyleContext* aStyleContext, + const nsCSSValue& aValue, + nsStyleImage& aResult) +{ + MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Function && + aValue.EqualsFunction(eCSSKeyword__moz_image_rect), + "the value is not valid -moz-image-rect()"); + + nsCSSValue::Array* arr = aValue.GetArrayValue(); + MOZ_ASSERT(arr && arr->Count() == 6, "invalid number of arguments"); + + // <uri> + if (arr->Item(1).GetUnit() == eCSSUnit_Image) { + SetStyleImageRequest([&](nsStyleImageRequest* req) { + aResult.SetImageRequest(do_AddRef(req)); + }, aStyleContext->PresContext(), arr->Item(1)); + } else { + NS_WARNING("nsCSSValue::Image::Image() failed?"); + } + + // <top>, <right>, <bottom>, <left> + nsStyleSides cropRect; + NS_FOR_CSS_SIDES(side) { + nsStyleCoord coord; + const nsCSSValue& val = arr->Item(2 + side); + +#ifdef DEBUG + bool unitOk = +#endif + SetAbsCoord(val, coord, SETCOORD_FACTOR | SETCOORD_PERCENT); + MOZ_ASSERT(unitOk, "Incorrect data structure created by CSS parser"); + cropRect.Set(side, coord); + } + aResult.SetCropRect(MakeUnique<nsStyleSides>(cropRect)); +} + +static void SetStyleImage(nsStyleContext* aStyleContext, + const nsCSSValue& aValue, + nsStyleImage& aResult, + RuleNodeCacheConditions& aConditions) +{ + if (aValue.GetUnit() == eCSSUnit_Null) { + return; + } + + aResult.SetNull(); + + switch (aValue.GetUnit()) { + case eCSSUnit_Image: + SetStyleImageRequest([&](nsStyleImageRequest* req) { + aResult.SetImageRequest(do_AddRef(req)); + }, aStyleContext->PresContext(), aValue); + break; + case eCSSUnit_Function: + if (aValue.EqualsFunction(eCSSKeyword__moz_image_rect)) { + SetStyleImageToImageRect(aStyleContext, aValue, aResult); + } else { + NS_NOTREACHED("-moz-image-rect() is the only expected function"); + } + break; + case eCSSUnit_Gradient: + { + nsStyleGradient* gradient = new nsStyleGradient(); + SetGradient(aValue, aStyleContext->PresContext(), aStyleContext, + *gradient, aConditions); + aResult.SetGradientData(gradient); + break; + } + case eCSSUnit_Element: + aResult.SetElementId(aValue.GetStringBufferValue()); + break; + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_None: + break; + case eCSSUnit_URL: + { +#ifdef DEBUG + // eCSSUnit_URL is expected only if + // 1. we have eCSSUnit_URL values for if-visited style contexts, which + // we can safely treat like 'none'. + // 2. aValue is a local-ref URL, e.g. url(#foo). + // 3. aValue is a not a local-ref URL, but it refers to an element in + // the current document. For example, the url of the current document + // is "http://foo.html" and aValue is url(http://foo.html#foo). + // + // We skip image download in TryToStartImageLoadOnValue under #2 and #3, + // and that's part of reasons we get eCSSUnit_URL instead of + // eCSSUnit_Image here. + + // Check #2. + bool isLocalRef = aValue.GetURLStructValue()->IsLocalRef(); + + // Check #3. + bool isEqualExceptRef = false; + if (!isLocalRef) { + nsIDocument* currentDoc = aStyleContext->PresContext()->Document(); + nsIURI* docURI = currentDoc->GetDocumentURI(); + nsIURI* imageURI = aValue.GetURLValue(); + imageURI->EqualsExceptRef(docURI, &isEqualExceptRef); + } + + MOZ_ASSERT(aStyleContext->IsStyleIfVisited() || isEqualExceptRef || + isLocalRef, + "unexpected unit; maybe nsCSSValue::Image::Image() failed?"); +#endif + + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unexpected Unit type."); + break; + } +} + +struct SetEnumValueHelper +{ + template<typename FieldT> + static void SetIntegerValue(FieldT&, const nsCSSValue&) + { + // FIXME Is it possible to turn this assertion into a compilation error? + MOZ_ASSERT_UNREACHABLE("inappropriate unit"); + } + +#define DEFINE_ENUM_CLASS_SETTER(type_, min_, max_) \ + static void SetEnumeratedValue(type_& aField, const nsCSSValue& aValue) \ + { \ + auto value = aValue.GetIntValue(); \ + MOZ_ASSERT(value >= static_cast<decltype(value)>(type_::min_) && \ + value <= static_cast<decltype(value)>(type_::max_), \ + "inappropriate value"); \ + aField = static_cast<type_>(value); \ + } + + DEFINE_ENUM_CLASS_SETTER(StyleBoxAlign, Stretch, End) + DEFINE_ENUM_CLASS_SETTER(StyleBoxDecorationBreak, Slice, Clone) + DEFINE_ENUM_CLASS_SETTER(StyleBoxDirection, Normal, Reverse) + DEFINE_ENUM_CLASS_SETTER(StyleBoxOrient, Horizontal, Vertical) + DEFINE_ENUM_CLASS_SETTER(StyleBoxPack, Start, Justify) + DEFINE_ENUM_CLASS_SETTER(StyleBoxSizing, Content, Border) + DEFINE_ENUM_CLASS_SETTER(StyleClear, None, Both) + DEFINE_ENUM_CLASS_SETTER(StyleFillRule, Nonzero, Evenodd) + DEFINE_ENUM_CLASS_SETTER(StyleFloat, None, InlineEnd) + DEFINE_ENUM_CLASS_SETTER(StyleFloatEdge, ContentBox, MarginBox) + DEFINE_ENUM_CLASS_SETTER(StyleUserFocus, None, SelectMenu) + DEFINE_ENUM_CLASS_SETTER(StyleUserSelect, None, MozText) + DEFINE_ENUM_CLASS_SETTER(StyleUserInput, None, Auto) + DEFINE_ENUM_CLASS_SETTER(StyleUserModify, ReadOnly, WriteOnly) + DEFINE_ENUM_CLASS_SETTER(StyleWindowDragging, Default, NoDrag) + DEFINE_ENUM_CLASS_SETTER(StyleOrient, Inline, Vertical) +#ifdef MOZ_XUL + DEFINE_ENUM_CLASS_SETTER(StyleDisplay, None, Popup) +#else + DEFINE_ENUM_CLASS_SETTER(StyleDisplay, None, InlineBox) +#endif + +#undef DEF_SET_ENUMERATED_VALUE +}; + +template<typename FieldT> +struct SetIntegerValueHelper +{ + static void SetIntegerValue(FieldT& aField, const nsCSSValue& aValue) + { + aField = aValue.GetIntValue(); + } + static void SetEnumeratedValue(FieldT& aField, const nsCSSValue& aValue) + { + aField = aValue.GetIntValue(); + } +}; + +template<typename FieldT> +struct SetValueHelper : Conditional<IsEnum<FieldT>::value, + SetEnumValueHelper, + SetIntegerValueHelper<FieldT>>::Type +{ + template<typename ValueT> + static void SetValue(FieldT& aField, const ValueT& aValue) + { + aField = aValue; + } + static void SetValue(FieldT&, unused_t) + { + // FIXME Is it possible to turn this assertion into a compilation error? + MOZ_ASSERT_UNREACHABLE("inappropriate unit"); + } +}; + + +// flags for SetValue - align values with SETCOORD_* constants +// where possible + +#define SETVAL_INTEGER 0x40 // I +#define SETVAL_ENUMERATED 0x80 // E +#define SETVAL_UNSET_INHERIT 0x00400000 +#define SETVAL_UNSET_INITIAL 0x00800000 + +// no caller cares whether aField was changed or not +template<typename FieldT, typename InitialT, + typename AutoT, typename NoneT, typename NormalT, typename SysFontT> +static void +SetValue(const nsCSSValue& aValue, FieldT& aField, + RuleNodeCacheConditions& aConditions, uint32_t aMask, + FieldT aParentValue, + InitialT aInitialValue, + AutoT aAutoValue, + NoneT aNoneValue, + NormalT aNormalValue, + SysFontT aSystemFontValue) +{ + typedef SetValueHelper<FieldT> Helper; + + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + return; + + // every caller of SetValue provides inherit and initial + // alternatives, so we don't require them to say so in the mask + case eCSSUnit_Inherit: + aConditions.SetUncacheable(); + aField = aParentValue; + return; + + case eCSSUnit_Initial: + Helper::SetValue(aField, aInitialValue); + return; + + // every caller provides one or other of these alternatives, + // but they have to say which + case eCSSUnit_Enumerated: + if (aMask & SETVAL_ENUMERATED) { + Helper::SetEnumeratedValue(aField, aValue); + return; + } + break; + + case eCSSUnit_Integer: + if (aMask & SETVAL_INTEGER) { + Helper::SetIntegerValue(aField, aValue); + return; + } + break; + + // remaining possibilities in descending order of frequency of use + case eCSSUnit_Auto: + Helper::SetValue(aField, aAutoValue); + return; + + case eCSSUnit_None: + Helper::SetValue(aField, aNoneValue); + return; + + case eCSSUnit_Normal: + Helper::SetValue(aField, aNormalValue); + return; + + case eCSSUnit_System_Font: + Helper::SetValue(aField, aSystemFontValue); + return; + + case eCSSUnit_Unset: + if (aMask & SETVAL_UNSET_INHERIT) { + aConditions.SetUncacheable(); + aField = aParentValue; + return; + } + if (aMask & SETVAL_UNSET_INITIAL) { + Helper::SetValue(aField, aInitialValue); + return; + } + break; + + default: + break; + } + + NS_NOTREACHED("SetValue: inappropriate unit"); +} + +template <typename FieldT, typename T1> +static void +SetValue(const nsCSSValue& aValue, FieldT& aField, + RuleNodeCacheConditions& aConditions, uint32_t aMask, + FieldT aParentValue, T1 aInitialValue) +{ + SetValue(aValue, aField, aConditions, aMask, aParentValue, + aInitialValue, Unused, Unused, Unused, Unused); +} + +// flags for SetFactor +#define SETFCT_POSITIVE 0x01 // assert value is >= 0.0f +#define SETFCT_OPACITY 0x02 // clamp value to [0.0f .. 1.0f] +#define SETFCT_NONE 0x04 // allow _None (uses aInitialValue). +#define SETFCT_UNSET_INHERIT 0x00400000 +#define SETFCT_UNSET_INITIAL 0x00800000 + +static void +SetFactor(const nsCSSValue& aValue, float& aField, RuleNodeCacheConditions& aConditions, + float aParentValue, float aInitialValue, uint32_t aFlags = 0) +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + return; + + case eCSSUnit_Number: + aField = aValue.GetFloatValue(); + if (aFlags & SETFCT_POSITIVE) { + NS_ASSERTION(aField >= 0.0f, "negative value for positive-only property"); + if (aField < 0.0f) + aField = 0.0f; + } + if (aFlags & SETFCT_OPACITY) { + if (aField < 0.0f) + aField = 0.0f; + if (aField > 1.0f) + aField = 1.0f; + } + return; + + case eCSSUnit_Inherit: + aConditions.SetUncacheable(); + aField = aParentValue; + return; + + case eCSSUnit_Initial: + aField = aInitialValue; + return; + + case eCSSUnit_None: + if (aFlags & SETFCT_NONE) { + aField = aInitialValue; + return; + } + break; + + case eCSSUnit_Unset: + if (aFlags & SETFCT_UNSET_INHERIT) { + aConditions.SetUncacheable(); + aField = aParentValue; + return; + } + if (aFlags & SETFCT_UNSET_INITIAL) { + aField = aInitialValue; + return; + } + break; + + default: + break; + } + + NS_NOTREACHED("SetFactor: inappropriate unit"); +} + +void* +nsRuleNode::operator new(size_t sz, nsPresContext* aPresContext) +{ + // Check the recycle list first. + return aPresContext->PresShell()->AllocateByObjectID(eArenaObjectID_nsRuleNode, sz); +} + +// Overridden to prevent the global delete from being called, since the memory +// came out of an nsIArena instead of the global delete operator's heap. +void +nsRuleNode::Destroy() +{ + // Destroy ourselves. + this->~nsRuleNode(); + + // Don't let the memory be freed, since it will be recycled + // instead. Don't call the global operator delete. + mPresContext->PresShell()->FreeByObjectID(eArenaObjectID_nsRuleNode, this); +} + +already_AddRefed<nsRuleNode> +nsRuleNode::CreateRootNode(nsPresContext* aPresContext) +{ + return do_AddRef(new (aPresContext) + nsRuleNode(aPresContext, nullptr, nullptr, SheetType::Unknown, false)); +} + +nsRuleNode::nsRuleNode(nsPresContext* aContext, nsRuleNode* aParent, + nsIStyleRule* aRule, SheetType aLevel, + bool aIsImportant) + : mPresContext(aContext), + mParent(aParent), + mRule(aRule), + mNextSibling(nullptr), + mDependentBits((uint32_t(aLevel) << NS_RULE_NODE_LEVEL_SHIFT) | + (aIsImportant ? NS_RULE_NODE_IS_IMPORTANT : 0)), + mNoneBits(aParent ? aParent->mNoneBits & NS_RULE_NODE_HAS_ANIMATION_DATA : + 0), + mRefCnt(0) +{ + MOZ_ASSERT(aContext); + MOZ_ASSERT(IsRoot() == !aRule, + "non-root rule nodes must have a rule"); + + mChildren.asVoid = nullptr; + MOZ_COUNT_CTOR(nsRuleNode); + + NS_ASSERTION(IsRoot() || GetLevel() == aLevel, "not enough bits"); + NS_ASSERTION(IsRoot() || IsImportantRule() == aIsImportant, "yikes"); + MOZ_ASSERT(aContext->StyleSet()->IsGecko(), + "ServoStyleSets should not have rule nodes"); + aContext->StyleSet()->AsGecko()->RuleNodeUnused(this, /* aMayGC = */ false); + + // nsStyleSet::GetContext depends on there being only one animation + // rule. + MOZ_ASSERT(IsRoot() || GetLevel() != SheetType::Animation || + mParent->IsRoot() || + mParent->GetLevel() != SheetType::Animation, + "must be only one rule at animation level"); +} + +nsRuleNode::~nsRuleNode() +{ + MOZ_ASSERT(!HaveChildren()); + MOZ_COUNT_DTOR(nsRuleNode); + if (mParent) { + mParent->RemoveChild(this); + } + + if (mStyleData.mResetData || mStyleData.mInheritedData) + mStyleData.Destroy(mDependentBits, mPresContext); +} + +nsRuleNode* +nsRuleNode::Transition(nsIStyleRule* aRule, SheetType aLevel, + bool aIsImportantRule) +{ +#ifdef DEBUG + { + RefPtr<css::Declaration> declaration(do_QueryObject(aRule)); + MOZ_ASSERT(!declaration || !declaration->IsMutable(), + "caller must call Declaration::SetImmutable first"); + } +#endif + + nsRuleNode* next = nullptr; + nsRuleNode::Key key(aRule, aLevel, aIsImportantRule); + + if (HaveChildren() && !ChildrenAreHashed()) { + int32_t numKids = 0; + nsRuleNode* curr = ChildrenList(); + while (curr && curr->GetKey() != key) { + curr = curr->mNextSibling; + ++numKids; + } + if (curr) + next = curr; + else if (numKids >= kMaxChildrenInList) + ConvertChildrenToHash(numKids); + } + + if (ChildrenAreHashed()) { + auto entry = + static_cast<ChildrenHashEntry*>(ChildrenHash()->Add(&key, fallible)); + if (!entry) { + NS_WARNING("out of memory"); + return this; + } + if (entry->mRuleNode) + next = entry->mRuleNode; + else { + next = entry->mRuleNode = new (mPresContext) + nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule); + } + } else if (!next) { + // Create the new entry in our list. + next = new (mPresContext) + nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule); + next->mNextSibling = ChildrenList(); + SetChildrenList(next); + } + + return next; +} + +nsRuleNode* +nsRuleNode::RuleTree() +{ + nsRuleNode* n = this; + while (n->mParent) { + n = n->mParent; + } + return n; +} + +void nsRuleNode::SetUsedDirectly() +{ + mDependentBits |= NS_RULE_NODE_USED_DIRECTLY; + + // Maintain the invariant that any rule node that is used directly has + // all structs that live in the rule tree cached (which + // nsRuleNode::GetStyleData depends on for speed). + if (mDependentBits & NS_STYLE_INHERIT_MASK) { + for (nsStyleStructID sid = nsStyleStructID(0); sid < nsStyleStructID_Length; + sid = nsStyleStructID(sid + 1)) { + uint32_t bit = nsCachedStyleData::GetBitForSID(sid); + if (mDependentBits & bit) { + nsRuleNode *source = mParent; + while ((source->mDependentBits & bit) && !source->IsUsedDirectly()) { + source = source->mParent; + } + void *data = source->mStyleData.GetStyleData(sid); + NS_ASSERTION(data, "unexpected null struct"); + mStyleData.SetStyleData(sid, mPresContext, data); + } + } + } +} + +void +nsRuleNode::ConvertChildrenToHash(int32_t aNumKids) +{ + NS_ASSERTION(!ChildrenAreHashed() && HaveChildren(), + "must have a non-empty list of children"); + PLDHashTable *hash = new PLDHashTable(&ChildrenHashOps, + sizeof(ChildrenHashEntry), + aNumKids); + for (nsRuleNode* curr = ChildrenList(); curr; curr = curr->mNextSibling) { + Key key = curr->GetKey(); + // This will never fail because of the initial size we gave the table. + auto entry = + static_cast<ChildrenHashEntry*>(hash->Add(&key)); + NS_ASSERTION(!entry->mRuleNode, "duplicate entries in list"); + entry->mRuleNode = curr; + } + SetChildrenHash(hash); +} + +void +nsRuleNode::RemoveChild(nsRuleNode* aNode) +{ + MOZ_ASSERT(HaveChildren()); + if (ChildrenAreHashed()) { + PLDHashTable* children = ChildrenHash(); + Key key = aNode->GetKey(); + MOZ_ASSERT(children->Search(&key)); + children->Remove(&key); + if (children->EntryCount() == 0) { + delete children; + mChildren.asVoid = nullptr; + } + } else { + // This linear traversal is unfortunate, but we do the same thing when + // adding nodes. The traversal is bounded by kMaxChildrenInList. + nsRuleNode** curr = &mChildren.asList; + while (*curr != aNode) { + curr = &((*curr)->mNextSibling); + MOZ_ASSERT(*curr); + } + *curr = (*curr)->mNextSibling; + + // If there was one element in the list, this sets mChildren.asList + // to 0, and HaveChildren() will return false. + } +} + +inline void +nsRuleNode::PropagateNoneBit(uint32_t aBit, nsRuleNode* aHighestNode) +{ + nsRuleNode* curr = this; + for (;;) { + NS_ASSERTION(!(curr->mNoneBits & aBit), "propagating too far"); + curr->mNoneBits |= aBit; + if (curr == aHighestNode) + break; + curr = curr->mParent; + } +} + +inline void +nsRuleNode::PropagateDependentBit(nsStyleStructID aSID, nsRuleNode* aHighestNode, + void* aStruct) +{ + NS_ASSERTION(aStruct, "expected struct"); + + uint32_t bit = nsCachedStyleData::GetBitForSID(aSID); + for (nsRuleNode* curr = this; curr != aHighestNode; curr = curr->mParent) { + if (curr->mDependentBits & bit) { +#ifdef DEBUG + while (curr != aHighestNode) { + NS_ASSERTION(curr->mDependentBits & bit, "bit not set"); + curr = curr->mParent; + } +#endif + break; + } + + curr->mDependentBits |= bit; + + if (curr->IsUsedDirectly()) { + curr->mStyleData.SetStyleData(aSID, mPresContext, aStruct); + } + } +} + +/* static */ void +nsRuleNode::PropagateGrandancestorBit(nsStyleContext* aContext, + nsStyleContext* aContextInheritedFrom) +{ + MOZ_ASSERT(aContext); + MOZ_ASSERT(aContextInheritedFrom && + aContextInheritedFrom != aContext, + "aContextInheritedFrom must be an ancestor of aContext"); + + for (nsStyleContext* context = aContext->GetParent(); + context != aContextInheritedFrom; + context = context->GetParent()) { + if (!context) { + MOZ_ASSERT(false, "aContextInheritedFrom must be an ancestor of " + "aContext's parent"); + break; + } + context->AddStyleBit(NS_STYLE_CHILD_USES_GRANDANCESTOR_STYLE); + } +} + +/* + * The following "Check" functions are used for determining what type of + * sharing can be used for the data on this rule node. MORE HERE... + */ + +/* + * a callback function that that can revise the result of + * CheckSpecifiedProperties before finishing; aResult is the current + * result, and it returns the revised one. + */ +typedef nsRuleNode::RuleDetail + (* CheckCallbackFn)(const nsRuleData* aRuleData, + nsRuleNode::RuleDetail aResult); + +/** + * @param aValue the value being examined + * @param aSpecifiedCount to be incremented by one if the value is specified + * @param aInheritedCount to be incremented by one if the value is set to inherit + * @param aUnsetCount to be incremented by one if the value is set to unset + */ +inline void +ExamineCSSValue(const nsCSSValue& aValue, + uint32_t& aSpecifiedCount, + uint32_t& aInheritedCount, + uint32_t& aUnsetCount) +{ + if (aValue.GetUnit() != eCSSUnit_Null) { + ++aSpecifiedCount; + if (aValue.GetUnit() == eCSSUnit_Inherit) { + ++aInheritedCount; + } else if (aValue.GetUnit() == eCSSUnit_Unset) { + ++aUnsetCount; + } + } +} + +static nsRuleNode::RuleDetail +CheckFontCallback(const nsRuleData* aRuleData, + nsRuleNode::RuleDetail aResult) +{ + // em, ex, percent, 'larger', and 'smaller' values on font-size depend + // on the parent context's font-size + // Likewise, 'lighter' and 'bolder' values of 'font-weight', and 'wider' + // and 'narrower' values of 'font-stretch' depend on the parent. + const nsCSSValue& size = *aRuleData->ValueForFontSize(); + const nsCSSValue& weight = *aRuleData->ValueForFontWeight(); + if ((size.IsRelativeLengthUnit() && size.GetUnit() != eCSSUnit_RootEM) || + size.GetUnit() == eCSSUnit_Percent || + (size.GetUnit() == eCSSUnit_Enumerated && + (size.GetIntValue() == NS_STYLE_FONT_SIZE_SMALLER || + size.GetIntValue() == NS_STYLE_FONT_SIZE_LARGER)) || + aRuleData->ValueForScriptLevel()->GetUnit() == eCSSUnit_Integer || + (weight.GetUnit() == eCSSUnit_Enumerated && + (weight.GetIntValue() == NS_STYLE_FONT_WEIGHT_BOLDER || + weight.GetIntValue() == NS_STYLE_FONT_WEIGHT_LIGHTER))) { + NS_ASSERTION(aResult == nsRuleNode::eRulePartialReset || + aResult == nsRuleNode::eRuleFullReset || + aResult == nsRuleNode::eRulePartialMixed || + aResult == nsRuleNode::eRuleFullMixed, + "we know we already have a reset-counted property"); + // Promote reset to mixed since we have something that depends on + // the parent. But never promote to inherited since that could + // cause inheritance of the exact value. + if (aResult == nsRuleNode::eRulePartialReset) + aResult = nsRuleNode::eRulePartialMixed; + else if (aResult == nsRuleNode::eRuleFullReset) + aResult = nsRuleNode::eRuleFullMixed; + } + + return aResult; +} + +static nsRuleNode::RuleDetail +CheckColorCallback(const nsRuleData* aRuleData, + nsRuleNode::RuleDetail aResult) +{ + // currentColor values for color require inheritance + const nsCSSValue* colorValue = aRuleData->ValueForColor(); + if (colorValue->GetUnit() == eCSSUnit_EnumColor && + colorValue->GetIntValue() == NS_COLOR_CURRENTCOLOR) { + NS_ASSERTION(aResult == nsRuleNode::eRuleFullReset, + "we should already be counted as full-reset"); + aResult = nsRuleNode::eRuleFullInherited; + } + + return aResult; +} + +static nsRuleNode::RuleDetail +CheckTextCallback(const nsRuleData* aRuleData, + nsRuleNode::RuleDetail aResult) +{ + const nsCSSValue* textAlignValue = aRuleData->ValueForTextAlign(); + if (textAlignValue->GetUnit() == eCSSUnit_Enumerated && + (textAlignValue->GetIntValue() == + NS_STYLE_TEXT_ALIGN_MOZ_CENTER_OR_INHERIT || + textAlignValue->GetIntValue() == NS_STYLE_TEXT_ALIGN_MATCH_PARENT)) { + // Promote reset to mixed since we have something that depends on + // the parent. + if (aResult == nsRuleNode::eRulePartialReset) + aResult = nsRuleNode::eRulePartialMixed; + else if (aResult == nsRuleNode::eRuleFullReset) + aResult = nsRuleNode::eRuleFullMixed; + } + + return aResult; +} + +static nsRuleNode::RuleDetail +CheckVariablesCallback(const nsRuleData* aRuleData, + nsRuleNode::RuleDetail aResult) +{ + // We don't actually have any properties on nsStyleVariables, so we do + // all of the RuleDetail calculation in here. + if (aRuleData->mVariables) { + return nsRuleNode::eRulePartialMixed; + } + return nsRuleNode::eRuleNone; +} + +#define FLAG_DATA_FOR_PROPERTY(name_, id_, method_, flags_, pref_, \ + parsevariant_, kwtable_, stylestructoffset_, \ + animtype_) \ + flags_, + +// The order here must match the enums in *CheckCounter in nsCSSProps.cpp. + +static const uint32_t gFontFlags[] = { +#define CSS_PROP_FONT FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_FONT +}; + +static const uint32_t gDisplayFlags[] = { +#define CSS_PROP_DISPLAY FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_DISPLAY +}; + +static const uint32_t gVisibilityFlags[] = { +#define CSS_PROP_VISIBILITY FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_VISIBILITY +}; + +static const uint32_t gMarginFlags[] = { +#define CSS_PROP_MARGIN FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_MARGIN +}; + +static const uint32_t gBorderFlags[] = { +#define CSS_PROP_BORDER FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_BORDER +}; + +static const uint32_t gPaddingFlags[] = { +#define CSS_PROP_PADDING FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_PADDING +}; + +static const uint32_t gOutlineFlags[] = { +#define CSS_PROP_OUTLINE FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_OUTLINE +}; + +static const uint32_t gListFlags[] = { +#define CSS_PROP_LIST FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_LIST +}; + +static const uint32_t gColorFlags[] = { +#define CSS_PROP_COLOR FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_COLOR +}; + +static const uint32_t gBackgroundFlags[] = { +#define CSS_PROP_BACKGROUND FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_BACKGROUND +}; + +static const uint32_t gPositionFlags[] = { +#define CSS_PROP_POSITION FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_POSITION +}; + +static const uint32_t gTableFlags[] = { +#define CSS_PROP_TABLE FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_TABLE +}; + +static const uint32_t gTableBorderFlags[] = { +#define CSS_PROP_TABLEBORDER FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_TABLEBORDER +}; + +static const uint32_t gContentFlags[] = { +#define CSS_PROP_CONTENT FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_CONTENT +}; + +static const uint32_t gTextFlags[] = { +#define CSS_PROP_TEXT FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_TEXT +}; + +static const uint32_t gTextResetFlags[] = { +#define CSS_PROP_TEXTRESET FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_TEXTRESET +}; + +static const uint32_t gUserInterfaceFlags[] = { +#define CSS_PROP_USERINTERFACE FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_USERINTERFACE +}; + +static const uint32_t gUIResetFlags[] = { +#define CSS_PROP_UIRESET FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_UIRESET +}; + +static const uint32_t gXULFlags[] = { +#define CSS_PROP_XUL FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_XUL +}; + +static const uint32_t gSVGFlags[] = { +#define CSS_PROP_SVG FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_SVG +}; + +static const uint32_t gSVGResetFlags[] = { +#define CSS_PROP_SVGRESET FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_SVGRESET +}; + +static const uint32_t gColumnFlags[] = { +#define CSS_PROP_COLUMN FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_COLUMN +}; + +// There are no properties in nsStyleVariables, but we can't have a +// zero length array. +static const uint32_t gVariablesFlags[] = { + 0, +#define CSS_PROP_VARIABLES FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_VARIABLES +}; +static_assert(sizeof(gVariablesFlags) == sizeof(uint32_t), + "if nsStyleVariables has properties now you can remove the dummy " + "gVariablesFlags entry"); + +static const uint32_t gEffectsFlags[] = { +#define CSS_PROP_EFFECTS FLAG_DATA_FOR_PROPERTY +#include "nsCSSPropList.h" +#undef CSS_PROP_EFFECTS +}; + +#undef FLAG_DATA_FOR_PROPERTY + +static const uint32_t* gFlagsByStruct[] = { + +#define STYLE_STRUCT(name, checkdata_cb) \ + g##name##Flags, +#include "nsStyleStructList.h" +#undef STYLE_STRUCT + +}; + +static const CheckCallbackFn gCheckCallbacks[] = { + +#define STYLE_STRUCT(name, checkdata_cb) \ + checkdata_cb, +#include "nsStyleStructList.h" +#undef STYLE_STRUCT + +}; + +#ifdef DEBUG +static bool +AreAllMathMLPropertiesUndefined(const nsRuleData* aRuleData) +{ + return + aRuleData->ValueForScriptLevel()->GetUnit() == eCSSUnit_Null && + aRuleData->ValueForScriptSizeMultiplier()->GetUnit() == eCSSUnit_Null && + aRuleData->ValueForScriptMinSize()->GetUnit() == eCSSUnit_Null && + aRuleData->ValueForMathVariant()->GetUnit() == eCSSUnit_Null && + aRuleData->ValueForMathDisplay()->GetUnit() == eCSSUnit_Null; +} +#endif + +inline nsRuleNode::RuleDetail +nsRuleNode::CheckSpecifiedProperties(const nsStyleStructID aSID, + const nsRuleData* aRuleData) +{ + // Build a count of the: + uint32_t total = 0, // total number of props in the struct + specified = 0, // number that were specified for this node + inherited = 0, // number that were 'inherit' (and not + // eCSSUnit_Inherit) for this node + unset = 0; // number that were 'unset' + + // See comment in nsRuleData.h above mValueOffsets. + MOZ_ASSERT(aRuleData->mValueOffsets[aSID] == 0, + "we assume the value offset is zero instead of adding it"); + for (nsCSSValue *values = aRuleData->mValueStorage, + *values_end = values + nsCSSProps::PropertyCountInStruct(aSID); + values != values_end; ++values) { + ++total; + ExamineCSSValue(*values, specified, inherited, unset); + } + + if (!nsCachedStyleData::IsReset(aSID)) { + // For inherited properties, 'unset' means the same as 'inherit'. + inherited += unset; + unset = 0; + } + +#if 0 + printf("CheckSpecifiedProperties: SID=%d total=%d spec=%d inh=%d.\n", + aSID, total, specified, inherited); +#endif + + NS_ASSERTION(aSID != eStyleStruct_Font || + mPresContext->Document()->GetMathMLEnabled() || + AreAllMathMLPropertiesUndefined(aRuleData), + "MathML style property was defined even though MathML is disabled"); + + /* + * Return the most specific information we can: prefer None or Full + * over Partial, and Reset or Inherited over Mixed, since we can + * optimize based on the edge cases and not the in-between cases. + */ + nsRuleNode::RuleDetail result; + if (inherited == total) + result = eRuleFullInherited; + else if (specified == total + // MathML defines 5 properties in Font that will never be set when + // MathML is not in use. Therefore if all but five + // properties have been set, and MathML is not enabled, we can treat + // this as fully specified. Code in nsMathMLElementFactory will + // rebuild the rule tree and style data when MathML is first enabled + // (see nsMathMLElement::BindToTree). + || (aSID == eStyleStruct_Font && specified + 5 == total && + !mPresContext->Document()->GetMathMLEnabled()) + ) { + if (inherited == 0) + result = eRuleFullReset; + else + result = eRuleFullMixed; + } else if (specified == 0) + result = eRuleNone; + else if (specified == inherited) + result = eRulePartialInherited; + else if (inherited == 0) + result = eRulePartialReset; + else + result = eRulePartialMixed; + + CheckCallbackFn cb = gCheckCallbacks[aSID]; + if (cb) { + result = (*cb)(aRuleData, result); + } + + return result; +} + +// If we need to restrict which properties apply to the style context, +// return the bit to check in nsCSSProp's flags table. Otherwise, +// return 0. +inline uint32_t +GetPseudoRestriction(nsStyleContext *aContext) +{ + // This needs to match nsStyleSet::WalkRestrictionRule. + uint32_t pseudoRestriction = 0; + nsIAtom *pseudoType = aContext->GetPseudo(); + if (pseudoType) { + if (pseudoType == nsCSSPseudoElements::firstLetter) { + pseudoRestriction = CSS_PROPERTY_APPLIES_TO_FIRST_LETTER; + } else if (pseudoType == nsCSSPseudoElements::firstLine) { + pseudoRestriction = CSS_PROPERTY_APPLIES_TO_FIRST_LINE; + } else if (pseudoType == nsCSSPseudoElements::placeholder) { + pseudoRestriction = CSS_PROPERTY_APPLIES_TO_PLACEHOLDER; + } + } + return pseudoRestriction; +} + +static void +UnsetPropertiesWithoutFlags(const nsStyleStructID aSID, + nsRuleData* aRuleData, + uint32_t aFlags) +{ + NS_ASSERTION(aFlags != 0, "aFlags must be nonzero"); + + const uint32_t *flagData = gFlagsByStruct[aSID]; + + // See comment in nsRuleData.h above mValueOffsets. + MOZ_ASSERT(aRuleData->mValueOffsets[aSID] == 0, + "we assume the value offset is zero instead of adding it"); + nsCSSValue *values = aRuleData->mValueStorage; + + for (size_t i = 0, i_end = nsCSSProps::PropertyCountInStruct(aSID); + i != i_end; ++i) { + if ((flagData[i] & aFlags) != aFlags) + values[i].Reset(); + } +} + +/** + * We allocate arrays of CSS values with alloca. (These arrays are a + * fixed size per style struct, but we don't want to waste the + * allocation and construction/destruction costs of the big structs when + * we're handling much smaller ones.) Since the lifetime of an alloca + * allocation is the life of the calling function, the caller must call + * alloca. However, to ensure that constructors and destructors are + * balanced, we do the constructor and destructor calling from this RAII + * class, AutoCSSValueArray. + */ +struct AutoCSSValueArray { + /** + * aStorage must be the result of alloca(aCount * sizeof(nsCSSValue)) + */ + AutoCSSValueArray(void* aStorage, size_t aCount) { + MOZ_ASSERT(size_t(aStorage) % NS_ALIGNMENT_OF(nsCSSValue) == 0, + "bad alignment from alloca"); + mCount = aCount; + // Don't use placement new[], since it might store extra data + // for the count (on Windows!). + mArray = static_cast<nsCSSValue*>(aStorage); + for (size_t i = 0; i < mCount; ++i) { + new (KnownNotNull, mArray + i) nsCSSValue(); + } + } + + ~AutoCSSValueArray() { + for (size_t i = 0; i < mCount; ++i) { + mArray[i].~nsCSSValue(); + } + } + + nsCSSValue* get() { return mArray; } + +private: + nsCSSValue *mArray; + size_t mCount; +}; + +/* static */ bool +nsRuleNode::ResolveVariableReferences(const nsStyleStructID aSID, + nsRuleData* aRuleData, + nsStyleContext* aContext) +{ + MOZ_ASSERT(aSID != eStyleStruct_Variables); + MOZ_ASSERT(aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(aSID)); + MOZ_ASSERT(aRuleData->mValueOffsets[aSID] == 0); + + nsCSSParser parser; + bool anyTokenStreams = false; + + // Look at each property in the nsRuleData for the given style struct. + size_t nprops = nsCSSProps::PropertyCountInStruct(aSID); + for (nsCSSValue* value = aRuleData->mValueStorage, + *values_end = aRuleData->mValueStorage + nprops; + value != values_end; value++) { + if (value->GetUnit() != eCSSUnit_TokenStream) { + continue; + } + + const CSSVariableValues* variables = + &aContext->StyleVariables()->mVariables; + nsCSSValueTokenStream* tokenStream = value->GetTokenStreamValue(); + + MOZ_ASSERT(tokenStream->mLevel != SheetType::Count, + "Token stream should have a defined level"); + + AutoRestore<SheetType> saveLevel(aRuleData->mLevel); + aRuleData->mLevel = tokenStream->mLevel; + + // Note that ParsePropertyWithVariableReferences relies on the fact + // that the nsCSSValue in aRuleData for the property we are re-parsing + // is still the token stream value. When + // ParsePropertyWithVariableReferences calls + // nsCSSExpandedDataBlock::MapRuleInfoInto, that function will add + // the ImageValue that is created into the token stream object's + // mImageValues table; see the comment above mImageValues for why. + + // XXX Should pass in sheet here (see bug 952338). + parser.ParsePropertyWithVariableReferences( + tokenStream->mPropertyID, tokenStream->mShorthandPropertyID, + tokenStream->mTokenStream, variables, aRuleData, + tokenStream->mSheetURI, tokenStream->mBaseURI, + tokenStream->mSheetPrincipal, nullptr, + tokenStream->mLineNumber, tokenStream->mLineOffset); + aRuleData->mConditions.SetUncacheable(); + anyTokenStreams = true; + } + + return anyTokenStreams; +} + +const void* +nsRuleNode::WalkRuleTree(const nsStyleStructID aSID, + nsStyleContext* aContext) +{ + // use placement new[] on the result of alloca() to allocate a + // variable-sized stack array, including execution of constructors, + // and use an RAII class to run the destructors too. + size_t nprops = nsCSSProps::PropertyCountInStruct(aSID); + void* dataStorage = alloca(nprops * sizeof(nsCSSValue)); + AutoCSSValueArray dataArray(dataStorage, nprops); + + nsRuleData ruleData(nsCachedStyleData::GetBitForSID(aSID), + dataArray.get(), mPresContext, aContext); + ruleData.mValueOffsets[aSID] = 0; + + // We start at the most specific rule in the tree. + void* startStruct = nullptr; + + nsRuleNode* ruleNode = this; + nsRuleNode* highestNode = nullptr; // The highest node in the rule tree + // that has the same properties + // specified for struct |aSID| as + // |this| does. + nsRuleNode* rootNode = this; // After the loop below, this will be the + // highest node that we've walked without + // finding cached data on the rule tree. + // If we don't find any cached data, it + // will be the root. (XXX misnamed) + RuleDetail detail = eRuleNone; + uint32_t bit = nsCachedStyleData::GetBitForSID(aSID); + + while (ruleNode) { + // See if this rule node has cached the fact that the remaining + // nodes along this path specify no data whatsoever. + if (ruleNode->mNoneBits & bit) + break; + + // If the dependent bit is set on a rule node for this struct, that + // means its rule won't have any information to add, so skip it. + // NOTE: If we exit the loop because of the !IsUsedDirectly() check, + // then we're guaranteed to break immediately afterwards due to a + // non-null startStruct. + while ((ruleNode->mDependentBits & bit) && !ruleNode->IsUsedDirectly()) { + NS_ASSERTION(ruleNode->mStyleData.GetStyleData(aSID) == nullptr, + "dependent bit with cached data makes no sense"); + // Climb up to the next rule in the tree (a less specific rule). + rootNode = ruleNode; + ruleNode = ruleNode->mParent; + NS_ASSERTION(!(ruleNode->mNoneBits & bit), "can't have both bits set"); + } + + // Check for cached data after the inner loop above -- otherwise + // we'll miss it. + startStruct = ruleNode->mStyleData.GetStyleData(aSID); + if (startStruct) + break; // We found a rule with fully specified data. We don't + // need to go up the tree any further, since the remainder + // of this branch has already been computed. + + // Ask the rule to fill in the properties that it specifies. + nsIStyleRule *rule = ruleNode->mRule; + if (rule) { + ruleData.mLevel = ruleNode->GetLevel(); + ruleData.mIsImportantRule = ruleNode->IsImportantRule(); + rule->MapRuleInfoInto(&ruleData); + } + + // Now we check to see how many properties have been specified by + // the rules we've examined so far. + RuleDetail oldDetail = detail; + detail = CheckSpecifiedProperties(aSID, &ruleData); + + if (oldDetail == eRuleNone && detail != eRuleNone) + highestNode = ruleNode; + + if (detail == eRuleFullReset || + detail == eRuleFullMixed || + detail == eRuleFullInherited) + break; // We don't need to examine any more rules. All properties + // have been fully specified. + + // Climb up to the next rule in the tree (a less specific rule). + rootNode = ruleNode; + ruleNode = ruleNode->mParent; + } + + bool recomputeDetail = false; + + // If we are computing a style struct other than nsStyleVariables, and + // ruleData has any properties with variable references (nsCSSValues of + // type eCSSUnit_TokenStream), then we need to resolve these. + if (aSID != eStyleStruct_Variables) { + // A property's value might have became 'inherit' after resolving + // variable references. (This happens when an inherited property + // fails to parse its resolved value.) We need to recompute + // |detail| in case this happened. + recomputeDetail = ResolveVariableReferences(aSID, &ruleData, aContext); + } + + // If needed, unset the properties that don't have a flag that allows + // them to be set for this style context. (For example, only some + // properties apply to :first-line and :first-letter.) + uint32_t pseudoRestriction = GetPseudoRestriction(aContext); + if (pseudoRestriction) { + UnsetPropertiesWithoutFlags(aSID, &ruleData, pseudoRestriction); + + // We need to recompute |detail| based on the restrictions we just applied. + // We can adjust |detail| arbitrarily because of the restriction + // rule added in nsStyleSet::WalkRestrictionRule. + recomputeDetail = true; + } + + if (recomputeDetail) { + detail = CheckSpecifiedProperties(aSID, &ruleData); + } + + NS_ASSERTION(!startStruct || (detail != eRuleFullReset && + detail != eRuleFullMixed && + detail != eRuleFullInherited), + "can't have start struct and be fully specified"); + + bool isReset = nsCachedStyleData::IsReset(aSID); + if (!highestNode) + highestNode = rootNode; + + MOZ_ASSERT(!(aSID == eStyleStruct_Variables && startStruct), + "if we start caching Variables structs in the rule tree, then " + "not forcing detail to eRulePartialMixed just below is no " + "longer valid"); + + if (detail == eRuleNone && isReset) { + // We specified absolutely no rule information for a reset struct, and we + // may or may not have found a parent rule in the tree that specified all + // the rule information. Regardless, we don't need to use any cache + // conditions if we cache this struct in the rule tree. + // + // Normally ruleData.mConditions would already indicate that the struct + // is cacheable without conditions if detail is eRuleNone, but because + // of the UnsetPropertiesWithoutFlags call above, we may have encountered + // some rules with dependencies, which we then cleared out of ruleData. + // + // ruleData.mConditions could also indicate we are not cacheable at all, + // such as when AnimValuesStyleRule prevents us from caching structs + // when attempting to apply animations to pseudos. + // + // So if we we are uncacheable, we leave it, but if we are cacheable + // with dependencies, we convert that to cacheable without dependencies. + if (ruleData.mConditions.CacheableWithDependencies()) { + MOZ_ASSERT(pseudoRestriction, + "should only be cacheable with dependencies if we had a " + "pseudo restriction"); + ruleData.mConditions.Clear(); + } else { + // XXXheycam We shouldn't have `|| GetLevel() == SheetType::Transition` + // in the assertion condition, but rule nodes created by + // ResolveStyleByAddingRules don't call SetIsAnimationRule(). + MOZ_ASSERT(ruleData.mConditions.CacheableWithoutDependencies() || + ((HasAnimationData() || + GetLevel() == SheetType::Transition) && + aContext->GetParent() && + aContext->GetParent()->HasPseudoElementData()), + "should only be uncacheable if we had an animation rule " + "and we're inside a pseudo"); + } + } + + if (!ruleData.mConditions.CacheableWithoutDependencies() && + aSID != eStyleStruct_Variables) { + // Treat as though some data is specified to avoid the optimizations and + // force data computation. + // + // We don't need to do this for Variables structs since we know those are + // never cached in the rule tree, and it avoids wasteful computation of a + // new Variables struct when we have no additional variable declarations, + // which otherwise could happen when there is an AnimValuesStyleRule + // (which calls SetUncacheable for style contexts with pseudo data). + detail = eRulePartialMixed; + } + + if (detail == eRuleNone && startStruct) { + // We specified absolutely no rule information, but a parent rule in the tree + // specified all the rule information. We set a bit along the branch from our + // node in the tree to the node that specified the data that tells nodes on that + // branch that they never need to examine their rules for this particular struct type + // ever again. + PropagateDependentBit(aSID, ruleNode, startStruct); + // For inherited structs, mark the struct (which will be set on + // the context by our caller) as not being owned by the context. + if (!isReset) { + aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID)); + } else if (HasAnimationData()) { + // If we have animation data, the struct should be cached on the style + // context so that we can peek the struct. + // See comment in AnimValuesStyleRule::MapRuleInfoInto. + StoreStyleOnContext(aContext, aSID, startStruct); + } + + return startStruct; + } + if ((!startStruct && !isReset && + (detail == eRuleNone || detail == eRulePartialInherited)) || + detail == eRuleFullInherited) { + // We specified no non-inherited information and neither did any of + // our parent rules. + + // We set a bit along the branch from the highest node (ruleNode) + // down to our node (this) indicating that no non-inherited data was + // specified. This bit is guaranteed to be set already on the path + // from the highest node to the root node in the case where + // (detail == eRuleNone), which is the most common case here. + // We must check |!isReset| because the Compute*Data functions for + // reset structs wouldn't handle none bits correctly. + if (highestNode != this && !isReset) + PropagateNoneBit(bit, highestNode); + + // All information must necessarily be inherited from our parent style context. + // In the absence of any computed data in the rule tree and with + // no rules specified that didn't have values of 'inherit', we should check our parent. + nsStyleContext* parentContext = aContext->GetParent(); + if (isReset) { + /* Reset structs don't inherit from first-line. */ + /* See similar code in COMPUTE_START_RESET */ + while (parentContext && + parentContext->GetPseudo() == nsCSSPseudoElements::firstLine) { + parentContext = parentContext->GetParent(); + } + if (parentContext && parentContext != aContext->GetParent()) { + PropagateGrandancestorBit(aContext, parentContext); + } + } + if (parentContext) { + // We have a parent, and so we should just inherit from the parent. + // Set the inherit bits on our context. These bits tell the style context that + // it never has to go back to the rule tree for data. Instead the style context tree + // should be walked to find the data. + const void* parentStruct = parentContext->StyleData(aSID); + aContext->AddStyleBit(bit); // makes const_cast OK. + aContext->SetStyle(aSID, const_cast<void*>(parentStruct)); + if (isReset) { + parentContext->AddStyleBit(NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE); + } + return parentStruct; + } + else + // We are the root. In the case of fonts, the default values just + // come from the pres context. + return SetDefaultOnRoot(aSID, aContext); + } + + typedef const void* (nsRuleNode::*ComputeFunc)(void*, const nsRuleData*, + nsStyleContext*, nsRuleNode*, + RuleDetail, + const RuleNodeCacheConditions); + static const ComputeFunc sComputeFuncs[] = { +#define STYLE_STRUCT(name, checkdata_cb) &nsRuleNode::Compute##name##Data, +#include "nsStyleStructList.h" +#undef STYLE_STRUCT + }; + + // We need to compute the data from the information that the rules specified. + return (this->*sComputeFuncs[aSID])(startStruct, &ruleData, aContext, + highestNode, detail, + ruleData.mConditions); +} + +const void* +nsRuleNode::SetDefaultOnRoot(const nsStyleStructID aSID, nsStyleContext* aContext) +{ + switch (aSID) { + case eStyleStruct_Font: + { + nsStyleFont* fontData = new (mPresContext) nsStyleFont(mPresContext); + nscoord minimumFontSize = mPresContext->MinFontSize(fontData->mLanguage); + + if (minimumFontSize > 0 && !mPresContext->IsChrome()) { + fontData->mFont.size = std::max(fontData->mSize, minimumFontSize); + } + else { + fontData->mFont.size = fontData->mSize; + } + aContext->SetStyle(eStyleStruct_Font, fontData); + return fontData; + } + case eStyleStruct_Display: + { + nsStyleDisplay* disp = new (mPresContext) nsStyleDisplay(mPresContext); + aContext->SetStyle(eStyleStruct_Display, disp); + return disp; + } + case eStyleStruct_Visibility: + { + nsStyleVisibility* vis = new (mPresContext) nsStyleVisibility(mPresContext); + aContext->SetStyle(eStyleStruct_Visibility, vis); + return vis; + } + case eStyleStruct_Text: + { + nsStyleText* text = new (mPresContext) nsStyleText(mPresContext); + aContext->SetStyle(eStyleStruct_Text, text); + return text; + } + case eStyleStruct_TextReset: + { + nsStyleTextReset* text = new (mPresContext) nsStyleTextReset(mPresContext); + aContext->SetStyle(eStyleStruct_TextReset, text); + return text; + } + case eStyleStruct_Color: + { + nsStyleColor* color = new (mPresContext) nsStyleColor(mPresContext); + aContext->SetStyle(eStyleStruct_Color, color); + return color; + } + case eStyleStruct_Background: + { + nsStyleBackground* bg = new (mPresContext) nsStyleBackground(mPresContext); + aContext->SetStyle(eStyleStruct_Background, bg); + return bg; + } + case eStyleStruct_Margin: + { + nsStyleMargin* margin = new (mPresContext) nsStyleMargin(mPresContext); + aContext->SetStyle(eStyleStruct_Margin, margin); + return margin; + } + case eStyleStruct_Border: + { + nsStyleBorder* border = new (mPresContext) nsStyleBorder(mPresContext); + aContext->SetStyle(eStyleStruct_Border, border); + return border; + } + case eStyleStruct_Padding: + { + nsStylePadding* padding = new (mPresContext) nsStylePadding(mPresContext); + aContext->SetStyle(eStyleStruct_Padding, padding); + return padding; + } + case eStyleStruct_Outline: + { + nsStyleOutline* outline = new (mPresContext) nsStyleOutline(mPresContext); + aContext->SetStyle(eStyleStruct_Outline, outline); + return outline; + } + case eStyleStruct_List: + { + nsStyleList* list = new (mPresContext) nsStyleList(mPresContext); + aContext->SetStyle(eStyleStruct_List, list); + return list; + } + case eStyleStruct_Position: + { + nsStylePosition* pos = new (mPresContext) nsStylePosition(mPresContext); + aContext->SetStyle(eStyleStruct_Position, pos); + return pos; + } + case eStyleStruct_Table: + { + nsStyleTable* table = new (mPresContext) nsStyleTable(mPresContext); + aContext->SetStyle(eStyleStruct_Table, table); + return table; + } + case eStyleStruct_TableBorder: + { + nsStyleTableBorder* table = new (mPresContext) nsStyleTableBorder(mPresContext); + aContext->SetStyle(eStyleStruct_TableBorder, table); + return table; + } + case eStyleStruct_Content: + { + nsStyleContent* content = new (mPresContext) nsStyleContent(mPresContext); + aContext->SetStyle(eStyleStruct_Content, content); + return content; + } + case eStyleStruct_UserInterface: + { + nsStyleUserInterface* ui = new (mPresContext) nsStyleUserInterface(mPresContext); + aContext->SetStyle(eStyleStruct_UserInterface, ui); + return ui; + } + case eStyleStruct_UIReset: + { + nsStyleUIReset* ui = new (mPresContext) nsStyleUIReset(mPresContext); + aContext->SetStyle(eStyleStruct_UIReset, ui); + return ui; + } + case eStyleStruct_XUL: + { + nsStyleXUL* xul = new (mPresContext) nsStyleXUL(mPresContext); + aContext->SetStyle(eStyleStruct_XUL, xul); + return xul; + } + case eStyleStruct_Column: + { + nsStyleColumn* column = new (mPresContext) nsStyleColumn(mPresContext); + aContext->SetStyle(eStyleStruct_Column, column); + return column; + } + case eStyleStruct_SVG: + { + nsStyleSVG* svg = new (mPresContext) nsStyleSVG(mPresContext); + aContext->SetStyle(eStyleStruct_SVG, svg); + return svg; + } + case eStyleStruct_SVGReset: + { + nsStyleSVGReset* svgReset = new (mPresContext) nsStyleSVGReset(mPresContext); + aContext->SetStyle(eStyleStruct_SVGReset, svgReset); + return svgReset; + } + case eStyleStruct_Variables: + { + nsStyleVariables* vars = new (mPresContext) nsStyleVariables(mPresContext); + aContext->SetStyle(eStyleStruct_Variables, vars); + return vars; + } + case eStyleStruct_Effects: + { + nsStyleEffects* effects = new (mPresContext) nsStyleEffects(mPresContext); + aContext->SetStyle(eStyleStruct_Effects, effects); + return effects; + } + default: + /* + * unhandled case: nsStyleStructID_Length. + * last item of nsStyleStructID, to know its length. + */ + MOZ_ASSERT(false, "unexpected SID"); + return nullptr; + } + return nullptr; +} + +/** + * Begin an nsRuleNode::Compute*Data function for an inherited struct. + * + * @param type_ The nsStyle* type this function computes. + * @param data_ Variable (declared here) holding the result of this + * function. + * @param parentdata_ Variable (declared here) holding the parent style + * context's data for this struct. + */ +#define COMPUTE_START_INHERITED(type_, data_, parentdata_) \ + NS_ASSERTION(aRuleDetail != eRuleFullInherited, \ + "should not have bothered calling Compute*Data"); \ + \ + nsStyleContext* parentContext = aContext->GetParent(); \ + \ + nsStyle##type_* data_ = nullptr; \ + mozilla::Maybe<nsStyle##type_> maybeFakeParentData; \ + const nsStyle##type_* parentdata_ = nullptr; \ + RuleNodeCacheConditions conditions = aConditions; \ + \ + /* If |conditions.Cacheable()| might be true by the time we're done, we */ \ + /* can't call parentContext->Style##type_() since it could recur into */ \ + /* setting the same struct on the same rule node, causing a leak. */ \ + if (aRuleDetail != eRuleFullReset && \ + (!aStartStruct || (aRuleDetail != eRulePartialReset && \ + aRuleDetail != eRuleNone))) { \ + if (parentContext) { \ + parentdata_ = parentContext->Style##type_(); \ + } else { \ + maybeFakeParentData.emplace(mPresContext); \ + parentdata_ = maybeFakeParentData.ptr(); \ + } \ + } \ + if (eStyleStruct_##type_ == eStyleStruct_Variables) \ + /* no need to copy construct an nsStyleVariables, as we will copy */ \ + /* inherited variables (and call SetUncacheable()) in */ \ + /* ComputeVariablesData */ \ + data_ = new (mPresContext) nsStyle##type_(mPresContext); \ + else if (aStartStruct) \ + /* We only need to compute the delta between this computed data and */ \ + /* our computed data. */ \ + data_ = new (mPresContext) \ + nsStyle##type_(*static_cast<nsStyle##type_*>(aStartStruct)); \ + else { \ + if (aRuleDetail != eRuleFullMixed && aRuleDetail != eRuleFullReset) { \ + /* No question. We will have to inherit. Go ahead and init */ \ + /* with inherited vals from parent. */ \ + conditions.SetUncacheable(); \ + if (parentdata_) \ + data_ = new (mPresContext) nsStyle##type_(*parentdata_); \ + else \ + data_ = new (mPresContext) nsStyle##type_(mPresContext); \ + } \ + else \ + data_ = new (mPresContext) nsStyle##type_(mPresContext); \ + } \ + \ + if (!parentdata_) \ + parentdata_ = data_; + +/** + * Begin an nsRuleNode::Compute*Data function for a reset struct. + * + * @param type_ The nsStyle* type this function computes. + * @param data_ Variable (declared here) holding the result of this + * function. + * @param parentdata_ Variable (declared here) holding the parent style + * context's data for this struct. + */ +#define COMPUTE_START_RESET(type_, data_, parentdata_) \ + NS_ASSERTION(aRuleDetail != eRuleFullInherited, \ + "should not have bothered calling Compute*Data"); \ + \ + nsStyleContext* parentContext = aContext->GetParent(); \ + /* Reset structs don't inherit from first-line */ \ + /* See similar code in WalkRuleTree */ \ + while (parentContext && \ + parentContext->GetPseudo() == nsCSSPseudoElements::firstLine) { \ + parentContext = parentContext->GetParent(); \ + } \ + \ + nsStyle##type_* data_; \ + if (aStartStruct) \ + /* We only need to compute the delta between this computed data and */ \ + /* our computed data. */ \ + data_ = new (mPresContext) \ + nsStyle##type_(*static_cast<nsStyle##type_*>(aStartStruct)); \ + else \ + data_ = new (mPresContext) nsStyle##type_(mPresContext); \ + \ + /* If |conditions.Cacheable()| might be true by the time we're done, we */ \ + /* can't call parentContext->Style##type_() since it could recur into */ \ + /* setting the same struct on the same rule node, causing a leak. */ \ + mozilla::Maybe<nsStyle##type_> maybeFakeParentData; \ + const nsStyle##type_* parentdata_ = data_; \ + if (aRuleDetail != eRuleFullReset && \ + aRuleDetail != eRulePartialReset && \ + aRuleDetail != eRuleNone) { \ + if (parentContext) { \ + parentdata_ = parentContext->Style##type_(); \ + } else { \ + maybeFakeParentData.emplace(mPresContext); \ + parentdata_ = maybeFakeParentData.ptr(); \ + } \ + } \ + RuleNodeCacheConditions conditions = aConditions; + +/** + * End an nsRuleNode::Compute*Data function for an inherited struct. + * + * @param type_ The nsStyle* type this function computes. + * @param data_ Variable holding the result of this function. + */ +#define COMPUTE_END_INHERITED(type_, data_) \ + NS_POSTCONDITION(!conditions.CacheableWithoutDependencies() || \ + aRuleDetail == eRuleFullReset || \ + (aStartStruct && aRuleDetail == eRulePartialReset), \ + "conditions.CacheableWithoutDependencies() must be false " \ + "for inherited structs unless all properties have been " \ + "specified with values other than inherit"); \ + if (conditions.CacheableWithoutDependencies()) { \ + /* We were fully specified and can therefore be cached right on the */ \ + /* rule node. */ \ + if (!aHighestNode->mStyleData.mInheritedData) { \ + aHighestNode->mStyleData.mInheritedData = \ + new (mPresContext) nsInheritedStyleData; \ + } \ + NS_ASSERTION(!aHighestNode->mStyleData.mInheritedData-> \ + mStyleStructs[eStyleStruct_##type_], \ + "Going to leak style data"); \ + aHighestNode->mStyleData.mInheritedData-> \ + mStyleStructs[eStyleStruct_##type_] = data_; \ + /* Propagate the bit down. */ \ + PropagateDependentBit(eStyleStruct_##type_, aHighestNode, data_); \ + /* Tell the style context that it doesn't own the data */ \ + aContext->AddStyleBit(NS_STYLE_INHERIT_BIT(type_)); \ + } \ + /* For inherited structs, our caller will cache the data on the */ \ + /* style context */ \ + \ + return data_; + +/** + * End an nsRuleNode::Compute*Data function for a reset struct. + * + * @param type_ The nsStyle* type this function computes. + * @param data_ Variable holding the result of this function. + */ +#define COMPUTE_END_RESET(type_, data_) \ + NS_POSTCONDITION(!conditions.CacheableWithoutDependencies() || \ + aRuleDetail == eRuleNone || \ + aRuleDetail == eRulePartialReset || \ + aRuleDetail == eRuleFullReset, \ + "conditions.CacheableWithoutDependencies() must be false " \ + "for reset structs if any properties were specified as " \ + "inherit"); \ + if (conditions.CacheableWithoutDependencies()) { \ + /* We were fully specified and can therefore be cached right on the */ \ + /* rule node. */ \ + if (!aHighestNode->mStyleData.mResetData) { \ + aHighestNode->mStyleData.mResetData = \ + new (mPresContext) nsConditionalResetStyleData; \ + } \ + NS_ASSERTION(!aHighestNode->mStyleData.mResetData-> \ + GetStyleData(eStyleStruct_##type_), \ + "Going to leak style data"); \ + aHighestNode->mStyleData.mResetData-> \ + SetStyleData(eStyleStruct_##type_, data_); \ + /* Propagate the bit down. */ \ + PropagateDependentBit(eStyleStruct_##type_, aHighestNode, data_); \ + if (HasAnimationData()) { \ + /* If we have animation data, the struct should be cached on the */ \ + /* style context so that we can peek the struct. */ \ + /* See comment in AnimValuesStyleRule::MapRuleInfoInto. */ \ + StoreStyleOnContext(aContext, eStyleStruct_##type_, data_); \ + } \ + } else if (conditions.Cacheable()) { \ + if (!mStyleData.mResetData) { \ + mStyleData.mResetData = new (mPresContext) nsConditionalResetStyleData; \ + } \ + mStyleData.mResetData-> \ + SetStyleData(eStyleStruct_##type_, mPresContext, data_, conditions); \ + /* Tell the style context that it doesn't own the data */ \ + aContext->AddStyleBit(NS_STYLE_INHERIT_BIT(type_)); \ + aContext->SetStyle(eStyleStruct_##type_, data_); \ + } else { \ + /* We can't be cached in the rule node. We have to be put right */ \ + /* on the style context. */ \ + aContext->SetStyle(eStyleStruct_##type_, data_); \ + if (aContext->GetParent()) { \ + /* This is pessimistic; we could be uncacheable because we had a */ \ + /* relative font-weight, for example, which does not need to defeat */ \ + /* the restyle optimizations in RestyleManager.cpp that look at */ \ + /* NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE. */ \ + aContext->GetParent()-> \ + AddStyleBit(NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE); \ + } \ + } \ + \ + return data_; + +// This function figures out how much scaling should be suppressed to +// satisfy scriptminsize. This is our attempt to implement +// http://www.w3.org/TR/MathML2/chapter3.html#id.3.3.4.2.2 +// This is called after mScriptLevel, mScriptMinSize and mScriptSizeMultiplier +// have been set in aFont. +// +// Here are the invariants we enforce: +// 1) A decrease in size must not reduce the size below minscriptsize. +// 2) An increase in size must not increase the size above the size we would +// have if minscriptsize had not been applied anywhere. +// 3) The scriptlevel-induced size change must between 1.0 and the parent's +// scriptsizemultiplier^(new script level - old script level), as close to the +// latter as possible subject to constraints 1 and 2. +static nscoord +ComputeScriptLevelSize(const nsStyleFont* aFont, const nsStyleFont* aParentFont, + nsPresContext* aPresContext, nscoord* aUnconstrainedSize) +{ + int32_t scriptLevelChange = + aFont->mScriptLevel - aParentFont->mScriptLevel; + if (scriptLevelChange == 0) { + *aUnconstrainedSize = aParentFont->mScriptUnconstrainedSize; + // Constraint #3 says that we cannot change size, and #1 and #2 are always + // satisfied with no change. It's important this be fast because it covers + // all non-MathML content. + return aParentFont->mSize; + } + + // Compute actual value of minScriptSize + nscoord minScriptSize = aParentFont->mScriptMinSize; + if (aFont->mAllowZoom) { + minScriptSize = nsStyleFont::ZoomText(aPresContext, minScriptSize); + } + + double scriptLevelScale = + pow(aParentFont->mScriptSizeMultiplier, scriptLevelChange); + // Compute the size we would have had if minscriptsize had never been + // applied, also prevent overflow (bug 413274) + *aUnconstrainedSize = + NSToCoordRoundWithClamp(aParentFont->mScriptUnconstrainedSize*scriptLevelScale); + // Compute the size we could get via scriptlevel change + nscoord scriptLevelSize = + NSToCoordRoundWithClamp(aParentFont->mSize*scriptLevelScale); + if (scriptLevelScale <= 1.0) { + if (aParentFont->mSize <= minScriptSize) { + // We can't decrease the font size at all, so just stick to no change + // (authors are allowed to explicitly set the font size smaller than + // minscriptsize) + return aParentFont->mSize; + } + // We can decrease, so apply constraint #1 + return std::max(minScriptSize, scriptLevelSize); + } else { + // scriptminsize can only make sizes larger than the unconstrained size + NS_ASSERTION(*aUnconstrainedSize <= scriptLevelSize, "How can this ever happen?"); + // Apply constraint #2 + return std::min(scriptLevelSize, std::max(*aUnconstrainedSize, minScriptSize)); + } +} + + +/* static */ nscoord +nsRuleNode::CalcFontPointSize(int32_t aHTMLSize, int32_t aBasePointSize, + nsPresContext* aPresContext, + nsFontSizeType aFontSizeType) +{ +#define sFontSizeTableMin 9 +#define sFontSizeTableMax 16 + +// This table seems to be the one used by MacIE5. We hope its adoption in Mozilla +// and eventually in WinIE5.5 will help to establish a standard rendering across +// platforms and browsers. For now, it is used only in Strict mode. More can be read +// in the document written by Todd Farhner at: +// http://style.verso.com/font_size_intervals/altintervals.html +// + static int32_t sStrictFontSizeTable[sFontSizeTableMax - sFontSizeTableMin + 1][8] = + { + { 9, 9, 9, 9, 11, 14, 18, 27}, + { 9, 9, 9, 10, 12, 15, 20, 30}, + { 9, 9, 10, 11, 13, 17, 22, 33}, + { 9, 9, 10, 12, 14, 18, 24, 36}, + { 9, 10, 12, 13, 16, 20, 26, 39}, + { 9, 10, 12, 14, 17, 21, 28, 42}, + { 9, 10, 13, 15, 18, 23, 30, 45}, + { 9, 10, 13, 16, 18, 24, 32, 48} + }; +// HTML 1 2 3 4 5 6 7 +// CSS xxs xs s m l xl xxl +// | +// user pref +// +//------------------------------------------------------------ +// +// This table gives us compatibility with WinNav4 for the default fonts only. +// In WinNav4, the default fonts were: +// +// Times/12pt == Times/16px at 96ppi +// Courier/10pt == Courier/13px at 96ppi +// +// The 2 lines below marked "anchored" have the exact pixel sizes used by +// WinNav4 for Times/12pt and Courier/10pt at 96ppi. As you can see, the +// HTML size 3 (user pref) for those 2 anchored lines is 13px and 16px. +// +// All values other than the anchored values were filled in by hand, never +// going below 9px, and maintaining a "diagonal" relationship. See for +// example the 13s -- they follow a diagonal line through the table. +// + static int32_t sQuirksFontSizeTable[sFontSizeTableMax - sFontSizeTableMin + 1][8] = + { + { 9, 9, 9, 9, 11, 14, 18, 28 }, + { 9, 9, 9, 10, 12, 15, 20, 31 }, + { 9, 9, 9, 11, 13, 17, 22, 34 }, + { 9, 9, 10, 12, 14, 18, 24, 37 }, + { 9, 9, 10, 13, 16, 20, 26, 40 }, // anchored (13) + { 9, 9, 11, 14, 17, 21, 28, 42 }, + { 9, 10, 12, 15, 17, 23, 30, 45 }, + { 9, 10, 13, 16, 18, 24, 32, 48 } // anchored (16) + }; +// HTML 1 2 3 4 5 6 7 +// CSS xxs xs s m l xl xxl +// | +// user pref + +#if 0 +// +// These are the exact pixel values used by WinIE5 at 96ppi. +// + { ?, 8, 11, 12, 13, 16, 21, 32 }, // smallest + { ?, 9, 12, 13, 16, 21, 27, 40 }, // smaller + { ?, 10, 13, 16, 18, 24, 32, 48 }, // medium + { ?, 13, 16, 19, 21, 27, 37, ?? }, // larger + { ?, 16, 19, 21, 24, 32, 43, ?? } // largest +// +// HTML 1 2 3 4 5 6 7 +// CSS ? ? ? ? ? ? ? ? +// +// (CSS not tested yet.) +// +#endif + + static int32_t sFontSizeFactors[8] = { 60,75,89,100,120,150,200,300 }; + + static int32_t sCSSColumns[7] = {0, 1, 2, 3, 4, 5, 6}; // xxs...xxl + static int32_t sHTMLColumns[7] = {1, 2, 3, 4, 5, 6, 7}; // 1...7 + + double dFontSize; + + if (aFontSizeType == eFontSize_HTML) { + aHTMLSize--; // input as 1-7 + } + + if (aHTMLSize < 0) + aHTMLSize = 0; + else if (aHTMLSize > 6) + aHTMLSize = 6; + + int32_t* column; + switch (aFontSizeType) + { + case eFontSize_HTML: column = sHTMLColumns; break; + case eFontSize_CSS: column = sCSSColumns; break; + } + + // Make special call specifically for fonts (needed PrintPreview) + int32_t fontSize = nsPresContext::AppUnitsToIntCSSPixels(aBasePointSize); + + if ((fontSize >= sFontSizeTableMin) && (fontSize <= sFontSizeTableMax)) + { + int32_t row = fontSize - sFontSizeTableMin; + + if (aPresContext->CompatibilityMode() == eCompatibility_NavQuirks) { + dFontSize = nsPresContext::CSSPixelsToAppUnits(sQuirksFontSizeTable[row][column[aHTMLSize]]); + } else { + dFontSize = nsPresContext::CSSPixelsToAppUnits(sStrictFontSizeTable[row][column[aHTMLSize]]); + } + } + else + { + int32_t factor = sFontSizeFactors[column[aHTMLSize]]; + dFontSize = (factor * aBasePointSize) / 100; + } + + + if (1.0 < dFontSize) { + return (nscoord)dFontSize; + } + return (nscoord)1; +} + + +//------------------------------------------------------------------------------ +// +//------------------------------------------------------------------------------ + +/* static */ nscoord +nsRuleNode::FindNextSmallerFontSize(nscoord aFontSize, int32_t aBasePointSize, + nsPresContext* aPresContext, + nsFontSizeType aFontSizeType) +{ + int32_t index; + int32_t indexMin; + int32_t indexMax; + float relativePosition; + nscoord smallerSize; + nscoord indexFontSize = aFontSize; // XXX initialize to quell a spurious gcc3.2 warning + nscoord smallestIndexFontSize; + nscoord largestIndexFontSize; + nscoord smallerIndexFontSize; + nscoord largerIndexFontSize; + + nscoord onePx = nsPresContext::CSSPixelsToAppUnits(1); + + if (aFontSizeType == eFontSize_HTML) { + indexMin = 1; + indexMax = 7; + } else { + indexMin = 0; + indexMax = 6; + } + + smallestIndexFontSize = CalcFontPointSize(indexMin, aBasePointSize, aPresContext, aFontSizeType); + largestIndexFontSize = CalcFontPointSize(indexMax, aBasePointSize, aPresContext, aFontSizeType); + if (aFontSize > smallestIndexFontSize) { + if (aFontSize < NSToCoordRound(float(largestIndexFontSize) * 1.5)) { // smaller will be in HTML table + // find largest index smaller than current + for (index = indexMax; index >= indexMin; index--) { + indexFontSize = CalcFontPointSize(index, aBasePointSize, aPresContext, aFontSizeType); + if (indexFontSize < aFontSize) + break; + } + // set up points beyond table for interpolation purposes + if (indexFontSize == smallestIndexFontSize) { + smallerIndexFontSize = indexFontSize - onePx; + largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType); + } else if (indexFontSize == largestIndexFontSize) { + smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType); + largerIndexFontSize = NSToCoordRound(float(largestIndexFontSize) * 1.5); + } else { + smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType); + largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType); + } + // compute the relative position of the parent size between the two closest indexed sizes + relativePosition = float(aFontSize - indexFontSize) / float(largerIndexFontSize - indexFontSize); + // set the new size to have the same relative position between the next smallest two indexed sizes + smallerSize = smallerIndexFontSize + NSToCoordRound(relativePosition * (indexFontSize - smallerIndexFontSize)); + } + else { // larger than HTML table, drop by 33% + smallerSize = NSToCoordRound(float(aFontSize) / 1.5); + } + } + else { // smaller than HTML table, drop by 1px + smallerSize = std::max(aFontSize - onePx, onePx); + } + return smallerSize; +} + +//------------------------------------------------------------------------------ +// +//------------------------------------------------------------------------------ + +/* static */ nscoord +nsRuleNode::FindNextLargerFontSize(nscoord aFontSize, int32_t aBasePointSize, + nsPresContext* aPresContext, + nsFontSizeType aFontSizeType) +{ + int32_t index; + int32_t indexMin; + int32_t indexMax; + float relativePosition; + nscoord adjustment; + nscoord largerSize; + nscoord indexFontSize = aFontSize; // XXX initialize to quell a spurious gcc3.2 warning + nscoord smallestIndexFontSize; + nscoord largestIndexFontSize; + nscoord smallerIndexFontSize; + nscoord largerIndexFontSize; + + nscoord onePx = nsPresContext::CSSPixelsToAppUnits(1); + + if (aFontSizeType == eFontSize_HTML) { + indexMin = 1; + indexMax = 7; + } else { + indexMin = 0; + indexMax = 6; + } + + smallestIndexFontSize = CalcFontPointSize(indexMin, aBasePointSize, aPresContext, aFontSizeType); + largestIndexFontSize = CalcFontPointSize(indexMax, aBasePointSize, aPresContext, aFontSizeType); + if (aFontSize > (smallestIndexFontSize - onePx)) { + if (aFontSize < largestIndexFontSize) { // larger will be in HTML table + // find smallest index larger than current + for (index = indexMin; index <= indexMax; index++) { + indexFontSize = CalcFontPointSize(index, aBasePointSize, aPresContext, aFontSizeType); + if (indexFontSize > aFontSize) + break; + } + // set up points beyond table for interpolation purposes + if (indexFontSize == smallestIndexFontSize) { + smallerIndexFontSize = indexFontSize - onePx; + largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType); + } else if (indexFontSize == largestIndexFontSize) { + smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType); + largerIndexFontSize = NSCoordSaturatingMultiply(largestIndexFontSize, 1.5); + } else { + smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType); + largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType); + } + // compute the relative position of the parent size between the two closest indexed sizes + relativePosition = float(aFontSize - smallerIndexFontSize) / float(indexFontSize - smallerIndexFontSize); + // set the new size to have the same relative position between the next largest two indexed sizes + adjustment = NSCoordSaturatingNonnegativeMultiply(largerIndexFontSize - indexFontSize, relativePosition); + largerSize = NSCoordSaturatingAdd(indexFontSize, adjustment); + } + else { // larger than HTML table, increase by 50% + largerSize = NSCoordSaturatingMultiply(aFontSize, 1.5); + } + } + else { // smaller than HTML table, increase by 1px + largerSize = NSCoordSaturatingAdd(aFontSize, onePx); + } + return largerSize; +} + +struct SetFontSizeCalcOps : public css::BasicCoordCalcOps, + public css::NumbersAlreadyNormalizedOps +{ + // The parameters beyond aValue that we need for CalcLengthWith. + const nscoord mParentSize; + const nsStyleFont* const mParentFont; + nsPresContext* const mPresContext; + nsStyleContext* const mStyleContext; + const bool mAtRoot; + RuleNodeCacheConditions& mConditions; + + SetFontSizeCalcOps(nscoord aParentSize, const nsStyleFont* aParentFont, + nsPresContext* aPresContext, + nsStyleContext* aStyleContext, + bool aAtRoot, + RuleNodeCacheConditions& aConditions) + : mParentSize(aParentSize), + mParentFont(aParentFont), + mPresContext(aPresContext), + mStyleContext(aStyleContext), + mAtRoot(aAtRoot), + mConditions(aConditions) + { + } + + result_type ComputeLeafValue(const nsCSSValue& aValue) + { + nscoord size; + if (aValue.IsLengthUnit()) { + // Note that font-based length units use the parent's size + // unadjusted for scriptlevel changes. A scriptlevel change + // between us and the parent is simply ignored. + size = CalcLengthWith(aValue, mParentSize, + mParentFont, + mStyleContext, mPresContext, mAtRoot, + true, mConditions); + if (!aValue.IsRelativeLengthUnit() && mParentFont->mAllowZoom) { + size = nsStyleFont::ZoomText(mPresContext, size); + } + } + else if (eCSSUnit_Percent == aValue.GetUnit()) { + mConditions.SetUncacheable(); + // Note that % units use the parent's size unadjusted for scriptlevel + // changes. A scriptlevel change between us and the parent is simply + // ignored. + // aValue.GetPercentValue() may be negative for, e.g., calc(-50%) + size = NSCoordSaturatingMultiply(mParentSize, aValue.GetPercentValue()); + } else { + MOZ_ASSERT(false, "unexpected value"); + size = mParentSize; + } + + return size; + } +}; + +/* static */ void +nsRuleNode::SetFontSize(nsPresContext* aPresContext, + nsStyleContext* aContext, + const nsRuleData* aRuleData, + const nsStyleFont* aFont, + const nsStyleFont* aParentFont, + nscoord* aSize, + const nsFont& aSystemFont, + nscoord aParentSize, + nscoord aScriptLevelAdjustedParentSize, + bool aUsedStartStruct, + bool aAtRoot, + RuleNodeCacheConditions& aConditions) +{ + // If false, means that *aSize has not been zoomed. If true, means that + // *aSize has been zoomed iff aParentFont->mAllowZoom is true. + bool sizeIsZoomedAccordingToParent = false; + + int32_t baseSize = (int32_t) aPresContext-> + GetDefaultFont(aFont->mGenericID, aFont->mLanguage)->size; + const nsCSSValue* sizeValue = aRuleData->ValueForFontSize(); + if (eCSSUnit_Enumerated == sizeValue->GetUnit()) { + int32_t value = sizeValue->GetIntValue(); + + if ((NS_STYLE_FONT_SIZE_XXSMALL <= value) && + (value <= NS_STYLE_FONT_SIZE_XXLARGE)) { + *aSize = CalcFontPointSize(value, baseSize, + aPresContext, eFontSize_CSS); + } + else if (NS_STYLE_FONT_SIZE_XXXLARGE == value) { + // <font size="7"> is not specified in CSS, so we don't use eFontSize_CSS. + *aSize = CalcFontPointSize(value, baseSize, aPresContext); + } + else if (NS_STYLE_FONT_SIZE_LARGER == value || + NS_STYLE_FONT_SIZE_SMALLER == value) { + aConditions.SetUncacheable(); + + // Un-zoom so we use the tables correctly. We'll then rezoom due + // to the |zoom = true| above. + // Note that relative units here use the parent's size unadjusted + // for scriptlevel changes. A scriptlevel change between us and the parent + // is simply ignored. + nscoord parentSize = aParentSize; + if (aParentFont->mAllowZoom) { + parentSize = nsStyleFont::UnZoomText(aPresContext, parentSize); + } + + if (NS_STYLE_FONT_SIZE_LARGER == value) { + *aSize = FindNextLargerFontSize(parentSize, + baseSize, aPresContext, eFontSize_CSS); + + NS_ASSERTION(*aSize >= parentSize, + "FindNextLargerFontSize failed"); + } + else { + *aSize = FindNextSmallerFontSize(parentSize, + baseSize, aPresContext, eFontSize_CSS); + NS_ASSERTION(*aSize < parentSize || + parentSize <= nsPresContext::CSSPixelsToAppUnits(1), + "FindNextSmallerFontSize failed"); + } + } else { + NS_NOTREACHED("unexpected value"); + } + } + else if (sizeValue->IsLengthUnit() || + sizeValue->GetUnit() == eCSSUnit_Percent || + sizeValue->IsCalcUnit()) { + SetFontSizeCalcOps ops(aParentSize, aParentFont, + aPresContext, aContext, + aAtRoot, + aConditions); + *aSize = css::ComputeCalc(*sizeValue, ops); + if (*aSize < 0) { + MOZ_ASSERT(sizeValue->IsCalcUnit(), + "negative lengths and percents should be rejected by parser"); + *aSize = 0; + } + // The calc ops will always zoom its result according to the value + // of aParentFont->mAllowZoom. + sizeIsZoomedAccordingToParent = true; + } + else if (eCSSUnit_System_Font == sizeValue->GetUnit()) { + // this becomes our cascading size + *aSize = aSystemFont.size; + } + else if (eCSSUnit_Inherit == sizeValue->GetUnit() || + eCSSUnit_Unset == sizeValue->GetUnit()) { + aConditions.SetUncacheable(); + // We apply scriptlevel change for this case, because the default is + // to inherit and we don't want explicit "inherit" to differ from the + // default. + *aSize = aScriptLevelAdjustedParentSize; + sizeIsZoomedAccordingToParent = true; + } + else if (eCSSUnit_Initial == sizeValue->GetUnit()) { + // The initial value is 'medium', which has magical sizing based on + // the generic font family, so do that here too. + *aSize = baseSize; + } else { + NS_ASSERTION(eCSSUnit_Null == sizeValue->GetUnit(), + "What kind of font-size value is this?"); + // if aUsedStartStruct is true, then every single property in the + // font struct is being set all at once. This means scriptlevel is not + // going to have any influence on the font size; there is no need to + // do anything here. + if (!aUsedStartStruct && aParentSize != aScriptLevelAdjustedParentSize) { + // There was no rule affecting the size but the size has been + // affected by the parent's size via scriptlevel change. So we cannot + // store the data in the rule tree. + aConditions.SetUncacheable(); + *aSize = aScriptLevelAdjustedParentSize; + sizeIsZoomedAccordingToParent = true; + } else { + return; + } + } + + // We want to zoom the cascaded size so that em-based measurements, + // line-heights, etc., work. + bool currentlyZoomed = sizeIsZoomedAccordingToParent && + aParentFont->mAllowZoom; + if (!currentlyZoomed && aFont->mAllowZoom) { + *aSize = nsStyleFont::ZoomText(aPresContext, *aSize); + } else if (currentlyZoomed && !aFont->mAllowZoom) { + *aSize = nsStyleFont::UnZoomText(aPresContext, *aSize); + } +} + +static int8_t ClampTo8Bit(int32_t aValue) { + if (aValue < -128) + return -128; + if (aValue > 127) + return 127; + return int8_t(aValue); +} + +/* static */ void +nsRuleNode::SetFont(nsPresContext* aPresContext, nsStyleContext* aContext, + uint8_t aGenericFontID, const nsRuleData* aRuleData, + const nsStyleFont* aParentFont, + nsStyleFont* aFont, bool aUsedStartStruct, + RuleNodeCacheConditions& aConditions) +{ + bool atRoot = !aContext->GetParent(); + + // -x-text-zoom: none, inherit, initial + bool allowZoom; + const nsCSSValue* textZoomValue = aRuleData->ValueForTextZoom(); + if (eCSSUnit_Null != textZoomValue->GetUnit()) { + if (eCSSUnit_Inherit == textZoomValue->GetUnit()) { + allowZoom = aParentFont->mAllowZoom; + } else if (eCSSUnit_None == textZoomValue->GetUnit()) { + allowZoom = false; + } else { + MOZ_ASSERT(eCSSUnit_Initial == textZoomValue->GetUnit(), + "unexpected unit"); + allowZoom = true; + } + aFont->EnableZoom(aPresContext, allowZoom); + } + + // mLanguage must be set before before any of the CalcLengthWith calls + // (direct calls or calls via SetFontSize) for the cases where |aParentFont| + // is the same as |aFont|. + // + // -x-lang: string, inherit + // This is not a real CSS property, it is an HTML attribute mapped to CSS. + const nsCSSValue* langValue = aRuleData->ValueForLang(); + if (eCSSUnit_Ident == langValue->GetUnit()) { + nsAutoString lang; + langValue->GetStringValue(lang); + + nsContentUtils::ASCIIToLower(lang); + aFont->mLanguage = NS_Atomize(lang); + aFont->mExplicitLanguage = true; + } + + const nsFont* defaultVariableFont = + aPresContext->GetDefaultFont(kPresContext_DefaultVariableFont_ID, + aFont->mLanguage); + + // -moz-system-font: enum (never inherit!) + static_assert( + NS_STYLE_FONT_CAPTION == LookAndFeel::eFont_Caption && + NS_STYLE_FONT_ICON == LookAndFeel::eFont_Icon && + NS_STYLE_FONT_MENU == LookAndFeel::eFont_Menu && + NS_STYLE_FONT_MESSAGE_BOX == LookAndFeel::eFont_MessageBox && + NS_STYLE_FONT_SMALL_CAPTION == LookAndFeel::eFont_SmallCaption && + NS_STYLE_FONT_STATUS_BAR == LookAndFeel::eFont_StatusBar && + NS_STYLE_FONT_WINDOW == LookAndFeel::eFont_Window && + NS_STYLE_FONT_DOCUMENT == LookAndFeel::eFont_Document && + NS_STYLE_FONT_WORKSPACE == LookAndFeel::eFont_Workspace && + NS_STYLE_FONT_DESKTOP == LookAndFeel::eFont_Desktop && + NS_STYLE_FONT_INFO == LookAndFeel::eFont_Info && + NS_STYLE_FONT_DIALOG == LookAndFeel::eFont_Dialog && + NS_STYLE_FONT_BUTTON == LookAndFeel::eFont_Button && + NS_STYLE_FONT_PULL_DOWN_MENU == LookAndFeel::eFont_PullDownMenu && + NS_STYLE_FONT_LIST == LookAndFeel::eFont_List && + NS_STYLE_FONT_FIELD == LookAndFeel::eFont_Field, + "LookAndFeel.h system-font constants out of sync with nsStyleConsts.h"); + + // Fall back to defaultVariableFont. + nsFont systemFont = *defaultVariableFont; + const nsCSSValue* systemFontValue = aRuleData->ValueForSystemFont(); + if (eCSSUnit_Enumerated == systemFontValue->GetUnit()) { + gfxFontStyle fontStyle; + LookAndFeel::FontID fontID = + (LookAndFeel::FontID)systemFontValue->GetIntValue(); + float devPerCSS = + (float)nsPresContext::AppUnitsPerCSSPixel() / + aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom(); + nsAutoString systemFontName; + if (LookAndFeel::GetFont(fontID, systemFontName, fontStyle, devPerCSS)) { + systemFontName.Trim("\"'"); + systemFont.fontlist = FontFamilyList(systemFontName, eUnquotedName); + systemFont.fontlist.SetDefaultFontType(eFamily_none); + systemFont.style = fontStyle.style; + systemFont.systemFont = fontStyle.systemFont; + systemFont.weight = fontStyle.weight; + systemFont.stretch = fontStyle.stretch; + systemFont.size = + NSFloatPixelsToAppUnits(fontStyle.size, + aPresContext->DeviceContext()-> + AppUnitsPerDevPixelAtUnitFullZoom()); + //systemFont.langGroup = fontStyle.langGroup; + systemFont.sizeAdjust = fontStyle.sizeAdjust; + +#ifdef XP_WIN + // XXXldb This platform-specific stuff should be in the + // LookAndFeel implementation, not here. + // XXXzw Should we even still *have* this code? It looks to be making + // old, probably obsolete assumptions. + + if (fontID == LookAndFeel::eFont_Field || + fontID == LookAndFeel::eFont_Button || + fontID == LookAndFeel::eFont_List) { + // As far as I can tell the system default fonts and sizes + // on MS-Windows for Buttons, Listboxes/Comboxes and Text Fields are + // all pre-determined and cannot be changed by either the control panel + // or programmatically. + // Fields (text fields) + // Button and Selects (listboxes/comboboxes) + // We use whatever font is defined by the system. Which it appears + // (and the assumption is) it is always a proportional font. Then we + // always use 2 points smaller than what the browser has defined as + // the default proportional font. + // Assumption: system defined font is proportional + systemFont.size = + std::max(defaultVariableFont->size - + nsPresContext::CSSPointsToAppUnits(2), 0); + } +#endif + } + } + + // font-family: font family list, enum, inherit + const nsCSSValue* familyValue = aRuleData->ValueForFontFamily(); + NS_ASSERTION(eCSSUnit_Enumerated != familyValue->GetUnit(), + "system fonts should not be in mFamily anymore"); + if (eCSSUnit_FontFamilyList == familyValue->GetUnit()) { + // set the correct font if we are using DocumentFonts OR we are overriding for XUL + // MJA: bug 31816 + bool useDocumentFonts = + aPresContext->GetCachedBoolPref(kPresContext_UseDocumentFonts); + if (aGenericFontID == kGenericFont_NONE || + (!useDocumentFonts && (aGenericFontID == kGenericFont_cursive || + aGenericFontID == kGenericFont_fantasy))) { + FontFamilyType defaultGeneric = + defaultVariableFont->fontlist.FirstGeneric(); + MOZ_ASSERT(defaultVariableFont->fontlist.Length() == 1 && + (defaultGeneric == eFamily_serif || + defaultGeneric == eFamily_sans_serif)); + if (defaultGeneric != eFamily_none) { + if (useDocumentFonts) { + aFont->mFont.fontlist.SetDefaultFontType(defaultGeneric); + } else { + // Either prioritize the first generic in the list, + // or (if there isn't one) prepend the default variable font. + if (!aFont->mFont.fontlist.PrioritizeFirstGeneric()) { + aFont->mFont.fontlist.PrependGeneric(defaultGeneric); + } + } + } + } else { + aFont->mFont.fontlist.SetDefaultFontType(eFamily_none); + } + aFont->mFont.systemFont = false; + // Technically this is redundant with the code below, but it's good + // to have since we'll still want it once we get rid of + // SetGenericFont (bug 380915). + aFont->mGenericID = aGenericFontID; + } + else if (eCSSUnit_System_Font == familyValue->GetUnit()) { + aFont->mFont.fontlist = systemFont.fontlist; + aFont->mFont.systemFont = true; + aFont->mGenericID = kGenericFont_NONE; + } + else if (eCSSUnit_Inherit == familyValue->GetUnit() || + eCSSUnit_Unset == familyValue->GetUnit()) { + aConditions.SetUncacheable(); + aFont->mFont.fontlist = aParentFont->mFont.fontlist; + aFont->mFont.systemFont = aParentFont->mFont.systemFont; + aFont->mGenericID = aParentFont->mGenericID; + } + else if (eCSSUnit_Initial == familyValue->GetUnit()) { + aFont->mFont.fontlist = defaultVariableFont->fontlist; + aFont->mFont.systemFont = defaultVariableFont->systemFont; + aFont->mGenericID = kGenericFont_NONE; + } + + // When we're in the loop in SetGenericFont, we must ensure that we + // always keep aFont->mFlags set to the correct generic. But we have + // to be careful not to touch it when we're called directly from + // ComputeFontData, because we could have a start struct. + if (aGenericFontID != kGenericFont_NONE) { + aFont->mGenericID = aGenericFontID; + } + + // -moz-math-variant: enum, inherit, initial + SetValue(*aRuleData->ValueForMathVariant(), aFont->mMathVariant, + aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mMathVariant, NS_MATHML_MATHVARIANT_NONE); + + // -moz-math-display: enum, inherit, initial + SetValue(*aRuleData->ValueForMathDisplay(), aFont->mMathDisplay, + aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mMathDisplay, NS_MATHML_DISPLAYSTYLE_INLINE); + + // font-smoothing: enum, inherit, initial + SetValue(*aRuleData->ValueForOsxFontSmoothing(), + aFont->mFont.smoothing, aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mFont.smoothing, + defaultVariableFont->smoothing); + + // font-style: enum, inherit, initial, -moz-system-font + if (aFont->mMathVariant != NS_MATHML_MATHVARIANT_NONE) { + // -moz-math-variant overrides font-style + aFont->mFont.style = NS_FONT_STYLE_NORMAL; + } else { + SetValue(*aRuleData->ValueForFontStyle(), + aFont->mFont.style, aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mFont.style, + defaultVariableFont->style, + Unused, Unused, Unused, systemFont.style); + } + + // font-weight: int, enum, inherit, initial, -moz-system-font + // special handling for enum + const nsCSSValue* weightValue = aRuleData->ValueForFontWeight(); + if (aFont->mMathVariant != NS_MATHML_MATHVARIANT_NONE) { + // -moz-math-variant overrides font-weight + aFont->mFont.weight = NS_FONT_WEIGHT_NORMAL; + } else if (eCSSUnit_Enumerated == weightValue->GetUnit()) { + int32_t value = weightValue->GetIntValue(); + switch (value) { + case NS_STYLE_FONT_WEIGHT_NORMAL: + case NS_STYLE_FONT_WEIGHT_BOLD: + aFont->mFont.weight = value; + break; + case NS_STYLE_FONT_WEIGHT_BOLDER: { + aConditions.SetUncacheable(); + int32_t inheritedValue = aParentFont->mFont.weight; + if (inheritedValue <= 300) { + aFont->mFont.weight = 400; + } else if (inheritedValue <= 500) { + aFont->mFont.weight = 700; + } else { + aFont->mFont.weight = 900; + } + break; + } + case NS_STYLE_FONT_WEIGHT_LIGHTER: { + aConditions.SetUncacheable(); + int32_t inheritedValue = aParentFont->mFont.weight; + if (inheritedValue < 600) { + aFont->mFont.weight = 100; + } else if (inheritedValue < 800) { + aFont->mFont.weight = 400; + } else { + aFont->mFont.weight = 700; + } + break; + } + } + } else + SetValue(*weightValue, aFont->mFont.weight, aConditions, + SETVAL_INTEGER | SETVAL_UNSET_INHERIT, + aParentFont->mFont.weight, + defaultVariableFont->weight, + Unused, Unused, Unused, systemFont.weight); + + // font-stretch: enum, inherit, initial, -moz-system-font + SetValue(*aRuleData->ValueForFontStretch(), + aFont->mFont.stretch, aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mFont.stretch, + defaultVariableFont->stretch, + Unused, Unused, Unused, systemFont.stretch); + + // Compute scriptlevel, scriptminsize and scriptsizemultiplier now so + // they're available for font-size computation. + + // -moz-script-min-size: length + const nsCSSValue* scriptMinSizeValue = aRuleData->ValueForScriptMinSize(); + if (scriptMinSizeValue->IsLengthUnit()) { + // scriptminsize in font units (em, ex) has to be interpreted relative + // to the parent font, or the size definitions are circular and we + // + aFont->mScriptMinSize = + CalcLengthWith(*scriptMinSizeValue, aParentFont->mSize, + aParentFont, + aContext, aPresContext, atRoot, true /* aUseUserFontSet */, + aConditions); + } + + // -moz-script-size-multiplier: factor, inherit, initial + SetFactor(*aRuleData->ValueForScriptSizeMultiplier(), + aFont->mScriptSizeMultiplier, + aConditions, aParentFont->mScriptSizeMultiplier, + NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER, + SETFCT_POSITIVE | SETFCT_UNSET_INHERIT); + + // -moz-script-level: integer, number, inherit + const nsCSSValue* scriptLevelValue = aRuleData->ValueForScriptLevel(); + if (eCSSUnit_Integer == scriptLevelValue->GetUnit()) { + // "relative" + aConditions.SetUncacheable(); + aFont->mScriptLevel = ClampTo8Bit(aParentFont->mScriptLevel + scriptLevelValue->GetIntValue()); + } + else if (eCSSUnit_Number == scriptLevelValue->GetUnit()) { + // "absolute" + aFont->mScriptLevel = ClampTo8Bit(int32_t(scriptLevelValue->GetFloatValue())); + } + else if (eCSSUnit_Auto == scriptLevelValue->GetUnit()) { + // auto + aConditions.SetUncacheable(); + aFont->mScriptLevel = ClampTo8Bit(aParentFont->mScriptLevel + + (aParentFont->mMathDisplay == + NS_MATHML_DISPLAYSTYLE_INLINE ? 1 : 0)); + } + else if (eCSSUnit_Inherit == scriptLevelValue->GetUnit() || + eCSSUnit_Unset == scriptLevelValue->GetUnit()) { + aConditions.SetUncacheable(); + aFont->mScriptLevel = aParentFont->mScriptLevel; + } + else if (eCSSUnit_Initial == scriptLevelValue->GetUnit()) { + aFont->mScriptLevel = 0; + } + + // font-kerning: none, enum, inherit, initial, -moz-system-font + SetValue(*aRuleData->ValueForFontKerning(), + aFont->mFont.kerning, aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mFont.kerning, + defaultVariableFont->kerning, + Unused, Unused, Unused, systemFont.kerning); + + // font-synthesis: none, enum (bit field), inherit, initial, -moz-system-font + SetValue(*aRuleData->ValueForFontSynthesis(), + aFont->mFont.synthesis, aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mFont.synthesis, + defaultVariableFont->synthesis, + Unused, /* none */ 0, Unused, systemFont.synthesis); + + // font-variant-alternates: normal, enum (bit field) + functions, inherit, + // initial, -moz-system-font + const nsCSSValue* variantAlternatesValue = + aRuleData->ValueForFontVariantAlternates(); + int32_t variantAlternates = 0; + + switch (variantAlternatesValue->GetUnit()) { + case eCSSUnit_Inherit: + case eCSSUnit_Unset: + aFont->mFont.CopyAlternates(aParentFont->mFont); + aConditions.SetUncacheable(); + break; + + case eCSSUnit_Initial: + case eCSSUnit_Normal: + aFont->mFont.variantAlternates = 0; + aFont->mFont.alternateValues.Clear(); + aFont->mFont.featureValueLookup = nullptr; + break; + + case eCSSUnit_Pair: + NS_ASSERTION(variantAlternatesValue->GetPairValue().mXValue.GetUnit() == + eCSSUnit_Enumerated, "strange unit for variantAlternates"); + variantAlternates = + variantAlternatesValue->GetPairValue().mXValue.GetIntValue(); + aFont->mFont.variantAlternates = variantAlternates; + + if (variantAlternates & NS_FONT_VARIANT_ALTERNATES_FUNCTIONAL_MASK) { + // fetch the feature lookup object from the styleset + MOZ_ASSERT(aPresContext->StyleSet()->IsGecko(), + "ServoStyleSets should not have rule nodes"); + aFont->mFont.featureValueLookup = + aPresContext->StyleSet()->AsGecko()->GetFontFeatureValuesLookup(); + + NS_ASSERTION(variantAlternatesValue->GetPairValue().mYValue.GetUnit() == + eCSSUnit_List, "function list not a list value"); + nsStyleUtil::ComputeFunctionalAlternates( + variantAlternatesValue->GetPairValue().mYValue.GetListValue(), + aFont->mFont.alternateValues); + } + break; + + default: + break; + } + + // font-variant-caps: normal, enum, inherit, initial, -moz-system-font + SetValue(*aRuleData->ValueForFontVariantCaps(), + aFont->mFont.variantCaps, aConditions, + SETVAL_ENUMERATED |SETVAL_UNSET_INHERIT, + aParentFont->mFont.variantCaps, + defaultVariableFont->variantCaps, + Unused, Unused, /* normal */ 0, systemFont.variantCaps); + + // font-variant-east-asian: normal, enum (bit field), inherit, initial, + // -moz-system-font + SetValue(*aRuleData->ValueForFontVariantEastAsian(), + aFont->mFont.variantEastAsian, aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mFont.variantEastAsian, + defaultVariableFont->variantEastAsian, + Unused, Unused, /* normal */ 0, systemFont.variantEastAsian); + + // font-variant-ligatures: normal, none, enum (bit field), inherit, initial, + // -moz-system-font + SetValue(*aRuleData->ValueForFontVariantLigatures(), + aFont->mFont.variantLigatures, aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mFont.variantLigatures, + defaultVariableFont->variantLigatures, + Unused, NS_FONT_VARIANT_LIGATURES_NONE, /* normal */ 0, + systemFont.variantLigatures); + + // font-variant-numeric: normal, enum (bit field), inherit, initial, + // -moz-system-font + SetValue(*aRuleData->ValueForFontVariantNumeric(), + aFont->mFont.variantNumeric, aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mFont.variantNumeric, + defaultVariableFont->variantNumeric, + Unused, Unused, /* normal */ 0, systemFont.variantNumeric); + + // font-variant-position: normal, enum, inherit, initial, + // -moz-system-font + SetValue(*aRuleData->ValueForFontVariantPosition(), + aFont->mFont.variantPosition, aConditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + aParentFont->mFont.variantPosition, + defaultVariableFont->variantPosition, + Unused, Unused, /* normal */ 0, systemFont.variantPosition); + + // font-feature-settings + const nsCSSValue* featureSettingsValue = + aRuleData->ValueForFontFeatureSettings(); + + switch (featureSettingsValue->GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Normal: + case eCSSUnit_Initial: + aFont->mFont.fontFeatureSettings.Clear(); + break; + + case eCSSUnit_Inherit: + case eCSSUnit_Unset: + aConditions.SetUncacheable(); + aFont->mFont.fontFeatureSettings = aParentFont->mFont.fontFeatureSettings; + break; + + case eCSSUnit_System_Font: + aFont->mFont.fontFeatureSettings = systemFont.fontFeatureSettings; + break; + + case eCSSUnit_PairList: + case eCSSUnit_PairListDep: + ComputeFontFeatures(featureSettingsValue->GetPairListValue(), + aFont->mFont.fontFeatureSettings); + break; + + default: + MOZ_ASSERT(false, "unexpected value unit"); + break; + } + + // font-language-override + const nsCSSValue* languageOverrideValue = + aRuleData->ValueForFontLanguageOverride(); + if (eCSSUnit_Inherit == languageOverrideValue->GetUnit() || + eCSSUnit_Unset == languageOverrideValue->GetUnit()) { + aConditions.SetUncacheable(); + aFont->mFont.languageOverride = aParentFont->mFont.languageOverride; + } else if (eCSSUnit_Normal == languageOverrideValue->GetUnit() || + eCSSUnit_Initial == languageOverrideValue->GetUnit()) { + aFont->mFont.languageOverride.Truncate(); + } else if (eCSSUnit_System_Font == languageOverrideValue->GetUnit()) { + aFont->mFont.languageOverride = systemFont.languageOverride; + } else if (eCSSUnit_String == languageOverrideValue->GetUnit()) { + languageOverrideValue->GetStringValue(aFont->mFont.languageOverride); + } + + // -moz-min-font-size-ratio: percent, inherit + const nsCSSValue* minFontSizeRatio = aRuleData->ValueForMinFontSizeRatio(); + switch (minFontSizeRatio->GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_Unset: + case eCSSUnit_Inherit: + aFont->mMinFontSizeRatio = aParentFont->mMinFontSizeRatio; + aConditions.SetUncacheable(); + break; + case eCSSUnit_Initial: + aFont->mMinFontSizeRatio = 100; // 100% + break; + case eCSSUnit_Percent: { + // While percentages are parsed as floating point numbers, we + // only store an integer in the range [0, 255] since that's all + // we need for now. + float percent = minFontSizeRatio->GetPercentValue() * 100; + if (percent < 0) { + percent = 0; + } else if (percent > 255) { + percent = 255; + } + aFont->mMinFontSizeRatio = uint8_t(percent); + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unknown unit for -moz-min-font-size-ratio"); + } + + nscoord scriptLevelAdjustedUnconstrainedParentSize; + + // font-size: enum, length, percent, inherit + nscoord scriptLevelAdjustedParentSize = + ComputeScriptLevelSize(aFont, aParentFont, aPresContext, + &scriptLevelAdjustedUnconstrainedParentSize); + NS_ASSERTION(!aUsedStartStruct || aFont->mScriptUnconstrainedSize == aFont->mSize, + "If we have a start struct, we should have reset everything coming in here"); + + // Compute whether we're affected by scriptMinSize *before* calling + // SetFontSize, since aParentFont might be the same as aFont. If it + // is, calling SetFontSize might throw off our calculation. + bool affectedByScriptMinSize = + aParentFont->mSize != aParentFont->mScriptUnconstrainedSize || + scriptLevelAdjustedParentSize != + scriptLevelAdjustedUnconstrainedParentSize; + + SetFontSize(aPresContext, aContext, + aRuleData, aFont, aParentFont, + &aFont->mSize, + systemFont, aParentFont->mSize, scriptLevelAdjustedParentSize, + aUsedStartStruct, atRoot, aConditions); + if (!aPresContext->Document()->GetMathMLEnabled()) { + MOZ_ASSERT(!affectedByScriptMinSize); + // If MathML is not enabled, we don't need to mark this node as + // uncacheable. If it becomes enabled, code in + // nsMathMLElementFactory will rebuild the rule tree and style data + // when MathML is first enabled (see nsMathMLElement::BindToTree). + aFont->mScriptUnconstrainedSize = aFont->mSize; + } else if (!affectedByScriptMinSize) { + // Fast path: we have not been affected by scriptminsize so we don't + // need to call SetFontSize again to compute the + // scriptminsize-unconstrained size. This is OK even if we have a + // start struct, because if we have a start struct then 'font-size' + // was specified and so scriptminsize has no effect. + aFont->mScriptUnconstrainedSize = aFont->mSize; + // It's possible we could, in the future, have a different parent, + // which would lead to a different affectedByScriptMinSize. + aConditions.SetUncacheable(); + } else { + // see previous else-if + aConditions.SetUncacheable(); + + // Use a separate conditions object because it might get a + // *different* font-size dependency. We can ignore it because we've + // already called SetUncacheable. + RuleNodeCacheConditions unconstrainedConditions; + + SetFontSize(aPresContext, aContext, + aRuleData, aFont, aParentFont, + &aFont->mScriptUnconstrainedSize, + systemFont, aParentFont->mScriptUnconstrainedSize, + scriptLevelAdjustedUnconstrainedParentSize, + aUsedStartStruct, atRoot, unconstrainedConditions); + } + NS_ASSERTION(aFont->mScriptUnconstrainedSize <= aFont->mSize, + "scriptminsize should never be making things bigger"); + + nscoord fontSize = aFont->mSize; + + // enforce the user' specified minimum font-size on the value that we expose + // (but don't change font-size:0, since that would unhide hidden text) + if (fontSize > 0) { + nscoord minFontSize = aPresContext->MinFontSize(aFont->mLanguage); + if (minFontSize < 0) { + minFontSize = 0; + } else { + minFontSize = (minFontSize * aFont->mMinFontSizeRatio) / 100; + } + if (fontSize < minFontSize && !aPresContext->IsChrome()) { + // override the minimum font-size constraint + fontSize = minFontSize; + } + } + aFont->mFont.size = fontSize; + + // font-size-adjust: number, none, inherit, initial, -moz-system-font + const nsCSSValue* sizeAdjustValue = aRuleData->ValueForFontSizeAdjust(); + if (eCSSUnit_System_Font == sizeAdjustValue->GetUnit()) { + aFont->mFont.sizeAdjust = systemFont.sizeAdjust; + } else + SetFactor(*sizeAdjustValue, aFont->mFont.sizeAdjust, + aConditions, aParentFont->mFont.sizeAdjust, -1.0f, + SETFCT_NONE | SETFCT_UNSET_INHERIT); +} + +/* static */ void +nsRuleNode::ComputeFontFeatures(const nsCSSValuePairList *aFeaturesList, + nsTArray<gfxFontFeature>& aFeatureSettings) +{ + aFeatureSettings.Clear(); + for (const nsCSSValuePairList* p = aFeaturesList; p; p = p->mNext) { + gfxFontFeature feat = {0, 0}; + + MOZ_ASSERT(aFeaturesList->mXValue.GetUnit() == eCSSUnit_String, + "unexpected value unit"); + + // tag is a 4-byte ASCII sequence + nsAutoString tag; + p->mXValue.GetStringValue(tag); + if (tag.Length() != 4) { + continue; + } + // parsing validates that these are ASCII chars + // tags are always big-endian + feat.mTag = (tag[0] << 24) | (tag[1] << 16) | (tag[2] << 8) | tag[3]; + + // value + NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Integer, + "should have found an integer unit"); + feat.mValue = p->mYValue.GetIntValue(); + + aFeatureSettings.AppendElement(feat); + } +} + +// This should die (bug 380915). +// +// SetGenericFont: +// - backtrack to an ancestor with the same generic font name (possibly +// up to the root where default values come from the presentation context) +// - re-apply cascading rules from there without caching intermediate values +/* static */ void +nsRuleNode::SetGenericFont(nsPresContext* aPresContext, + nsStyleContext* aContext, + uint8_t aGenericFontID, + nsStyleFont* aFont) +{ + // walk up the contexts until a context with the desired generic font + AutoTArray<nsStyleContext*, 8> contextPath; + contextPath.AppendElement(aContext); + nsStyleContext* higherContext = aContext->GetParent(); + while (higherContext) { + if (higherContext->StyleFont()->mGenericID == aGenericFontID) { + // done walking up the higher contexts + break; + } + contextPath.AppendElement(higherContext); + higherContext = higherContext->GetParent(); + } + + // re-apply the cascading rules, starting from the higher context + + // If we stopped earlier because we reached the root of the style tree, + // we will start with the default generic font from the presentation + // context. Otherwise we start with the higher context. + const nsFont* defaultFont = + aPresContext->GetDefaultFont(aGenericFontID, aFont->mLanguage); + nsStyleFont parentFont(*defaultFont, aPresContext); + if (higherContext) { + const nsStyleFont* tmpFont = higherContext->StyleFont(); + parentFont = *tmpFont; + } + *aFont = parentFont; + + uint32_t fontBit = nsCachedStyleData::GetBitForSID(eStyleStruct_Font); + + // use placement new[] on the result of alloca() to allocate a + // variable-sized stack array, including execution of constructors, + // and use an RAII class to run the destructors too. + size_t nprops = nsCSSProps::PropertyCountInStruct(eStyleStruct_Font); + void* dataStorage = alloca(nprops * sizeof(nsCSSValue)); + + for (int32_t i = contextPath.Length() - 1; i >= 0; --i) { + nsStyleContext* context = contextPath[i]; + AutoCSSValueArray dataArray(dataStorage, nprops); + + nsRuleData ruleData(NS_STYLE_INHERIT_BIT(Font), dataArray.get(), + aPresContext, context); + ruleData.mValueOffsets[eStyleStruct_Font] = 0; + + // Trimmed down version of ::WalkRuleTree() to re-apply the style rules + // Note that we *do* need to do this for our own data, since what is + // in |fontData| in ComputeFontData is only for the rules below + // aStartStruct. + for (nsRuleNode* ruleNode = context->RuleNode(); ruleNode; + ruleNode = ruleNode->GetParent()) { + if (ruleNode->mNoneBits & fontBit) + // no more font rules on this branch, get out + break; + + nsIStyleRule *rule = ruleNode->GetRule(); + if (rule) { + ruleData.mLevel = ruleNode->GetLevel(); + ruleData.mIsImportantRule = ruleNode->IsImportantRule(); + rule->MapRuleInfoInto(&ruleData); + } + } + + // Compute the delta from the information that the rules specified + + // Avoid unnecessary operations in SetFont(). But we care if it's + // the final value that we're computing. + if (i != 0) + ruleData.ValueForFontFamily()->Reset(); + + ResolveVariableReferences(eStyleStruct_Font, &ruleData, aContext); + + RuleNodeCacheConditions dummy; + nsRuleNode::SetFont(aPresContext, context, + aGenericFontID, &ruleData, &parentFont, aFont, + false, dummy); + + parentFont = *aFont; + } + + if (higherContext && contextPath.Length() > 1) { + // contextPath is a list of all ancestor style contexts, so it must have + // at least two elements for it to result in a dependency on grandancestor + // styles. + PropagateGrandancestorBit(aContext, higherContext); + } +} + +const void* +nsRuleNode::ComputeFontData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_INHERITED(Font, font, parentFont) + + // NOTE: The |aRuleDetail| passed in is a little bit conservative due + // to the -moz-system-font property. We really don't need to consider + // it here in determining whether to cache in the rule tree. However, + // we do need to consider it in WalkRuleTree when deciding whether to + // walk further up the tree. So this means that when the font struct + // is fully specified using *longhand* properties (excluding + // -moz-system-font), we won't cache in the rule tree even though we + // could. However, it's pretty unlikely authors will do that + // (although there is a pretty good chance they'll fully specify it + // using the 'font' shorthand). + + // Figure out if we are a generic font + uint8_t generic = kGenericFont_NONE; + // XXXldb What if we would have had a string if we hadn't been doing + // the optimization with a non-null aStartStruct? + const nsCSSValue* familyValue = aRuleData->ValueForFontFamily(); + if (eCSSUnit_FontFamilyList == familyValue->GetUnit()) { + const FontFamilyList* fontlist = familyValue->GetFontFamilyListValue(); + FontFamilyList& fl = font->mFont.fontlist; + fl = *fontlist; + + // extract the first generic in the fontlist, if exists + FontFamilyType fontType = fontlist->FirstGeneric(); + + // if only a single generic, set the generic type + if (fontlist->Length() == 1) { + switch (fontType) { + case eFamily_serif: + generic = kGenericFont_serif; + break; + case eFamily_sans_serif: + generic = kGenericFont_sans_serif; + break; + case eFamily_monospace: + generic = kGenericFont_monospace; + break; + case eFamily_cursive: + generic = kGenericFont_cursive; + break; + case eFamily_fantasy: + generic = kGenericFont_fantasy; + break; + case eFamily_moz_fixed: + generic = kGenericFont_moz_fixed; + break; + default: + break; + } + } + } + + // Now compute our font struct + if (generic == kGenericFont_NONE) { + // continue the normal processing + nsRuleNode::SetFont(mPresContext, aContext, generic, + aRuleData, parentFont, font, + aStartStruct != nullptr, conditions); + } + else { + // re-calculate the font as a generic font + conditions.SetUncacheable(); + nsRuleNode::SetGenericFont(mPresContext, aContext, generic, + font); + } + + COMPUTE_END_INHERITED(Font, font) +} + +template <typename T> +inline uint32_t ListLength(const T* aList) +{ + uint32_t len = 0; + while (aList) { + len++; + aList = aList->mNext; + } + return len; +} + +static already_AddRefed<nsCSSShadowArray> +GetShadowData(const nsCSSValueList* aList, + nsStyleContext* aContext, + bool aIsBoxShadow, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + uint32_t arrayLength = ListLength(aList); + + MOZ_ASSERT(arrayLength > 0, + "Non-null text-shadow list, yet we counted 0 items."); + RefPtr<nsCSSShadowArray> shadowList = + new(arrayLength) nsCSSShadowArray(arrayLength); + + if (!shadowList) + return nullptr; + + nsStyleCoord tempCoord; + DebugOnly<bool> unitOK; + for (nsCSSShadowItem* item = shadowList->ShadowAt(0); + aList; + aList = aList->mNext, ++item) { + MOZ_ASSERT(aList->mValue.GetUnit() == eCSSUnit_Array, + "expecting a plain array value"); + nsCSSValue::Array *arr = aList->mValue.GetArrayValue(); + // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT + unitOK = SetCoord(arr->Item(0), tempCoord, nsStyleCoord(), + SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY, + aContext, aPresContext, aConditions); + NS_ASSERTION(unitOK, "unexpected unit"); + item->mXOffset = tempCoord.GetCoordValue(); + + unitOK = SetCoord(arr->Item(1), tempCoord, nsStyleCoord(), + SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY, + aContext, aPresContext, aConditions); + NS_ASSERTION(unitOK, "unexpected unit"); + item->mYOffset = tempCoord.GetCoordValue(); + + // Blur radius is optional in the current box-shadow spec + if (arr->Item(2).GetUnit() != eCSSUnit_Null) { + unitOK = SetCoord(arr->Item(2), tempCoord, nsStyleCoord(), + SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY | + SETCOORD_CALC_CLAMP_NONNEGATIVE, + aContext, aPresContext, aConditions); + NS_ASSERTION(unitOK, "unexpected unit"); + item->mRadius = tempCoord.GetCoordValue(); + } else { + item->mRadius = 0; + } + + // Find the spread radius + if (aIsBoxShadow && arr->Item(3).GetUnit() != eCSSUnit_Null) { + unitOK = SetCoord(arr->Item(3), tempCoord, nsStyleCoord(), + SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY, + aContext, aPresContext, aConditions); + NS_ASSERTION(unitOK, "unexpected unit"); + item->mSpread = tempCoord.GetCoordValue(); + } else { + item->mSpread = 0; + } + + if (arr->Item(4).GetUnit() != eCSSUnit_Null) { + item->mHasColor = true; + // 2nd argument can be bogus since inherit is not a valid color + unitOK = SetColor(arr->Item(4), 0, aPresContext, aContext, item->mColor, + aConditions); + NS_ASSERTION(unitOK, "unexpected unit"); + } + + if (aIsBoxShadow && arr->Item(5).GetUnit() == eCSSUnit_Enumerated) { + NS_ASSERTION(arr->Item(5).GetIntValue() + == uint8_t(StyleBoxShadowType::Inset), + "invalid keyword type for box shadow"); + item->mInset = true; + } else { + item->mInset = false; + } + } + + return shadowList.forget(); +} + +struct TextEmphasisChars +{ + const char16_t* mFilled; + const char16_t* mOpen; +}; + +#define TEXT_EMPHASIS_CHARS_LIST() \ + TEXT_EMPHASIS_CHARS_ITEM(u"", u"", NONE) \ + TEXT_EMPHASIS_CHARS_ITEM(u"\u2022", u"\u25e6", DOT) \ + TEXT_EMPHASIS_CHARS_ITEM(u"\u25cf", u"\u25cb", CIRCLE) \ + TEXT_EMPHASIS_CHARS_ITEM(u"\u25c9", u"\u25ce", DOUBLE_CIRCLE) \ + TEXT_EMPHASIS_CHARS_ITEM(u"\u25b2", u"\u25b3", TRIANGLE) \ + TEXT_EMPHASIS_CHARS_ITEM(u"\ufe45", u"\ufe46", SESAME) + +static constexpr TextEmphasisChars kTextEmphasisChars[] = +{ +#define TEXT_EMPHASIS_CHARS_ITEM(filled_, open_, type_) \ + { filled_, open_ }, // type_ + TEXT_EMPHASIS_CHARS_LIST() +#undef TEXT_EMPHASIS_CHARS_ITEM +}; + +#define TEXT_EMPHASIS_CHARS_ITEM(filled_, open_, type_) \ + static_assert(ArrayLength(filled_) <= 2 && \ + ArrayLength(open_) <= 2, \ + "emphasis marks should have no more than one char"); \ + static_assert( \ + *kTextEmphasisChars[NS_STYLE_TEXT_EMPHASIS_STYLE_##type_].mFilled == \ + *filled_, "filled " #type_ " should be " #filled_); \ + static_assert( \ + *kTextEmphasisChars[NS_STYLE_TEXT_EMPHASIS_STYLE_##type_].mOpen == \ + *open_, "open " #type_ " should be " #open_); +TEXT_EMPHASIS_CHARS_LIST() +#undef TEXT_EMPHASIS_CHARS_ITEM + +#undef TEXT_EMPHASIS_CHARS_LIST + +static void +TruncateStringToSingleGrapheme(nsAString& aStr) +{ + unicode::ClusterIterator iter(aStr.Data(), aStr.Length()); + if (!iter.AtEnd()) { + iter.Next(); + if (!iter.AtEnd()) { + // Not mutating the string for common cases helps memory use + // since we share the buffer from the specified style into the + // computed style. + aStr.Truncate(iter - aStr.Data()); + } + } +} + +struct LineHeightCalcObj +{ + float mLineHeight; + bool mIsNumber; +}; + +struct SetLineHeightCalcOps : public css::NumbersAlreadyNormalizedOps +{ + typedef LineHeightCalcObj result_type; + nsStyleContext* const mStyleContext; + nsPresContext* const mPresContext; + RuleNodeCacheConditions& mConditions; + + SetLineHeightCalcOps(nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) + : mStyleContext(aStyleContext), + mPresContext(aPresContext), + mConditions(aConditions) + { + } + + result_type + MergeAdditive(nsCSSUnit aCalcFunction, + result_type aValue1, result_type aValue2) + { + MOZ_ASSERT(aValue1.mIsNumber == aValue2.mIsNumber); + + LineHeightCalcObj result; + result.mIsNumber = aValue1.mIsNumber; + if (aCalcFunction == eCSSUnit_Calc_Plus) { + result.mLineHeight = aValue1.mLineHeight + aValue2.mLineHeight; + return result; + } + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus, + "unexpected unit"); + result.mLineHeight = aValue1.mLineHeight - aValue2.mLineHeight; + return result; + } + + result_type + MergeMultiplicativeL(nsCSSUnit aCalcFunction, + float aValue1, result_type aValue2) + { + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L, + "unexpected unit"); + LineHeightCalcObj result; + result.mIsNumber = aValue2.mIsNumber; + result.mLineHeight = aValue1 * aValue2.mLineHeight; + return result; + } + + result_type + MergeMultiplicativeR(nsCSSUnit aCalcFunction, + result_type aValue1, float aValue2) + { + LineHeightCalcObj result; + result.mIsNumber = aValue1.mIsNumber; + if (aCalcFunction == eCSSUnit_Calc_Times_R) { + result.mLineHeight = aValue1.mLineHeight * aValue2; + return result; + } + MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Divided, + "unexpected unit"); + result.mLineHeight = aValue1.mLineHeight / aValue2; + return result; + } + + result_type ComputeLeafValue(const nsCSSValue& aValue) + { + LineHeightCalcObj result; + if (aValue.IsLengthUnit()) { + result.mIsNumber = false; + result.mLineHeight = CalcLength(aValue, mStyleContext, + mPresContext, mConditions); + } + else if (eCSSUnit_Percent == aValue.GetUnit()) { + mConditions.SetUncacheable(); + result.mIsNumber = false; + nscoord fontSize = mStyleContext->StyleFont()->mFont.size; + result.mLineHeight = fontSize * aValue.GetPercentValue(); + } + else if (eCSSUnit_Number == aValue.GetUnit()) { + result.mIsNumber = true; + result.mLineHeight = aValue.GetFloatValue(); + } else { + MOZ_ASSERT(false, "unexpected value"); + result.mIsNumber = true; + result.mLineHeight = 1.0f; + } + + return result; + } +}; + +const void* +nsRuleNode::ComputeTextData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_INHERITED(Text, text, parentText) + + auto setComplexColor = [&](const nsCSSValue* aValue, + StyleComplexColor nsStyleText::* aField) { + SetComplexColor<eUnsetInherit>(*aValue, parentText->*aField, + StyleComplexColor::CurrentColor(), + mPresContext, text->*aField, conditions); + }; + + // tab-size: integer, inherit + SetValue(*aRuleData->ValueForTabSize(), + text->mTabSize, conditions, + SETVAL_INTEGER | SETVAL_UNSET_INHERIT, parentText->mTabSize, + NS_STYLE_TABSIZE_INITIAL); + + // letter-spacing: normal, length, inherit + SetCoord(*aRuleData->ValueForLetterSpacing(), + text->mLetterSpacing, parentText->mLetterSpacing, + SETCOORD_LH | SETCOORD_NORMAL | SETCOORD_INITIAL_NORMAL | + SETCOORD_CALC_LENGTH_ONLY | SETCOORD_UNSET_INHERIT, + aContext, mPresContext, conditions); + + // text-shadow: none, list, inherit, initial + const nsCSSValue* textShadowValue = aRuleData->ValueForTextShadow(); + if (textShadowValue->GetUnit() != eCSSUnit_Null) { + text->mTextShadow = nullptr; + + // Don't need to handle none/initial explicitly: The above assignment + // takes care of that + if (textShadowValue->GetUnit() == eCSSUnit_Inherit || + textShadowValue->GetUnit() == eCSSUnit_Unset) { + conditions.SetUncacheable(); + text->mTextShadow = parentText->mTextShadow; + } else if (textShadowValue->GetUnit() == eCSSUnit_List || + textShadowValue->GetUnit() == eCSSUnit_ListDep) { + // List of arrays + text->mTextShadow = GetShadowData(textShadowValue->GetListValue(), + aContext, false, mPresContext, conditions); + } + } + + // line-height: normal, number, length, percent, calc, inherit + const nsCSSValue* lineHeightValue = aRuleData->ValueForLineHeight(); + if (eCSSUnit_Percent == lineHeightValue->GetUnit()) { + conditions.SetUncacheable(); + // Use |mFont.size| to pick up minimum font size. + text->mLineHeight.SetCoordValue( + NSToCoordRound(float(aContext->StyleFont()->mFont.size) * + lineHeightValue->GetPercentValue())); + } + else if (eCSSUnit_Initial == lineHeightValue->GetUnit() || + eCSSUnit_System_Font == lineHeightValue->GetUnit()) { + text->mLineHeight.SetNormalValue(); + } + else if (eCSSUnit_Calc == lineHeightValue->GetUnit()) { + SetLineHeightCalcOps ops(aContext, mPresContext, conditions); + LineHeightCalcObj obj = css::ComputeCalc(*lineHeightValue, ops); + if (obj.mIsNumber) { + text->mLineHeight.SetFactorValue(obj.mLineHeight); + } else { + text->mLineHeight.SetCoordValue( + NSToCoordRoundWithClamp(obj.mLineHeight)); + } + } + else { + SetCoord(*lineHeightValue, text->mLineHeight, parentText->mLineHeight, + SETCOORD_LEH | SETCOORD_FACTOR | SETCOORD_NORMAL | + SETCOORD_UNSET_INHERIT, + aContext, mPresContext, conditions); + if (lineHeightValue->IsLengthUnit() && + !lineHeightValue->IsRelativeLengthUnit()) { + nscoord lh = nsStyleFont::ZoomText(mPresContext, + text->mLineHeight.GetCoordValue()); + + conditions.SetUncacheable(); + const nsStyleFont *font = aContext->StyleFont(); + nscoord minimumFontSize = mPresContext->MinFontSize(font->mLanguage); + + if (minimumFontSize > 0 && !mPresContext->IsChrome()) { + if (font->mSize != 0) { + lh = nscoord(float(lh) * float(font->mFont.size) / float(font->mSize)); + } else { + lh = minimumFontSize; + } + } + text->mLineHeight.SetCoordValue(lh); + } + } + + + // text-align: enum, string, pair(enum|string), inherit, initial + // NOTE: string is not implemented yet. + const nsCSSValue* textAlignValue = aRuleData->ValueForTextAlign(); + text->mTextAlignTrue = false; + if (eCSSUnit_String == textAlignValue->GetUnit()) { + NS_NOTYETIMPLEMENTED("align string"); + } else if (eCSSUnit_Enumerated == textAlignValue->GetUnit() && + NS_STYLE_TEXT_ALIGN_MOZ_CENTER_OR_INHERIT == + textAlignValue->GetIntValue()) { + conditions.SetUncacheable(); + uint8_t parentAlign = parentText->mTextAlign; + text->mTextAlign = (NS_STYLE_TEXT_ALIGN_START == parentAlign) ? + NS_STYLE_TEXT_ALIGN_CENTER : parentAlign; + } else if (eCSSUnit_Enumerated == textAlignValue->GetUnit() && + NS_STYLE_TEXT_ALIGN_MATCH_PARENT == + textAlignValue->GetIntValue()) { + conditions.SetUncacheable(); + nsStyleContext* parent = aContext->GetParent(); + if (parent) { + uint8_t parentAlign = parentText->mTextAlign; + uint8_t parentDirection = parent->StyleVisibility()->mDirection; + switch (parentAlign) { + case NS_STYLE_TEXT_ALIGN_START: + text->mTextAlign = parentDirection == NS_STYLE_DIRECTION_RTL ? + NS_STYLE_TEXT_ALIGN_RIGHT : NS_STYLE_TEXT_ALIGN_LEFT; + break; + + case NS_STYLE_TEXT_ALIGN_END: + text->mTextAlign = parentDirection == NS_STYLE_DIRECTION_RTL ? + NS_STYLE_TEXT_ALIGN_LEFT : NS_STYLE_TEXT_ALIGN_RIGHT; + break; + + default: + text->mTextAlign = parentAlign; + } + } + } else { + if (eCSSUnit_Pair == textAlignValue->GetUnit()) { + // Two values were specified, one must be 'true'. + text->mTextAlignTrue = true; + const nsCSSValuePair& textAlignValuePair = textAlignValue->GetPairValue(); + textAlignValue = &textAlignValuePair.mXValue; + if (eCSSUnit_Enumerated == textAlignValue->GetUnit()) { + if (textAlignValue->GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) { + textAlignValue = &textAlignValuePair.mYValue; + } + } else if (eCSSUnit_String == textAlignValue->GetUnit()) { + NS_NOTYETIMPLEMENTED("align string"); + } + } else if (eCSSUnit_Inherit == textAlignValue->GetUnit() || + eCSSUnit_Unset == textAlignValue->GetUnit()) { + text->mTextAlignTrue = parentText->mTextAlignTrue; + } + SetValue(*textAlignValue, text->mTextAlign, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mTextAlign, + NS_STYLE_TEXT_ALIGN_START); + } + + // text-align-last: enum, pair(enum), inherit, initial + const nsCSSValue* textAlignLastValue = aRuleData->ValueForTextAlignLast(); + text->mTextAlignLastTrue = false; + if (eCSSUnit_Pair == textAlignLastValue->GetUnit()) { + // Two values were specified, one must be 'true'. + text->mTextAlignLastTrue = true; + const nsCSSValuePair& textAlignLastValuePair = textAlignLastValue->GetPairValue(); + textAlignLastValue = &textAlignLastValuePair.mXValue; + if (eCSSUnit_Enumerated == textAlignLastValue->GetUnit()) { + if (textAlignLastValue->GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) { + textAlignLastValue = &textAlignLastValuePair.mYValue; + } + } + } else if (eCSSUnit_Inherit == textAlignLastValue->GetUnit() || + eCSSUnit_Unset == textAlignLastValue->GetUnit()) { + text->mTextAlignLastTrue = parentText->mTextAlignLastTrue; + } + SetValue(*textAlignLastValue, text->mTextAlignLast, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mTextAlignLast, + NS_STYLE_TEXT_ALIGN_AUTO); + + // text-indent: length, percent, calc, inherit, initial + SetCoord(*aRuleData->ValueForTextIndent(), text->mTextIndent, parentText->mTextIndent, + SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INHERIT, + aContext, mPresContext, conditions); + + // text-transform: enum, inherit, initial + SetValue(*aRuleData->ValueForTextTransform(), text->mTextTransform, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mTextTransform, + NS_STYLE_TEXT_TRANSFORM_NONE); + + // white-space: enum, inherit, initial + SetValue(*aRuleData->ValueForWhiteSpace(), text->mWhiteSpace, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mWhiteSpace, + NS_STYLE_WHITESPACE_NORMAL); + + // word-break: enum, inherit, initial + SetValue(*aRuleData->ValueForWordBreak(), text->mWordBreak, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mWordBreak, + NS_STYLE_WORDBREAK_NORMAL); + + // word-spacing: normal, length, percent, inherit + const nsCSSValue* wordSpacingValue = aRuleData->ValueForWordSpacing(); + if (wordSpacingValue->GetUnit() == eCSSUnit_Normal) { + // Do this so that "normal" computes to 0px, as the CSS 2.1 spec requires. + text->mWordSpacing.SetCoordValue(0); + } else { + SetCoord(*aRuleData->ValueForWordSpacing(), + text->mWordSpacing, parentText->mWordSpacing, + SETCOORD_LPH | SETCOORD_INITIAL_ZERO | + SETCOORD_STORE_CALC | SETCOORD_UNSET_INHERIT, + aContext, mPresContext, conditions); + } + + // overflow-wrap: enum, inherit, initial + SetValue(*aRuleData->ValueForOverflowWrap(), text->mOverflowWrap, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mOverflowWrap, + NS_STYLE_OVERFLOWWRAP_NORMAL); + + // hyphens: enum, inherit, initial + SetValue(*aRuleData->ValueForHyphens(), text->mHyphens, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mHyphens, + NS_STYLE_HYPHENS_MANUAL); + + // ruby-align: enum, inherit, initial + SetValue(*aRuleData->ValueForRubyAlign(), + text->mRubyAlign, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mRubyAlign, + NS_STYLE_RUBY_ALIGN_SPACE_AROUND); + + // ruby-position: enum, inherit, initial + SetValue(*aRuleData->ValueForRubyPosition(), + text->mRubyPosition, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mRubyPosition, + NS_STYLE_RUBY_POSITION_OVER); + + // text-size-adjust: none, auto, inherit, initial + SetValue(*aRuleData->ValueForTextSizeAdjust(), text->mTextSizeAdjust, + conditions, SETVAL_UNSET_INHERIT, + parentText->mTextSizeAdjust, + /* initial */ NS_STYLE_TEXT_SIZE_ADJUST_AUTO, + /* auto */ NS_STYLE_TEXT_SIZE_ADJUST_AUTO, + /* none */ NS_STYLE_TEXT_SIZE_ADJUST_NONE, Unused, Unused); + + // text-combine-upright: enum, inherit, initial + SetValue(*aRuleData->ValueForTextCombineUpright(), + text->mTextCombineUpright, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mTextCombineUpright, + NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE); + + // text-emphasis-color: color, string, inherit, initial + setComplexColor(aRuleData->ValueForTextEmphasisColor(), + &nsStyleText::mTextEmphasisColor); + + // text-emphasis-position: enum, inherit, initial + SetValue(*aRuleData->ValueForTextEmphasisPosition(), + text->mTextEmphasisPosition, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mTextEmphasisPosition, + NS_STYLE_TEXT_EMPHASIS_POSITION_OVER | + NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT); + + // text-emphasis-style: string, enum, inherit, initial + const nsCSSValue* textEmphasisStyleValue = + aRuleData->ValueForTextEmphasisStyle(); + switch (textEmphasisStyleValue->GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_Initial: + case eCSSUnit_None: { + text->mTextEmphasisStyle = NS_STYLE_TEXT_EMPHASIS_STYLE_NONE; + text->mTextEmphasisStyleString = u""; + break; + } + case eCSSUnit_Inherit: + case eCSSUnit_Unset: { + conditions.SetUncacheable(); + text->mTextEmphasisStyle = parentText->mTextEmphasisStyle; + text->mTextEmphasisStyleString = parentText->mTextEmphasisStyleString; + break; + } + case eCSSUnit_Enumerated: { + auto style = textEmphasisStyleValue->GetIntValue(); + // If shape part is not specified, compute it according to the + // writing-mode. Note that, if the fill part (filled/open) is not + // specified, we compute it to filled per spec. Since that value + // is zero, no additional computation is needed. See the assertion + // in CSSParserImpl::ParseTextEmphasisStyle(). + if (!(style & NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK)) { + conditions.SetUncacheable(); + if (WritingMode(aContext).IsVertical()) { + style |= NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME; + } else { + style |= NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE; + } + } + text->mTextEmphasisStyle = style; + size_t shape = style & NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK; + MOZ_ASSERT(shape > 0 && shape < ArrayLength(kTextEmphasisChars)); + const TextEmphasisChars& chars = kTextEmphasisChars[shape]; + text->mTextEmphasisStyleString = + (style & NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) == + NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED ? chars.mFilled : chars.mOpen; + break; + } + case eCSSUnit_String: { + text->mTextEmphasisStyle = NS_STYLE_TEXT_EMPHASIS_STYLE_STRING; + nsString strValue; + textEmphasisStyleValue->GetStringValue(strValue); + TruncateStringToSingleGrapheme(strValue); + text->mTextEmphasisStyleString = strValue; + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unknown value unit type"); + } + + // text-rendering: enum, inherit, initial + SetValue(*aRuleData->ValueForTextRendering(), + text->mTextRendering, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mTextRendering, + NS_STYLE_TEXT_RENDERING_AUTO); + + // -webkit-text-fill-color: color, string, inherit, initial + setComplexColor(aRuleData->ValueForWebkitTextFillColor(), + &nsStyleText::mWebkitTextFillColor); + + // -webkit-text-stroke-color: color, string, inherit, initial + setComplexColor(aRuleData->ValueForWebkitTextStrokeColor(), + &nsStyleText::mWebkitTextStrokeColor); + + // -webkit-text-stroke-width: length, inherit, initial, enum + const nsCSSValue* + webkitTextStrokeWidthValue = aRuleData->ValueForWebkitTextStrokeWidth(); + if (webkitTextStrokeWidthValue->GetUnit() == eCSSUnit_Enumerated) { + NS_ASSERTION(webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN || + webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM || + webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK, + "Unexpected enum value"); + text->mWebkitTextStrokeWidth.SetCoordValue( + mPresContext->GetBorderWidthTable()[webkitTextStrokeWidthValue->GetIntValue()]); + } else { + SetCoord(*webkitTextStrokeWidthValue, text->mWebkitTextStrokeWidth, + parentText->mWebkitTextStrokeWidth, + SETCOORD_LH | SETCOORD_CALC_LENGTH_ONLY | + SETCOORD_CALC_CLAMP_NONNEGATIVE | + SETCOORD_INITIAL_ZERO | SETCOORD_UNSET_INHERIT, + aContext, mPresContext, conditions); + } + + // -moz-control-character-visibility: enum, inherit, initial + SetValue(*aRuleData->ValueForControlCharacterVisibility(), + text->mControlCharacterVisibility, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentText->mControlCharacterVisibility, + nsCSSParser::ControlCharVisibilityDefault()); + + COMPUTE_END_INHERITED(Text, text) +} + +const void* +nsRuleNode::ComputeTextResetData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(TextReset, text, parentText) + + // text-decoration-line: enum (bit field), inherit, initial + const nsCSSValue* decorationLineValue = + aRuleData->ValueForTextDecorationLine(); + if (eCSSUnit_Enumerated == decorationLineValue->GetUnit()) { + int32_t td = decorationLineValue->GetIntValue(); + text->mTextDecorationLine = td; + if (td & NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS) { + bool underlineLinks = + mPresContext->GetCachedBoolPref(kPresContext_UnderlineLinks); + if (underlineLinks) { + text->mTextDecorationLine |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; + } + else { + text->mTextDecorationLine &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; + } + } + } else if (eCSSUnit_Inherit == decorationLineValue->GetUnit()) { + conditions.SetUncacheable(); + text->mTextDecorationLine = parentText->mTextDecorationLine; + } else if (eCSSUnit_Initial == decorationLineValue->GetUnit() || + eCSSUnit_Unset == decorationLineValue->GetUnit()) { + text->mTextDecorationLine = NS_STYLE_TEXT_DECORATION_LINE_NONE; + } + + // text-decoration-color: color, string, enum, inherit, initial + SetComplexColor<eUnsetInitial>(*aRuleData->ValueForTextDecorationColor(), + parentText->mTextDecorationColor, + StyleComplexColor::CurrentColor(), + mPresContext, + text->mTextDecorationColor, conditions); + + // text-decoration-style: enum, inherit, initial + const nsCSSValue* decorationStyleValue = + aRuleData->ValueForTextDecorationStyle(); + if (eCSSUnit_Enumerated == decorationStyleValue->GetUnit()) { + text->mTextDecorationStyle = decorationStyleValue->GetIntValue(); + } else if (eCSSUnit_Inherit == decorationStyleValue->GetUnit()) { + text->mTextDecorationStyle = parentText->mTextDecorationStyle; + conditions.SetUncacheable(); + } else if (eCSSUnit_Initial == decorationStyleValue->GetUnit() || + eCSSUnit_Unset == decorationStyleValue->GetUnit()) { + text->mTextDecorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; + } + + // text-overflow: enum, string, pair(enum|string), inherit, initial + const nsCSSValue* textOverflowValue = + aRuleData->ValueForTextOverflow(); + if (eCSSUnit_Initial == textOverflowValue->GetUnit() || + eCSSUnit_Unset == textOverflowValue->GetUnit()) { + text->mTextOverflow = nsStyleTextOverflow(); + } else if (eCSSUnit_Inherit == textOverflowValue->GetUnit()) { + conditions.SetUncacheable(); + text->mTextOverflow = parentText->mTextOverflow; + } else if (eCSSUnit_Enumerated == textOverflowValue->GetUnit()) { + // A single enumerated value. + SetValue(*textOverflowValue, text->mTextOverflow.mRight.mType, + conditions, + SETVAL_ENUMERATED, parentText->mTextOverflow.mRight.mType, + NS_STYLE_TEXT_OVERFLOW_CLIP); + text->mTextOverflow.mRight.mString.Truncate(); + text->mTextOverflow.mLeft.mType = NS_STYLE_TEXT_OVERFLOW_CLIP; + text->mTextOverflow.mLeft.mString.Truncate(); + text->mTextOverflow.mLogicalDirections = true; + } else if (eCSSUnit_String == textOverflowValue->GetUnit()) { + // A single string value. + text->mTextOverflow.mRight.mType = NS_STYLE_TEXT_OVERFLOW_STRING; + textOverflowValue->GetStringValue(text->mTextOverflow.mRight.mString); + text->mTextOverflow.mLeft.mType = NS_STYLE_TEXT_OVERFLOW_CLIP; + text->mTextOverflow.mLeft.mString.Truncate(); + text->mTextOverflow.mLogicalDirections = true; + } else if (eCSSUnit_Pair == textOverflowValue->GetUnit()) { + // Two values were specified. + text->mTextOverflow.mLogicalDirections = false; + const nsCSSValuePair& textOverflowValuePair = + textOverflowValue->GetPairValue(); + + const nsCSSValue *textOverflowLeftValue = &textOverflowValuePair.mXValue; + if (eCSSUnit_Enumerated == textOverflowLeftValue->GetUnit()) { + SetValue(*textOverflowLeftValue, text->mTextOverflow.mLeft.mType, + conditions, + SETVAL_ENUMERATED, parentText->mTextOverflow.mLeft.mType, + NS_STYLE_TEXT_OVERFLOW_CLIP); + text->mTextOverflow.mLeft.mString.Truncate(); + } else if (eCSSUnit_String == textOverflowLeftValue->GetUnit()) { + textOverflowLeftValue->GetStringValue(text->mTextOverflow.mLeft.mString); + text->mTextOverflow.mLeft.mType = NS_STYLE_TEXT_OVERFLOW_STRING; + } + + const nsCSSValue *textOverflowRightValue = &textOverflowValuePair.mYValue; + if (eCSSUnit_Enumerated == textOverflowRightValue->GetUnit()) { + SetValue(*textOverflowRightValue, text->mTextOverflow.mRight.mType, + conditions, + SETVAL_ENUMERATED, parentText->mTextOverflow.mRight.mType, + NS_STYLE_TEXT_OVERFLOW_CLIP); + text->mTextOverflow.mRight.mString.Truncate(); + } else if (eCSSUnit_String == textOverflowRightValue->GetUnit()) { + textOverflowRightValue->GetStringValue(text->mTextOverflow.mRight.mString); + text->mTextOverflow.mRight.mType = NS_STYLE_TEXT_OVERFLOW_STRING; + } + } + + // unicode-bidi: enum, inherit, initial + SetValue(*aRuleData->ValueForUnicodeBidi(), text->mUnicodeBidi, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentText->mUnicodeBidi, + NS_STYLE_UNICODE_BIDI_NORMAL); + + // initial-letter: normal, number, array(number, integer?), initial + const nsCSSValue* initialLetterValue = aRuleData->ValueForInitialLetter(); + if (initialLetterValue->GetUnit() == eCSSUnit_Null) { + // We don't want to change anything in this case. + } else if (initialLetterValue->GetUnit() == eCSSUnit_Inherit) { + conditions.SetUncacheable(); + text->mInitialLetterSink = parentText->mInitialLetterSink; + text->mInitialLetterSize = parentText->mInitialLetterSize; + } else if (initialLetterValue->GetUnit() == eCSSUnit_Initial || + initialLetterValue->GetUnit() == eCSSUnit_Unset || + initialLetterValue->GetUnit() == eCSSUnit_Normal) { + // Use invalid values in initial-letter property to mean normal. So we can + // determine whether it is normal by checking mInitialLetterSink == 0. + text->mInitialLetterSink = 0; + text->mInitialLetterSize = 0.0f; + } else if (initialLetterValue->GetUnit() == eCSSUnit_Array) { + const nsCSSValue& firstValue = initialLetterValue->GetArrayValue()->Item(0); + const nsCSSValue& secondValue = initialLetterValue->GetArrayValue()->Item(1); + MOZ_ASSERT(firstValue.GetUnit() == eCSSUnit_Number && + secondValue.GetUnit() == eCSSUnit_Integer, + "unexpected value unit"); + text->mInitialLetterSize = firstValue.GetFloatValue(); + text->mInitialLetterSink = secondValue.GetIntValue(); + } else if (initialLetterValue->GetUnit() == eCSSUnit_Number) { + text->mInitialLetterSize = initialLetterValue->GetFloatValue(); + text->mInitialLetterSink = NSToCoordFloorClamped(text->mInitialLetterSize); + } else { + MOZ_ASSERT_UNREACHABLE("unknown unit for initial-letter"); + } + + COMPUTE_END_RESET(TextReset, text) +} + +const void* +nsRuleNode::ComputeUserInterfaceData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_INHERITED(UserInterface, ui, parentUI) + + // cursor: enum, url, inherit + const nsCSSValue* cursorValue = aRuleData->ValueForCursor(); + nsCSSUnit cursorUnit = cursorValue->GetUnit(); + if (cursorUnit != eCSSUnit_Null) { + ui->mCursorImages.Clear(); + + if (cursorUnit == eCSSUnit_Inherit || + cursorUnit == eCSSUnit_Unset) { + conditions.SetUncacheable(); + ui->mCursor = parentUI->mCursor; + ui->mCursorImages = parentUI->mCursorImages; + } + else if (cursorUnit == eCSSUnit_Initial) { + ui->mCursor = NS_STYLE_CURSOR_AUTO; + } + else { + // The parser will never create a list that is *all* URL values -- + // that's invalid. + MOZ_ASSERT(cursorUnit == eCSSUnit_List || cursorUnit == eCSSUnit_ListDep, + "unrecognized cursor unit"); + const nsCSSValueList* list = cursorValue->GetListValue(); + for ( ; list->mValue.GetUnit() == eCSSUnit_Array; list = list->mNext) { + nsCSSValue::Array* arr = list->mValue.GetArrayValue(); + imgRequestProxy* req = + GetImageRequest(aContext->PresContext(), arr->Item(0)); + if (req) { + nsCursorImage* item = ui->mCursorImages.AppendElement(); + item->SetImage(req); + if (arr->Item(1).GetUnit() != eCSSUnit_Null) { + item->mHaveHotspot = true; + item->mHotspotX = arr->Item(1).GetFloatValue(); + item->mHotspotY = arr->Item(2).GetFloatValue(); + } + } + } + + NS_ASSERTION(list, "Must have non-array value at the end"); + NS_ASSERTION(list->mValue.GetUnit() == eCSSUnit_Enumerated, + "Unexpected fallback value at end of cursor list"); + ui->mCursor = list->mValue.GetIntValue(); + } + } + + // user-input: enum, inherit, initial + SetValue(*aRuleData->ValueForUserInput(), + ui->mUserInput, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentUI->mUserInput, + StyleUserInput::Auto); + + // user-modify: enum, inherit, initial + SetValue(*aRuleData->ValueForUserModify(), + ui->mUserModify, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentUI->mUserModify, + StyleUserModify::ReadOnly); + + // user-focus: enum, inherit, initial + SetValue(*aRuleData->ValueForUserFocus(), + ui->mUserFocus, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentUI->mUserFocus, + StyleUserFocus::None); + + // pointer-events: enum, inherit, initial + SetValue(*aRuleData->ValueForPointerEvents(), ui->mPointerEvents, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentUI->mPointerEvents, + NS_STYLE_POINTER_EVENTS_AUTO); + + COMPUTE_END_INHERITED(UserInterface, ui) +} + +const void* +nsRuleNode::ComputeUIResetData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(UIReset, ui, parentUI) + + // user-select: enum, inherit, initial + SetValue(*aRuleData->ValueForUserSelect(), + ui->mUserSelect, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentUI->mUserSelect, + StyleUserSelect::Auto); + + // ime-mode: enum, inherit, initial + SetValue(*aRuleData->ValueForImeMode(), + ui->mIMEMode, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentUI->mIMEMode, + NS_STYLE_IME_MODE_AUTO); + + // force-broken-image-icons: integer, inherit, initial + SetValue(*aRuleData->ValueForForceBrokenImageIcon(), + ui->mForceBrokenImageIcon, + conditions, + SETVAL_INTEGER | SETVAL_UNSET_INITIAL, + parentUI->mForceBrokenImageIcon, 0); + + // -moz-window-dragging: enum, inherit, initial + SetValue(*aRuleData->ValueForWindowDragging(), + ui->mWindowDragging, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentUI->mWindowDragging, + StyleWindowDragging::Default); + + // -moz-window-shadow: enum, inherit, initial + SetValue(*aRuleData->ValueForWindowShadow(), + ui->mWindowShadow, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentUI->mWindowShadow, + NS_STYLE_WINDOW_SHADOW_DEFAULT); + + COMPUTE_END_RESET(UIReset, ui) +} + +// Information about each transition or animation property that is +// constant. +struct TransitionPropInfo { + nsCSSPropertyID property; + // Location of the count of the property's computed value. + uint32_t nsStyleDisplay::* sdCount; +}; + +// Each property's index in this array must match its index in the +// mutable array |transitionPropData| below. +static const TransitionPropInfo transitionPropInfo[4] = { + { eCSSProperty_transition_delay, + &nsStyleDisplay::mTransitionDelayCount }, + { eCSSProperty_transition_duration, + &nsStyleDisplay::mTransitionDurationCount }, + { eCSSProperty_transition_property, + &nsStyleDisplay::mTransitionPropertyCount }, + { eCSSProperty_transition_timing_function, + &nsStyleDisplay::mTransitionTimingFunctionCount }, +}; + +// Each property's index in this array must match its index in the +// mutable array |animationPropData| below. +static const TransitionPropInfo animationPropInfo[8] = { + { eCSSProperty_animation_delay, + &nsStyleDisplay::mAnimationDelayCount }, + { eCSSProperty_animation_duration, + &nsStyleDisplay::mAnimationDurationCount }, + { eCSSProperty_animation_name, + &nsStyleDisplay::mAnimationNameCount }, + { eCSSProperty_animation_timing_function, + &nsStyleDisplay::mAnimationTimingFunctionCount }, + { eCSSProperty_animation_direction, + &nsStyleDisplay::mAnimationDirectionCount }, + { eCSSProperty_animation_fill_mode, + &nsStyleDisplay::mAnimationFillModeCount }, + { eCSSProperty_animation_play_state, + &nsStyleDisplay::mAnimationPlayStateCount }, + { eCSSProperty_animation_iteration_count, + &nsStyleDisplay::mAnimationIterationCountCount }, +}; + +// Information about each transition or animation property that changes +// during ComputeDisplayData. +struct TransitionPropData { + const nsCSSValueList *list; + nsCSSUnit unit; + uint32_t num; +}; + +static uint32_t +CountTransitionProps(const TransitionPropInfo* aInfo, + TransitionPropData* aData, + size_t aLength, + nsStyleDisplay* aDisplay, + const nsStyleDisplay* aParentDisplay, + const nsRuleData* aRuleData, + RuleNodeCacheConditions& aConditions) +{ + // The four transition properties or eight animation properties are + // stored in nsCSSDisplay in a single array for all properties. The + // number of transitions is equal to the number of items in the + // longest property's value. Properties that have fewer values than + // the longest are filled in by repeating the list. However, this + // repetition does not extend the computed value of that particular + // property (for purposes of inheritance, or, in our code, for when + // other properties are overridden by a more specific rule). + + // But actually, since the spec isn't clear yet, we'll fully compute + // all of them (so we can switch easily later), but only care about + // the ones up to the number of items for 'transition-property', per + // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html . + + // Transitions are difficult to handle correctly because of this. For + // example, we need to handle scenarios such as: + // * a more general rule specifies transition-property: a, b, c; + // * a more specific rule overrides as transition-property: d; + // + // If only the general rule applied, we would fill in the extra + // properties (duration, delay, etc) with initial values to create 3 + // fully-specified transitions. But when the more specific rule + // applies, we should only create a single transition. In order to do + // this we need to remember which properties were explicitly specified + // and which ones were just filled in with initial values to get a + // fully-specified transition, which we do by remembering the number + // of values for each property. + + uint32_t numTransitions = 0; + for (size_t i = 0; i < aLength; ++i) { + const TransitionPropInfo& info = aInfo[i]; + TransitionPropData& data = aData[i]; + + // cache whether any of the properties are specified as 'inherit' so + // we can use it below + + const nsCSSValue& value = *aRuleData->ValueFor(info.property); + data.unit = value.GetUnit(); + data.list = (value.GetUnit() == eCSSUnit_List || + value.GetUnit() == eCSSUnit_ListDep) + ? value.GetListValue() : nullptr; + + // General algorithm to determine how many total transitions we need + // to build. For each property: + // - if there is no value specified in for the property in + // displayData, use the values from the start struct, but only if + // they were explicitly specified + // - if there is a value specified for the property in displayData: + // - if the value is 'inherit', count the number of values for + // that property are specified by the parent, but only those + // that were explicitly specified + // - otherwise, count the number of values specified in displayData + + + // calculate number of elements + if (data.unit == eCSSUnit_Inherit) { + data.num = aParentDisplay->*(info.sdCount); + aConditions.SetUncacheable(); + } else if (data.list) { + data.num = ListLength(data.list); + } else { + data.num = aDisplay->*(info.sdCount); + } + if (data.num > numTransitions) + numTransitions = data.num; + } + + return numTransitions; +} + +/* static */ void +nsRuleNode::ComputeTimingFunction(const nsCSSValue& aValue, + nsTimingFunction& aResult) +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Enumerated: + aResult = nsTimingFunction(aValue.GetIntValue()); + break; + case eCSSUnit_Cubic_Bezier: + { + nsCSSValue::Array* array = aValue.GetArrayValue(); + NS_ASSERTION(array && array->Count() == 4, + "Need 4 control points"); + aResult = nsTimingFunction(array->Item(0).GetFloatValue(), + array->Item(1).GetFloatValue(), + array->Item(2).GetFloatValue(), + array->Item(3).GetFloatValue()); + } + break; + case eCSSUnit_Steps: + { + nsCSSValue::Array* array = aValue.GetArrayValue(); + NS_ASSERTION(array && array->Count() == 2, + "Need 2 items"); + NS_ASSERTION(array->Item(0).GetUnit() == eCSSUnit_Integer, + "unexpected first value"); + NS_ASSERTION(array->Item(1).GetUnit() == eCSSUnit_Enumerated && + (array->Item(1).GetIntValue() == + NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START || + array->Item(1).GetIntValue() == + NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END || + array->Item(1).GetIntValue() == -1), + "unexpected second value"); + nsTimingFunction::Type type = + (array->Item(1).GetIntValue() == + NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START) ? + nsTimingFunction::Type::StepStart : + nsTimingFunction::Type::StepEnd; + aResult = nsTimingFunction(type, array->Item(0).GetIntValue()); + } + break; + default: + NS_NOTREACHED("Invalid transition property unit"); + } +} + +static uint8_t +GetWillChangeBitFieldFromPropFlags(const nsCSSPropertyID& aProp) +{ + uint8_t willChangeBitField = 0; + if (nsCSSProps::PropHasFlags(aProp, CSS_PROPERTY_CREATES_STACKING_CONTEXT)) { + willChangeBitField |= NS_STYLE_WILL_CHANGE_STACKING_CONTEXT; + } + + if (nsCSSProps::PropHasFlags(aProp, CSS_PROPERTY_FIXPOS_CB)) { + willChangeBitField |= NS_STYLE_WILL_CHANGE_FIXPOS_CB; + } + + if (nsCSSProps::PropHasFlags(aProp, CSS_PROPERTY_ABSPOS_CB)) { + willChangeBitField |= NS_STYLE_WILL_CHANGE_ABSPOS_CB; + } + + return willChangeBitField; +} + +const void* +nsRuleNode::ComputeDisplayData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(Display, display, parentDisplay) + + // We may have ended up with aStartStruct's values of mDisplay and + // mFloat, but those may not be correct if our style data overrides + // its position or float properties. Reset to mOriginalDisplay and + // mOriginalFloat; if it turns out we still need the display/floats + // adjustments, we'll do them below. + display->mDisplay = display->mOriginalDisplay; + display->mFloat = display->mOriginalFloat; + + // Each property's index in this array must match its index in the + // const array |transitionPropInfo| above. + TransitionPropData transitionPropData[4]; + TransitionPropData& delay = transitionPropData[0]; + TransitionPropData& duration = transitionPropData[1]; + TransitionPropData& property = transitionPropData[2]; + TransitionPropData& timingFunction = transitionPropData[3]; + +#define FOR_ALL_TRANSITION_PROPS(var_) \ + for (uint32_t var_ = 0; var_ < 4; ++var_) + + // CSS Transitions + uint32_t numTransitions = + CountTransitionProps(transitionPropInfo, transitionPropData, + ArrayLength(transitionPropData), + display, parentDisplay, aRuleData, + conditions); + + display->mTransitions.SetLengthNonZero(numTransitions); + + FOR_ALL_TRANSITION_PROPS(p) { + const TransitionPropInfo& i = transitionPropInfo[p]; + TransitionPropData& d = transitionPropData[p]; + + display->*(i.sdCount) = d.num; + } + + // Fill in the transitions we just allocated with the appropriate values. + for (uint32_t i = 0; i < numTransitions; ++i) { + StyleTransition *transition = &display->mTransitions[i]; + + if (i >= delay.num) { + MOZ_ASSERT(delay.num, "delay.num must be greater than 0"); + transition->SetDelay(display->mTransitions[i % delay.num].GetDelay()); + } else if (delay.unit == eCSSUnit_Inherit) { + // FIXME (Bug 522599) (for all transition properties): write a test that + // detects when this was wrong for i >= delay.num if parent had + // count for this property not equal to length + MOZ_ASSERT(i < parentDisplay->mTransitionDelayCount, + "delay.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + transition->SetDelay(parentDisplay->mTransitions[i].GetDelay()); + } else if (delay.unit == eCSSUnit_Initial || + delay.unit == eCSSUnit_Unset) { + transition->SetDelay(0.0); + } else if (delay.list) { + switch (delay.list->mValue.GetUnit()) { + case eCSSUnit_Seconds: + transition->SetDelay(PR_MSEC_PER_SEC * + delay.list->mValue.GetFloatValue()); + break; + case eCSSUnit_Milliseconds: + transition->SetDelay(delay.list->mValue.GetFloatValue()); + break; + default: + NS_NOTREACHED("Invalid delay unit"); + } + } + + if (i >= duration.num) { + MOZ_ASSERT(duration.num, "duration.num must be greater than 0"); + transition->SetDuration( + display->mTransitions[i % duration.num].GetDuration()); + } else if (duration.unit == eCSSUnit_Inherit) { + MOZ_ASSERT(i < parentDisplay->mTransitionDurationCount, + "duration.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + transition->SetDuration(parentDisplay->mTransitions[i].GetDuration()); + } else if (duration.unit == eCSSUnit_Initial || + duration.unit == eCSSUnit_Unset) { + transition->SetDuration(0.0); + } else if (duration.list) { + switch (duration.list->mValue.GetUnit()) { + case eCSSUnit_Seconds: + transition->SetDuration(PR_MSEC_PER_SEC * + duration.list->mValue.GetFloatValue()); + break; + case eCSSUnit_Milliseconds: + transition->SetDuration(duration.list->mValue.GetFloatValue()); + break; + default: + NS_NOTREACHED("Invalid duration unit"); + } + } + + if (i >= property.num) { + MOZ_ASSERT(property.num, "property.num must be greater than 0"); + transition->CopyPropertyFrom(display->mTransitions[i % property.num]); + } else if (property.unit == eCSSUnit_Inherit) { + MOZ_ASSERT(i < parentDisplay->mTransitionPropertyCount, + "property.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + transition->CopyPropertyFrom(parentDisplay->mTransitions[i]); + } else if (property.unit == eCSSUnit_Initial || + property.unit == eCSSUnit_Unset) { + transition->SetProperty(eCSSPropertyExtra_all_properties); + } else if (property.unit == eCSSUnit_None) { + transition->SetProperty(eCSSPropertyExtra_no_properties); + } else if (property.list) { + const nsCSSValue &val = property.list->mValue; + + if (val.GetUnit() == eCSSUnit_Ident) { + nsDependentString + propertyStr(property.list->mValue.GetStringBufferValue()); + nsCSSPropertyID prop = + nsCSSProps::LookupProperty(propertyStr, + CSSEnabledState::eForAllContent); + if (prop == eCSSProperty_UNKNOWN || + prop == eCSSPropertyExtra_variable) { + transition->SetUnknownProperty(prop, propertyStr); + } else { + transition->SetProperty(prop); + } + } else { + MOZ_ASSERT(val.GetUnit() == eCSSUnit_All, + "Invalid transition property unit"); + transition->SetProperty(eCSSPropertyExtra_all_properties); + } + } + + if (i >= timingFunction.num) { + MOZ_ASSERT(timingFunction.num, + "timingFunction.num must be greater than 0"); + transition->SetTimingFunction( + display->mTransitions[i % timingFunction.num].GetTimingFunction()); + } else if (timingFunction.unit == eCSSUnit_Inherit) { + MOZ_ASSERT(i < parentDisplay->mTransitionTimingFunctionCount, + "timingFunction.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + transition->SetTimingFunction( + parentDisplay->mTransitions[i].GetTimingFunction()); + } else if (timingFunction.unit == eCSSUnit_Initial || + timingFunction.unit == eCSSUnit_Unset) { + transition->SetTimingFunction( + nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE)); + } else if (timingFunction.list) { + ComputeTimingFunction(timingFunction.list->mValue, + transition->TimingFunctionSlot()); + } + + FOR_ALL_TRANSITION_PROPS(p) { + const TransitionPropInfo& info = transitionPropInfo[p]; + TransitionPropData& d = transitionPropData[p]; + + // if we're at the end of the list, start at the beginning and repeat + // until we're out of transitions to populate + if (d.list) { + d.list = d.list->mNext ? d.list->mNext : + aRuleData->ValueFor(info.property)->GetListValue(); + } + } + } + + // Each property's index in this array must match its index in the + // const array |animationPropInfo| above. + TransitionPropData animationPropData[8]; + TransitionPropData& animDelay = animationPropData[0]; + TransitionPropData& animDuration = animationPropData[1]; + TransitionPropData& animName = animationPropData[2]; + TransitionPropData& animTimingFunction = animationPropData[3]; + TransitionPropData& animDirection = animationPropData[4]; + TransitionPropData& animFillMode = animationPropData[5]; + TransitionPropData& animPlayState = animationPropData[6]; + TransitionPropData& animIterationCount = animationPropData[7]; + +#define FOR_ALL_ANIMATION_PROPS(var_) \ + for (uint32_t var_ = 0; var_ < 8; ++var_) + + // CSS Animations. + + uint32_t numAnimations = + CountTransitionProps(animationPropInfo, animationPropData, + ArrayLength(animationPropData), + display, parentDisplay, aRuleData, + conditions); + + display->mAnimations.SetLengthNonZero(numAnimations); + + FOR_ALL_ANIMATION_PROPS(p) { + const TransitionPropInfo& i = animationPropInfo[p]; + TransitionPropData& d = animationPropData[p]; + + display->*(i.sdCount) = d.num; + } + + // Fill in the animations we just allocated with the appropriate values. + for (uint32_t i = 0; i < numAnimations; ++i) { + StyleAnimation *animation = &display->mAnimations[i]; + + if (i >= animDelay.num) { + MOZ_ASSERT(animDelay.num, "animDelay.num must be greater than 0"); + animation->SetDelay(display->mAnimations[i % animDelay.num].GetDelay()); + } else if (animDelay.unit == eCSSUnit_Inherit) { + // FIXME (Bug 522599) (for all animation properties): write a test that + // detects when this was wrong for i >= animDelay.num if parent had + // count for this property not equal to length + MOZ_ASSERT(i < parentDisplay->mAnimationDelayCount, + "animDelay.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + animation->SetDelay(parentDisplay->mAnimations[i].GetDelay()); + } else if (animDelay.unit == eCSSUnit_Initial || + animDelay.unit == eCSSUnit_Unset) { + animation->SetDelay(0.0); + } else if (animDelay.list) { + switch (animDelay.list->mValue.GetUnit()) { + case eCSSUnit_Seconds: + animation->SetDelay(PR_MSEC_PER_SEC * + animDelay.list->mValue.GetFloatValue()); + break; + case eCSSUnit_Milliseconds: + animation->SetDelay(animDelay.list->mValue.GetFloatValue()); + break; + default: + NS_NOTREACHED("Invalid delay unit"); + } + } + + if (i >= animDuration.num) { + MOZ_ASSERT(animDuration.num, "animDuration.num must be greater than 0"); + animation->SetDuration( + display->mAnimations[i % animDuration.num].GetDuration()); + } else if (animDuration.unit == eCSSUnit_Inherit) { + MOZ_ASSERT(i < parentDisplay->mAnimationDurationCount, + "animDuration.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + animation->SetDuration(parentDisplay->mAnimations[i].GetDuration()); + } else if (animDuration.unit == eCSSUnit_Initial || + animDuration.unit == eCSSUnit_Unset) { + animation->SetDuration(0.0); + } else if (animDuration.list) { + switch (animDuration.list->mValue.GetUnit()) { + case eCSSUnit_Seconds: + animation->SetDuration(PR_MSEC_PER_SEC * + animDuration.list->mValue.GetFloatValue()); + break; + case eCSSUnit_Milliseconds: + animation->SetDuration(animDuration.list->mValue.GetFloatValue()); + break; + default: + NS_NOTREACHED("Invalid duration unit"); + } + } + + if (i >= animName.num) { + MOZ_ASSERT(animName.num, "animName.num must be greater than 0"); + animation->SetName(display->mAnimations[i % animName.num].GetName()); + } else if (animName.unit == eCSSUnit_Inherit) { + MOZ_ASSERT(i < parentDisplay->mAnimationNameCount, + "animName.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + animation->SetName(parentDisplay->mAnimations[i].GetName()); + } else if (animName.unit == eCSSUnit_Initial || + animName.unit == eCSSUnit_Unset) { + animation->SetName(EmptyString()); + } else if (animName.list) { + switch (animName.list->mValue.GetUnit()) { + case eCSSUnit_Ident: { + nsDependentString + nameStr(animName.list->mValue.GetStringBufferValue()); + animation->SetName(nameStr); + break; + } + case eCSSUnit_None: { + animation->SetName(EmptyString()); + break; + } + default: + MOZ_ASSERT(false, "Invalid animation-name unit"); + } + } + + if (i >= animTimingFunction.num) { + MOZ_ASSERT(animTimingFunction.num, + "animTimingFunction.num must be greater than 0"); + animation->SetTimingFunction( + display->mAnimations[i % animTimingFunction.num].GetTimingFunction()); + } else if (animTimingFunction.unit == eCSSUnit_Inherit) { + MOZ_ASSERT(i < parentDisplay->mAnimationTimingFunctionCount, + "animTimingFunction.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + animation->SetTimingFunction( + parentDisplay->mAnimations[i].GetTimingFunction()); + } else if (animTimingFunction.unit == eCSSUnit_Initial || + animTimingFunction.unit == eCSSUnit_Unset) { + animation->SetTimingFunction( + nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE)); + } else if (animTimingFunction.list) { + ComputeTimingFunction(animTimingFunction.list->mValue, + animation->TimingFunctionSlot()); + } + + if (i >= animDirection.num) { + MOZ_ASSERT(animDirection.num, + "animDirection.num must be greater than 0"); + animation->SetDirection(display->mAnimations[i % animDirection.num].GetDirection()); + } else if (animDirection.unit == eCSSUnit_Inherit) { + MOZ_ASSERT(i < parentDisplay->mAnimationDirectionCount, + "animDirection.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + animation->SetDirection(parentDisplay->mAnimations[i].GetDirection()); + } else if (animDirection.unit == eCSSUnit_Initial || + animDirection.unit == eCSSUnit_Unset) { + animation->SetDirection(dom::PlaybackDirection::Normal); + } else if (animDirection.list) { + MOZ_ASSERT(animDirection.list->mValue.GetUnit() == eCSSUnit_Enumerated, + "Invalid animation-direction unit"); + + animation->SetDirection( + static_cast<dom::PlaybackDirection>(animDirection.list->mValue.GetIntValue())); + } + + if (i >= animFillMode.num) { + MOZ_ASSERT(animFillMode.num, "animFillMode.num must be greater than 0"); + animation->SetFillMode(display->mAnimations[i % animFillMode.num].GetFillMode()); + } else if (animFillMode.unit == eCSSUnit_Inherit) { + MOZ_ASSERT(i < parentDisplay->mAnimationFillModeCount, + "animFillMode.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + animation->SetFillMode(parentDisplay->mAnimations[i].GetFillMode()); + } else if (animFillMode.unit == eCSSUnit_Initial || + animFillMode.unit == eCSSUnit_Unset) { + animation->SetFillMode(dom::FillMode::None); + } else if (animFillMode.list) { + MOZ_ASSERT(animFillMode.list->mValue.GetUnit() == eCSSUnit_Enumerated, + "Invalid animation-fill-mode unit"); + + animation->SetFillMode( + static_cast<dom::FillMode>(animFillMode.list->mValue.GetIntValue())); + } + + if (i >= animPlayState.num) { + MOZ_ASSERT(animPlayState.num, + "animPlayState.num must be greater than 0"); + animation->SetPlayState(display->mAnimations[i % animPlayState.num].GetPlayState()); + } else if (animPlayState.unit == eCSSUnit_Inherit) { + MOZ_ASSERT(i < parentDisplay->mAnimationPlayStateCount, + "animPlayState.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + animation->SetPlayState(parentDisplay->mAnimations[i].GetPlayState()); + } else if (animPlayState.unit == eCSSUnit_Initial || + animPlayState.unit == eCSSUnit_Unset) { + animation->SetPlayState(NS_STYLE_ANIMATION_PLAY_STATE_RUNNING); + } else if (animPlayState.list) { + MOZ_ASSERT(animPlayState.list->mValue.GetUnit() == eCSSUnit_Enumerated, + "Invalid animation-play-state unit"); + + animation->SetPlayState(animPlayState.list->mValue.GetIntValue()); + } + + if (i >= animIterationCount.num) { + MOZ_ASSERT(animIterationCount.num, + "animIterationCount.num must be greater than 0"); + animation->SetIterationCount(display->mAnimations[i % animIterationCount.num].GetIterationCount()); + } else if (animIterationCount.unit == eCSSUnit_Inherit) { + MOZ_ASSERT(i < parentDisplay->mAnimationIterationCountCount, + "animIterationCount.num computed incorrectly"); + MOZ_ASSERT(!conditions.Cacheable(), + "should have made conditions.Cacheable() false above"); + animation->SetIterationCount(parentDisplay->mAnimations[i].GetIterationCount()); + } else if (animIterationCount.unit == eCSSUnit_Initial || + animIterationCount.unit == eCSSUnit_Unset) { + animation->SetIterationCount(1.0f); + } else if (animIterationCount.list) { + switch (animIterationCount.list->mValue.GetUnit()) { + case eCSSUnit_Enumerated: + MOZ_ASSERT(animIterationCount.list->mValue.GetIntValue() == + NS_STYLE_ANIMATION_ITERATION_COUNT_INFINITE, + "unexpected value"); + animation->SetIterationCount(NS_IEEEPositiveInfinity()); + break; + case eCSSUnit_Number: + animation->SetIterationCount( + animIterationCount.list->mValue.GetFloatValue()); + break; + default: + MOZ_ASSERT(false, + "unexpected animation-iteration-count unit"); + } + } + + FOR_ALL_ANIMATION_PROPS(p) { + const TransitionPropInfo& info = animationPropInfo[p]; + TransitionPropData& d = animationPropData[p]; + + // if we're at the end of the list, start at the beginning and repeat + // until we're out of animations to populate + if (d.list) { + d.list = d.list->mNext ? d.list->mNext : + aRuleData->ValueFor(info.property)->GetListValue(); + } + } + } + + // display: enum, inherit, initial + SetValue(*aRuleData->ValueForDisplay(), display->mDisplay, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mDisplay, + StyleDisplay::Inline); + + // contain: none, enum, inherit, initial + SetValue(*aRuleData->ValueForContain(), display->mContain, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mContain, + NS_STYLE_CONTAIN_NONE, Unused, + NS_STYLE_CONTAIN_NONE, Unused, Unused); + + // scroll-behavior: enum, inherit, initial + SetValue(*aRuleData->ValueForScrollBehavior(), display->mScrollBehavior, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mScrollBehavior, NS_STYLE_SCROLL_BEHAVIOR_AUTO); + + // scroll-snap-type-x: none, enum, inherit, initial + SetValue(*aRuleData->ValueForScrollSnapTypeX(), display->mScrollSnapTypeX, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mScrollSnapTypeX, NS_STYLE_SCROLL_SNAP_TYPE_NONE); + + // scroll-snap-type-y: none, enum, inherit, initial + SetValue(*aRuleData->ValueForScrollSnapTypeY(), display->mScrollSnapTypeY, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mScrollSnapTypeY, NS_STYLE_SCROLL_SNAP_TYPE_NONE); + + // scroll-snap-points-x: none, inherit, initial + const nsCSSValue& scrollSnapPointsX = *aRuleData->ValueForScrollSnapPointsX(); + switch (scrollSnapPointsX.GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_None: + display->mScrollSnapPointsX.SetNoneValue(); + break; + case eCSSUnit_Inherit: + display->mScrollSnapPointsX = parentDisplay->mScrollSnapPointsX; + conditions.SetUncacheable(); + break; + case eCSSUnit_Function: { + nsCSSValue::Array* func = scrollSnapPointsX.GetArrayValue(); + NS_ASSERTION(func->Item(0).GetKeywordValue() == eCSSKeyword_repeat, + "Expected repeat(), got another function name"); + nsStyleCoord coord; + if (SetCoord(func->Item(1), coord, nsStyleCoord(), + SETCOORD_LP | SETCOORD_STORE_CALC | + SETCOORD_CALC_CLAMP_NONNEGATIVE, + aContext, mPresContext, conditions)) { + NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord || + coord.GetUnit() == eStyleUnit_Percent || + coord.GetUnit() == eStyleUnit_Calc, + "unexpected unit"); + display->mScrollSnapPointsX = coord; + } + break; + } + default: + NS_NOTREACHED("unexpected unit"); + } + + // scroll-snap-points-y: none, inherit, initial + const nsCSSValue& scrollSnapPointsY = *aRuleData->ValueForScrollSnapPointsY(); + switch (scrollSnapPointsY.GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_None: + display->mScrollSnapPointsY.SetNoneValue(); + break; + case eCSSUnit_Inherit: + display->mScrollSnapPointsY = parentDisplay->mScrollSnapPointsY; + conditions.SetUncacheable(); + break; + case eCSSUnit_Function: { + nsCSSValue::Array* func = scrollSnapPointsY.GetArrayValue(); + NS_ASSERTION(func->Item(0).GetKeywordValue() == eCSSKeyword_repeat, + "Expected repeat(), got another function name"); + nsStyleCoord coord; + if (SetCoord(func->Item(1), coord, nsStyleCoord(), + SETCOORD_LP | SETCOORD_STORE_CALC | + SETCOORD_CALC_CLAMP_NONNEGATIVE, + aContext, mPresContext, conditions)) { + NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord || + coord.GetUnit() == eStyleUnit_Percent || + coord.GetUnit() == eStyleUnit_Calc, + "unexpected unit"); + display->mScrollSnapPointsY = coord; + } + break; + } + default: + NS_NOTREACHED("unexpected unit"); + } + + // scroll-snap-destination: inherit, initial + const nsCSSValue& snapDestination = *aRuleData->ValueForScrollSnapDestination(); + switch (snapDestination.GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_Initial: + case eCSSUnit_Unset: + display->mScrollSnapDestination.SetInitialZeroValues(); + break; + case eCSSUnit_Inherit: + display->mScrollSnapDestination = parentDisplay->mScrollSnapDestination; + conditions.SetUncacheable(); + break; + default: { + ComputePositionValue(aContext, snapDestination, + display->mScrollSnapDestination, conditions); + } + } + + // scroll-snap-coordinate: none, inherit, initial + const nsCSSValue& snapCoordinate = *aRuleData->ValueForScrollSnapCoordinate(); + switch (snapCoordinate.GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_None: + // Unset and Initial is none, indicated by an empty array + display->mScrollSnapCoordinate.Clear(); + break; + case eCSSUnit_Inherit: + display->mScrollSnapCoordinate = parentDisplay->mScrollSnapCoordinate; + conditions.SetUncacheable(); + break; + case eCSSUnit_List: { + display->mScrollSnapCoordinate.Clear(); + const nsCSSValueList* item = snapCoordinate.GetListValue(); + do { + NS_ASSERTION(item->mValue.GetUnit() != eCSSUnit_Null && + item->mValue.GetUnit() != eCSSUnit_Inherit && + item->mValue.GetUnit() != eCSSUnit_Initial && + item->mValue.GetUnit() != eCSSUnit_Unset, + "unexpected unit"); + Position* pos = display->mScrollSnapCoordinate.AppendElement(); + ComputePositionValue(aContext, item->mValue, *pos, conditions); + item = item->mNext; + } while(item); + break; + } + default: + NS_NOTREACHED("unexpected unit"); + } + + // isolation: enum, inherit, initial + SetValue(*aRuleData->ValueForIsolation(), display->mIsolation, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mIsolation, NS_STYLE_ISOLATION_AUTO); + + // -moz-top-layer: enum, inherit, initial + SetValue(*aRuleData->ValueForTopLayer(), display->mTopLayer, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mTopLayer, NS_STYLE_TOP_LAYER_NONE); + + // Backup original display value for calculation of a hypothetical + // box (CSS2 10.6.4/10.6.5), in addition to getting our style data right later. + // See ReflowInput::CalculateHypotheticalBox + display->mOriginalDisplay = display->mDisplay; + + // appearance: enum, inherit, initial + SetValue(*aRuleData->ValueForAppearance(), + display->mAppearance, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mAppearance, + NS_THEME_NONE); + + // binding: url, none, inherit + const nsCSSValue* bindingValue = aRuleData->ValueForBinding(); + if (eCSSUnit_URL == bindingValue->GetUnit()) { + mozilla::css::URLValue* url = bindingValue->GetURLStructValue(); + NS_ASSERTION(url, "What's going on here?"); + + if (MOZ_LIKELY(url->GetURI())) { + display->mBinding = url; + } else { + display->mBinding = nullptr; + } + } + else if (eCSSUnit_None == bindingValue->GetUnit() || + eCSSUnit_Initial == bindingValue->GetUnit() || + eCSSUnit_Unset == bindingValue->GetUnit()) { + display->mBinding = nullptr; + } + else if (eCSSUnit_Inherit == bindingValue->GetUnit()) { + conditions.SetUncacheable(); + display->mBinding = parentDisplay->mBinding; + } + + // position: enum, inherit, initial + SetValue(*aRuleData->ValueForPosition(), display->mPosition, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mPosition, + NS_STYLE_POSITION_STATIC); + // If an element is put in the top layer, while it is not absolutely + // positioned, the position value should be computed to 'absolute' per + // the Fullscreen API spec. + if (display->mTopLayer != NS_STYLE_TOP_LAYER_NONE && + !display->IsAbsolutelyPositionedStyle()) { + display->mPosition = NS_STYLE_POSITION_ABSOLUTE; + // We cannot cache this struct because otherwise it may be used as + // an aStartStruct for some other elements. + conditions.SetUncacheable(); + } + + // clear: enum, inherit, initial + SetValue(*aRuleData->ValueForClear(), display->mBreakType, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mBreakType, + StyleClear::None); + + // temp fix for bug 24000 + // Map 'auto' and 'avoid' to false, and 'always', 'left', and + // 'right' to true. + // "A conforming user agent may interpret the values 'left' and + // 'right' as 'always'." - CSS2.1, section 13.3.1 + const nsCSSValue* breakBeforeValue = aRuleData->ValueForPageBreakBefore(); + if (eCSSUnit_Enumerated == breakBeforeValue->GetUnit()) { + display->mBreakBefore = + (NS_STYLE_PAGE_BREAK_AVOID != breakBeforeValue->GetIntValue() && + NS_STYLE_PAGE_BREAK_AUTO != breakBeforeValue->GetIntValue()); + } + else if (eCSSUnit_Initial == breakBeforeValue->GetUnit() || + eCSSUnit_Unset == breakBeforeValue->GetUnit()) { + display->mBreakBefore = false; + } + else if (eCSSUnit_Inherit == breakBeforeValue->GetUnit()) { + conditions.SetUncacheable(); + display->mBreakBefore = parentDisplay->mBreakBefore; + } + + const nsCSSValue* breakAfterValue = aRuleData->ValueForPageBreakAfter(); + if (eCSSUnit_Enumerated == breakAfterValue->GetUnit()) { + display->mBreakAfter = + (NS_STYLE_PAGE_BREAK_AVOID != breakAfterValue->GetIntValue() && + NS_STYLE_PAGE_BREAK_AUTO != breakAfterValue->GetIntValue()); + } + else if (eCSSUnit_Initial == breakAfterValue->GetUnit() || + eCSSUnit_Unset == breakAfterValue->GetUnit()) { + display->mBreakAfter = false; + } + else if (eCSSUnit_Inherit == breakAfterValue->GetUnit()) { + conditions.SetUncacheable(); + display->mBreakAfter = parentDisplay->mBreakAfter; + } + // end temp fix + + // page-break-inside: enum, inherit, initial + SetValue(*aRuleData->ValueForPageBreakInside(), + display->mBreakInside, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mBreakInside, + NS_STYLE_PAGE_BREAK_AUTO); + + // touch-action: none, auto, enum, inherit, initial + SetValue(*aRuleData->ValueForTouchAction(), display->mTouchAction, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mTouchAction, + /* initial */ NS_STYLE_TOUCH_ACTION_AUTO, + /* auto */ NS_STYLE_TOUCH_ACTION_AUTO, + /* none */ NS_STYLE_TOUCH_ACTION_NONE, Unused, Unused); + + // float: enum, inherit, initial + SetValue(*aRuleData->ValueForFloat(), + display->mFloat, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mFloat, + StyleFloat::None); + // Save mFloat in mOriginalFloat in case we need it later + display->mOriginalFloat = display->mFloat; + + // overflow-x: enum, inherit, initial + SetValue(*aRuleData->ValueForOverflowX(), + display->mOverflowX, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mOverflowX, + NS_STYLE_OVERFLOW_VISIBLE); + + // overflow-y: enum, inherit, initial + SetValue(*aRuleData->ValueForOverflowY(), + display->mOverflowY, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mOverflowY, + NS_STYLE_OVERFLOW_VISIBLE); + + // CSS3 overflow-x and overflow-y require some fixup as well in some + // cases. NS_STYLE_OVERFLOW_VISIBLE and NS_STYLE_OVERFLOW_CLIP are + // meaningful only when used in both dimensions. + if (display->mOverflowX != display->mOverflowY && + (display->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE || + display->mOverflowX == NS_STYLE_OVERFLOW_CLIP || + display->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE || + display->mOverflowY == NS_STYLE_OVERFLOW_CLIP)) { + // We can't store in the rule tree since a more specific rule might + // change these conditions. + conditions.SetUncacheable(); + + // NS_STYLE_OVERFLOW_CLIP is a deprecated value, so if it's specified + // in only one dimension, convert it to NS_STYLE_OVERFLOW_HIDDEN. + if (display->mOverflowX == NS_STYLE_OVERFLOW_CLIP) + display->mOverflowX = NS_STYLE_OVERFLOW_HIDDEN; + if (display->mOverflowY == NS_STYLE_OVERFLOW_CLIP) + display->mOverflowY = NS_STYLE_OVERFLOW_HIDDEN; + + // If 'visible' is specified but doesn't match the other dimension, it + // turns into 'auto'. + if (display->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) + display->mOverflowX = NS_STYLE_OVERFLOW_AUTO; + if (display->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE) + display->mOverflowY = NS_STYLE_OVERFLOW_AUTO; + } + + // When 'contain: paint', update overflow from 'visible' to 'clip'. + if (display->IsContainPaint()) { + // XXX This actually sets overflow-[x|y] to -moz-hidden-unscrollable. + if (display->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) { + // This uncacheability (and the one below) could be fixed by adding + // mOriginalOverflowX and mOriginalOverflowY fields, if necessary. + display->mOverflowX = NS_STYLE_OVERFLOW_CLIP; + conditions.SetUncacheable(); + } + if (display->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE) { + display->mOverflowY = NS_STYLE_OVERFLOW_CLIP; + conditions.SetUncacheable(); + } + } + + SetValue(*aRuleData->ValueForOverflowClipBox(), display->mOverflowClipBox, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mOverflowClipBox, + NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX); + + SetValue(*aRuleData->ValueForResize(), display->mResize, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mResize, + NS_STYLE_RESIZE_NONE); + + if (display->mDisplay != StyleDisplay::None) { + // CSS2 9.7 specifies display type corrections dealing with 'float' + // and 'position'. Since generated content can't be floated or + // positioned, we can deal with it here. + + nsIAtom* pseudo = aContext->GetPseudo(); + if (pseudo && display->mDisplay == StyleDisplay::Contents) { + // We don't want to create frames for anonymous content using a parent + // frame that is for content above the root of the anon tree. + // (XXX what we really should check here is not GetPseudo() but if there's + // a 'content' property value that implies anon content but we can't + // check that here since that's a different struct(?)) + // We might get display:contents to work for CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS + // pseudos (:first-letter etc) in the future, but those have a lot of + // special handling in frame construction so they are also unsupported + // for now. + display->mOriginalDisplay = display->mDisplay = StyleDisplay::Inline; + conditions.SetUncacheable(); + } + + // Inherit a <fieldset> grid/flex display type into its anon content frame. + if (pseudo == nsCSSAnonBoxes::fieldsetContent) { + MOZ_ASSERT(display->mDisplay == StyleDisplay::Block, + "forms.css should have set 'display:block'"); + switch (parentDisplay->mDisplay) { + case StyleDisplay::Grid: + case StyleDisplay::InlineGrid: + display->mDisplay = StyleDisplay::Grid; + conditions.SetUncacheable(); + break; + case StyleDisplay::Flex: + case StyleDisplay::InlineFlex: + display->mDisplay = StyleDisplay::Flex; + conditions.SetUncacheable(); + break; + default: + break; // Do nothing + } + } + + if (nsCSSPseudoElements::firstLetter == pseudo) { + // a non-floating first-letter must be inline + // XXX this fix can go away once bug 103189 is fixed correctly + // Note that we reset mOriginalDisplay to enforce the invariant that it equals mDisplay if we're not positioned or floating. + display->mOriginalDisplay = display->mDisplay = StyleDisplay::Inline; + + // We can't cache the data in the rule tree since if a more specific + // rule has 'float: left' we'll end up with the wrong 'display' + // property. + conditions.SetUncacheable(); + } + + if (display->IsAbsolutelyPositionedStyle()) { + // 1) if position is 'absolute' or 'fixed' then display must be + // block-level and float must be 'none' + EnsureBlockDisplay(display->mDisplay); + display->mFloat = StyleFloat::None; + + // Note that it's OK to cache this struct in the ruletree + // because it's fine as-is for any style context that points to + // it directly, and any use of it as aStartStruct (e.g. if a + // more specific rule sets "position: static") will use + // mOriginalDisplay and mOriginalFloat, which we have carefully + // not changed. + } else if (display->mFloat != StyleFloat::None) { + // 2) if float is not none, and display is not none, then we must + // set a block-level 'display' type per CSS2.1 section 9.7. + EnsureBlockDisplay(display->mDisplay); + + // Note that it's OK to cache this struct in the ruletree + // because it's fine as-is for any style context that points to + // it directly, and any use of it as aStartStruct (e.g. if a + // more specific rule sets "float: none") will use + // mOriginalDisplay, which we have carefully not changed. + } + + if (display->IsContainPaint()) { + // An element with contain:paint or contain:layout needs to "be a + // formatting context". For the purposes of the "display" property, that + // just means we need to promote "display:inline" to "inline-block". + // XXX We may also need to promote ruby display vals; see bug 1179349. + + // It's okay to cache this change in the rule tree for the same + // reasons as floats in the previous condition. + if (display->mDisplay == StyleDisplay::Inline) { + display->mDisplay = StyleDisplay::InlineBlock; + } + } + } + + /* Convert the nsCSSValueList into an nsTArray<nsTransformFunction *>. */ + const nsCSSValue* transformValue = aRuleData->ValueForTransform(); + switch (transformValue->GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_None: + display->mSpecifiedTransform = nullptr; + break; + + case eCSSUnit_Inherit: + display->mSpecifiedTransform = parentDisplay->mSpecifiedTransform; + conditions.SetUncacheable(); + break; + + case eCSSUnit_SharedList: { + nsCSSValueSharedList* list = transformValue->GetSharedListValue(); + nsCSSValueList* head = list->mHead; + MOZ_ASSERT(head, "transform list must have at least one item"); + // can get a _None in here from transform animation + if (head->mValue.GetUnit() == eCSSUnit_None) { + MOZ_ASSERT(head->mNext == nullptr, "none must be alone"); + display->mSpecifiedTransform = nullptr; + } else { + display->mSpecifiedTransform = list; + } + break; + } + + default: + MOZ_ASSERT(false, "unrecognized transform unit"); + } + + /* Convert the nsCSSValueList into a will-change bitfield for fast lookup */ + const nsCSSValue* willChangeValue = aRuleData->ValueForWillChange(); + switch (willChangeValue->GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_List: + case eCSSUnit_ListDep: { + display->mWillChange.Clear(); + display->mWillChangeBitField = 0; + for (const nsCSSValueList* item = willChangeValue->GetListValue(); + item; item = item->mNext) + { + if (item->mValue.UnitHasStringValue()) { + nsAutoString buffer; + item->mValue.GetStringValue(buffer); + display->mWillChange.AppendElement(buffer); + + if (buffer.EqualsLiteral("transform")) { + display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_TRANSFORM; + } + if (buffer.EqualsLiteral("opacity")) { + display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_OPACITY; + } + if (buffer.EqualsLiteral("scroll-position")) { + display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_SCROLL; + } + + nsCSSPropertyID prop = + nsCSSProps::LookupProperty(buffer, CSSEnabledState::eForAllContent); + if (prop != eCSSProperty_UNKNOWN && + prop != eCSSPropertyExtra_variable) { + // If the property given is a shorthand, it indicates the expectation + // for all the longhands the shorthand expands to. + if (nsCSSProps::IsShorthand(prop)) { + for (const nsCSSPropertyID* shorthands = + nsCSSProps::SubpropertyEntryFor(prop); + *shorthands != eCSSProperty_UNKNOWN; ++shorthands) { + display->mWillChangeBitField |= GetWillChangeBitFieldFromPropFlags(*shorthands); + } + } else { + display->mWillChangeBitField |= GetWillChangeBitFieldFromPropFlags(prop); + } + } + } + } + break; + } + + case eCSSUnit_Inherit: + display->mWillChange = parentDisplay->mWillChange; + display->mWillChangeBitField = parentDisplay->mWillChangeBitField; + conditions.SetUncacheable(); + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_Auto: + display->mWillChange.Clear(); + display->mWillChangeBitField = 0; + break; + + default: + MOZ_ASSERT(false, "unrecognized will-change unit"); + } + + // vertical-align: enum, length, percent, calc, inherit + const nsCSSValue* verticalAlignValue = aRuleData->ValueForVerticalAlign(); + if (!SetCoord(*verticalAlignValue, display->mVerticalAlign, + parentDisplay->mVerticalAlign, + SETCOORD_LPH | SETCOORD_ENUMERATED | SETCOORD_STORE_CALC, + aContext, mPresContext, conditions)) { + if (eCSSUnit_Initial == verticalAlignValue->GetUnit() || + eCSSUnit_Unset == verticalAlignValue->GetUnit()) { + display->mVerticalAlign.SetIntValue(NS_STYLE_VERTICAL_ALIGN_BASELINE, + eStyleUnit_Enumerated); + } + } + + /* Convert -moz-transform-origin. */ + const nsCSSValue* transformOriginValue = + aRuleData->ValueForTransformOrigin(); + if (transformOriginValue->GetUnit() != eCSSUnit_Null) { + const nsCSSValue& valX = + transformOriginValue->GetUnit() == eCSSUnit_Triplet ? + transformOriginValue->GetTripletValue().mXValue : *transformOriginValue; + const nsCSSValue& valY = + transformOriginValue->GetUnit() == eCSSUnit_Triplet ? + transformOriginValue->GetTripletValue().mYValue : *transformOriginValue; + const nsCSSValue& valZ = + transformOriginValue->GetUnit() == eCSSUnit_Triplet ? + transformOriginValue->GetTripletValue().mZValue : *transformOriginValue; + + mozilla::DebugOnly<bool> cX = + SetCoord(valX, display->mTransformOrigin[0], + parentDisplay->mTransformOrigin[0], + SETCOORD_LPH | SETCOORD_INITIAL_HALF | + SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + mozilla::DebugOnly<bool> cY = + SetCoord(valY, display->mTransformOrigin[1], + parentDisplay->mTransformOrigin[1], + SETCOORD_LPH | SETCOORD_INITIAL_HALF | + SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + if (valZ.GetUnit() == eCSSUnit_Null) { + // Null for the z component means a 0 translation, not + // unspecified, as we have already checked the triplet + // value for Null. + display->mTransformOrigin[2].SetCoordValue(0); + } else { + mozilla::DebugOnly<bool> cZ = + SetCoord(valZ, display->mTransformOrigin[2], + parentDisplay->mTransformOrigin[2], + SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + MOZ_ASSERT(cY == cZ, "changed one but not the other"); + } + MOZ_ASSERT(cX == cY, "changed one but not the other"); + NS_ASSERTION(cX, "Malformed -moz-transform-origin parse!"); + } + + const nsCSSValue* perspectiveOriginValue = + aRuleData->ValueForPerspectiveOrigin(); + if (perspectiveOriginValue->GetUnit() != eCSSUnit_Null) { + mozilla::DebugOnly<bool> result = + SetPairCoords(*perspectiveOriginValue, + display->mPerspectiveOrigin[0], + display->mPerspectiveOrigin[1], + parentDisplay->mPerspectiveOrigin[0], + parentDisplay->mPerspectiveOrigin[1], + SETCOORD_LPH | SETCOORD_INITIAL_HALF | + SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + NS_ASSERTION(result, "Malformed -moz-perspective-origin parse!"); + } + + SetCoord(*aRuleData->ValueForPerspective(), + display->mChildPerspective, parentDisplay->mChildPerspective, + SETCOORD_LAH | SETCOORD_INITIAL_NONE | SETCOORD_NONE | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + SetValue(*aRuleData->ValueForBackfaceVisibility(), + display->mBackfaceVisibility, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mBackfaceVisibility, + NS_STYLE_BACKFACE_VISIBILITY_VISIBLE); + + // transform-style: enum, inherit, initial + SetValue(*aRuleData->ValueForTransformStyle(), + display->mTransformStyle, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mTransformStyle, + NS_STYLE_TRANSFORM_STYLE_FLAT); + + // transform-box: enum, inherit, initial + SetValue(*aRuleData->ValueForTransformBox(), + display->mTransformBox, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mTransformBox, + NS_STYLE_TRANSFORM_BOX_BORDER_BOX); + + // orient: enum, inherit, initial + SetValue(*aRuleData->ValueForOrient(), + display->mOrient, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentDisplay->mOrient, + StyleOrient::Inline); + + // shape-outside: none | [ <basic-shape> || <shape-box> ] | <image> + const nsCSSValue* shapeOutsideValue = aRuleData->ValueForShapeOutside(); + switch (shapeOutsideValue->GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_None: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + display->mShapeOutside = StyleShapeOutside(); + break; + case eCSSUnit_Inherit: + conditions.SetUncacheable(); + display->mShapeOutside = parentDisplay->mShapeOutside; + break; + case eCSSUnit_URL: { + display->mShapeOutside = StyleShapeOutside(); + display->mShapeOutside.SetURL(shapeOutsideValue->GetURLStructValue()); + break; + } + case eCSSUnit_Array: { + display->mShapeOutside = StyleShapeOutside(); + SetStyleShapeSourceToCSSValue(&display->mShapeOutside, shapeOutsideValue, + aContext, mPresContext, conditions); + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unrecognized shape-outside unit!"); + } + + COMPUTE_END_RESET(Display, display) +} + +const void* +nsRuleNode::ComputeVisibilityData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_INHERITED(Visibility, visibility, parentVisibility) + + // IMPORTANT: No properties in this struct have lengths in them. We + // depend on this since CalcLengthWith can call StyleVisibility() + // to get the language for resolving fonts! + + // direction: enum, inherit, initial + SetValue(*aRuleData->ValueForDirection(), visibility->mDirection, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentVisibility->mDirection, + (GET_BIDI_OPTION_DIRECTION(mPresContext->GetBidi()) + == IBMBIDI_TEXTDIRECTION_RTL) + ? NS_STYLE_DIRECTION_RTL : NS_STYLE_DIRECTION_LTR); + + // visibility: enum, inherit, initial + SetValue(*aRuleData->ValueForVisibility(), visibility->mVisible, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentVisibility->mVisible, + NS_STYLE_VISIBILITY_VISIBLE); + + // image-rendering: enum, inherit + SetValue(*aRuleData->ValueForImageRendering(), + visibility->mImageRendering, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentVisibility->mImageRendering, + NS_STYLE_IMAGE_RENDERING_AUTO); + + // writing-mode: enum, inherit, initial + SetValue(*aRuleData->ValueForWritingMode(), visibility->mWritingMode, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentVisibility->mWritingMode, + NS_STYLE_WRITING_MODE_HORIZONTAL_TB); + + // text-orientation: enum, inherit, initial + SetValue(*aRuleData->ValueForTextOrientation(), visibility->mTextOrientation, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentVisibility->mTextOrientation, + NS_STYLE_TEXT_ORIENTATION_MIXED); + + // image-orientation: enum, inherit, initial + const nsCSSValue* orientation = aRuleData->ValueForImageOrientation(); + if (orientation->GetUnit() == eCSSUnit_Inherit || + orientation->GetUnit() == eCSSUnit_Unset) { + conditions.SetUncacheable(); + visibility->mImageOrientation = parentVisibility->mImageOrientation; + } else if (orientation->GetUnit() == eCSSUnit_Initial) { + visibility->mImageOrientation = nsStyleImageOrientation(); + } else if (orientation->IsAngularUnit()) { + double angle = orientation->GetAngleValueInRadians(); + visibility->mImageOrientation = + nsStyleImageOrientation::CreateAsAngleAndFlip(angle, false); + } else if (orientation->GetUnit() == eCSSUnit_Array) { + const nsCSSValue::Array* array = orientation->GetArrayValue(); + MOZ_ASSERT(array->Item(0).IsAngularUnit(), + "First image-orientation value is not an angle"); + MOZ_ASSERT(array->Item(1).GetUnit() == eCSSUnit_Enumerated && + array->Item(1).GetIntValue() == NS_STYLE_IMAGE_ORIENTATION_FLIP, + "Second image-orientation value is not 'flip'"); + double angle = array->Item(0).GetAngleValueInRadians(); + visibility->mImageOrientation = + nsStyleImageOrientation::CreateAsAngleAndFlip(angle, true); + + } else if (orientation->GetUnit() == eCSSUnit_Enumerated) { + switch (orientation->GetIntValue()) { + case NS_STYLE_IMAGE_ORIENTATION_FLIP: + visibility->mImageOrientation = nsStyleImageOrientation::CreateAsFlip(); + break; + case NS_STYLE_IMAGE_ORIENTATION_FROM_IMAGE: + visibility->mImageOrientation = nsStyleImageOrientation::CreateAsFromImage(); + break; + default: + NS_NOTREACHED("Invalid image-orientation enumerated value"); + } + } else { + MOZ_ASSERT(orientation->GetUnit() == eCSSUnit_Null, "Should be null unit"); + } + + SetValue(*aRuleData->ValueForColorAdjust(), visibility->mColorAdjust, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentVisibility->mColorAdjust, + NS_STYLE_COLOR_ADJUST_ECONOMY); + + COMPUTE_END_INHERITED(Visibility, visibility) +} + +const void* +nsRuleNode::ComputeColorData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_INHERITED(Color, color, parentColor) + + // color: color, string, inherit + // Special case for currentColor. According to CSS3, setting color to 'currentColor' + // should behave as if it is inherited + const nsCSSValue* colorValue = aRuleData->ValueForColor(); + if ((colorValue->GetUnit() == eCSSUnit_EnumColor && + colorValue->GetIntValue() == NS_COLOR_CURRENTCOLOR) || + colorValue->GetUnit() == eCSSUnit_Unset) { + color->mColor = parentColor->mColor; + conditions.SetUncacheable(); + } + else if (colorValue->GetUnit() == eCSSUnit_Initial) { + color->mColor = mPresContext->DefaultColor(); + } + else { + SetColor(*colorValue, parentColor->mColor, mPresContext, aContext, + color->mColor, conditions); + } + + COMPUTE_END_INHERITED(Color, color) +} + +// information about how to compute values for background-* properties +template <class SpecifiedValueItem, class ComputedValueItem> +struct BackgroundItemComputer { +}; + +template <> +struct BackgroundItemComputer<nsCSSValueList, uint8_t> +{ + static void ComputeValue(nsStyleContext* aStyleContext, + const nsCSSValueList* aSpecifiedValue, + uint8_t& aComputedValue, + RuleNodeCacheConditions& aConditions) + { + SetValue(aSpecifiedValue->mValue, aComputedValue, aConditions, + SETVAL_ENUMERATED, uint8_t(0), 0); + } +}; + +template <> +struct BackgroundItemComputer<nsCSSValuePairList, nsStyleImageLayers::Repeat> +{ + static void ComputeValue(nsStyleContext* aStyleContext, + const nsCSSValuePairList* aSpecifiedValue, + nsStyleImageLayers::Repeat& aComputedValue, + RuleNodeCacheConditions& aConditions) + { + NS_ASSERTION(aSpecifiedValue->mXValue.GetUnit() == eCSSUnit_Enumerated && + (aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Enumerated || + aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Null), + "Invalid unit"); + + bool hasContraction = true; + uint8_t value = aSpecifiedValue->mXValue.GetIntValue(); + switch (value) { + case NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X: + aComputedValue.mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT; + aComputedValue.mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT; + break; + case NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y: + aComputedValue.mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT; + aComputedValue.mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT; + break; + default: + NS_ASSERTION(value == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT || + value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT || + value == NS_STYLE_IMAGELAYER_REPEAT_SPACE || + value == NS_STYLE_IMAGELAYER_REPEAT_ROUND, "Unexpected value"); + aComputedValue.mXRepeat = value; + hasContraction = false; + break; + } + + if (hasContraction) { + NS_ASSERTION(aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Null, + "Invalid unit."); + return; + } + + switch (aSpecifiedValue->mYValue.GetUnit()) { + case eCSSUnit_Null: + aComputedValue.mYRepeat = aComputedValue.mXRepeat; + break; + case eCSSUnit_Enumerated: + value = aSpecifiedValue->mYValue.GetIntValue(); + NS_ASSERTION(value == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT || + value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT || + value == NS_STYLE_IMAGELAYER_REPEAT_SPACE || + value == NS_STYLE_IMAGELAYER_REPEAT_ROUND, "Unexpected value"); + aComputedValue.mYRepeat = value; + break; + default: + NS_NOTREACHED("Unexpected CSS value"); + break; + } + } +}; + +template <> +struct BackgroundItemComputer<nsCSSValueList, nsStyleImage> +{ + static void ComputeValue(nsStyleContext* aStyleContext, + const nsCSSValueList* aSpecifiedValue, + nsStyleImage& aComputedValue, + RuleNodeCacheConditions& aConditions) + { + SetStyleImage(aStyleContext, aSpecifiedValue->mValue, aComputedValue, + aConditions); + } +}; + +template <> +struct BackgroundItemComputer<nsCSSValueList, RefPtr<css::URLValueData>> +{ + static void ComputeValue(nsStyleContext* aStyleContext, + const nsCSSValueList* aSpecifiedValue, + RefPtr<css::URLValueData>& aComputedValue, + RuleNodeCacheConditions& aConditions) + { + switch (aSpecifiedValue->mValue.GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_URL: + aComputedValue = aSpecifiedValue->mValue.GetURLStructValue(); + break; + case eCSSUnit_Image: + aComputedValue = aSpecifiedValue->mValue.GetImageStructValue(); + break; + default: + aComputedValue = nullptr; + break; + } + } +}; + +/* Helper function for ComputePositionValue. + * This function computes a single PositionCoord from two nsCSSValue objects, + * which represent an edge and an offset from that edge. + */ +static void +ComputePositionCoord(nsStyleContext* aStyleContext, + const nsCSSValue& aEdge, + const nsCSSValue& aOffset, + Position::Coord* aResult, + RuleNodeCacheConditions& aConditions) +{ + if (eCSSUnit_Percent == aOffset.GetUnit()) { + aResult->mLength = 0; + aResult->mPercent = aOffset.GetPercentValue(); + aResult->mHasPercent = true; + } else if (aOffset.IsLengthUnit()) { + aResult->mLength = CalcLength(aOffset, aStyleContext, + aStyleContext->PresContext(), + aConditions); + aResult->mPercent = 0.0f; + aResult->mHasPercent = false; + } else if (aOffset.IsCalcUnit()) { + LengthPercentPairCalcOps ops(aStyleContext, + aStyleContext->PresContext(), + aConditions); + nsRuleNode::ComputedCalc vals = ComputeCalc(aOffset, ops); + aResult->mLength = vals.mLength; + aResult->mPercent = vals.mPercent; + aResult->mHasPercent = ops.mHasPercent; + } else { + aResult->mLength = 0; + aResult->mPercent = 0.0f; + aResult->mHasPercent = false; + NS_ASSERTION(aOffset.GetUnit() == eCSSUnit_Null, "unexpected unit"); + } + + if (eCSSUnit_Enumerated == aEdge.GetUnit()) { + int sign; + if (aEdge.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_BOTTOM | + NS_STYLE_IMAGELAYER_POSITION_RIGHT)) { + sign = -1; + } else { + sign = 1; + } + aResult->mPercent = GetFloatFromBoxPosition(aEdge.GetIntValue()) + + sign * aResult->mPercent; + aResult->mLength = sign * aResult->mLength; + aResult->mHasPercent = true; + } else { + NS_ASSERTION(eCSSUnit_Null == aEdge.GetUnit(), "unexpected unit"); + } +} + +/* Helper function to convert a CSS <position> specified value into its + * computed-style form. */ +static void +ComputePositionValue(nsStyleContext* aStyleContext, + const nsCSSValue& aValue, + Position& aComputedValue, + RuleNodeCacheConditions& aConditions) +{ + NS_ASSERTION(aValue.GetUnit() == eCSSUnit_Array, + "unexpected unit for CSS <position> value"); + + RefPtr<nsCSSValue::Array> positionArray = aValue.GetArrayValue(); + NS_ASSERTION(positionArray->Count() == 4, + "unexpected number of values in CSS <position> value"); + + const nsCSSValue &xEdge = positionArray->Item(0); + const nsCSSValue &xOffset = positionArray->Item(1); + const nsCSSValue &yEdge = positionArray->Item(2); + const nsCSSValue &yOffset = positionArray->Item(3); + + NS_ASSERTION((eCSSUnit_Enumerated == xEdge.GetUnit() || + eCSSUnit_Null == xEdge.GetUnit()) && + (eCSSUnit_Enumerated == yEdge.GetUnit() || + eCSSUnit_Null == yEdge.GetUnit()) && + eCSSUnit_Enumerated != xOffset.GetUnit() && + eCSSUnit_Enumerated != yOffset.GetUnit(), + "Invalid background position"); + + ComputePositionCoord(aStyleContext, xEdge, xOffset, + &aComputedValue.mXPosition, + aConditions); + + ComputePositionCoord(aStyleContext, yEdge, yOffset, + &aComputedValue.mYPosition, + aConditions); +} + +/* Helper function to convert the -x or -y part of a CSS <position> specified + * value into its computed-style form. */ +static void +ComputePositionCoordValue(nsStyleContext* aStyleContext, + const nsCSSValue& aValue, + Position::Coord& aComputedValue, + RuleNodeCacheConditions& aConditions) +{ + NS_ASSERTION(aValue.GetUnit() == eCSSUnit_Array, + "unexpected unit for position coord value"); + + RefPtr<nsCSSValue::Array> positionArray = aValue.GetArrayValue(); + NS_ASSERTION(positionArray->Count() == 2, + "unexpected number of values, expecting one edge and one offset"); + + const nsCSSValue &edge = positionArray->Item(0); + const nsCSSValue &offset = positionArray->Item(1); + + NS_ASSERTION((eCSSUnit_Enumerated == edge.GetUnit() || + eCSSUnit_Null == edge.GetUnit()) && + eCSSUnit_Enumerated != offset.GetUnit(), + "Invalid background position"); + + ComputePositionCoord(aStyleContext, edge, offset, + &aComputedValue, + aConditions); +} + +struct BackgroundSizeAxis { + nsCSSValue nsCSSValuePairList::* specified; + nsStyleImageLayers::Size::Dimension nsStyleImageLayers::Size::* result; + uint8_t nsStyleImageLayers::Size::* type; +}; + +static const BackgroundSizeAxis gBGSizeAxes[] = { + { &nsCSSValuePairList::mXValue, + &nsStyleImageLayers::Size::mWidth, + &nsStyleImageLayers::Size::mWidthType }, + { &nsCSSValuePairList::mYValue, + &nsStyleImageLayers::Size::mHeight, + &nsStyleImageLayers::Size::mHeightType } +}; + +template <> +struct BackgroundItemComputer<nsCSSValuePairList, nsStyleImageLayers::Size> +{ + static void ComputeValue(nsStyleContext* aStyleContext, + const nsCSSValuePairList* aSpecifiedValue, + nsStyleImageLayers::Size& aComputedValue, + RuleNodeCacheConditions& aConditions) + { + nsStyleImageLayers::Size &size = aComputedValue; + for (const BackgroundSizeAxis *axis = gBGSizeAxes, + *axis_end = ArrayEnd(gBGSizeAxes); + axis < axis_end; ++axis) { + const nsCSSValue &specified = aSpecifiedValue->*(axis->specified); + if (eCSSUnit_Auto == specified.GetUnit()) { + size.*(axis->type) = nsStyleImageLayers::Size::eAuto; + } + else if (eCSSUnit_Enumerated == specified.GetUnit()) { + static_assert(nsStyleImageLayers::Size::eContain == + NS_STYLE_IMAGELAYER_SIZE_CONTAIN && + nsStyleImageLayers::Size::eCover == + NS_STYLE_IMAGELAYER_SIZE_COVER, + "background size constants out of sync"); + MOZ_ASSERT(specified.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_CONTAIN || + specified.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_COVER, + "invalid enumerated value for size coordinate"); + size.*(axis->type) = specified.GetIntValue(); + } + else if (eCSSUnit_Null == specified.GetUnit()) { + MOZ_ASSERT(axis == gBGSizeAxes + 1, + "null allowed only as height value, and only " + "for contain/cover/initial/inherit"); +#ifdef DEBUG + { + const nsCSSValue &widthValue = aSpecifiedValue->mXValue; + MOZ_ASSERT(widthValue.GetUnit() != eCSSUnit_Inherit && + widthValue.GetUnit() != eCSSUnit_Initial && + widthValue.GetUnit() != eCSSUnit_Unset, + "initial/inherit/unset should already have been handled"); + MOZ_ASSERT(widthValue.GetUnit() == eCSSUnit_Enumerated && + (widthValue.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_CONTAIN || + widthValue.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_COVER), + "null height value not corresponding to allowable " + "non-null width value"); + } +#endif + size.*(axis->type) = size.mWidthType; + } + else if (eCSSUnit_Percent == specified.GetUnit()) { + (size.*(axis->result)).mLength = 0; + (size.*(axis->result)).mPercent = specified.GetPercentValue(); + (size.*(axis->result)).mHasPercent = true; + size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage; + } + else if (specified.IsLengthUnit()) { + (size.*(axis->result)).mLength = + CalcLength(specified, aStyleContext, aStyleContext->PresContext(), + aConditions); + (size.*(axis->result)).mPercent = 0.0f; + (size.*(axis->result)).mHasPercent = false; + size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage; + } else { + MOZ_ASSERT(specified.IsCalcUnit(), "unexpected unit"); + LengthPercentPairCalcOps ops(aStyleContext, + aStyleContext->PresContext(), + aConditions); + nsRuleNode::ComputedCalc vals = ComputeCalc(specified, ops); + (size.*(axis->result)).mLength = vals.mLength; + (size.*(axis->result)).mPercent = vals.mPercent; + (size.*(axis->result)).mHasPercent = ops.mHasPercent; + size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage; + } + } + + MOZ_ASSERT(size.mWidthType < nsStyleImageLayers::Size::eDimensionType_COUNT, + "bad width type"); + MOZ_ASSERT(size.mHeightType < nsStyleImageLayers::Size::eDimensionType_COUNT, + "bad height type"); + MOZ_ASSERT((size.mWidthType != nsStyleImageLayers::Size::eContain && + size.mWidthType != nsStyleImageLayers::Size::eCover) || + size.mWidthType == size.mHeightType, + "contain/cover apply to both dimensions or to neither"); + } +}; + +template <class ComputedValueItem> +static void +SetImageLayerList(nsStyleContext* aStyleContext, + const nsCSSValue& aValue, + nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers, + const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers, + ComputedValueItem nsStyleImageLayers::Layer::* aResultLocation, + ComputedValueItem aInitialValue, + uint32_t aParentItemCount, + uint32_t& aItemCount, + uint32_t& aMaxItemCount, + bool& aRebuild, + RuleNodeCacheConditions& aConditions) +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Inherit: + aRebuild = true; + aConditions.SetUncacheable(); + aLayers.EnsureLengthAtLeast(aParentItemCount); + aItemCount = aParentItemCount; + for (uint32_t i = 0; i < aParentItemCount; ++i) { + aLayers[i].*aResultLocation = aParentLayers[i].*aResultLocation; + } + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + aRebuild = true; + aItemCount = 1; + aLayers[0].*aResultLocation = aInitialValue; + break; + + case eCSSUnit_List: + case eCSSUnit_ListDep: { + aRebuild = true; + aItemCount = 0; + const nsCSSValueList* item = aValue.GetListValue(); + do { + NS_ASSERTION(item->mValue.GetUnit() != eCSSUnit_Null && + item->mValue.GetUnit() != eCSSUnit_Inherit && + item->mValue.GetUnit() != eCSSUnit_Initial && + item->mValue.GetUnit() != eCSSUnit_Unset, + "unexpected unit"); + ++aItemCount; + aLayers.EnsureLengthAtLeast(aItemCount); + BackgroundItemComputer<nsCSSValueList, ComputedValueItem> + ::ComputeValue(aStyleContext, item, + aLayers[aItemCount-1].*aResultLocation, + aConditions); + item = item->mNext; + } while (item); + break; + } + + default: + MOZ_ASSERT(false, "unexpected unit"); + } + + if (aItemCount > aMaxItemCount) + aMaxItemCount = aItemCount; +} + +// The same as SetImageLayerList, but for values stored in +// layer.mPosition.*aResultLocation instead of layer.*aResultLocation. +// This code is duplicated because it would be annoying to make +// SetImageLayerList generic enough to handle both cases. +static void +SetImageLayerPositionCoordList( + nsStyleContext* aStyleContext, + const nsCSSValue& aValue, + nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers, + const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers, + Position::Coord + Position::* aResultLocation, + Position::Coord aInitialValue, + uint32_t aParentItemCount, + uint32_t& aItemCount, + uint32_t& aMaxItemCount, + bool& aRebuild, + RuleNodeCacheConditions& aConditions) +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Inherit: + aRebuild = true; + aConditions.SetUncacheable(); + aLayers.EnsureLengthAtLeast(aParentItemCount); + aItemCount = aParentItemCount; + for (uint32_t i = 0; i < aParentItemCount; ++i) { + aLayers[i].mPosition.*aResultLocation = aParentLayers[i].mPosition.*aResultLocation; + } + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + aRebuild = true; + aItemCount = 1; + aLayers[0].mPosition.*aResultLocation = aInitialValue; + break; + + case eCSSUnit_List: + case eCSSUnit_ListDep: { + aRebuild = true; + aItemCount = 0; + const nsCSSValueList* item = aValue.GetListValue(); + do { + NS_ASSERTION(item->mValue.GetUnit() != eCSSUnit_Null && + item->mValue.GetUnit() != eCSSUnit_Inherit && + item->mValue.GetUnit() != eCSSUnit_Initial && + item->mValue.GetUnit() != eCSSUnit_Unset, + "unexpected unit"); + ++aItemCount; + aLayers.EnsureLengthAtLeast(aItemCount); + + ComputePositionCoordValue(aStyleContext, item->mValue, + aLayers[aItemCount-1].mPosition.*aResultLocation, + aConditions); + item = item->mNext; + } while (item); + break; + } + + default: + MOZ_ASSERT(false, "unexpected unit"); + } + + if (aItemCount > aMaxItemCount) + aMaxItemCount = aItemCount; +} + +template <class ComputedValueItem> +static void +SetImageLayerPairList(nsStyleContext* aStyleContext, + const nsCSSValue& aValue, + nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers, + const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers, + ComputedValueItem nsStyleImageLayers::Layer::* + aResultLocation, + ComputedValueItem aInitialValue, + uint32_t aParentItemCount, + uint32_t& aItemCount, + uint32_t& aMaxItemCount, + bool& aRebuild, + RuleNodeCacheConditions& aConditions) +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Inherit: + aRebuild = true; + aConditions.SetUncacheable(); + aLayers.EnsureLengthAtLeast(aParentItemCount); + aItemCount = aParentItemCount; + for (uint32_t i = 0; i < aParentItemCount; ++i) { + aLayers[i].*aResultLocation = aParentLayers[i].*aResultLocation; + } + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + aRebuild = true; + aItemCount = 1; + aLayers[0].*aResultLocation = aInitialValue; + break; + + case eCSSUnit_PairList: + case eCSSUnit_PairListDep: { + aRebuild = true; + aItemCount = 0; + const nsCSSValuePairList* item = aValue.GetPairListValue(); + do { + NS_ASSERTION(item->mXValue.GetUnit() != eCSSUnit_Inherit && + item->mXValue.GetUnit() != eCSSUnit_Initial && + item->mXValue.GetUnit() != eCSSUnit_Unset && + item->mYValue.GetUnit() != eCSSUnit_Inherit && + item->mYValue.GetUnit() != eCSSUnit_Initial && + item->mYValue.GetUnit() != eCSSUnit_Unset, + "unexpected unit"); + ++aItemCount; + aLayers.EnsureLengthAtLeast(aItemCount); + BackgroundItemComputer<nsCSSValuePairList, ComputedValueItem> + ::ComputeValue(aStyleContext, item, + aLayers[aItemCount-1].*aResultLocation, + aConditions); + item = item->mNext; + } while (item); + break; + } + + default: + MOZ_ASSERT(false, "unexpected unit"); + } + + if (aItemCount > aMaxItemCount) + aMaxItemCount = aItemCount; +} + +template <class ComputedValueItem> +static void +FillImageLayerList( + nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers, + ComputedValueItem nsStyleImageLayers::Layer::* aResultLocation, + uint32_t aItemCount, uint32_t aFillCount) +{ + NS_PRECONDITION(aFillCount <= aLayers.Length(), "unexpected array length"); + for (uint32_t sourceLayer = 0, destLayer = aItemCount; + destLayer < aFillCount; + ++sourceLayer, ++destLayer) { + aLayers[destLayer].*aResultLocation = + aLayers[sourceLayer].*aResultLocation; + } +} + +// The same as FillImageLayerList, but for values stored in +// layer.mPosition.*aResultLocation instead of layer.*aResultLocation. +static void +FillImageLayerPositionCoordList( + nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers, + Position::Coord + Position::* aResultLocation, + uint32_t aItemCount, uint32_t aFillCount) +{ + NS_PRECONDITION(aFillCount <= aLayers.Length(), "unexpected array length"); + for (uint32_t sourceLayer = 0, destLayer = aItemCount; + destLayer < aFillCount; + ++sourceLayer, ++destLayer) { + aLayers[destLayer].mPosition.*aResultLocation = + aLayers[sourceLayer].mPosition.*aResultLocation; + } +} + +/* static */ +void +nsRuleNode::FillAllBackgroundLists(nsStyleImageLayers& aImage, + uint32_t aMaxItemCount) +{ + // Delete any extra items. We need to keep layers in which any + // property was specified. + aImage.mLayers.TruncateLengthNonZero(aMaxItemCount); + + uint32_t fillCount = aImage.mImageCount; + FillImageLayerList(aImage.mLayers, + &nsStyleImageLayers::Layer::mImage, + aImage.mImageCount, fillCount); + FillImageLayerList(aImage.mLayers, + &nsStyleImageLayers::Layer::mRepeat, + aImage.mRepeatCount, fillCount); + FillImageLayerList(aImage.mLayers, + &nsStyleImageLayers::Layer::mAttachment, + aImage.mAttachmentCount, fillCount); + FillImageLayerList(aImage.mLayers, + &nsStyleImageLayers::Layer::mClip, + aImage.mClipCount, fillCount); + FillImageLayerList(aImage.mLayers, + &nsStyleImageLayers::Layer::mBlendMode, + aImage.mBlendModeCount, fillCount); + FillImageLayerList(aImage.mLayers, + &nsStyleImageLayers::Layer::mOrigin, + aImage.mOriginCount, fillCount); + FillImageLayerPositionCoordList(aImage.mLayers, + &Position::mXPosition, + aImage.mPositionXCount, fillCount); + FillImageLayerPositionCoordList(aImage.mLayers, + &Position::mYPosition, + aImage.mPositionYCount, fillCount); + FillImageLayerList(aImage.mLayers, + &nsStyleImageLayers::Layer::mSize, + aImage.mSizeCount, fillCount); +} + +const void* +nsRuleNode::ComputeBackgroundData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(Background, bg, parentBG) + + // background-color: color, string, inherit + const nsCSSValue* backColorValue = aRuleData->ValueForBackgroundColor(); + if (eCSSUnit_Initial == backColorValue->GetUnit() || + eCSSUnit_Unset == backColorValue->GetUnit()) { + bg->mBackgroundColor = NS_RGBA(0, 0, 0, 0); + } else if (!SetColor(*backColorValue, parentBG->mBackgroundColor, + mPresContext, aContext, bg->mBackgroundColor, + conditions)) { + NS_ASSERTION(eCSSUnit_Null == backColorValue->GetUnit(), + "unexpected color unit"); + } + + uint32_t maxItemCount = 1; + bool rebuild = false; + + // background-image: url (stored as image), none, inherit [list] + nsStyleImage initialImage; + SetImageLayerList(aContext, *aRuleData->ValueForBackgroundImage(), + bg->mImage.mLayers, + parentBG->mImage.mLayers, + &nsStyleImageLayers::Layer::mImage, + initialImage, parentBG->mImage.mImageCount, + bg->mImage.mImageCount, + maxItemCount, rebuild, conditions); + + // background-repeat: enum, inherit, initial [pair list] + nsStyleImageLayers::Repeat initialRepeat; + initialRepeat.SetInitialValues(); + SetImageLayerPairList(aContext, *aRuleData->ValueForBackgroundRepeat(), + bg->mImage.mLayers, + parentBG->mImage.mLayers, + &nsStyleImageLayers::Layer::mRepeat, + initialRepeat, parentBG->mImage.mRepeatCount, + bg->mImage.mRepeatCount, maxItemCount, rebuild, + conditions); + + // background-attachment: enum, inherit, initial [list] + SetImageLayerList(aContext, *aRuleData->ValueForBackgroundAttachment(), + bg->mImage.mLayers, parentBG->mImage.mLayers, + &nsStyleImageLayers::Layer::mAttachment, + uint8_t(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL), + parentBG->mImage.mAttachmentCount, + bg->mImage.mAttachmentCount, maxItemCount, rebuild, + conditions); + + // background-clip: enum, inherit, initial [list] + SetImageLayerList(aContext, *aRuleData->ValueForBackgroundClip(), + bg->mImage.mLayers, + parentBG->mImage.mLayers, + &nsStyleImageLayers::Layer::mClip, + uint8_t(NS_STYLE_IMAGELAYER_CLIP_BORDER), + parentBG->mImage.mClipCount, + bg->mImage.mClipCount, maxItemCount, rebuild, conditions); + + // background-blend-mode: enum, inherit, initial [list] + SetImageLayerList(aContext, *aRuleData->ValueForBackgroundBlendMode(), + bg->mImage.mLayers, + parentBG->mImage.mLayers, + &nsStyleImageLayers::Layer::mBlendMode, + uint8_t(NS_STYLE_BLEND_NORMAL), + parentBG->mImage.mBlendModeCount, + bg->mImage.mBlendModeCount, maxItemCount, rebuild, + conditions); + + // background-origin: enum, inherit, initial [list] + SetImageLayerList(aContext, *aRuleData->ValueForBackgroundOrigin(), + bg->mImage.mLayers, + parentBG->mImage.mLayers, + &nsStyleImageLayers::Layer::mOrigin, + uint8_t(NS_STYLE_IMAGELAYER_ORIGIN_PADDING), + parentBG->mImage.mOriginCount, + bg->mImage.mOriginCount, maxItemCount, rebuild, + conditions); + + // background-position-x/y: enum, length, percent (flags), inherit [list] + Position::Coord initialPositionCoord; + initialPositionCoord.mPercent = 0.0f; + initialPositionCoord.mLength = 0; + initialPositionCoord.mHasPercent = true; + + SetImageLayerPositionCoordList( + aContext, *aRuleData->ValueForBackgroundPositionX(), + bg->mImage.mLayers, + parentBG->mImage.mLayers, + &Position::mXPosition, + initialPositionCoord, parentBG->mImage.mPositionXCount, + bg->mImage.mPositionXCount, maxItemCount, rebuild, + conditions); + SetImageLayerPositionCoordList( + aContext, *aRuleData->ValueForBackgroundPositionY(), + bg->mImage.mLayers, + parentBG->mImage.mLayers, + &Position::mYPosition, + initialPositionCoord, parentBG->mImage.mPositionYCount, + bg->mImage.mPositionYCount, maxItemCount, rebuild, + conditions); + + // background-size: enum, length, auto, inherit, initial [pair list] + nsStyleImageLayers::Size initialSize; + initialSize.SetInitialValues(); + SetImageLayerPairList(aContext, *aRuleData->ValueForBackgroundSize(), + bg->mImage.mLayers, + parentBG->mImage.mLayers, + &nsStyleImageLayers::Layer::mSize, + initialSize, parentBG->mImage.mSizeCount, + bg->mImage.mSizeCount, maxItemCount, rebuild, + conditions); + + if (rebuild) { + FillAllBackgroundLists(bg->mImage, maxItemCount); + } + + COMPUTE_END_RESET(Background, bg) +} + +const void* +nsRuleNode::ComputeMarginData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(Margin, margin, parentMargin) + + // margin: length, percent, calc, inherit + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(eCSSProperty_margin); + nsStyleCoord coord; + NS_FOR_CSS_SIDES(side) { + nsStyleCoord parentCoord = parentMargin->mMargin.Get(side); + if (SetCoord(*aRuleData->ValueFor(subprops[side]), + coord, parentCoord, + SETCOORD_LPAH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + margin->mMargin.Set(side, coord); + } + } + + COMPUTE_END_RESET(Margin, margin) +} + +static void +SetBorderImageRect(const nsCSSValue& aValue, + /** outparam */ nsCSSRect& aRect) +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + aRect.Reset(); + break; + case eCSSUnit_Rect: + aRect = aValue.GetRectValue(); + break; + case eCSSUnit_Inherit: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + aRect.SetAllSidesTo(aValue); + break; + default: + NS_ASSERTION(false, "Unexpected border image value for rect."); + } +} + +static void +SetBorderImagePair(const nsCSSValue& aValue, + /** outparam */ nsCSSValuePair& aPair) +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + aPair.Reset(); + break; + case eCSSUnit_Pair: + aPair = aValue.GetPairValue(); + break; + case eCSSUnit_Inherit: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + aPair.SetBothValuesTo(aValue); + break; + default: + NS_ASSERTION(false, "Unexpected border image value for pair."); + } +} + +static void +SetBorderImageSlice(const nsCSSValue& aValue, + /** outparam */ nsCSSValue& aSlice, + /** outparam */ nsCSSValue& aFill) +{ + const nsCSSValueList* valueList; + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + aSlice.Reset(); + aFill.Reset(); + break; + case eCSSUnit_List: + // Get slice dimensions. + valueList = aValue.GetListValue(); + aSlice = valueList->mValue; + + // Get "fill" keyword. + valueList = valueList->mNext; + if (valueList) { + aFill = valueList->mValue; + } else { + aFill.SetInitialValue(); + } + break; + case eCSSUnit_Inherit: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + aSlice = aValue; + aFill = aValue; + break; + default: + NS_ASSERTION(false, "Unexpected border image value for pair."); + } +} + +const void* +nsRuleNode::ComputeBorderData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(Border, border, parentBorder) + + // box-decoration-break: enum, inherit, initial + SetValue(*aRuleData->ValueForBoxDecorationBreak(), + border->mBoxDecorationBreak, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentBorder->mBoxDecorationBreak, + StyleBoxDecorationBreak::Slice); + + // border-width, border-*-width: length, enum, inherit + nsStyleCoord coord; + { + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_width); + NS_FOR_CSS_SIDES(side) { + const nsCSSValue& value = *aRuleData->ValueFor(subprops[side]); + NS_ASSERTION(eCSSUnit_Percent != value.GetUnit(), + "Percentage borders not implemented yet " + "If implementing, make sure to fix all consumers of " + "nsStyleBorder, the IsPercentageAwareChild method, " + "the nsAbsoluteContainingBlock::FrameDependsOnContainer " + "method, the " + "nsLineLayout::IsPercentageAwareReplacedElement method " + "and probably some other places"); + if (eCSSUnit_Enumerated == value.GetUnit()) { + NS_ASSERTION(value.GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN || + value.GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM || + value.GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK, + "Unexpected enum value"); + border->SetBorderWidth(side, + (mPresContext->GetBorderWidthTable())[value.GetIntValue()]); + } + // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT + else if (SetCoord(value, coord, nsStyleCoord(), + SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY, + aContext, mPresContext, conditions)) { + NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit"); + // clamp negative calc() to 0. + border->SetBorderWidth(side, std::max(coord.GetCoordValue(), 0)); + } + else if (eCSSUnit_Inherit == value.GetUnit()) { + conditions.SetUncacheable(); + border->SetBorderWidth(side, + parentBorder->GetComputedBorder().Side(side)); + } + else if (eCSSUnit_Initial == value.GetUnit() || + eCSSUnit_Unset == value.GetUnit()) { + border->SetBorderWidth(side, + (mPresContext->GetBorderWidthTable())[NS_STYLE_BORDER_WIDTH_MEDIUM]); + } + else { + NS_ASSERTION(eCSSUnit_Null == value.GetUnit(), + "missing case handling border width"); + } + } + } + + // border-style, border-*-style: enum, inherit + { + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_style); + NS_FOR_CSS_SIDES(side) { + const nsCSSValue& value = *aRuleData->ValueFor(subprops[side]); + nsCSSUnit unit = value.GetUnit(); + MOZ_ASSERT(eCSSUnit_None != unit, + "'none' should be handled as enumerated value"); + if (eCSSUnit_Enumerated == unit) { + border->SetBorderStyle(side, value.GetIntValue()); + } + else if (eCSSUnit_Initial == unit || + eCSSUnit_Unset == unit) { + border->SetBorderStyle(side, NS_STYLE_BORDER_STYLE_NONE); + } + else if (eCSSUnit_Inherit == unit) { + conditions.SetUncacheable(); + border->SetBorderStyle(side, parentBorder->GetBorderStyle(side)); + } + } + } + + // -moz-border-*-colors: color, string, enum, none, inherit/initial + nscolor borderColor; + nscolor unused = NS_RGB(0,0,0); + + static const nsCSSPropertyID borderColorsProps[] = { + eCSSProperty_border_top_colors, + eCSSProperty_border_right_colors, + eCSSProperty_border_bottom_colors, + eCSSProperty_border_left_colors + }; + + NS_FOR_CSS_SIDES(side) { + const nsCSSValue& value = *aRuleData->ValueFor(borderColorsProps[side]); + switch (value.GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_None: + border->ClearBorderColors(side); + break; + + case eCSSUnit_Inherit: { + conditions.SetUncacheable(); + border->ClearBorderColors(side); + if (parentContext) { + nsBorderColors *parentColors; + parentBorder->GetCompositeColors(side, &parentColors); + if (parentColors) { + border->EnsureBorderColors(); + border->mBorderColors[side] = parentColors->Clone(); + } + } + break; + } + + case eCSSUnit_List: + case eCSSUnit_ListDep: { + // Some composite border color information has been specified for this + // border side. + border->EnsureBorderColors(); + border->ClearBorderColors(side); + const nsCSSValueList* list = value.GetListValue(); + while (list) { + if (SetColor(list->mValue, unused, mPresContext, + aContext, borderColor, conditions)) + border->AppendBorderColor(side, borderColor); + else { + NS_NOTREACHED("unexpected item in -moz-border-*-colors list"); + } + list = list->mNext; + } + break; + } + + default: + MOZ_ASSERT(false, "unrecognized border color unit"); + } + } + + // border-color, border-*-color: color, string, enum, inherit + { + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color); + NS_FOR_CSS_SIDES(side) { + SetComplexColor<eUnsetInitial>(*aRuleData->ValueFor(subprops[side]), + parentBorder->mBorderColor[side], + StyleComplexColor::CurrentColor(), + mPresContext, + border->mBorderColor[side], conditions); + } + } + + // border-radius: length, percent, inherit + { + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_radius); + NS_FOR_CSS_FULL_CORNERS(corner) { + int cx = NS_FULL_TO_HALF_CORNER(corner, false); + int cy = NS_FULL_TO_HALF_CORNER(corner, true); + const nsCSSValue& radius = *aRuleData->ValueFor(subprops[corner]); + nsStyleCoord parentX = parentBorder->mBorderRadius.Get(cx); + nsStyleCoord parentY = parentBorder->mBorderRadius.Get(cy); + nsStyleCoord coordX, coordY; + + if (SetPairCoords(radius, coordX, coordY, parentX, parentY, + SETCOORD_LPH | SETCOORD_INITIAL_ZERO | + SETCOORD_STORE_CALC | SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + border->mBorderRadius.Set(cx, coordX); + border->mBorderRadius.Set(cy, coordY); + } + } + } + + // float-edge: enum, inherit, initial + SetValue(*aRuleData->ValueForFloatEdge(), + border->mFloatEdge, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentBorder->mFloatEdge, + StyleFloatEdge::ContentBox); + + // border-image-source + const nsCSSValue* borderImageSource = aRuleData->ValueForBorderImageSource(); + if (borderImageSource->GetUnit() == eCSSUnit_Inherit) { + conditions.SetUncacheable(); + border->mBorderImageSource = parentBorder->mBorderImageSource; + } else { + SetStyleImage(aContext, + *borderImageSource, + border->mBorderImageSource, + conditions); + } + + nsCSSValue borderImageSliceValue; + nsCSSValue borderImageSliceFill; + SetBorderImageSlice(*aRuleData->ValueForBorderImageSlice(), + borderImageSliceValue, borderImageSliceFill); + + // border-image-slice: fill + SetValue(borderImageSliceFill, + border->mBorderImageFill, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentBorder->mBorderImageFill, + NS_STYLE_BORDER_IMAGE_SLICE_NOFILL); + + nsCSSRect borderImageSlice; + SetBorderImageRect(borderImageSliceValue, borderImageSlice); + + nsCSSRect borderImageWidth; + SetBorderImageRect(*aRuleData->ValueForBorderImageWidth(), + borderImageWidth); + + nsCSSRect borderImageOutset; + SetBorderImageRect(*aRuleData->ValueForBorderImageOutset(), + borderImageOutset); + + NS_FOR_CSS_SIDES (side) { + // border-image-slice + if (SetCoord(borderImageSlice.*(nsCSSRect::sides[side]), coord, + parentBorder->mBorderImageSlice.Get(side), + SETCOORD_FACTOR | SETCOORD_PERCENT | + SETCOORD_INHERIT | SETCOORD_INITIAL_HUNDRED_PCT | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + border->mBorderImageSlice.Set(side, coord); + } + + // border-image-width + // 'auto' here means "same as slice" + if (SetCoord(borderImageWidth.*(nsCSSRect::sides[side]), coord, + parentBorder->mBorderImageWidth.Get(side), + SETCOORD_LPAH | SETCOORD_FACTOR | SETCOORD_INITIAL_FACTOR_ONE | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + border->mBorderImageWidth.Set(side, coord); + } + + // border-image-outset + if (SetCoord(borderImageOutset.*(nsCSSRect::sides[side]), coord, + parentBorder->mBorderImageOutset.Get(side), + SETCOORD_LENGTH | SETCOORD_FACTOR | + SETCOORD_INHERIT | SETCOORD_INITIAL_FACTOR_ZERO | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + border->mBorderImageOutset.Set(side, coord); + } + } + + // border-image-repeat + nsCSSValuePair borderImageRepeat; + SetBorderImagePair(*aRuleData->ValueForBorderImageRepeat(), + borderImageRepeat); + + SetValue(borderImageRepeat.mXValue, + border->mBorderImageRepeatH, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentBorder->mBorderImageRepeatH, + NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH); + + SetValue(borderImageRepeat.mYValue, + border->mBorderImageRepeatV, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentBorder->mBorderImageRepeatV, + NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH); + + COMPUTE_END_RESET(Border, border) +} + +const void* +nsRuleNode::ComputePaddingData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(Padding, padding, parentPadding) + + // padding: length, percent, calc, inherit + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(eCSSProperty_padding); + nsStyleCoord coord; + NS_FOR_CSS_SIDES(side) { + nsStyleCoord parentCoord = parentPadding->mPadding.Get(side); + if (SetCoord(*aRuleData->ValueFor(subprops[side]), + coord, parentCoord, + SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + padding->mPadding.Set(side, coord); + } + } + + COMPUTE_END_RESET(Padding, padding) +} + +const void* +nsRuleNode::ComputeOutlineData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(Outline, outline, parentOutline) + + // outline-width: length, enum, inherit + const nsCSSValue* outlineWidthValue = aRuleData->ValueForOutlineWidth(); + if (eCSSUnit_Initial == outlineWidthValue->GetUnit() || + eCSSUnit_Unset == outlineWidthValue->GetUnit()) { + outline->mOutlineWidth = + nsStyleCoord(NS_STYLE_BORDER_WIDTH_MEDIUM, eStyleUnit_Enumerated); + } + else { + SetCoord(*outlineWidthValue, outline->mOutlineWidth, + parentOutline->mOutlineWidth, + SETCOORD_LEH | SETCOORD_CALC_LENGTH_ONLY, aContext, + mPresContext, conditions); + } + + // outline-offset: length, inherit + nsStyleCoord tempCoord; + const nsCSSValue* outlineOffsetValue = aRuleData->ValueForOutlineOffset(); + if (SetCoord(*outlineOffsetValue, tempCoord, + nsStyleCoord(parentOutline->mOutlineOffset, + nsStyleCoord::CoordConstructor), + SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_CALC_LENGTH_ONLY | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + outline->mOutlineOffset = tempCoord.GetCoordValue(); + } else { + NS_ASSERTION(outlineOffsetValue->GetUnit() == eCSSUnit_Null, + "unexpected unit"); + } + + // outline-color: color, string, enum, inherit + SetComplexColor<eUnsetInitial>(*aRuleData->ValueForOutlineColor(), + parentOutline->mOutlineColor, + StyleComplexColor::CurrentColor(), + mPresContext, + outline->mOutlineColor, conditions); + + // -moz-outline-radius: length, percent, inherit + { + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(eCSSProperty__moz_outline_radius); + NS_FOR_CSS_FULL_CORNERS(corner) { + int cx = NS_FULL_TO_HALF_CORNER(corner, false); + int cy = NS_FULL_TO_HALF_CORNER(corner, true); + const nsCSSValue& radius = *aRuleData->ValueFor(subprops[corner]); + nsStyleCoord parentX = parentOutline->mOutlineRadius.Get(cx); + nsStyleCoord parentY = parentOutline->mOutlineRadius.Get(cy); + nsStyleCoord coordX, coordY; + + if (SetPairCoords(radius, coordX, coordY, parentX, parentY, + SETCOORD_LPH | SETCOORD_INITIAL_ZERO | + SETCOORD_STORE_CALC | SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + outline->mOutlineRadius.Set(cx, coordX); + outline->mOutlineRadius.Set(cy, coordY); + } + } + } + + // outline-style: enum, inherit, initial + // cannot use SetValue because of SetOutlineStyle + const nsCSSValue* outlineStyleValue = aRuleData->ValueForOutlineStyle(); + nsCSSUnit unit = outlineStyleValue->GetUnit(); + MOZ_ASSERT(eCSSUnit_None != unit && eCSSUnit_Auto != unit, + "'none' and 'auto' should be handled as enumerated values"); + if (eCSSUnit_Enumerated == unit) { + outline->mOutlineStyle = outlineStyleValue->GetIntValue(); + } else if (eCSSUnit_Initial == unit || + eCSSUnit_Unset == unit) { + outline->mOutlineStyle = NS_STYLE_BORDER_STYLE_NONE; + } else if (eCSSUnit_Inherit == unit) { + conditions.SetUncacheable(); + outline->mOutlineStyle = parentOutline->mOutlineStyle; + } + + outline->RecalcData(); + COMPUTE_END_RESET(Outline, outline) +} + +const void* +nsRuleNode::ComputeListData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_INHERITED(List, list, parentList) + + // quotes: inherit, initial, none, [string string]+ + const nsCSSValue* quotesValue = aRuleData->ValueForQuotes(); + switch (quotesValue->GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_Inherit: + case eCSSUnit_Unset: + conditions.SetUncacheable(); + list->SetQuotesInherit(parentList); + break; + case eCSSUnit_Initial: + list->SetQuotesInitial(); + break; + case eCSSUnit_None: + list->SetQuotesNone(); + break; + case eCSSUnit_PairList: + case eCSSUnit_PairListDep: { + const nsCSSValuePairList* ourQuotes = quotesValue->GetPairListValue(); + + nsStyleQuoteValues::QuotePairArray quotePairs; + quotePairs.SetLength(ListLength(ourQuotes)); + + size_t index = 0; + nsAutoString buffer; + while (ourQuotes) { + MOZ_ASSERT(ourQuotes->mXValue.GetUnit() == eCSSUnit_String && + ourQuotes->mYValue.GetUnit() == eCSSUnit_String, + "improper list contents for quotes"); + quotePairs[index].first = ourQuotes->mXValue.GetStringValue(buffer); + quotePairs[index].second = ourQuotes->mYValue.GetStringValue(buffer); + ++index; + ourQuotes = ourQuotes->mNext; + } + list->SetQuotes(Move(quotePairs)); + break; + } + default: + MOZ_ASSERT(false, "unexpected value unit"); + } + + // list-style-type: string, none, inherit, initial + const nsCSSValue* typeValue = aRuleData->ValueForListStyleType(); + switch (typeValue->GetUnit()) { + case eCSSUnit_Unset: + case eCSSUnit_Inherit: { + conditions.SetUncacheable(); + list->SetCounterStyle(parentList->GetCounterStyle()); + break; + } + case eCSSUnit_Initial: + list->SetListStyleType(NS_LITERAL_STRING("disc"), mPresContext); + break; + case eCSSUnit_Ident: { + nsString typeIdent; + typeValue->GetStringValue(typeIdent); + list->SetListStyleType(typeIdent, mPresContext); + break; + } + case eCSSUnit_String: { + nsString str; + typeValue->GetStringValue(str); + list->SetCounterStyle(new AnonymousCounterStyle(str)); + break; + } + case eCSSUnit_Enumerated: { + // For compatibility with html attribute map. + // This branch should never be called for value from CSS. + int32_t intValue = typeValue->GetIntValue(); + nsAutoString name; + switch (intValue) { + case NS_STYLE_LIST_STYLE_LOWER_ROMAN: + name.AssignLiteral(u"lower-roman"); + break; + case NS_STYLE_LIST_STYLE_UPPER_ROMAN: + name.AssignLiteral(u"upper-roman"); + break; + case NS_STYLE_LIST_STYLE_LOWER_ALPHA: + name.AssignLiteral(u"lower-alpha"); + break; + case NS_STYLE_LIST_STYLE_UPPER_ALPHA: + name.AssignLiteral(u"upper-alpha"); + break; + default: + CopyASCIItoUTF16(nsCSSProps::ValueToKeyword( + intValue, nsCSSProps::kListStyleKTable), name); + break; + } + list->SetListStyleType(name, mPresContext); + break; + } + case eCSSUnit_Symbols: + list->SetCounterStyle(new AnonymousCounterStyle(typeValue->GetArrayValue())); + break; + case eCSSUnit_Null: + break; + default: + NS_NOTREACHED("Unexpected value unit"); + } + + // list-style-image: url, none, inherit + const nsCSSValue* imageValue = aRuleData->ValueForListStyleImage(); + if (eCSSUnit_Image == imageValue->GetUnit()) { + SetStyleImageRequest([&](nsStyleImageRequest* req) { + list->mListStyleImage = req; + }, mPresContext, *imageValue, nsStyleImageRequest::Mode(0)); + } + else if (eCSSUnit_None == imageValue->GetUnit() || + eCSSUnit_Initial == imageValue->GetUnit()) { + list->mListStyleImage = nullptr; + } + else if (eCSSUnit_Inherit == imageValue->GetUnit() || + eCSSUnit_Unset == imageValue->GetUnit()) { + conditions.SetUncacheable(); + list->mListStyleImage = parentList->mListStyleImage; + } + + // list-style-position: enum, inherit, initial + SetValue(*aRuleData->ValueForListStylePosition(), + list->mListStylePosition, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentList->mListStylePosition, + NS_STYLE_LIST_STYLE_POSITION_OUTSIDE); + + // image region property: length, auto, inherit + const nsCSSValue* imageRegionValue = aRuleData->ValueForImageRegion(); + switch (imageRegionValue->GetUnit()) { + case eCSSUnit_Inherit: + case eCSSUnit_Unset: + conditions.SetUncacheable(); + list->mImageRegion = parentList->mImageRegion; + break; + + case eCSSUnit_Initial: + case eCSSUnit_Auto: + list->mImageRegion.SetRect(0,0,0,0); + break; + + case eCSSUnit_Null: + break; + + case eCSSUnit_Rect: { + const nsCSSRect& rgnRect = imageRegionValue->GetRectValue(); + + if (rgnRect.mTop.GetUnit() == eCSSUnit_Auto) + list->mImageRegion.y = 0; + else if (rgnRect.mTop.IsLengthUnit()) + list->mImageRegion.y = + CalcLength(rgnRect.mTop, aContext, mPresContext, conditions); + + if (rgnRect.mBottom.GetUnit() == eCSSUnit_Auto) + list->mImageRegion.height = 0; + else if (rgnRect.mBottom.IsLengthUnit()) + list->mImageRegion.height = + CalcLength(rgnRect.mBottom, aContext, mPresContext, + conditions) - list->mImageRegion.y; + + if (rgnRect.mLeft.GetUnit() == eCSSUnit_Auto) + list->mImageRegion.x = 0; + else if (rgnRect.mLeft.IsLengthUnit()) + list->mImageRegion.x = + CalcLength(rgnRect.mLeft, aContext, mPresContext, conditions); + + if (rgnRect.mRight.GetUnit() == eCSSUnit_Auto) + list->mImageRegion.width = 0; + else if (rgnRect.mRight.IsLengthUnit()) + list->mImageRegion.width = + CalcLength(rgnRect.mRight, aContext, mPresContext, + conditions) - list->mImageRegion.x; + break; + } + + default: + MOZ_ASSERT(false, "unrecognized image-region unit"); + } + + COMPUTE_END_INHERITED(List, list) +} + +static void +SetGridTrackBreadth(const nsCSSValue& aValue, + nsStyleCoord& aResult, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + nsCSSUnit unit = aValue.GetUnit(); + if (unit == eCSSUnit_FlexFraction) { + aResult.SetFlexFractionValue(aValue.GetFloatValue()); + } else if (unit == eCSSUnit_Auto) { + aResult.SetAutoValue(); + } else if (unit == eCSSUnit_None) { + // For fit-content(). + aResult.SetNoneValue(); + } else { + MOZ_ASSERT(unit != eCSSUnit_Inherit && unit != eCSSUnit_Unset, + "Unexpected value that would use dummyParentCoord"); + const nsStyleCoord dummyParentCoord; + DebugOnly<bool> stored = + SetCoord(aValue, aResult, dummyParentCoord, + SETCOORD_LPE | SETCOORD_STORE_CALC, + aStyleContext, aPresContext, aConditions); + MOZ_ASSERT(stored, "invalid <track-size> value"); + } +} + +static void +SetGridTrackSize(const nsCSSValue& aValue, + nsStyleCoord& aResultMin, + nsStyleCoord& aResultMax, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + if (aValue.GetUnit() == eCSSUnit_Function) { + nsCSSValue::Array* func = aValue.GetArrayValue(); + auto funcName = func->Item(0).GetKeywordValue(); + if (funcName == eCSSKeyword_minmax) { + SetGridTrackBreadth(func->Item(1), aResultMin, + aStyleContext, aPresContext, aConditions); + SetGridTrackBreadth(func->Item(2), aResultMax, + aStyleContext, aPresContext, aConditions); + } else if (funcName == eCSSKeyword_fit_content) { + // We represent fit-content(L) as 'none' min-sizing and L max-sizing. + SetGridTrackBreadth(nsCSSValue(eCSSUnit_None), aResultMin, + aStyleContext, aPresContext, aConditions); + SetGridTrackBreadth(func->Item(1), aResultMax, + aStyleContext, aPresContext, aConditions); + } else { + NS_ERROR("Expected minmax() or fit-content(), got another function name"); + } + } else { + // A single <track-breadth>, + // specifies identical min and max sizing functions. + SetGridTrackBreadth(aValue, aResultMin, + aStyleContext, aPresContext, aConditions); + aResultMax = aResultMin; + } +} + +static void +SetGridAutoColumnsRows(const nsCSSValue& aValue, + nsStyleCoord& aResultMin, + nsStyleCoord& aResultMax, + const nsStyleCoord& aParentValueMin, + const nsStyleCoord& aParentValueMax, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) + +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Inherit: + aConditions.SetUncacheable(); + aResultMin = aParentValueMin; + aResultMax = aParentValueMax; + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + // The initial value is 'auto', + // which computes to 'minmax(auto, auto)'. + // (Explicitly-specified 'auto' values are handled in SetGridTrackSize.) + aResultMin.SetAutoValue(); + aResultMax.SetAutoValue(); + break; + + default: + SetGridTrackSize(aValue, aResultMin, aResultMax, + aStyleContext, aPresContext, aConditions); + } +} + +static void +AppendGridLineNames(const nsCSSValue& aValue, + nsTArray<nsString>& aNameList) +{ + // Compute a <line-names> value + // Null unit means empty list, nothing more to do. + if (aValue.GetUnit() != eCSSUnit_Null) { + const nsCSSValueList* item = aValue.GetListValue(); + do { + nsString* name = aNameList.AppendElement(); + item->mValue.GetStringValue(*name); + item = item->mNext; + } while (item); + } +} + +static void +SetGridTrackList(const nsCSSValue& aValue, + nsStyleGridTemplate& aResult, + const nsStyleGridTemplate& aParentValue, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) + +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Inherit: + aConditions.SetUncacheable(); + aResult.mIsSubgrid = aParentValue.mIsSubgrid; + aResult.mLineNameLists = aParentValue.mLineNameLists; + aResult.mMinTrackSizingFunctions = aParentValue.mMinTrackSizingFunctions; + aResult.mMaxTrackSizingFunctions = aParentValue.mMaxTrackSizingFunctions; + aResult.mRepeatAutoLineNameListBefore = aParentValue.mRepeatAutoLineNameListBefore; + aResult.mRepeatAutoLineNameListAfter = aParentValue.mRepeatAutoLineNameListAfter; + aResult.mRepeatAutoIndex = aParentValue.mRepeatAutoIndex; + aResult.mIsAutoFill = aParentValue.mIsAutoFill; + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_None: + aResult.mIsSubgrid = false; + aResult.mLineNameLists.Clear(); + aResult.mMinTrackSizingFunctions.Clear(); + aResult.mMaxTrackSizingFunctions.Clear(); + aResult.mRepeatAutoLineNameListBefore.Clear(); + aResult.mRepeatAutoLineNameListAfter.Clear(); + aResult.mRepeatAutoIndex = -1; + aResult.mIsAutoFill = false; + break; + + default: + aResult.mLineNameLists.Clear(); + aResult.mMinTrackSizingFunctions.Clear(); + aResult.mMaxTrackSizingFunctions.Clear(); + aResult.mRepeatAutoLineNameListBefore.Clear(); + aResult.mRepeatAutoLineNameListAfter.Clear(); + aResult.mRepeatAutoIndex = -1; + aResult.mIsAutoFill = false; + const nsCSSValueList* item = aValue.GetListValue(); + if (item->mValue.GetUnit() == eCSSUnit_Enumerated && + item->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) { + // subgrid <line-name-list>? + aResult.mIsSubgrid = true; + item = item->mNext; + for (int32_t i = 0; item && i < nsStyleGridLine::kMaxLine; ++i) { + if (item->mValue.GetUnit() == eCSSUnit_Pair) { + // This is a 'auto-fill' <name-repeat> expression. + const nsCSSValuePair& pair = item->mValue.GetPairValue(); + MOZ_ASSERT(aResult.mRepeatAutoIndex == -1, + "can only have one <name-repeat> with auto-fill"); + aResult.mRepeatAutoIndex = i; + aResult.mIsAutoFill = true; + MOZ_ASSERT(pair.mXValue.GetIntValue() == NS_STYLE_GRID_REPEAT_AUTO_FILL, + "unexpected repeat() enum value for subgrid"); + const nsCSSValueList* list = pair.mYValue.GetListValue(); + AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListBefore); + } else { + AppendGridLineNames(item->mValue, + *aResult.mLineNameLists.AppendElement()); + } + item = item->mNext; + } + } else { + // <track-list> + // The list is expected to have odd number of items, at least 3 + // starting with a <line-names> (sub list of identifiers), + // and alternating between that and <track-size>. + aResult.mIsSubgrid = false; + for (int32_t line = 1; ; ++line) { + AppendGridLineNames(item->mValue, + *aResult.mLineNameLists.AppendElement()); + item = item->mNext; + + if (!item || line == nsStyleGridLine::kMaxLine) { + break; + } + + if (item->mValue.GetUnit() == eCSSUnit_Pair) { + // This is a 'auto-fill' / 'auto-fit' <auto-repeat> expression. + const nsCSSValuePair& pair = item->mValue.GetPairValue(); + MOZ_ASSERT(aResult.mRepeatAutoIndex == -1, + "can only have one <auto-repeat>"); + aResult.mRepeatAutoIndex = line - 1; + switch (pair.mXValue.GetIntValue()) { + case NS_STYLE_GRID_REPEAT_AUTO_FILL: + aResult.mIsAutoFill = true; + break; + case NS_STYLE_GRID_REPEAT_AUTO_FIT: + aResult.mIsAutoFill = false; + break; + default: + MOZ_ASSERT_UNREACHABLE("unexpected repeat() enum value"); + } + const nsCSSValueList* list = pair.mYValue.GetListValue(); + AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListBefore); + list = list->mNext; + nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement(); + nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement(); + SetGridTrackSize(list->mValue, min, max, + aStyleContext, aPresContext, aConditions); + list = list->mNext; + AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListAfter); + } else { + nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement(); + nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement(); + SetGridTrackSize(item->mValue, min, max, + aStyleContext, aPresContext, aConditions); + } + + item = item->mNext; + MOZ_ASSERT(item, "Expected a eCSSUnit_List of odd length"); + } + MOZ_ASSERT(!aResult.mMinTrackSizingFunctions.IsEmpty() && + aResult.mMinTrackSizingFunctions.Length() == + aResult.mMaxTrackSizingFunctions.Length() && + aResult.mMinTrackSizingFunctions.Length() + 1 == + aResult.mLineNameLists.Length(), + "Inconstistent array lengths for nsStyleGridTemplate"); + } + } +} + +static void +SetGridTemplateAreas(const nsCSSValue& aValue, + RefPtr<css::GridTemplateAreasValue>* aResult, + css::GridTemplateAreasValue* aParentValue, + RuleNodeCacheConditions& aConditions) +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Inherit: + aConditions.SetUncacheable(); + *aResult = aParentValue; + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_None: + *aResult = nullptr; + break; + + default: + *aResult = aValue.GetGridTemplateAreas(); + } +} + +static void +SetGridLine(const nsCSSValue& aValue, + nsStyleGridLine& aResult, + const nsStyleGridLine& aParentValue, + RuleNodeCacheConditions& aConditions) + +{ + switch (aValue.GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Inherit: + aConditions.SetUncacheable(); + aResult = aParentValue; + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_Auto: + aResult.SetAuto(); + break; + + default: + aResult.SetAuto(); // Reset any existing value. + const nsCSSValueList* item = aValue.GetListValue(); + do { + if (item->mValue.GetUnit() == eCSSUnit_Enumerated) { + aResult.mHasSpan = true; + } else if (item->mValue.GetUnit() == eCSSUnit_Integer) { + aResult.mInteger = clamped(item->mValue.GetIntValue(), + nsStyleGridLine::kMinLine, + nsStyleGridLine::kMaxLine); + } else if (item->mValue.GetUnit() == eCSSUnit_Ident) { + item->mValue.GetStringValue(aResult.mLineName); + } else { + NS_ASSERTION(false, "Unexpected unit"); + } + item = item->mNext; + } while (item); + MOZ_ASSERT(!aResult.IsAuto(), + "should have set something away from default value"); + } +} + +const void* +nsRuleNode::ComputePositionData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(Position, pos, parentPos) + + // box offsets: length, percent, calc, auto, inherit + static const nsCSSPropertyID offsetProps[] = { + eCSSProperty_top, + eCSSProperty_right, + eCSSProperty_bottom, + eCSSProperty_left + }; + nsStyleCoord coord; + NS_FOR_CSS_SIDES(side) { + nsStyleCoord parentCoord = parentPos->mOffset.Get(side); + if (SetCoord(*aRuleData->ValueFor(offsetProps[side]), + coord, parentCoord, + SETCOORD_LPAH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + pos->mOffset.Set(side, coord); + } + } + + // We allow the enumerated box size property values -moz-min-content, etc. to + // be specified on both the {,min-,max-}width properties and the + // {,min-,max-}height properties, regardless of the writing mode. This is + // because the writing mode is not determined until here, at computed value + // time. Since we do not support layout behavior of these keywords on the + // block-axis properties, we turn them into unset if we find them in + // that case. + + WritingMode wm(aContext); + bool vertical = wm.IsVertical(); + + const nsCSSValue* width = aRuleData->ValueForWidth(); + if (width->GetUnit() == eCSSUnit_Enumerated) { + conditions.SetWritingModeDependency(wm.GetBits()); + } + SetCoord(width->GetUnit() == eCSSUnit_Enumerated && vertical ? + nsCSSValue(eCSSUnit_Unset) : *width, + pos->mWidth, parentPos->mWidth, + SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + const nsCSSValue* minWidth = aRuleData->ValueForMinWidth(); + if (minWidth->GetUnit() == eCSSUnit_Enumerated) { + conditions.SetWritingModeDependency(wm.GetBits()); + } + SetCoord(minWidth->GetUnit() == eCSSUnit_Enumerated && vertical ? + nsCSSValue(eCSSUnit_Unset) : *minWidth, + pos->mMinWidth, parentPos->mMinWidth, + SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + const nsCSSValue* maxWidth = aRuleData->ValueForMaxWidth(); + if (maxWidth->GetUnit() == eCSSUnit_Enumerated) { + conditions.SetWritingModeDependency(wm.GetBits()); + } + SetCoord(maxWidth->GetUnit() == eCSSUnit_Enumerated && vertical ? + nsCSSValue(eCSSUnit_Unset) : *maxWidth, + pos->mMaxWidth, parentPos->mMaxWidth, + SETCOORD_LPOEH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + const nsCSSValue* height = aRuleData->ValueForHeight(); + if (height->GetUnit() == eCSSUnit_Enumerated) { + conditions.SetWritingModeDependency(wm.GetBits()); + } + SetCoord(height->GetUnit() == eCSSUnit_Enumerated && !vertical ? + nsCSSValue(eCSSUnit_Unset) : *height, + pos->mHeight, parentPos->mHeight, + SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + const nsCSSValue* minHeight = aRuleData->ValueForMinHeight(); + if (minHeight->GetUnit() == eCSSUnit_Enumerated) { + conditions.SetWritingModeDependency(wm.GetBits()); + } + SetCoord(minHeight->GetUnit() == eCSSUnit_Enumerated && !vertical ? + nsCSSValue(eCSSUnit_Unset) : *minHeight, + pos->mMinHeight, parentPos->mMinHeight, + SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + const nsCSSValue* maxHeight = aRuleData->ValueForMaxHeight(); + if (maxHeight->GetUnit() == eCSSUnit_Enumerated) { + conditions.SetWritingModeDependency(wm.GetBits()); + } + SetCoord(maxHeight->GetUnit() == eCSSUnit_Enumerated && !vertical ? + nsCSSValue(eCSSUnit_Unset) : *maxHeight, + pos->mMaxHeight, parentPos->mMaxHeight, + SETCOORD_LPOEH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + // box-sizing: enum, inherit, initial + SetValue(*aRuleData->ValueForBoxSizing(), + pos->mBoxSizing, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentPos->mBoxSizing, + StyleBoxSizing::Content); + + // align-content: enum, inherit, initial + SetValue(*aRuleData->ValueForAlignContent(), + pos->mAlignContent, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentPos->mAlignContent, + NS_STYLE_ALIGN_NORMAL); + + // align-items: enum, inherit, initial + SetValue(*aRuleData->ValueForAlignItems(), + pos->mAlignItems, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentPos->mAlignItems, + NS_STYLE_ALIGN_NORMAL); + + // align-self: enum, inherit, initial + SetValue(*aRuleData->ValueForAlignSelf(), + pos->mAlignSelf, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentPos->mAlignSelf, + NS_STYLE_ALIGN_AUTO); + + // justify-content: enum, inherit, initial + SetValue(*aRuleData->ValueForJustifyContent(), + pos->mJustifyContent, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentPos->mJustifyContent, + NS_STYLE_JUSTIFY_NORMAL); + + // justify-items: enum, inherit, initial + const auto& justifyItemsValue = *aRuleData->ValueForJustifyItems(); + if (MOZ_UNLIKELY(justifyItemsValue.GetUnit() == eCSSUnit_Inherit)) { + if (MOZ_LIKELY(parentContext)) { + pos->mJustifyItems = + parentPos->ComputedJustifyItems(parentContext->GetParent()); + } else { + pos->mJustifyItems = NS_STYLE_JUSTIFY_NORMAL; + } + conditions.SetUncacheable(); + } else { + SetValue(justifyItemsValue, + pos->mJustifyItems, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentPos->mJustifyItems, // unused, we handle 'inherit' above + NS_STYLE_JUSTIFY_AUTO); + } + + // justify-self: enum, inherit, initial + SetValue(*aRuleData->ValueForJustifySelf(), + pos->mJustifySelf, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentPos->mJustifySelf, + NS_STYLE_JUSTIFY_AUTO); + + // flex-basis: auto, length, percent, enum, calc, inherit, initial + // (Note: The flags here should match those used for 'width' property above.) + SetCoord(*aRuleData->ValueForFlexBasis(), pos->mFlexBasis, parentPos->mFlexBasis, + SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + // flex-direction: enum, inherit, initial + SetValue(*aRuleData->ValueForFlexDirection(), + pos->mFlexDirection, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentPos->mFlexDirection, + NS_STYLE_FLEX_DIRECTION_ROW); + + // flex-grow: float, inherit, initial + SetFactor(*aRuleData->ValueForFlexGrow(), + pos->mFlexGrow, conditions, + parentPos->mFlexGrow, 0.0f, + SETFCT_UNSET_INITIAL); + + // flex-shrink: float, inherit, initial + SetFactor(*aRuleData->ValueForFlexShrink(), + pos->mFlexShrink, conditions, + parentPos->mFlexShrink, 1.0f, + SETFCT_UNSET_INITIAL); + + // flex-wrap: enum, inherit, initial + SetValue(*aRuleData->ValueForFlexWrap(), + pos->mFlexWrap, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentPos->mFlexWrap, + NS_STYLE_FLEX_WRAP_NOWRAP); + + // order: integer, inherit, initial + SetValue(*aRuleData->ValueForOrder(), + pos->mOrder, conditions, + SETVAL_INTEGER | SETVAL_UNSET_INITIAL, + parentPos->mOrder, + NS_STYLE_ORDER_INITIAL); + + // object-fit: enum, inherit, initial + SetValue(*aRuleData->ValueForObjectFit(), + pos->mObjectFit, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentPos->mObjectFit, + NS_STYLE_OBJECT_FIT_FILL); + + // object-position + const nsCSSValue& objectPosition = *aRuleData->ValueForObjectPosition(); + switch (objectPosition.GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_Inherit: + conditions.SetUncacheable(); + pos->mObjectPosition = parentPos->mObjectPosition; + break; + case eCSSUnit_Initial: + case eCSSUnit_Unset: + pos->mObjectPosition.SetInitialPercentValues(0.5f); + break; + default: + ComputePositionValue(aContext, objectPosition, + pos->mObjectPosition, conditions); + } + + // grid-auto-flow + const nsCSSValue& gridAutoFlow = *aRuleData->ValueForGridAutoFlow(); + switch (gridAutoFlow.GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_Inherit: + conditions.SetUncacheable(); + pos->mGridAutoFlow = parentPos->mGridAutoFlow; + break; + case eCSSUnit_Initial: + case eCSSUnit_Unset: + pos->mGridAutoFlow = NS_STYLE_GRID_AUTO_FLOW_ROW; + break; + default: + NS_ASSERTION(gridAutoFlow.GetUnit() == eCSSUnit_Enumerated, + "Unexpected unit"); + pos->mGridAutoFlow = gridAutoFlow.GetIntValue(); + } + + // grid-auto-columns + SetGridAutoColumnsRows(*aRuleData->ValueForGridAutoColumns(), + pos->mGridAutoColumnsMin, + pos->mGridAutoColumnsMax, + parentPos->mGridAutoColumnsMin, + parentPos->mGridAutoColumnsMax, + aContext, mPresContext, conditions); + + // grid-auto-rows + SetGridAutoColumnsRows(*aRuleData->ValueForGridAutoRows(), + pos->mGridAutoRowsMin, + pos->mGridAutoRowsMax, + parentPos->mGridAutoRowsMin, + parentPos->mGridAutoRowsMax, + aContext, mPresContext, conditions); + + // grid-template-columns + SetGridTrackList(*aRuleData->ValueForGridTemplateColumns(), + pos->mGridTemplateColumns, parentPos->mGridTemplateColumns, + aContext, mPresContext, conditions); + + // grid-template-rows + SetGridTrackList(*aRuleData->ValueForGridTemplateRows(), + pos->mGridTemplateRows, parentPos->mGridTemplateRows, + aContext, mPresContext, conditions); + + // grid-tempate-areas + SetGridTemplateAreas(*aRuleData->ValueForGridTemplateAreas(), + &pos->mGridTemplateAreas, + parentPos->mGridTemplateAreas, + conditions); + + // grid-column-start + SetGridLine(*aRuleData->ValueForGridColumnStart(), + pos->mGridColumnStart, + parentPos->mGridColumnStart, + conditions); + + // grid-column-end + SetGridLine(*aRuleData->ValueForGridColumnEnd(), + pos->mGridColumnEnd, + parentPos->mGridColumnEnd, + conditions); + + // grid-row-start + SetGridLine(*aRuleData->ValueForGridRowStart(), + pos->mGridRowStart, + parentPos->mGridRowStart, + conditions); + + // grid-row-end + SetGridLine(*aRuleData->ValueForGridRowEnd(), + pos->mGridRowEnd, + parentPos->mGridRowEnd, + conditions); + + // grid-column-gap + if (SetCoord(*aRuleData->ValueForGridColumnGap(), + pos->mGridColumnGap, parentPos->mGridColumnGap, + SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC | + SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + } else { + MOZ_ASSERT(aRuleData->ValueForGridColumnGap()->GetUnit() == eCSSUnit_Null, + "unexpected unit"); + } + + // grid-row-gap + if (SetCoord(*aRuleData->ValueForGridRowGap(), + pos->mGridRowGap, parentPos->mGridRowGap, + SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC | + SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions)) { + } else { + MOZ_ASSERT(aRuleData->ValueForGridRowGap()->GetUnit() == eCSSUnit_Null, + "unexpected unit"); + } + + // z-index + const nsCSSValue* zIndexValue = aRuleData->ValueForZIndex(); + if (! SetCoord(*zIndexValue, pos->mZIndex, parentPos->mZIndex, + SETCOORD_IA | SETCOORD_INITIAL_AUTO | SETCOORD_UNSET_INITIAL, + aContext, nullptr, conditions)) { + if (eCSSUnit_Inherit == zIndexValue->GetUnit()) { + // handle inherit, because it's ok to inherit 'auto' here + conditions.SetUncacheable(); + pos->mZIndex = parentPos->mZIndex; + } + } + + COMPUTE_END_RESET(Position, pos) +} + +const void* +nsRuleNode::ComputeTableData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(Table, table, parentTable) + + // table-layout: enum, inherit, initial + SetValue(*aRuleData->ValueForTableLayout(), + table->mLayoutStrategy, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentTable->mLayoutStrategy, + NS_STYLE_TABLE_LAYOUT_AUTO); + + // span: pixels (not a real CSS prop) + const nsCSSValue* spanValue = aRuleData->ValueForSpan(); + if (eCSSUnit_Enumerated == spanValue->GetUnit() || + eCSSUnit_Integer == spanValue->GetUnit()) + table->mSpan = spanValue->GetIntValue(); + + COMPUTE_END_RESET(Table, table) +} + +const void* +nsRuleNode::ComputeTableBorderData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_INHERITED(TableBorder, table, parentTable) + + // border-collapse: enum, inherit, initial + SetValue(*aRuleData->ValueForBorderCollapse(), table->mBorderCollapse, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentTable->mBorderCollapse, + NS_STYLE_BORDER_SEPARATE); + + const nsCSSValue* borderSpacingValue = aRuleData->ValueForBorderSpacing(); + // border-spacing: pair(length), inherit + if (borderSpacingValue->GetUnit() != eCSSUnit_Null) { + nsStyleCoord parentCol(parentTable->mBorderSpacingCol, + nsStyleCoord::CoordConstructor); + nsStyleCoord parentRow(parentTable->mBorderSpacingRow, + nsStyleCoord::CoordConstructor); + nsStyleCoord coordCol, coordRow; + +#ifdef DEBUG + bool result = +#endif + SetPairCoords(*borderSpacingValue, + coordCol, coordRow, parentCol, parentRow, + SETCOORD_LH | SETCOORD_INITIAL_ZERO | + SETCOORD_CALC_LENGTH_ONLY | + SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INHERIT, + aContext, mPresContext, conditions); + NS_ASSERTION(result, "malformed table border value"); + table->mBorderSpacingCol = coordCol.GetCoordValue(); + table->mBorderSpacingRow = coordRow.GetCoordValue(); + } + + // caption-side: enum, inherit, initial + SetValue(*aRuleData->ValueForCaptionSide(), + table->mCaptionSide, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentTable->mCaptionSide, + NS_STYLE_CAPTION_SIDE_TOP); + + // empty-cells: enum, inherit, initial + SetValue(*aRuleData->ValueForEmptyCells(), + table->mEmptyCells, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentTable->mEmptyCells, + NS_STYLE_TABLE_EMPTY_CELLS_SHOW); + + COMPUTE_END_INHERITED(TableBorder, table) +} + +const void* +nsRuleNode::ComputeContentData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + uint32_t count; + nsAutoString buffer; + + COMPUTE_START_RESET(Content, content, parentContent) + + // content: [string, url, counter, attr, enum]+, normal, none, inherit + const nsCSSValue* contentValue = aRuleData->ValueForContent(); + switch (contentValue->GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Normal: + case eCSSUnit_None: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + // "normal", "none", "initial" and "unset" all mean no content + content->AllocateContents(0); + break; + + case eCSSUnit_Inherit: + conditions.SetUncacheable(); + count = parentContent->ContentCount(); + content->AllocateContents(count); + while (0 < count--) { + content->ContentAt(count) = parentContent->ContentAt(count); + } + break; + + case eCSSUnit_Enumerated: { + MOZ_ASSERT(contentValue->GetIntValue() == NS_STYLE_CONTENT_ALT_CONTENT, + "unrecognized solitary content keyword"); + content->AllocateContents(1); + nsStyleContentData& data = content->ContentAt(0); + data.mType = eStyleContentType_AltContent; + data.mContent.mString = nullptr; + break; + } + + case eCSSUnit_List: + case eCSSUnit_ListDep: { + const nsCSSValueList* contentValueList = contentValue->GetListValue(); + count = 0; + while (contentValueList) { + count++; + contentValueList = contentValueList->mNext; + } + content->AllocateContents(count); + const nsAutoString nullStr; + count = 0; + contentValueList = contentValue->GetListValue(); + while (contentValueList) { + const nsCSSValue& value = contentValueList->mValue; + nsCSSUnit unit = value.GetUnit(); + nsStyleContentType type; + nsStyleContentData &data = content->ContentAt(count++); + switch (unit) { + case eCSSUnit_String: type = eStyleContentType_String; break; + case eCSSUnit_Image: type = eStyleContentType_Image; break; + case eCSSUnit_Attr: type = eStyleContentType_Attr; break; + case eCSSUnit_Counter: type = eStyleContentType_Counter; break; + case eCSSUnit_Counters: type = eStyleContentType_Counters; break; + case eCSSUnit_Enumerated: + switch (value.GetIntValue()) { + case NS_STYLE_CONTENT_OPEN_QUOTE: + type = eStyleContentType_OpenQuote; break; + case NS_STYLE_CONTENT_CLOSE_QUOTE: + type = eStyleContentType_CloseQuote; break; + case NS_STYLE_CONTENT_NO_OPEN_QUOTE: + type = eStyleContentType_NoOpenQuote; break; + case NS_STYLE_CONTENT_NO_CLOSE_QUOTE: + type = eStyleContentType_NoCloseQuote; break; + default: + NS_ERROR("bad content value"); + type = eStyleContentType_Uninitialized; + } + break; + default: + NS_ERROR("bad content type"); + type = eStyleContentType_Uninitialized; + } + data.mType = type; + if (type == eStyleContentType_Image) { + SetImageRequest([&](imgRequestProxy* req) { + data.SetImage(req); + }, mPresContext, value); + } else if (type <= eStyleContentType_Attr) { + value.GetStringValue(buffer); + data.mContent.mString = NS_strdup(buffer.get()); + } else if (type <= eStyleContentType_Counters) { + data.mContent.mCounters = value.GetArrayValue(); + data.mContent.mCounters->AddRef(); + } else { + data.mContent.mString = nullptr; + } + contentValueList = contentValueList->mNext; + } + break; + } + + default: + MOZ_ASSERT(false, "unrecognized content unit"); + } + + // counter-increment: [string [int]]+, none, inherit + const nsCSSValue* counterIncrementValue = + aRuleData->ValueForCounterIncrement(); + switch (counterIncrementValue->GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_None: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + content->AllocateCounterIncrements(0); + break; + + case eCSSUnit_Inherit: + conditions.SetUncacheable(); + count = parentContent->CounterIncrementCount(); + content->AllocateCounterIncrements(count); + while (count--) { + const nsStyleCounterData& data = parentContent->CounterIncrementAt(count); + content->SetCounterIncrementAt(count, data.mCounter, data.mValue); + } + break; + + case eCSSUnit_PairList: + case eCSSUnit_PairListDep: { + const nsCSSValuePairList* ourIncrement = + counterIncrementValue->GetPairListValue(); + MOZ_ASSERT(ourIncrement->mXValue.GetUnit() == eCSSUnit_Ident, + "unexpected value unit"); + count = ListLength(ourIncrement); + content->AllocateCounterIncrements(count); + + count = 0; + for (const nsCSSValuePairList* p = ourIncrement; p; p = p->mNext, count++) { + int32_t increment; + if (p->mYValue.GetUnit() == eCSSUnit_Integer) { + increment = p->mYValue.GetIntValue(); + } else { + increment = 1; + } + p->mXValue.GetStringValue(buffer); + content->SetCounterIncrementAt(count, buffer, increment); + } + break; + } + + default: + MOZ_ASSERT(false, "unexpected value unit"); + } + + // counter-reset: [string [int]]+, none, inherit + const nsCSSValue* counterResetValue = aRuleData->ValueForCounterReset(); + switch (counterResetValue->GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_None: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + content->AllocateCounterResets(0); + break; + + case eCSSUnit_Inherit: + conditions.SetUncacheable(); + count = parentContent->CounterResetCount(); + content->AllocateCounterResets(count); + while (0 < count--) { + const nsStyleCounterData& data = parentContent->CounterResetAt(count); + content->SetCounterResetAt(count, data.mCounter, data.mValue); + } + break; + + case eCSSUnit_PairList: + case eCSSUnit_PairListDep: { + const nsCSSValuePairList* ourReset = + counterResetValue->GetPairListValue(); + MOZ_ASSERT(ourReset->mXValue.GetUnit() == eCSSUnit_Ident, + "unexpected value unit"); + count = ListLength(ourReset); + content->AllocateCounterResets(count); + count = 0; + for (const nsCSSValuePairList* p = ourReset; p; p = p->mNext, count++) { + int32_t reset; + if (p->mYValue.GetUnit() == eCSSUnit_Integer) { + reset = p->mYValue.GetIntValue(); + } else { + reset = 0; + } + p->mXValue.GetStringValue(buffer); + content->SetCounterResetAt(count, buffer, reset); + } + break; + } + + default: + MOZ_ASSERT(false, "unexpected value unit"); + } + + // If we ended up with an image, track it. + for (uint32_t i = 0; i < content->ContentCount(); ++i) { + if ((content->ContentAt(i).mType == eStyleContentType_Image) && + content->ContentAt(i).mContent.mImage) { + content->ContentAt(i).TrackImage( + aContext->PresContext()->Document()->ImageTracker()); + } + } + + COMPUTE_END_RESET(Content, content) +} + +const void* +nsRuleNode::ComputeXULData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(XUL, xul, parentXUL) + + // box-align: enum, inherit, initial + SetValue(*aRuleData->ValueForBoxAlign(), + xul->mBoxAlign, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentXUL->mBoxAlign, + StyleBoxAlign::Stretch); + + // box-direction: enum, inherit, initial + SetValue(*aRuleData->ValueForBoxDirection(), + xul->mBoxDirection, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentXUL->mBoxDirection, + StyleBoxDirection::Normal); + + // box-flex: factor, inherit + SetFactor(*aRuleData->ValueForBoxFlex(), + xul->mBoxFlex, conditions, + parentXUL->mBoxFlex, 0.0f, + SETFCT_UNSET_INITIAL); + + // box-orient: enum, inherit, initial + SetValue(*aRuleData->ValueForBoxOrient(), + xul->mBoxOrient, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentXUL->mBoxOrient, + StyleBoxOrient::Horizontal); + + // box-pack: enum, inherit, initial + SetValue(*aRuleData->ValueForBoxPack(), + xul->mBoxPack, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentXUL->mBoxPack, + StyleBoxPack::Start); + + // box-ordinal-group: integer, inherit, initial + SetValue(*aRuleData->ValueForBoxOrdinalGroup(), + xul->mBoxOrdinal, conditions, + SETVAL_INTEGER | SETVAL_UNSET_INITIAL, + parentXUL->mBoxOrdinal, 1); + + const nsCSSValue* stackSizingValue = aRuleData->ValueForStackSizing(); + if (eCSSUnit_Inherit == stackSizingValue->GetUnit()) { + conditions.SetUncacheable(); + xul->mStretchStack = parentXUL->mStretchStack; + } else if (eCSSUnit_Initial == stackSizingValue->GetUnit() || + eCSSUnit_Unset == stackSizingValue->GetUnit()) { + xul->mStretchStack = true; + } else if (eCSSUnit_Enumerated == stackSizingValue->GetUnit()) { + xul->mStretchStack = stackSizingValue->GetIntValue() == + NS_STYLE_STACK_SIZING_STRETCH_TO_FIT; + } + + COMPUTE_END_RESET(XUL, xul) +} + +const void* +nsRuleNode::ComputeColumnData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(Column, column, parent) + + // column-width: length, auto, inherit + SetCoord(*aRuleData->ValueForColumnWidth(), + column->mColumnWidth, parent->mColumnWidth, + SETCOORD_LAH | SETCOORD_INITIAL_AUTO | + SETCOORD_CALC_LENGTH_ONLY | SETCOORD_CALC_CLAMP_NONNEGATIVE | + SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + + // column-gap: length, inherit, normal + SetCoord(*aRuleData->ValueForColumnGap(), + column->mColumnGap, parent->mColumnGap, + SETCOORD_LH | SETCOORD_NORMAL | SETCOORD_INITIAL_NORMAL | + SETCOORD_CALC_LENGTH_ONLY | SETCOORD_UNSET_INITIAL, + aContext, mPresContext, conditions); + // clamp negative calc() to 0 + if (column->mColumnGap.GetUnit() == eStyleUnit_Coord) { + column->mColumnGap.SetCoordValue( + std::max(column->mColumnGap.GetCoordValue(), 0)); + } + + // column-count: auto, integer, inherit + const nsCSSValue* columnCountValue = aRuleData->ValueForColumnCount(); + if (eCSSUnit_Auto == columnCountValue->GetUnit() || + eCSSUnit_Initial == columnCountValue->GetUnit() || + eCSSUnit_Unset == columnCountValue->GetUnit()) { + column->mColumnCount = NS_STYLE_COLUMN_COUNT_AUTO; + } else if (eCSSUnit_Integer == columnCountValue->GetUnit()) { + column->mColumnCount = columnCountValue->GetIntValue(); + // Max kMaxColumnCount columns - wallpaper for bug 345583. + column->mColumnCount = std::min(column->mColumnCount, + nsStyleColumn::kMaxColumnCount); + } else if (eCSSUnit_Inherit == columnCountValue->GetUnit()) { + conditions.SetUncacheable(); + column->mColumnCount = parent->mColumnCount; + } + + // column-rule-width: length, enum, inherit + const nsCSSValue& widthValue = *aRuleData->ValueForColumnRuleWidth(); + if (eCSSUnit_Initial == widthValue.GetUnit() || + eCSSUnit_Unset == widthValue.GetUnit()) { + column->SetColumnRuleWidth( + (mPresContext->GetBorderWidthTable())[NS_STYLE_BORDER_WIDTH_MEDIUM]); + } + else if (eCSSUnit_Enumerated == widthValue.GetUnit()) { + NS_ASSERTION(widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN || + widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM || + widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK, + "Unexpected enum value"); + column->SetColumnRuleWidth( + (mPresContext->GetBorderWidthTable())[widthValue.GetIntValue()]); + } + else if (eCSSUnit_Inherit == widthValue.GetUnit()) { + column->SetColumnRuleWidth(parent->GetComputedColumnRuleWidth()); + conditions.SetUncacheable(); + } + else if (widthValue.IsLengthUnit() || widthValue.IsCalcUnit()) { + nscoord len = + CalcLength(widthValue, aContext, mPresContext, conditions); + if (len < 0) { + // FIXME: This is untested (by test_value_storage.html) for + // column-rule-width since it gets covered up by the border + // rounding code. + NS_ASSERTION(widthValue.IsCalcUnit(), + "parser should have rejected negative length"); + len = 0; + } + column->SetColumnRuleWidth(len); + } + + // column-rule-style: enum, inherit + const nsCSSValue& styleValue = *aRuleData->ValueForColumnRuleStyle(); + MOZ_ASSERT(eCSSUnit_None != styleValue.GetUnit(), + "'none' should be handled as enumerated value"); + if (eCSSUnit_Enumerated == styleValue.GetUnit()) { + column->mColumnRuleStyle = styleValue.GetIntValue(); + } + else if (eCSSUnit_Initial == styleValue.GetUnit() || + eCSSUnit_Unset == styleValue.GetUnit()) { + column->mColumnRuleStyle = NS_STYLE_BORDER_STYLE_NONE; + } + else if (eCSSUnit_Inherit == styleValue.GetUnit()) { + conditions.SetUncacheable(); + column->mColumnRuleStyle = parent->mColumnRuleStyle; + } + + // column-rule-color: color, inherit + SetComplexColor<eUnsetInitial>(*aRuleData->ValueForColumnRuleColor(), + parent->mColumnRuleColor, + StyleComplexColor::CurrentColor(), + mPresContext, + column->mColumnRuleColor, conditions); + + // column-fill: enum + SetValue(*aRuleData->ValueForColumnFill(), + column->mColumnFill, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parent->mColumnFill, + NS_STYLE_COLUMN_FILL_BALANCE); + + COMPUTE_END_RESET(Column, column) +} + +static void +SetSVGPaint(const nsCSSValue& aValue, const nsStyleSVGPaint& parentPaint, + nsPresContext* aPresContext, nsStyleContext *aContext, + nsStyleSVGPaint& aResult, nsStyleSVGPaintType aInitialPaintType, + RuleNodeCacheConditions& aConditions) +{ + MOZ_ASSERT(aInitialPaintType == eStyleSVGPaintType_None || + aInitialPaintType == eStyleSVGPaintType_Color, + "SetSVGPaint only supports initial values being either 'black' " + "(represented by eStyleSVGPaintType_Color) or none (by " + "eStyleSVGPaintType_None)"); + + nscolor color; + + if (aValue.GetUnit() == eCSSUnit_Inherit || + aValue.GetUnit() == eCSSUnit_Unset) { + aResult = parentPaint; + aConditions.SetUncacheable(); + } else if (aValue.GetUnit() == eCSSUnit_None) { + aResult.SetNone(); + } else if (aValue.GetUnit() == eCSSUnit_Initial) { + if (aInitialPaintType == eStyleSVGPaintType_None) { + aResult.SetNone(); + } else { + aResult.SetColor(NS_RGB(0, 0, 0)); + } + } else if (SetColor(aValue, NS_RGB(0, 0, 0), aPresContext, aContext, + color, aConditions)) { + aResult.SetColor(color); + } else if (aValue.GetUnit() == eCSSUnit_Pair) { + const nsCSSValuePair& pair = aValue.GetPairValue(); + + nscolor fallback; + if (pair.mYValue.GetUnit() == eCSSUnit_None) { + fallback = NS_RGBA(0, 0, 0, 0); + } else { + MOZ_ASSERT(pair.mYValue.GetUnit() != eCSSUnit_Inherit, + "cannot inherit fallback colour"); + SetColor(pair.mYValue, NS_RGB(0, 0, 0), aPresContext, aContext, + fallback, aConditions); + } + + if (pair.mXValue.GetUnit() == eCSSUnit_URL) { + aResult.SetPaintServer(pair.mXValue.GetURLStructValue(), fallback); + } else if (pair.mXValue.GetUnit() == eCSSUnit_Enumerated) { + + switch (pair.mXValue.GetIntValue()) { + case NS_COLOR_CONTEXT_FILL: + aResult.SetContextValue(eStyleSVGPaintType_ContextFill, fallback); + break; + case NS_COLOR_CONTEXT_STROKE: + aResult.SetContextValue(eStyleSVGPaintType_ContextStroke, fallback); + break; + default: + NS_NOTREACHED("unknown keyword as paint server value"); + } + + } else { + NS_NOTREACHED("malformed paint server value"); + } + + } else { + MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Null, + "malformed paint server value"); + } +} + +static void +SetSVGOpacity(const nsCSSValue& aValue, + float& aOpacityField, nsStyleSVGOpacitySource& aOpacityTypeField, + RuleNodeCacheConditions& aConditions, + float aParentOpacity, nsStyleSVGOpacitySource aParentOpacityType) +{ + if (eCSSUnit_Enumerated == aValue.GetUnit()) { + switch (aValue.GetIntValue()) { + case NS_STYLE_CONTEXT_FILL_OPACITY: + aOpacityTypeField = eStyleSVGOpacitySource_ContextFillOpacity; + break; + case NS_STYLE_CONTEXT_STROKE_OPACITY: + aOpacityTypeField = eStyleSVGOpacitySource_ContextStrokeOpacity; + break; + default: + NS_NOTREACHED("SetSVGOpacity: Unknown keyword"); + } + // Fall back on fully opaque + aOpacityField = 1.0f; + } else if (eCSSUnit_Inherit == aValue.GetUnit() || + eCSSUnit_Unset == aValue.GetUnit()) { + aConditions.SetUncacheable(); + aOpacityField = aParentOpacity; + aOpacityTypeField = aParentOpacityType; + } else if (eCSSUnit_Null != aValue.GetUnit()) { + SetFactor(aValue, aOpacityField, aConditions, + aParentOpacity, 1.0f, SETFCT_OPACITY); + aOpacityTypeField = eStyleSVGOpacitySource_Normal; + } +} + +/* static */ +void +nsRuleNode::FillAllMaskLists(nsStyleImageLayers& aMask, + uint32_t aMaxItemCount) +{ + + // Delete any extra items. We need to keep layers in which any + // property was specified. + aMask.mLayers.TruncateLengthNonZero(aMaxItemCount); + + uint32_t fillCount = aMask.mImageCount; + + FillImageLayerList(aMask.mLayers, + &nsStyleImageLayers::Layer::mImage, + aMask.mImageCount, fillCount); + FillImageLayerList(aMask.mLayers, + &nsStyleImageLayers::Layer::mSourceURI, + aMask.mImageCount, fillCount); + FillImageLayerList(aMask.mLayers, + &nsStyleImageLayers::Layer::mRepeat, + aMask.mRepeatCount, fillCount); + FillImageLayerList(aMask.mLayers, + &nsStyleImageLayers::Layer::mClip, + aMask.mClipCount, fillCount); + FillImageLayerList(aMask.mLayers, + &nsStyleImageLayers::Layer::mOrigin, + aMask.mOriginCount, fillCount); + FillImageLayerPositionCoordList(aMask.mLayers, + &Position::mXPosition, + aMask.mPositionXCount, fillCount); + FillImageLayerPositionCoordList(aMask.mLayers, + &Position::mYPosition, + aMask.mPositionYCount, fillCount); + FillImageLayerList(aMask.mLayers, + &nsStyleImageLayers::Layer::mSize, + aMask.mSizeCount, fillCount); + FillImageLayerList(aMask.mLayers, + &nsStyleImageLayers::Layer::mMaskMode, + aMask.mMaskModeCount, fillCount); + FillImageLayerList(aMask.mLayers, + &nsStyleImageLayers::Layer::mComposite, + aMask.mCompositeCount, fillCount); +} + +const void* +nsRuleNode::ComputeSVGData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_INHERITED(SVG, svg, parentSVG) + + // clip-rule: enum, inherit, initial + SetValue(*aRuleData->ValueForClipRule(), + svg->mClipRule, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentSVG->mClipRule, + StyleFillRule::Nonzero); + + // color-interpolation: enum, inherit, initial + SetValue(*aRuleData->ValueForColorInterpolation(), + svg->mColorInterpolation, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentSVG->mColorInterpolation, + NS_STYLE_COLOR_INTERPOLATION_SRGB); + + // color-interpolation-filters: enum, inherit, initial + SetValue(*aRuleData->ValueForColorInterpolationFilters(), + svg->mColorInterpolationFilters, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentSVG->mColorInterpolationFilters, + NS_STYLE_COLOR_INTERPOLATION_LINEARRGB); + + // fill: + SetSVGPaint(*aRuleData->ValueForFill(), + parentSVG->mFill, mPresContext, aContext, + svg->mFill, eStyleSVGPaintType_Color, conditions); + + // fill-opacity: factor, inherit, initial, + // context-fill-opacity, context-stroke-opacity + nsStyleSVGOpacitySource contextFillOpacity = svg->FillOpacitySource(); + SetSVGOpacity(*aRuleData->ValueForFillOpacity(), + svg->mFillOpacity, contextFillOpacity, conditions, + parentSVG->mFillOpacity, parentSVG->FillOpacitySource()); + svg->SetFillOpacitySource(contextFillOpacity); + + // fill-rule: enum, inherit, initial + SetValue(*aRuleData->ValueForFillRule(), + svg->mFillRule, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentSVG->mFillRule, + StyleFillRule::Nonzero); + + // marker-end: url, none, inherit + const nsCSSValue* markerEndValue = aRuleData->ValueForMarkerEnd(); + if (eCSSUnit_URL == markerEndValue->GetUnit()) { + svg->mMarkerEnd = markerEndValue->GetURLStructValue(); + } else if (eCSSUnit_None == markerEndValue->GetUnit() || + eCSSUnit_Initial == markerEndValue->GetUnit()) { + svg->mMarkerEnd = nullptr; + } else if (eCSSUnit_Inherit == markerEndValue->GetUnit() || + eCSSUnit_Unset == markerEndValue->GetUnit()) { + conditions.SetUncacheable(); + svg->mMarkerEnd = parentSVG->mMarkerEnd; + } + + // marker-mid: url, none, inherit + const nsCSSValue* markerMidValue = aRuleData->ValueForMarkerMid(); + if (eCSSUnit_URL == markerMidValue->GetUnit()) { + svg->mMarkerMid = markerMidValue->GetURLStructValue(); + } else if (eCSSUnit_None == markerMidValue->GetUnit() || + eCSSUnit_Initial == markerMidValue->GetUnit()) { + svg->mMarkerMid = nullptr; + } else if (eCSSUnit_Inherit == markerMidValue->GetUnit() || + eCSSUnit_Unset == markerMidValue->GetUnit()) { + conditions.SetUncacheable(); + svg->mMarkerMid = parentSVG->mMarkerMid; + } + + // marker-start: url, none, inherit + const nsCSSValue* markerStartValue = aRuleData->ValueForMarkerStart(); + if (eCSSUnit_URL == markerStartValue->GetUnit()) { + svg->mMarkerStart = markerStartValue->GetURLStructValue(); + } else if (eCSSUnit_None == markerStartValue->GetUnit() || + eCSSUnit_Initial == markerStartValue->GetUnit()) { + svg->mMarkerStart = nullptr; + } else if (eCSSUnit_Inherit == markerStartValue->GetUnit() || + eCSSUnit_Unset == markerStartValue->GetUnit()) { + conditions.SetUncacheable(); + svg->mMarkerStart = parentSVG->mMarkerStart; + } + + // paint-order: enum (bit field), inherit, initial + const nsCSSValue* paintOrderValue = aRuleData->ValueForPaintOrder(); + switch (paintOrderValue->GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Enumerated: + static_assert + (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8, + "SVGStyleStruct::mPaintOrder not big enough"); + svg->mPaintOrder = static_cast<uint8_t>(paintOrderValue->GetIntValue()); + break; + + case eCSSUnit_Inherit: + case eCSSUnit_Unset: + conditions.SetUncacheable(); + svg->mPaintOrder = parentSVG->mPaintOrder; + break; + + case eCSSUnit_Initial: + svg->mPaintOrder = NS_STYLE_PAINT_ORDER_NORMAL; + break; + + default: + NS_NOTREACHED("unexpected unit"); + } + + // shape-rendering: enum, inherit + SetValue(*aRuleData->ValueForShapeRendering(), + svg->mShapeRendering, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentSVG->mShapeRendering, + NS_STYLE_SHAPE_RENDERING_AUTO); + + // stroke: + SetSVGPaint(*aRuleData->ValueForStroke(), + parentSVG->mStroke, mPresContext, aContext, + svg->mStroke, eStyleSVGPaintType_None, conditions); + + // stroke-dasharray: <dasharray>, none, inherit, context-value + const nsCSSValue* strokeDasharrayValue = aRuleData->ValueForStrokeDasharray(); + switch (strokeDasharrayValue->GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Inherit: + case eCSSUnit_Unset: + conditions.SetUncacheable(); + svg->SetStrokeDasharrayFromObject(parentSVG->StrokeDasharrayFromObject()); + svg->mStrokeDasharray = parentSVG->mStrokeDasharray; + break; + + case eCSSUnit_Enumerated: + MOZ_ASSERT(strokeDasharrayValue->GetIntValue() == + NS_STYLE_STROKE_PROP_CONTEXT_VALUE, + "Unknown keyword for stroke-dasharray"); + svg->SetStrokeDasharrayFromObject(true); + svg->mStrokeDasharray.Clear(); + break; + + case eCSSUnit_Initial: + case eCSSUnit_None: + svg->SetStrokeDasharrayFromObject(false); + svg->mStrokeDasharray.Clear(); + break; + + case eCSSUnit_List: + case eCSSUnit_ListDep: { + svg->SetStrokeDasharrayFromObject(false); + svg->mStrokeDasharray.Clear(); + + // count number of values + const nsCSSValueList *value = strokeDasharrayValue->GetListValue(); + uint32_t strokeDasharrayLength = ListLength(value); + + MOZ_ASSERT(strokeDasharrayLength != 0, "no dasharray items"); + + svg->mStrokeDasharray.SetLength(strokeDasharrayLength); + + uint32_t i = 0; + while (nullptr != value) { + SetCoord(value->mValue, + svg->mStrokeDasharray[i++], nsStyleCoord(), + SETCOORD_LP | SETCOORD_FACTOR, + aContext, mPresContext, conditions); + value = value->mNext; + } + break; + } + + default: + MOZ_ASSERT(false, "unrecognized dasharray unit"); + } + + // stroke-dashoffset: <dashoffset>, inherit + const nsCSSValue *strokeDashoffsetValue = + aRuleData->ValueForStrokeDashoffset(); + svg->SetStrokeDashoffsetFromObject( + strokeDashoffsetValue->GetUnit() == eCSSUnit_Enumerated && + strokeDashoffsetValue->GetIntValue() == NS_STYLE_STROKE_PROP_CONTEXT_VALUE); + if (svg->StrokeDashoffsetFromObject()) { + svg->mStrokeDashoffset.SetCoordValue(0); + } else { + SetCoord(*aRuleData->ValueForStrokeDashoffset(), + svg->mStrokeDashoffset, parentSVG->mStrokeDashoffset, + SETCOORD_LPH | SETCOORD_FACTOR | SETCOORD_INITIAL_ZERO | + SETCOORD_UNSET_INHERIT, + aContext, mPresContext, conditions); + } + + // stroke-linecap: enum, inherit, initial + SetValue(*aRuleData->ValueForStrokeLinecap(), + svg->mStrokeLinecap, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentSVG->mStrokeLinecap, + NS_STYLE_STROKE_LINECAP_BUTT); + + // stroke-linejoin: enum, inherit, initial + SetValue(*aRuleData->ValueForStrokeLinejoin(), + svg->mStrokeLinejoin, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentSVG->mStrokeLinejoin, + NS_STYLE_STROKE_LINEJOIN_MITER); + + // stroke-miterlimit: <miterlimit>, inherit + SetFactor(*aRuleData->ValueForStrokeMiterlimit(), + svg->mStrokeMiterlimit, + conditions, + parentSVG->mStrokeMiterlimit, 4.0f, + SETFCT_UNSET_INHERIT); + + // stroke-opacity: + nsStyleSVGOpacitySource contextStrokeOpacity = svg->StrokeOpacitySource(); + SetSVGOpacity(*aRuleData->ValueForStrokeOpacity(), + svg->mStrokeOpacity, contextStrokeOpacity, conditions, + parentSVG->mStrokeOpacity, parentSVG->StrokeOpacitySource()); + svg->SetStrokeOpacitySource(contextStrokeOpacity); + + // stroke-width: + const nsCSSValue* strokeWidthValue = aRuleData->ValueForStrokeWidth(); + switch (strokeWidthValue->GetUnit()) { + case eCSSUnit_Enumerated: + MOZ_ASSERT(strokeWidthValue->GetIntValue() == + NS_STYLE_STROKE_PROP_CONTEXT_VALUE, + "Unrecognized keyword for stroke-width"); + svg->SetStrokeWidthFromObject(true); + svg->mStrokeWidth.SetCoordValue(nsPresContext::CSSPixelsToAppUnits(1)); + break; + + case eCSSUnit_Initial: + svg->SetStrokeWidthFromObject(false); + svg->mStrokeWidth.SetCoordValue(nsPresContext::CSSPixelsToAppUnits(1)); + break; + + default: + svg->SetStrokeWidthFromObject(false); + SetCoord(*strokeWidthValue, + svg->mStrokeWidth, parentSVG->mStrokeWidth, + SETCOORD_LPH | SETCOORD_FACTOR | SETCOORD_UNSET_INHERIT, + aContext, mPresContext, conditions); + } + + // text-anchor: enum, inherit, initial + SetValue(*aRuleData->ValueForTextAnchor(), + svg->mTextAnchor, conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT, + parentSVG->mTextAnchor, + NS_STYLE_TEXT_ANCHOR_START); + + COMPUTE_END_INHERITED(SVG, svg) +} + +static already_AddRefed<StyleBasicShape> +GetStyleBasicShapeFromCSSValue(const nsCSSValue& aValue, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + RefPtr<StyleBasicShape> basicShape; + + nsCSSValue::Array* shapeFunction = aValue.GetArrayValue(); + nsCSSKeyword functionName = + (nsCSSKeyword)shapeFunction->Item(0).GetIntValue(); + + if (functionName == eCSSKeyword_polygon) { + MOZ_ASSERT(!basicShape, "did not expect value"); + basicShape = new StyleBasicShape(StyleBasicShapeType::Polygon); + MOZ_ASSERT(shapeFunction->Count() > 1, + "polygon has wrong number of arguments"); + size_t j = 1; + if (shapeFunction->Item(j).GetUnit() == eCSSUnit_Enumerated) { + StyleFillRule rule; + SetEnumValueHelper::SetEnumeratedValue(rule, shapeFunction->Item(j)); + basicShape->SetFillRule(rule); + ++j; + } + const int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH | + SETCOORD_STORE_CALC; + const nsCSSValuePairList* curPair = + shapeFunction->Item(j).GetPairListValue(); + nsTArray<nsStyleCoord>& coordinates = basicShape->Coordinates(); + while (curPair) { + nsStyleCoord xCoord, yCoord; + DebugOnly<bool> didSetCoordX = SetCoord(curPair->mXValue, xCoord, + nsStyleCoord(), mask, + aStyleContext, aPresContext, + aConditions); + coordinates.AppendElement(xCoord); + MOZ_ASSERT(didSetCoordX, "unexpected x coordinate unit"); + DebugOnly<bool> didSetCoordY = SetCoord(curPair->mYValue, yCoord, + nsStyleCoord(), mask, + aStyleContext, aPresContext, + aConditions); + coordinates.AppendElement(yCoord); + MOZ_ASSERT(didSetCoordY, "unexpected y coordinate unit"); + curPair = curPair->mNext; + } + } else if (functionName == eCSSKeyword_circle || + functionName == eCSSKeyword_ellipse) { + StyleBasicShapeType type = functionName == eCSSKeyword_circle ? + StyleBasicShapeType::Circle : + StyleBasicShapeType::Ellipse; + MOZ_ASSERT(!basicShape, "did not expect value"); + basicShape = new StyleBasicShape(type); + const int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH | + SETCOORD_STORE_CALC | SETCOORD_ENUMERATED; + size_t count = type == StyleBasicShapeType::Circle ? 2 : 3; + MOZ_ASSERT(shapeFunction->Count() == count + 1, + "unexpected arguments count"); + MOZ_ASSERT(type == StyleBasicShapeType::Circle || + (shapeFunction->Item(1).GetUnit() == eCSSUnit_Null) == + (shapeFunction->Item(2).GetUnit() == eCSSUnit_Null), + "ellipse should have two radii or none"); + for (size_t j = 1; j < count; ++j) { + const nsCSSValue& val = shapeFunction->Item(j); + nsStyleCoord radius; + if (val.GetUnit() != eCSSUnit_Null) { + DebugOnly<bool> didSetRadius = SetCoord(val, radius, + nsStyleCoord(), mask, + aStyleContext, + aPresContext, + aConditions); + MOZ_ASSERT(didSetRadius, "unexpected radius unit"); + } else { + radius.SetIntValue(NS_RADIUS_CLOSEST_SIDE, eStyleUnit_Enumerated); + } + basicShape->Coordinates().AppendElement(radius); + } + const nsCSSValue& positionVal = shapeFunction->Item(count); + if (positionVal.GetUnit() == eCSSUnit_Array) { + ComputePositionValue(aStyleContext, positionVal, + basicShape->GetPosition(), + aConditions); + } else { + MOZ_ASSERT(positionVal.GetUnit() == eCSSUnit_Null, + "expected no value"); + } + } else if (functionName == eCSSKeyword_inset) { + MOZ_ASSERT(!basicShape, "did not expect value"); + basicShape = new StyleBasicShape(StyleBasicShapeType::Inset); + MOZ_ASSERT(shapeFunction->Count() == 6, + "inset function has wrong number of arguments"); + MOZ_ASSERT(shapeFunction->Item(1).GetUnit() != eCSSUnit_Null, + "no shape arguments defined"); + const int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH | + SETCOORD_STORE_CALC; + nsTArray<nsStyleCoord>& coords = basicShape->Coordinates(); + for (size_t j = 1; j <= 4; ++j) { + const nsCSSValue& val = shapeFunction->Item(j); + nsStyleCoord inset; + // Fill missing values to get 4 at the end. + if (val.GetUnit() == eCSSUnit_Null) { + if (j == 4) { + inset = coords[1]; + } else { + MOZ_ASSERT(j != 1, "first argument not specified"); + inset = coords[0]; + } + } else { + DebugOnly<bool> didSetInset = SetCoord(val, inset, + nsStyleCoord(), mask, + aStyleContext, aPresContext, + aConditions); + MOZ_ASSERT(didSetInset, "unexpected inset unit"); + } + coords.AppendElement(inset); + } + + nsStyleCorners& insetRadius = basicShape->GetRadius(); + if (shapeFunction->Item(5).GetUnit() == eCSSUnit_Array) { + nsCSSValue::Array* radiiArray = shapeFunction->Item(5).GetArrayValue(); + NS_FOR_CSS_FULL_CORNERS(corner) { + int cx = NS_FULL_TO_HALF_CORNER(corner, false); + int cy = NS_FULL_TO_HALF_CORNER(corner, true); + const nsCSSValue& radius = radiiArray->Item(corner); + nsStyleCoord coordX, coordY; + DebugOnly<bool> didSetRadii = SetPairCoords(radius, coordX, coordY, + nsStyleCoord(), + nsStyleCoord(), mask, + aStyleContext, + aPresContext, + aConditions); + MOZ_ASSERT(didSetRadii, "unexpected radius unit"); + insetRadius.Set(cx, coordX); + insetRadius.Set(cy, coordY); + } + } else { + MOZ_ASSERT(shapeFunction->Item(5).GetUnit() == eCSSUnit_Null, + "unexpected value"); + // Initialize border-radius + nsStyleCoord zero; + zero.SetCoordValue(0); + NS_FOR_CSS_HALF_CORNERS(j) { + insetRadius.Set(j, zero); + } + } + } else { + NS_NOTREACHED("unexpected basic shape function"); + } + + return basicShape.forget(); +} + +template<typename ReferenceBox> +static void +SetStyleShapeSourceToCSSValue( + StyleShapeSource<ReferenceBox>* aShapeSource, + const nsCSSValue* aValue, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + MOZ_ASSERT(aValue->GetUnit() == eCSSUnit_Array, + "expected a basic shape or reference box"); + + const nsCSSValue::Array* array = aValue->GetArrayValue(); + MOZ_ASSERT(array->Count() == 1 || array->Count() == 2, + "Expect one or both of a shape function and a reference box"); + + ReferenceBox referenceBox = ReferenceBox::NoBox; + RefPtr<StyleBasicShape> basicShape; + + for (size_t i = 0; i < array->Count(); ++i) { + const nsCSSValue& item = array->Item(i); + if (item.GetUnit() == eCSSUnit_Enumerated) { + referenceBox = static_cast<ReferenceBox>(item.GetIntValue()); + } else if (item.GetUnit() == eCSSUnit_Function) { + basicShape = GetStyleBasicShapeFromCSSValue(item, aStyleContext, + aPresContext, aConditions); + } else { + MOZ_ASSERT_UNREACHABLE("Unexpected unit!"); + return; + } + } + + if (basicShape) { + aShapeSource->SetBasicShape(basicShape, referenceBox); + } else { + aShapeSource->SetReferenceBox(referenceBox); + } +} + +// Returns true if the nsStyleFilter was successfully set using the nsCSSValue. +static bool +SetStyleFilterToCSSValue(nsStyleFilter* aStyleFilter, + const nsCSSValue& aValue, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + RuleNodeCacheConditions& aConditions) +{ + nsCSSUnit unit = aValue.GetUnit(); + if (unit == eCSSUnit_URL) { + return aStyleFilter->SetURL(aValue.GetURLStructValue()); + } + + MOZ_ASSERT(unit == eCSSUnit_Function, "expected a filter function"); + + nsCSSValue::Array* filterFunction = aValue.GetArrayValue(); + nsCSSKeyword functionName = + (nsCSSKeyword)filterFunction->Item(0).GetIntValue(); + + int32_t type; + DebugOnly<bool> foundKeyword = + nsCSSProps::FindKeyword(functionName, + nsCSSProps::kFilterFunctionKTable, + type); + MOZ_ASSERT(foundKeyword, "unknown filter type"); + if (type == NS_STYLE_FILTER_DROP_SHADOW) { + RefPtr<nsCSSShadowArray> shadowArray = GetShadowData( + filterFunction->Item(1).GetListValue(), + aStyleContext, + false, + aPresContext, + aConditions); + aStyleFilter->SetDropShadow(shadowArray); + return true; + } + + int32_t mask = SETCOORD_PERCENT | SETCOORD_FACTOR; + if (type == NS_STYLE_FILTER_BLUR) { + mask = SETCOORD_LENGTH | + SETCOORD_CALC_LENGTH_ONLY | + SETCOORD_CALC_CLAMP_NONNEGATIVE; + } else if (type == NS_STYLE_FILTER_HUE_ROTATE) { + mask = SETCOORD_ANGLE; + } + + MOZ_ASSERT(filterFunction->Count() == 2, + "all filter functions should have exactly one argument"); + + nsCSSValue& arg = filterFunction->Item(1); + nsStyleCoord filterParameter; + DebugOnly<bool> didSetCoord = SetCoord(arg, filterParameter, + nsStyleCoord(), mask, + aStyleContext, aPresContext, + aConditions); + aStyleFilter->SetFilterParameter(filterParameter, type); + MOZ_ASSERT(didSetCoord, "unexpected unit"); + return true; +} + +const void* +nsRuleNode::ComputeSVGResetData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(SVGReset, svgReset, parentSVGReset) + + // stop-color: + const nsCSSValue* stopColorValue = aRuleData->ValueForStopColor(); + if (eCSSUnit_Initial == stopColorValue->GetUnit() || + eCSSUnit_Unset == stopColorValue->GetUnit()) { + svgReset->mStopColor = NS_RGB(0, 0, 0); + } else { + SetColor(*stopColorValue, parentSVGReset->mStopColor, + mPresContext, aContext, svgReset->mStopColor, conditions); + } + + // flood-color: + const nsCSSValue* floodColorValue = aRuleData->ValueForFloodColor(); + if (eCSSUnit_Initial == floodColorValue->GetUnit() || + eCSSUnit_Unset == floodColorValue->GetUnit()) { + svgReset->mFloodColor = NS_RGB(0, 0, 0); + } else { + SetColor(*floodColorValue, parentSVGReset->mFloodColor, + mPresContext, aContext, svgReset->mFloodColor, conditions); + } + + // lighting-color: + const nsCSSValue* lightingColorValue = aRuleData->ValueForLightingColor(); + if (eCSSUnit_Initial == lightingColorValue->GetUnit() || + eCSSUnit_Unset == lightingColorValue->GetUnit()) { + svgReset->mLightingColor = NS_RGB(255, 255, 255); + } else { + SetColor(*lightingColorValue, parentSVGReset->mLightingColor, + mPresContext, aContext, svgReset->mLightingColor, + conditions); + } + + // clip-path: url, <basic-shape> || <geometry-box>, none, inherit + const nsCSSValue* clipPathValue = aRuleData->ValueForClipPath(); + switch (clipPathValue->GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_None: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + svgReset->mClipPath = StyleClipPath(); + break; + case eCSSUnit_Inherit: + conditions.SetUncacheable(); + svgReset->mClipPath = parentSVGReset->mClipPath; + break; + case eCSSUnit_URL: { + svgReset->mClipPath = StyleClipPath(); + svgReset->mClipPath.SetURL(clipPathValue->GetURLStructValue()); + break; + } + case eCSSUnit_Array: { + svgReset->mClipPath = StyleClipPath(); + SetStyleShapeSourceToCSSValue(&svgReset->mClipPath, clipPathValue, aContext, + mPresContext, conditions); + break; + } + default: + NS_NOTREACHED("unexpected unit"); + } + + // stop-opacity: + SetFactor(*aRuleData->ValueForStopOpacity(), + svgReset->mStopOpacity, conditions, + parentSVGReset->mStopOpacity, 1.0f, + SETFCT_OPACITY | SETFCT_UNSET_INITIAL); + + // flood-opacity: + SetFactor(*aRuleData->ValueForFloodOpacity(), + svgReset->mFloodOpacity, conditions, + parentSVGReset->mFloodOpacity, 1.0f, + SETFCT_OPACITY | SETFCT_UNSET_INITIAL); + + // dominant-baseline: enum, inherit, initial + SetValue(*aRuleData->ValueForDominantBaseline(), + svgReset->mDominantBaseline, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentSVGReset->mDominantBaseline, + NS_STYLE_DOMINANT_BASELINE_AUTO); + + // vector-effect: enum, inherit, initial + SetValue(*aRuleData->ValueForVectorEffect(), + svgReset->mVectorEffect, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentSVGReset->mVectorEffect, + NS_STYLE_VECTOR_EFFECT_NONE); + + // mask-type: enum, inherit, initial + SetValue(*aRuleData->ValueForMaskType(), + svgReset->mMaskType, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentSVGReset->mMaskType, + NS_STYLE_MASK_TYPE_LUMINANCE); + +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + uint32_t maxItemCount = 1; + bool rebuild = false; + + // mask-image: none | <url> | <image-list> | <element-reference> | <gradient> + nsStyleImage initialImage; + SetImageLayerList(aContext, *aRuleData->ValueForMaskImage(), + svgReset->mMask.mLayers, + parentSVGReset->mMask.mLayers, + &nsStyleImageLayers::Layer::mImage, + initialImage, parentSVGReset->mMask.mImageCount, + svgReset->mMask.mImageCount, + maxItemCount, rebuild, conditions); + SetImageLayerList(aContext, *aRuleData->ValueForMaskImage(), + svgReset->mMask.mLayers, + parentSVGReset->mMask.mLayers, + &nsStyleImageLayers::Layer::mSourceURI, + RefPtr<css::URLValueData>(), + parentSVGReset->mMask.mImageCount, + svgReset->mMask.mImageCount, + maxItemCount, rebuild, conditions); + + // mask-repeat: enum, inherit, initial [pair list] + nsStyleImageLayers::Repeat initialRepeat; + initialRepeat.SetInitialValues(); + SetImageLayerPairList(aContext, *aRuleData->ValueForMaskRepeat(), + svgReset->mMask.mLayers, + parentSVGReset->mMask.mLayers, + &nsStyleImageLayers::Layer::mRepeat, + initialRepeat, parentSVGReset->mMask.mRepeatCount, + svgReset->mMask.mRepeatCount, maxItemCount, rebuild, + conditions); + + // mask-clip: enum, inherit, initial [list] + SetImageLayerList(aContext, *aRuleData->ValueForMaskClip(), + svgReset->mMask.mLayers, + parentSVGReset->mMask.mLayers, + &nsStyleImageLayers::Layer::mClip, + uint8_t(NS_STYLE_IMAGELAYER_CLIP_BORDER), + parentSVGReset->mMask.mClipCount, + svgReset->mMask.mClipCount, maxItemCount, rebuild, + conditions); + + // mask-origin: enum, inherit, initial [list] + SetImageLayerList(aContext, *aRuleData->ValueForMaskOrigin(), + svgReset->mMask.mLayers, + parentSVGReset->mMask.mLayers, + &nsStyleImageLayers::Layer::mOrigin, + uint8_t(NS_STYLE_IMAGELAYER_ORIGIN_BORDER), + parentSVGReset->mMask.mOriginCount, + svgReset->mMask.mOriginCount, maxItemCount, rebuild, + conditions); + + // mask-position-x/y: enum, length, percent (flags), inherit [list] + Position::Coord initialPositionCoord; + initialPositionCoord.mPercent = 0.0f; + initialPositionCoord.mLength = 0; + initialPositionCoord.mHasPercent = true; + + SetImageLayerPositionCoordList( + aContext, *aRuleData->ValueForMaskPositionX(), + svgReset->mMask.mLayers, + parentSVGReset->mMask.mLayers, + &Position::mXPosition, + initialPositionCoord, parentSVGReset->mMask.mPositionXCount, + svgReset->mMask.mPositionXCount, maxItemCount, rebuild, + conditions); + SetImageLayerPositionCoordList( + aContext, *aRuleData->ValueForMaskPositionY(), + svgReset->mMask.mLayers, + parentSVGReset->mMask.mLayers, + &Position::mYPosition, + initialPositionCoord, parentSVGReset->mMask.mPositionYCount, + svgReset->mMask.mPositionYCount, maxItemCount, rebuild, + conditions); + + // mask-size: enum, length, auto, inherit, initial [pair list] + nsStyleImageLayers::Size initialSize; + initialSize.SetInitialValues(); + SetImageLayerPairList(aContext, *aRuleData->ValueForMaskSize(), + svgReset->mMask.mLayers, + parentSVGReset->mMask.mLayers, + &nsStyleImageLayers::Layer::mSize, + initialSize, parentSVGReset->mMask.mSizeCount, + svgReset->mMask.mSizeCount, maxItemCount, rebuild, + conditions); + + // mask-mode: enum, inherit, initial [list] + SetImageLayerList(aContext, *aRuleData->ValueForMaskMode(), + svgReset->mMask.mLayers, + parentSVGReset->mMask.mLayers, + &nsStyleImageLayers::Layer::mMaskMode, + uint8_t(NS_STYLE_MASK_MODE_MATCH_SOURCE), + parentSVGReset->mMask.mMaskModeCount, + svgReset->mMask.mMaskModeCount, maxItemCount, rebuild, conditions); + + // mask-composite: enum, inherit, initial [list] + SetImageLayerList(aContext, *aRuleData->ValueForMaskComposite(), + svgReset->mMask.mLayers, + parentSVGReset->mMask.mLayers, + &nsStyleImageLayers::Layer::mComposite, + uint8_t(NS_STYLE_MASK_COMPOSITE_ADD), + parentSVGReset->mMask.mCompositeCount, + svgReset->mMask.mCompositeCount, maxItemCount, rebuild, conditions); + + if (rebuild) { + FillAllBackgroundLists(svgReset->mMask, maxItemCount); + } +#else + // mask: none | <url> + const nsCSSValue* maskValue = aRuleData->ValueForMask(); + if (eCSSUnit_URL == maskValue->GetUnit()) { + svgReset->mMask.mLayers[0].mSourceURI = maskValue->GetURLStructValue(); + } else if (eCSSUnit_None == maskValue->GetUnit() || + eCSSUnit_Initial == maskValue->GetUnit() || + eCSSUnit_Unset == maskValue->GetUnit()) { + svgReset->mMask.mLayers[0].mSourceURI = nullptr; + } else if (eCSSUnit_Inherit == maskValue->GetUnit()) { + conditions.SetUncacheable(); + svgReset->mMask.mLayers[0].mSourceURI = + parentSVGReset->mMask.mLayers[0].mSourceURI; + } +#endif + + COMPUTE_END_RESET(SVGReset, svgReset) +} + +const void* +nsRuleNode::ComputeVariablesData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_INHERITED(Variables, variables, parentVariables) + + MOZ_ASSERT(aRuleData->mVariables, + "shouldn't be in ComputeVariablesData if there were no variable " + "declarations specified"); + + CSSVariableResolver resolver(&variables->mVariables); + resolver.Resolve(&parentVariables->mVariables, + aRuleData->mVariables); + conditions.SetUncacheable(); + + COMPUTE_END_INHERITED(Variables, variables) +} + +const void* +nsRuleNode::ComputeEffectsData(void* aStartStruct, + const nsRuleData* aRuleData, + nsStyleContext* aContext, + nsRuleNode* aHighestNode, + const RuleDetail aRuleDetail, + const RuleNodeCacheConditions aConditions) +{ + COMPUTE_START_RESET(Effects, effects, parentEffects) + + // filter: url, none, inherit + const nsCSSValue* filterValue = aRuleData->ValueForFilter(); + switch (filterValue->GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_None: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + effects->mFilters.Clear(); + break; + case eCSSUnit_Inherit: + conditions.SetUncacheable(); + effects->mFilters = parentEffects->mFilters; + break; + case eCSSUnit_List: + case eCSSUnit_ListDep: { + effects->mFilters.Clear(); + const nsCSSValueList* cur = filterValue->GetListValue(); + while (cur) { + nsStyleFilter styleFilter; + if (!SetStyleFilterToCSSValue(&styleFilter, cur->mValue, aContext, + mPresContext, conditions)) { + effects->mFilters.Clear(); + break; + } + MOZ_ASSERT(styleFilter.GetType() != NS_STYLE_FILTER_NONE, + "filter should be set"); + effects->mFilters.AppendElement(styleFilter); + cur = cur->mNext; + } + break; + } + default: + NS_NOTREACHED("unexpected unit"); + } + + // box-shadow: none, list, inherit, initial + const nsCSSValue* boxShadowValue = aRuleData->ValueForBoxShadow(); + switch (boxShadowValue->GetUnit()) { + case eCSSUnit_Null: + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_None: + effects->mBoxShadow = nullptr; + break; + + case eCSSUnit_Inherit: + effects->mBoxShadow = parentEffects->mBoxShadow; + conditions.SetUncacheable(); + break; + + case eCSSUnit_List: + case eCSSUnit_ListDep: + effects->mBoxShadow = GetShadowData(boxShadowValue->GetListValue(), + aContext, true, mPresContext, conditions); + break; + + default: + MOZ_ASSERT(false, "unrecognized shadow unit"); + } + + // clip property: length, auto, inherit + const nsCSSValue* clipValue = aRuleData->ValueForClip(); + switch (clipValue->GetUnit()) { + case eCSSUnit_Inherit: + conditions.SetUncacheable(); + effects->mClipFlags = parentEffects->mClipFlags; + effects->mClip = parentEffects->mClip; + break; + + case eCSSUnit_Initial: + case eCSSUnit_Unset: + case eCSSUnit_Auto: + effects->mClipFlags = NS_STYLE_CLIP_AUTO; + effects->mClip.SetRect(0,0,0,0); + break; + + case eCSSUnit_Null: + break; + + case eCSSUnit_Rect: { + const nsCSSRect& clipRect = clipValue->GetRectValue(); + + effects->mClipFlags = NS_STYLE_CLIP_RECT; + + if (clipRect.mTop.GetUnit() == eCSSUnit_Auto) { + effects->mClip.y = 0; + effects->mClipFlags |= NS_STYLE_CLIP_TOP_AUTO; + } + else if (clipRect.mTop.IsLengthUnit()) { + effects->mClip.y = CalcLength(clipRect.mTop, aContext, + mPresContext, conditions); + } + + if (clipRect.mBottom.GetUnit() == eCSSUnit_Auto) { + // Setting to NS_MAXSIZE for the 'auto' case ensures that + // the clip rect is nonempty. It is important that mClip be + // nonempty if the actual clip rect could be nonempty. + effects->mClip.height = NS_MAXSIZE; + effects->mClipFlags |= NS_STYLE_CLIP_BOTTOM_AUTO; + } + else if (clipRect.mBottom.IsLengthUnit()) { + effects->mClip.height = CalcLength(clipRect.mBottom, aContext, + mPresContext, conditions) - + effects->mClip.y; + } + + if (clipRect.mLeft.GetUnit() == eCSSUnit_Auto) { + effects->mClip.x = 0; + effects->mClipFlags |= NS_STYLE_CLIP_LEFT_AUTO; + } + else if (clipRect.mLeft.IsLengthUnit()) { + effects->mClip.x = CalcLength(clipRect.mLeft, aContext, + mPresContext, conditions); + } + + if (clipRect.mRight.GetUnit() == eCSSUnit_Auto) { + // Setting to NS_MAXSIZE for the 'auto' case ensures that + // the clip rect is nonempty. It is important that mClip be + // nonempty if the actual clip rect could be nonempty. + effects->mClip.width = NS_MAXSIZE; + effects->mClipFlags |= NS_STYLE_CLIP_RIGHT_AUTO; + } + else if (clipRect.mRight.IsLengthUnit()) { + effects->mClip.width = CalcLength(clipRect.mRight, aContext, + mPresContext, conditions) - + effects->mClip.x; + } + break; + } + + default: + MOZ_ASSERT(false, "unrecognized clip unit"); + } + + // opacity: factor, inherit, initial + SetFactor(*aRuleData->ValueForOpacity(), effects->mOpacity, conditions, + parentEffects->mOpacity, 1.0f, + SETFCT_OPACITY | SETFCT_UNSET_INITIAL); + + // mix-blend-mode: enum, inherit, initial + SetValue(*aRuleData->ValueForMixBlendMode(), effects->mMixBlendMode, + conditions, + SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL, + parentEffects->mMixBlendMode, NS_STYLE_BLEND_NORMAL); + + COMPUTE_END_RESET(Effects, effects) +} + +const void* +nsRuleNode::GetStyleData(nsStyleStructID aSID, + nsStyleContext* aContext, + bool aComputeData) +{ + NS_ASSERTION(IsUsedDirectly(), + "if we ever call this on rule nodes that aren't used " + "directly, we should adjust handling of mDependentBits " + "in some way."); + MOZ_ASSERT(!aContext->GetCachedStyleData(aSID), + "style context should not have cached data for struct"); + + const void *data; + + // Never use cached data for animated style inside a pseudo-element; + // see comment on cacheability in AnimValuesStyleRule::MapRuleInfoInto. + if (!(HasAnimationData() && ParentHasPseudoElementData(aContext))) { + data = mStyleData.GetStyleData(aSID, aContext, aComputeData); + if (MOZ_LIKELY(data != nullptr)) { + // For inherited structs, mark the struct (which will be set on + // the context by our caller) as not being owned by the context. + if (!nsCachedStyleData::IsReset(aSID)) { + aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID)); + } else if (HasAnimationData()) { + // If we have animation data, the struct should be cached on the style + // context so that we can peek the struct. + // See comment in AnimValuesStyleRule::MapRuleInfoInto. + StoreStyleOnContext(aContext, aSID, const_cast<void*>(data)); + } + + return data; // We have a fully specified struct. Just return it. + } + } + + if (MOZ_UNLIKELY(!aComputeData)) + return nullptr; + + // Nothing is cached. We'll have to delve further and examine our rules. + data = WalkRuleTree(aSID, aContext); + + MOZ_ASSERT(data, "should have aborted on out-of-memory"); + return data; +} + +void +nsRuleNode::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, + nsCSSValue* aValue) +{ + for (nsRuleNode* node = this; node; node = node->GetParent()) { + nsIStyleRule* rule = node->GetRule(); + if (!rule) { + continue; + } + if (rule->GetDiscretelyAnimatedCSSValue(aProperty, aValue)) { + return; + } + } +} + +/* static */ bool +nsRuleNode::HasAuthorSpecifiedRules(nsStyleContext* aStyleContext, + uint32_t ruleTypeMask, + bool aAuthorColorsAllowed) +{ +#ifdef MOZ_STYLO + if (aStyleContext->StyleSource().IsServoComputedValues()) { + NS_WARNING("stylo: nsRuleNode::HasAuthorSpecifiedRules not implemented"); + return true; + } +#endif + + uint32_t inheritBits = 0; + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND) + inheritBits |= NS_STYLE_INHERIT_BIT(Background); + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER) + inheritBits |= NS_STYLE_INHERIT_BIT(Border); + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING) + inheritBits |= NS_STYLE_INHERIT_BIT(Padding); + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW) + inheritBits |= NS_STYLE_INHERIT_BIT(Text); + + // properties in the SIDS, whether or not we care about them + size_t nprops = 0, + backgroundOffset, borderOffset, paddingOffset, textShadowOffset; + + // We put the reset properties the start of the nsCSSValue array.... + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND) { + backgroundOffset = nprops; + nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Background); + } + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER) { + borderOffset = nprops; + nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Border); + } + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING) { + paddingOffset = nprops; + nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Padding); + } + + // ...and the inherited properties at the end of the array. + size_t inheritedOffset = nprops; + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW) { + textShadowOffset = nprops; + nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Text); + } + + void* dataStorage = alloca(nprops * sizeof(nsCSSValue)); + AutoCSSValueArray dataArray(dataStorage, nprops); + + /* We're relying on the use of |aStyleContext| not mutating it! */ + nsRuleData ruleData(inheritBits, dataArray.get(), + aStyleContext->PresContext(), aStyleContext); + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND) { + ruleData.mValueOffsets[eStyleStruct_Background] = backgroundOffset; + } + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER) { + ruleData.mValueOffsets[eStyleStruct_Border] = borderOffset; + } + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING) { + ruleData.mValueOffsets[eStyleStruct_Padding] = paddingOffset; + } + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW) { + ruleData.mValueOffsets[eStyleStruct_Text] = textShadowOffset; + } + + static const nsCSSPropertyID backgroundValues[] = { + eCSSProperty_background_color, + eCSSProperty_background_image, + }; + + static const nsCSSPropertyID borderValues[] = { + eCSSProperty_border_top_color, + eCSSProperty_border_top_style, + eCSSProperty_border_top_width, + eCSSProperty_border_right_color, + eCSSProperty_border_right_style, + eCSSProperty_border_right_width, + eCSSProperty_border_bottom_color, + eCSSProperty_border_bottom_style, + eCSSProperty_border_bottom_width, + eCSSProperty_border_left_color, + eCSSProperty_border_left_style, + eCSSProperty_border_left_width, + eCSSProperty_border_top_left_radius, + eCSSProperty_border_top_right_radius, + eCSSProperty_border_bottom_right_radius, + eCSSProperty_border_bottom_left_radius, + }; + + static const nsCSSPropertyID paddingValues[] = { + eCSSProperty_padding_top, + eCSSProperty_padding_right, + eCSSProperty_padding_bottom, + eCSSProperty_padding_left, + }; + + static const nsCSSPropertyID textShadowValues[] = { + eCSSProperty_text_shadow + }; + + // Number of properties we care about + size_t nValues = 0; + + nsCSSValue* values[MOZ_ARRAY_LENGTH(backgroundValues) + + MOZ_ARRAY_LENGTH(borderValues) + + MOZ_ARRAY_LENGTH(paddingValues) + + MOZ_ARRAY_LENGTH(textShadowValues)]; + + nsCSSPropertyID properties[MOZ_ARRAY_LENGTH(backgroundValues) + + MOZ_ARRAY_LENGTH(borderValues) + + MOZ_ARRAY_LENGTH(paddingValues) + + MOZ_ARRAY_LENGTH(textShadowValues)]; + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND) { + for (uint32_t i = 0, i_end = ArrayLength(backgroundValues); + i < i_end; ++i) { + properties[nValues] = backgroundValues[i]; + values[nValues++] = ruleData.ValueFor(backgroundValues[i]); + } + } + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER) { + for (uint32_t i = 0, i_end = ArrayLength(borderValues); + i < i_end; ++i) { + properties[nValues] = borderValues[i]; + values[nValues++] = ruleData.ValueFor(borderValues[i]); + } + } + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING) { + for (uint32_t i = 0, i_end = ArrayLength(paddingValues); + i < i_end; ++i) { + properties[nValues] = paddingValues[i]; + values[nValues++] = ruleData.ValueFor(paddingValues[i]); + } + } + + if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW) { + for (uint32_t i = 0, i_end = ArrayLength(textShadowValues); + i < i_end; ++i) { + properties[nValues] = textShadowValues[i]; + values[nValues++] = ruleData.ValueFor(textShadowValues[i]); + } + } + + nsStyleContext* styleContext = aStyleContext; + + // We need to be careful not to count styles covered up by user-important or + // UA-important declarations. But we do want to catch explicit inherit + // styling in those and check our parent style context to see whether we have + // user styling for those properties. Note that we don't care here about + // inheritance due to lack of a specified value, since all the properties we + // care about are reset properties. + bool haveExplicitUAInherit; + do { + haveExplicitUAInherit = false; + for (nsRuleNode* ruleNode = styleContext->RuleNode(); ruleNode; + ruleNode = ruleNode->GetParent()) { + nsIStyleRule *rule = ruleNode->GetRule(); + if (rule) { + ruleData.mLevel = ruleNode->GetLevel(); + ruleData.mIsImportantRule = ruleNode->IsImportantRule(); + + rule->MapRuleInfoInto(&ruleData); + + if (ruleData.mLevel == SheetType::Agent || + ruleData.mLevel == SheetType::User) { + // This is a rule whose effect we want to ignore, so if any of + // the properties we care about were set, set them to the dummy + // value that they'll never otherwise get. + for (uint32_t i = 0; i < nValues; ++i) { + nsCSSUnit unit = values[i]->GetUnit(); + if (unit != eCSSUnit_Null && + unit != eCSSUnit_Dummy && + unit != eCSSUnit_DummyInherit) { + if (unit == eCSSUnit_Inherit || + (i >= inheritedOffset && unit == eCSSUnit_Unset)) { + haveExplicitUAInherit = true; + values[i]->SetDummyInheritValue(); + } else { + values[i]->SetDummyValue(); + } + } + } + } else { + // If any of the values we care about was set by the above rule, + // we have author style. + for (uint32_t i = 0; i < nValues; ++i) { + if (values[i]->GetUnit() != eCSSUnit_Null && + values[i]->GetUnit() != eCSSUnit_Dummy && // see above + values[i]->GetUnit() != eCSSUnit_DummyInherit) { + // If author colors are not allowed, only claim to have + // author-specified rules if we're looking at a non-color + // property or if we're looking at the background color and it's + // set to transparent. Anything else should get set to a dummy + // value instead. + if (aAuthorColorsAllowed || + !nsCSSProps::PropHasFlags(properties[i], + CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED) || + (properties[i] == eCSSProperty_background_color && + !values[i]->IsNonTransparentColor())) { + return true; + } + + values[i]->SetDummyValue(); + } + } + } + } + } + + if (haveExplicitUAInherit) { + // reset all the eCSSUnit_Null values to eCSSUnit_Dummy (since they're + // not styled by the author, or by anyone else), and then reset all the + // eCSSUnit_DummyInherit values to eCSSUnit_Null (so we will be able to + // detect them being styled by the author) and move up to our parent + // style context. + for (uint32_t i = 0; i < nValues; ++i) + if (values[i]->GetUnit() == eCSSUnit_Null) + values[i]->SetDummyValue(); + for (uint32_t i = 0; i < nValues; ++i) + if (values[i]->GetUnit() == eCSSUnit_DummyInherit) + values[i]->Reset(); + styleContext = styleContext->GetParent(); + } + } while (haveExplicitUAInherit && styleContext); + + return false; +} + +/* static */ void +nsRuleNode::ComputePropertiesOverridingAnimation( + const nsTArray<nsCSSPropertyID>& aProperties, + nsStyleContext* aStyleContext, + nsCSSPropertyIDSet& aPropertiesOverridden) +{ + /* + * Set up an nsRuleData with all the structs needed for all of the + * properties in aProperties. + */ + uint32_t structBits = 0; + size_t nprops = 0; + size_t offsets[nsStyleStructID_Length]; + for (size_t propIdx = 0, propEnd = aProperties.Length(); + propIdx < propEnd; ++propIdx) { + nsCSSPropertyID prop = aProperties[propIdx]; + nsStyleStructID sid = nsCSSProps::kSIDTable[prop]; + uint32_t bit = nsCachedStyleData::GetBitForSID(sid); + if (!(structBits & bit)) { + structBits |= bit; + offsets[sid] = nprops; + nprops += nsCSSProps::PropertyCountInStruct(sid); + } + } + + void* dataStorage = alloca(nprops * sizeof(nsCSSValue)); + AutoCSSValueArray dataArray(dataStorage, nprops); + + // We're relying on the use of |aStyleContext| not mutating it! + nsRuleData ruleData(structBits, dataArray.get(), + aStyleContext->PresContext(), aStyleContext); + for (nsStyleStructID sid = nsStyleStructID(0); + sid < nsStyleStructID_Length; sid = nsStyleStructID(sid + 1)) { + if (structBits & nsCachedStyleData::GetBitForSID(sid)) { + ruleData.mValueOffsets[sid] = offsets[sid]; + } + } + + /* + * Actually walk up the rule tree until we're someplace less + * specific than animations. + */ + for (nsRuleNode* ruleNode = aStyleContext->RuleNode(); ruleNode; + ruleNode = ruleNode->GetParent()) { + nsIStyleRule *rule = ruleNode->GetRule(); + if (rule) { + ruleData.mLevel = ruleNode->GetLevel(); + ruleData.mIsImportantRule = ruleNode->IsImportantRule(); + + // Transitions are the only non-!important level overriding + // animations in the cascade ordering. They also don't actually + // override animations, since transitions are suppressed when both + // are present. And since we might not have called + // UpdateCascadeResults (which updates when they are suppressed + // due to the presence of animations for the same element and + // property) for transitions yet (which will make their + // MapRuleInfoInto skip the properties that are currently + // animating), we should skip them explicitly. + if (ruleData.mLevel == SheetType::Transition) { + continue; + } + + if (!ruleData.mIsImportantRule) { + // We're now equal to or less than the animation level; stop. + break; + } + + rule->MapRuleInfoInto(&ruleData); + } + } + + /* + * Fill in which properties were overridden. + */ + for (size_t propIdx = 0, propEnd = aProperties.Length(); + propIdx < propEnd; ++propIdx) { + nsCSSPropertyID prop = aProperties[propIdx]; + if (ruleData.ValueFor(prop)->GetUnit() != eCSSUnit_Null) { + aPropertiesOverridden.AddProperty(prop); + } + } +} + +/* static */ +bool +nsRuleNode::ComputeColor(const nsCSSValue& aValue, nsPresContext* aPresContext, + nsStyleContext* aStyleContext, nscolor& aResult) +{ + MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Inherit, + "aValue shouldn't have eCSSUnit_Inherit"); + MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Initial, + "aValue shouldn't have eCSSUnit_Initial"); + MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Unset, + "aValue shouldn't have eCSSUnit_Unset"); + + RuleNodeCacheConditions conditions; + bool ok = SetColor(aValue, NS_RGB(0, 0, 0), aPresContext, aStyleContext, + aResult, conditions); + MOZ_ASSERT(ok || !(aPresContext && aStyleContext)); + return ok; +} + +/* static */ bool +nsRuleNode::ParentHasPseudoElementData(nsStyleContext* aContext) +{ + nsStyleContext* parent = aContext->GetParent(); + return parent && parent->HasPseudoElementData(); +} + +/* static */ void +nsRuleNode::StoreStyleOnContext(nsStyleContext* aContext, + nsStyleStructID aSID, + void* aStruct) +{ + aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID)); + aContext->SetStyle(aSID, aStruct); +} + +#ifdef DEBUG +bool +nsRuleNode::ContextHasCachedData(nsStyleContext* aContext, + nsStyleStructID aSID) +{ + return !!aContext->GetCachedStyleData(aSID); +} +#endif |