Bug 1273706 - Part 26: Implement lazy computation of computed CSS registered property values on CSSVariableValues. r?heycam
MozReview-Commit-ID: JmFECk2q1dH
--- a/layout/style/CSSVariableValues.cpp
+++ b/layout/style/CSSVariableValues.cpp
@@ -3,16 +3,18 @@
* 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/. */
/* computed CSS Variable values */
#include "mozilla/CSSVariableValues.h"
#include "mozilla/CSSVariableResolver.h"
+#include "nsStyleContext.h"
+#include "nsPresContext.h"
namespace mozilla {
CSSVariableValues::CSSVariableValues()
{
MOZ_COUNT_CTOR(CSSVariableValues);
}
@@ -59,16 +61,24 @@ CSSVariableValues::operator==(const CSSV
if (!mVariables[thisIndex].mExpr.Equals(otherExpr)) {
return false;
}
}
return true;
}
+void
+CSSVariableValues::SetContext(nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext)
+{
+ mStyleContext = aStyleContext;
+ mPresContext = aPresContext;
+}
+
size_t
CSSVariableValues::Count() const
{
return mVariables.Length();
}
bool
CSSVariableValues::Get(const nsAString& aName, nsAString& aExpr) const
@@ -97,16 +107,58 @@ CSSVariableValues::Get(const nsAString&
aType = mVariables[id].mType;
aContext = mVariables[id].mContext;
aFirstToken = mVariables[id].mFirstToken;
aLastToken = mVariables[id].mLastToken;
return true;
}
bool
+CSSVariableValues::Compute(const nsAString& aName,
+ const CSSComputedValue** aValue) const
+{
+ size_t id;
+ if (!mVariableIDs.Get(aName, &id)) {
+ return false;
+ }
+ if (!mVariables[id].mComputed.isSome()) {
+ MOZ_ASSERT(mStyleContext && mPresContext);
+
+ const nsCSSValue& value = mVariables[id].mValue;
+ const CSSValueType& type = mVariables[id].mType;
+ if (value.GetUnit() == eCSSUnit_List) {
+ const nsCSSValueList* inTerms = value.GetListValue();
+ MOZ_ASSERT(inTerms,
+ "X+ requires one or more terms. Should have been caught by "
+ "ParseTypedValue.");
+ nsTArray<CSSComputedValue::SingleTerm> outTerms;
+ while (inTerms) {
+ auto term = ComputeCSSValueTerm(inTerms->mValue, type, mStyleContext,
+ mPresContext);
+ MOZ_ASSERT(term.GetType() == type);
+ outTerms.AppendElement(term);
+ inTerms = inTerms->mNext;
+ }
+
+ mVariables[id].mComputed =
+ Some(CSSComputedValue(CSSComputedValue::List(Move(outTerms))));
+ } else {
+ CSSComputedValue::SingleTerm term =
+ ComputeCSSValueTerm(value, type, mStyleContext, mPresContext);
+ MOZ_ASSERT(term.GetType() == type);
+
+ mVariables[id].mComputed = Some(CSSComputedValue(Move(term)));
+ }
+ }
+ // We know the Maybe isn't going to become None after this.
+ *aValue = mVariables[id].mComputed.ptr();
+ return true;
+}
+
+bool
CSSVariableValues::Has(const nsAString& aName) const
{
return mVariableIDs.Get(aName, nullptr);
}
void
CSSVariableValues::GetVariableAt(size_t aIndex, nsAString& aName) const
{
@@ -115,16 +167,18 @@ CSSVariableValues::GetVariableAt(size_t
void
CSSVariableValues::Put(const nsAString& aName, nsString aExpr,
nsCSSValue aValue, CSSValueType aType,
CSSVariableExprContext aContext,
nsCSSTokenSerializationType aFirstToken,
nsCSSTokenSerializationType aLastToken)
{
+ MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Null);
+ MOZ_ASSERT(!aExpr.IsEmpty());
size_t id;
if (mVariableIDs.Get(aName, &id)) {
mVariables[id].mExpr = aExpr;
mVariables[id].mValue = aValue;
mVariables[id].mType = aType;
mVariables[id].mContext = aContext;
mVariables[id].mFirstToken = aFirstToken;
mVariables[id].mLastToken = aLastToken;
--- a/layout/style/CSSVariableValues.h
+++ b/layout/style/CSSVariableValues.h
@@ -6,16 +6,17 @@
/* computed CSS Variable values */
#ifndef mozilla_CSSVariableValues_h
#define mozilla_CSSVariableValues_h
#include "mozilla/CSSComputedValue.h"
#include "mozilla/CSSValueType.h"
#include "mozilla/CSSVariableRegistration.h"
+#include "mozilla/RefPtr.h"
#include "nsCSSScanner.h"
#include "nsCSSValue.h"
#include "nsDataHashtable.h"
#include "nsTArray.h"
namespace mozilla {
class CSSVariableResolver;
@@ -30,28 +31,47 @@ public:
#endif
CSSVariableValues& operator=(const CSSVariableValues& aOther);
bool operator==(const CSSVariableValues& aOther) const;
bool operator!=(const CSSVariableValues& aOther) const
{ return !(*this == aOther); }
/**
+ * Sets the nsStyleContext and nsPresContext used by this object for
+ * computing variable values.
+ *
+ * It is an error to call this more than once.
+ */
+ void SetContext(nsStyleContext* aStyleContext, nsPresContext* aPresContext);
+
+ /**
* Gets the value of a variable in this set of computed variables.
*
* @param aName The variable name (not including any "--" prefix that would
* be part of the custom property name).
* @param aExpr Out parameter into which the expression of the variable will
* be stored.
* @return Whether a variable with the given name was found. When false
* is returned, aExpr will not be modified.
*/
bool Get(const nsAString& aName, nsAString& aExpr) const;
/**
+ * Lazily computes and gets the CSSComputedValue for the variable with the
+ * given name.
+ *
+ * @param aName The variable name (sans "--" prefix).
+ * @return True iff |aName| corresponds to a registered custom property.
+ * Computed values of unregistered variables are just their token stream
+ * values, which can be retrieved using Get.
+ */
+ bool Compute(const nsAString& aName, const CSSComputedValue** aValue) const;
+
+ /**
* Gets the value of a variable in this set of computed variables, along
* with information on the types of tokens that appear at the start and
* end of the token stream.
*
* @param aName The variable name (not including any "--" prefix that would
* be part of the custom property name).
* @param aExpr Out parameter into which the expression of the variable will
* be stored.
@@ -139,22 +159,26 @@ private:
, mContext(aContext)
, mFirstToken(aFirstToken)
, mLastToken(aLastToken)
{}
nsString mVariableName;
nsString mExpr;
+
nsCSSValue mValue;
CSSValueType mType;
CSSVariableExprContext mContext;
nsCSSTokenSerializationType mFirstToken;
nsCSSTokenSerializationType mLastToken;
+
+ // mutable -- lazily computed.
+ mutable Maybe<CSSComputedValue> mComputed;
};
/**
* Adds all the variables from aOther into this object.
*/
void CopyVariablesFrom(const CSSVariableValues& aOther);
/**
@@ -162,13 +186,25 @@ private:
* mVariables.
*/
nsDataHashtable<nsStringHashKey, size_t> mVariableIDs;
/**
* Array of variables, indexed by variable ID.
*/
nsTArray<Variable> mVariables;
+
+ /**
+ * nsStyleContext used to compute variable values.
+ * It's a regular pointer because we expect that this is owned by the style context:
+ * nsStyleContext -> nsStyleVariables -> CSSVariableValues
+ */
+ nsStyleContext* mStyleContext;
+
+ /**
+ * nsPresContext used to compute variable values.
+ */
+ RefPtr<nsPresContext> mPresContext;
};
} // namespace mozilla
#endif
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -639,29 +639,32 @@ struct LengthPercentPairCalcOps : public
typedef nsRuleNode::ComputedCalc result_type;
LengthPercentPairCalcOps(nsStyleContext* aContext,
nsPresContext* aPresContext,
RuleNodeCacheConditions& aConditions)
: mContext(aContext),
mPresContext(aPresContext),
mConditions(aConditions),
- mHasPercent(false) {}
+ mHasPercent(false),
+ mHasLength(false) {}
nsStyleContext* mContext;
nsPresContext* mPresContext;
RuleNodeCacheConditions& mConditions;
bool mHasPercent;
+ bool mHasLength;
result_type ComputeLeafValue(const nsCSSValue& aValue)
{
if (aValue.GetUnit() == eCSSUnit_Percent) {
mHasPercent = true;
return result_type(0, aValue.GetPercentValue());
}
+ mHasLength = true;
return result_type(CalcLength(aValue, mContext, mPresContext,
mConditions),
0.0f);
}
result_type
MergeAdditive(nsCSSUnit aCalcFunction,
result_type aValue1, result_type aValue2)
@@ -10202,27 +10205,203 @@ nsRuleNode::ComputeVariablesData(void* a
specified = new CSSVariableDeclarations(nullptr);
} else {
specified = aRuleData->mVariables;
}
const CSSVariableRegistrations* registrations =
CSSVariableRegistrationsOfStyleContext(aContext);
CSSVariableResolver resolver(&variables->mVariables, registrations);
+ variables->mVariables.SetContext(aContext, mPresContext);
resolver.Resolve(&parentVariables->mVariables, specified, variables->mHasUninherited);
if (!aRuleData->mVariables)
delete specified;
conditions.SetUncacheable();
COMPUTE_END_INHERITED(Variables, variables)
}
+namespace mozilla {
+
+CSSComputedValue::SingleTerm
+ComputeCSSValueTerm(const nsCSSValue& aValue, CSSValueType aType,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext)
+{
+ MOZ_ASSERT(!(aValue.GetUnit() >= eCSSUnit_Inherit &&
+ aValue.GetUnit() <= eCSSUnit_Unset),
+ "These values should have been resolved by the variable resolver.");
+ MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_List);
+
+ // We don't actually keep track of these. The Variables struct is always set
+ // to uncacheable. In the future, this could be optimized.
+ RuleNodeCacheConditions conditions;
+
+ switch (aType) {
+ case CSSValueType::Number:
+ if (aValue.GetUnit() == eCSSUnit_Calc) {
+ mozilla::css::ReduceNumberCalcOps ops;
+ float number = mozilla::css::ComputeCalc(aValue, ops);
+ return CSSComputedValue::Number(nsCSSValue(number, eCSSUnit_Number));
+ } else {
+ return CSSComputedValue::Number(aValue);
+ }
+ case CSSValueType::Length:
+ case CSSValueType::Percentage:
+ case CSSValueType::LengthPercentage:
+ // This is the messiest case, but it's actually pretty simple.
+ // It just looks messy because of the overlap between values that can be
+ // <length>, <percentage>, and <length-percentage>.
+ if (aValue.IsLengthUnit()) {
+ nscoord computed = nsRuleNode::CalcLength(aValue, aStyleContext,
+ aPresContext, conditions);
+ nsStyleCoord coord;
+ coord.SetCoordValue(computed);
+ switch (aType) {
+ case CSSValueType::Length:
+ return CSSComputedValue::Length(coord);
+ case CSSValueType::LengthPercentage:
+ return CSSComputedValue::LengthPercentage(coord);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Shouldn't have parsed as a length.");
+ }
+ }
+ if (aValue.GetUnit() == eCSSUnit_Percent) {
+ nsStyleCoord coord(aValue.GetPercentValue(), eStyleUnit_Percent);
+ switch (aType) {
+ case CSSValueType::Percentage:
+ return CSSComputedValue::Percentage(coord);
+ case CSSValueType::LengthPercentage:
+ return CSSComputedValue::LengthPercentage(coord);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Shouldn't have parsed as a percentage.");
+ }
+ }
+ // <length>s and <percentage>s can both be calc()s.
+ // See CSSParserImpl::ParseCalc.
+ if (aValue.GetUnit() == eCSSUnit_Calc) {
+ LengthPercentPairCalcOps ops(aStyleContext, aPresContext, conditions);
+ nsRuleNode::ComputedCalc computed = ComputeCalc(aValue, ops);
+ switch (aType) {
+ case CSSValueType::Length: {
+ MOZ_ASSERT(computed.mPercent == 0);
+ nsStyleCoord coord;
+ coord.SetCoordValue(computed.mLength);
+ return CSSComputedValue::Length(Move(coord));
+ }
+ case CSSValueType::Percentage:
+ MOZ_ASSERT(computed.mLength == 0);
+ return CSSComputedValue::Percentage(nsStyleCoord(computed.mPercent,
+ eStyleUnit_Percent));
+ case CSSValueType::LengthPercentage: {
+ nsStyleCoord coord;
+ if (ops.mHasPercent && ops.mHasLength) {
+ nsStyleCoord::Calc* calc = new nsStyleCoord::Calc;
+ calc->mLength = computed.mLength;
+ calc->mPercent = computed.mPercent;
+ calc->mHasPercent = true;
+ coord.SetCalcValue(calc);
+ } else if (ops.mHasPercent) {
+ coord.SetPercentValue(computed.mPercent);
+ } else if (ops.mHasLength) {
+ coord.SetCoordValue(computed.mLength);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("This means we parsed an empty calc "
+ "expression as valid.");
+ }
+ return CSSComputedValue::LengthPercentage(coord);
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Shouldn't be in this switch case!");
+ }
+ }
+ break;
+ case CSSValueType::Color: {
+ if (aValue.GetUnit() == eCSSUnit_EnumColor &&
+ aValue.GetIntValue() == NS_COLOR_CURRENTCOLOR) {
+ // currentColor. Set nsCSSValue as-is.
+ return CSSComputedValue::Color(aValue, NS_COLOR_CURRENTCOLOR);
+ }
+ nscolor color;
+ MOZ_ALWAYS_TRUE(nsRuleNode::ComputeColor(aValue, aPresContext,
+ aStyleContext, color));
+ return CSSComputedValue::Color(aValue, color);
+ }
+ case CSSValueType::Image: {
+ if (aValue.GetUnit() == eCSSUnit_URL) {
+ return CSSComputedValue::Image(aValue);
+ } else {
+ nsStyleImage image;
+ SetStyleImage(aStyleContext, aValue, image, conditions);
+ return CSSComputedValue::Image(image);
+ }
+ }
+ case CSSValueType::URL:
+ return CSSComputedValue::URL(aValue);
+ case CSSValueType::Integer:
+ if (aValue.GetUnit() == eCSSUnit_Calc) {
+ mozilla::css::ReduceIntegerCalcOps ops;
+ int integer = mozilla::css::ComputeCalc(aValue, ops);
+ return CSSComputedValue::Integer(nsStyleCoord(integer,
+ eStyleUnit_Integer));
+ } else {
+ return CSSComputedValue::Integer(nsStyleCoord(aValue.GetIntValue(),
+ eStyleUnit_Integer));
+ }
+ case CSSValueType::Angle:
+ return CSSComputedValue::Angle(aValue);
+ case CSSValueType::Time:
+ return CSSComputedValue::Time(aValue);
+ case CSSValueType::Resolution:
+ return CSSComputedValue::Resolution(aValue);
+ case CSSValueType::TransformFunction: {
+ const nsCSSValue::Array* input = aValue.GetArrayValue();
+ nsCSSKeyword keyword = input->Item(0).GetKeywordValue();
+ nsCSSValue outputVal;
+ nsCSSValue::Array* output = outputVal.InitFunction(keyword, input->Count() - 1);
+
+ for (size_t argID = 0; argID < input->Count() - 1; argID++) {
+ const nsCSSValue& arg = input->Item(1 + argID);
+ // <transform-function> arguments can currently be <length>s,
+ // <number>s, and <angle>s.
+ if (arg.IsLengthUnit()) {
+ nscoord computed = nsRuleNode::CalcLength(arg, aStyleContext,
+ aPresContext, conditions);
+ output->Item(1 + argID) =
+ nsCSSValue(nsPresContext::AppUnitsToFloatCSSPixels(computed),
+ eCSSUnit_Pixel);
+ } else if (arg.IsAngularUnit()) {
+ output->Item(1 + argID) = arg;
+ } else {
+ MOZ_ASSERT(arg.GetUnit() == eCSSUnit_Number,
+ "Currently, <transform-function> arguments can only be "
+ "<length>s, <number>s, or <angle>s.");
+ output->Item(1 + argID) = arg;
+ }
+ }
+ return CSSComputedValue::TransformFunction(Move(outputVal));
+ }
+ case CSSValueType::CustomIdent:
+ return CSSComputedValue::CustomIdent(aValue);
+ case CSSValueType::SpecificIdent:
+ return CSSComputedValue::SpecificIdent(aValue);
+ case CSSValueType::TokenStream:
+ return CSSComputedValue::TokenStream(aValue);
+ case CSSValueType::COUNT:
+ MOZ_ASSERT_UNREACHABLE("COUNT isn't a real value type.");
+ }
+ MOZ_ASSERT_UNREACHABLE("Cases must be exhaustive.");
+ return CSSComputedValue::TokenStream(nsCSSValue());
+}
+
+} // namespace mozilla
+
const void*
nsRuleNode::ComputeEffectsData(void* aStartStruct,
const nsRuleData* aRuleData,
nsStyleContext* aContext,
nsRuleNode* aHighestNode,
const RuleDetail aRuleDetail,
const RuleNodeCacheConditions aConditions)
{
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -25,16 +25,25 @@ class nsCSSValue;
class nsIStyleRule;
class nsStyleContext;
class nsStyleCoord;
struct nsCSSRect;
struct nsCSSValueList;
struct nsCSSValuePairList;
struct nsRuleData;
+namespace mozilla {
+
+CSSComputedValue::SingleTerm
+ComputeCSSValueTerm(const nsCSSValue& aValue, CSSValueType aType,
+ nsStyleContext* aStyleContext,
+ nsPresContext* aPresContext);
+
+} // namespace mozilla
+
struct nsInheritedStyleData
{
mozilla::RangedArray<void*,
nsStyleStructID_Inherited_Start,
nsStyleStructID_Inherited_Count> mStyleStructs;
void* operator new(size_t sz, nsPresContext* aContext) CPP_THROW_NEW {
return aContext->PresShell()->