From 0bd2b965c3b9be5de6cb2c0597f03d6413ac6b56 Mon Sep 17 00:00:00 2001 From: Max Vujovic Date: Mon, 22 Jul 2013 15:08:33 -0700 Subject: [PATCH] Bug 895182 - [CSS Filters] Implement parsing for blur, brightness, contrast, grayscale, invert, opacity, saturate, sepia. Co-authored with Dirk Schulze (krit). r=heycam --- .../en-US/chrome/layout/css.properties | 5 + layout/base/nsLayoutUtils.cpp | 16 ++ layout/base/nsLayoutUtils.h | 5 + layout/generic/nsFrame.cpp | 2 +- layout/style/nsCSSKeywordList.h | 8 + layout/style/nsCSSParser.cpp | 144 ++++++++++++++ layout/style/nsCSSPropList.h | 4 +- layout/style/nsCSSProps.h | 5 +- layout/style/nsComputedDOMStyle.cpp | 93 ++++++++- layout/style/nsComputedDOMStyle.h | 5 + layout/style/nsRuleNode.cpp | 98 +++++++++- layout/style/nsStyleStruct.cpp | 49 ++++- layout/style/nsStyleStruct.h | 37 +++- layout/style/test/property_database.js | 177 ++++++++++++++++++ layout/svg/nsSVGEffects.cpp | 6 +- layout/svg/nsSVGIntegrationUtils.cpp | 2 +- layout/svg/nsSVGUtils.cpp | 2 +- modules/libpref/src/init/all.js | 3 + 18 files changed, 630 insertions(+), 31 deletions(-) diff --git a/dom/locales/en-US/chrome/layout/css.properties b/dom/locales/en-US/chrome/layout/css.properties index 8a476f87d974..8deeea9d3e05 100644 --- a/dom/locales/en-US/chrome/layout/css.properties +++ b/dom/locales/en-US/chrome/layout/css.properties @@ -137,3 +137,8 @@ PESupportsConditionExpectedCloseParen=Expected ')' while parsing supports condit PESupportsConditionExpectedStart2=Expected 'not', '(', or function while parsing supports condition but found '%1$S'. PESupportsConditionExpectedNot=Expected 'not' while parsing supports condition but found '%1$S'. PESupportsGroupRuleStart=Expected '{' to begin @supports rule but found '%1$S'. +PEFilterEOF=filter +PEExpectedNoneOrURL=Expected 'none' or URL but found '%1$S'. +PEExpectedNoneOrURLOrFilterFunction=Expected 'none', URL, or filter function but found '%1$S'. +PEExpectedNonnegativeNP=Expected non-negative number or percentage. +PEFilterFunctionArgumentsParsingError=Error in parsing arguments for filter function. diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 7acf55bd140d..dddbf7941f4e 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -384,6 +384,22 @@ nsLayoutUtils::AnimatedImageLayersEnabled() return sAnimatedImageLayersEnabled; } +bool +nsLayoutUtils::CSSFiltersEnabled() +{ + static bool sCSSFiltersEnabled; + static bool sCSSFiltersPrefCached = false; + + if (!sCSSFiltersPrefCached) { + sCSSFiltersPrefCached = true; + Preferences::AddBoolVarCache(&sCSSFiltersEnabled, + "layout.css.filters.enabled", + false); + } + + return sCSSFiltersEnabled; +} + void nsLayoutUtils::UnionChildOverflow(nsIFrame* aFrame, nsOverflowAreas& aOverflowAreas) diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 0efdf5d7e0c9..a6ad34415365 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -1622,6 +1622,11 @@ public: */ static bool AnimatedImageLayersEnabled(); + /** + * Checks if we should enable parsing for CSS Filters. + */ + static bool CSSFiltersEnabled(); + /** * Unions the overflow areas of all non-popup children of aFrame with * aOverflowAreas. diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 83d2f8bc6294..a17f4cfaccb8 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -4964,7 +4964,7 @@ ComputeOutlineAndEffectsRect(nsIFrame* aFrame, // For SVG frames, we only need to account for filters. // TODO: We could also take account of clipPath and mask to reduce the // visual overflow, but that's not essential. - if (aFrame->StyleSVGReset()->mFilter) { + if (aFrame->StyleSVGReset()->SingleFilter()) { if (aStoreRectProperties) { aFrame->Properties(). Set(nsIFrame::PreEffectsBBoxProperty(), new nsRect(r)); diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index 3dab8884994a..0205580ac601 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -182,6 +182,7 @@ CSS_KEY(bidi-override, bidi_override) CSS_KEY(blink, blink) CSS_KEY(block, block) CSS_KEY(block-axis, block_axis) +CSS_KEY(blur, blur) CSS_KEY(bold, bold) CSS_KEY(bolder, bolder) CSS_KEY(border-box, border_box) @@ -191,6 +192,7 @@ CSS_KEY(bottom-outside, bottom_outside) CSS_KEY(bounding-box, bounding_box) CSS_KEY(break-all, break_all) CSS_KEY(break-word, break_word) +CSS_KEY(brightness, brightness) CSS_KEY(button, button) CSS_KEY(buttonface, buttonface) CSS_KEY(buttonhighlight, buttonhighlight) @@ -220,6 +222,7 @@ CSS_KEY(contain, contain) CSS_KEY(content-box, content_box) CSS_KEY(context-menu, context_menu) CSS_KEY(continuous, continuous) +CSS_KEY(contrast, contrast) CSS_KEY(copy, copy) CSS_KEY(contextual, contextual) CSS_KEY(cover, cover) @@ -270,6 +273,7 @@ CSS_KEY(forwards, forwards) CSS_KEY(full-width, full_width) CSS_KEY(georgian, georgian) CSS_KEY(grad, grad) +CSS_KEY(grayscale, grayscale) CSS_KEY(graytext, graytext) CSS_KEY(groove, groove) CSS_KEY(hebrew, hebrew) @@ -306,6 +310,7 @@ CSS_KEY(inline-table, inline_table) CSS_KEY(inset, inset) CSS_KEY(inside, inside) CSS_KEY(interpolatematrix, interpolatematrix) +CSS_KEY(invert, invert) CSS_KEY(italic, italic) CSS_KEY(jis78, jis78) CSS_KEY(jis83, jis83) @@ -369,6 +374,7 @@ CSS_KEY(nw-resize, nw_resize) CSS_KEY(nwse-resize, nwse_resize) CSS_KEY(oblique, oblique) CSS_KEY(oldstyle-nums, oldstyle_nums) +CSS_KEY(opacity, opacity) CSS_KEY(open-quote, open_quote) CSS_KEY(ordinal, ordinal) CSS_KEY(ornaments, ornaments) @@ -418,6 +424,7 @@ CSS_KEY(ruby, ruby) CSS_KEY(running, running) CSS_KEY(s, s) CSS_KEY(s-resize, s_resize) +CSS_KEY(saturate, saturate) CSS_KEY(scale, scale) CSS_KEY(scale3d, scale3d) CSS_KEY(scalex, scalex) @@ -435,6 +442,7 @@ CSS_KEY(select-same, select_same) CSS_KEY(semi-condensed, semi_condensed) CSS_KEY(semi-expanded, semi_expanded) CSS_KEY(separate, separate) +CSS_KEY(sepia, sepia) CSS_KEY(show, show) CSS_KEY(simplified, simplified) CSS_KEY(skew, skew) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 70072b626be5..069e248c41a9 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -604,6 +604,10 @@ protected: /* Functions for transform-origin/perspective-origin Parsing */ bool ParseTransformOrigin(bool aPerspective); + /* Functions for filter parsing */ + bool ParseFilter(); + bool ParseSingleFilter(nsCSSValue* aValue); + /* Find and return the namespace ID associated with aPrefix. If aPrefix has not been declared in an @namespace rule, returns kNameSpaceID_Unknown. */ @@ -6470,6 +6474,8 @@ CSSParserImpl::ParsePropertyByFunction(nsCSSProperty aPropID) return ParseCounterData(aPropID); case eCSSProperty_cursor: return ParseCursor(); + case eCSSProperty_filter: + return ParseFilter(); case eCSSProperty_flex: return ParseFlex(); case eCSSProperty_font: @@ -10032,6 +10038,144 @@ bool CSSParserImpl::ParseTransformOrigin(bool aPerspective) return true; } +/** + * Reads a single url or filter function from the tokenizer stream, reporting an + * error if something goes wrong. + */ +bool +CSSParserImpl::ParseSingleFilter(nsCSSValue* aValue) +{ + if (ParseVariant(*aValue, VARIANT_URL, nullptr)) { + return true; + } + + if (!nsLayoutUtils::CSSFiltersEnabled()) { + // With CSS Filters disabled, we should only accept an SVG reference filter. + REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL); + return false; + } + + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEFilterEOF); + return false; + } + + if (mToken.mType != eCSSToken_Function) { + REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction); + return false; + } + + // Set up the parsing rules based on the filter function. + int32_t variantMask = VARIANT_PN; + bool rejectNegativeArgument = true; + bool clampArgumentToOne = false; + nsCSSKeyword functionName = nsCSSKeywords::LookupKeyword(mToken.mIdent); + switch (functionName) { + case eCSSKeyword_blur: + variantMask = VARIANT_LCALC | VARIANT_NONNEGATIVE_DIMENSION; + // VARIANT_NONNEGATIVE_DIMENSION will already reject negative lengths. + rejectNegativeArgument = false; + break; + case eCSSKeyword_grayscale: + case eCSSKeyword_invert: + case eCSSKeyword_sepia: + case eCSSKeyword_opacity: + clampArgumentToOne = true; + break; + case eCSSKeyword_brightness: + case eCSSKeyword_contrast: + case eCSSKeyword_saturate: + break; + default: + // Unrecognized filter function. + REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction); + SkipUntil(')'); + return false; + } + + // Parse the function. + uint16_t minElems = 1U; + uint16_t maxElems = 1U; + uint32_t allVariants = 0; + if (!ParseFunction(functionName, &variantMask, allVariants, + minElems, maxElems, *aValue)) { + REPORT_UNEXPECTED(PEFilterFunctionArgumentsParsingError); + return false; + } + + // Get the first and only argument to the filter function. + NS_ABORT_IF_FALSE(aValue->GetUnit() == eCSSUnit_Function, + "expected a filter function"); + NS_ABORT_IF_FALSE(aValue->UnitHasArrayValue(), + "filter function should be an array"); + NS_ABORT_IF_FALSE(aValue->GetArrayValue()->Count() == 2, + "filter function should have exactly one argument"); + nsCSSValue& arg = aValue->GetArrayValue()->Item(1); + + if (rejectNegativeArgument && + ((arg.GetUnit() == eCSSUnit_Percent && arg.GetPercentValue() < 0.0f) || + (arg.GetUnit() == eCSSUnit_Number && arg.GetFloatValue() < 0.0f))) { + REPORT_UNEXPECTED(PEExpectedNonnegativeNP); + return false; + } + + if (clampArgumentToOne) { + if (arg.GetUnit() == eCSSUnit_Number && + arg.GetFloatValue() > 1.0f) { + arg.SetFloatValue(1.0f, arg.GetUnit()); + } else if (arg.GetUnit() == eCSSUnit_Percent && + arg.GetPercentValue() > 1.0f) { + arg.SetPercentValue(1.0f); + } + } + + return true; +} + +/** + * Parses a filter property value by continuously reading in urls and/or filter + * functions and constructing a list. + * + * When CSS Filters are enabled, the filter property accepts one or more SVG + * reference filters and/or CSS filter functions. + * e.g. filter: url(#my-filter-1) blur(3px) url(#my-filter-2) grayscale(50%); + * + * When CSS Filters are disabled, the filter property only accepts one SVG + * reference filter. + * e.g. filter: url(#my-filter); + */ +bool +CSSParserImpl::ParseFilter() +{ + nsCSSValue value; + if (ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { + // 'inherit', 'initial', and 'none' must be alone + if (!ExpectEndProperty()) { + return false; + } + } else { + nsCSSValueList* cur = value.SetListValue(); + while (cur) { + if (!ParseSingleFilter(&cur->mValue)) { + return false; + } + if (CheckEndProperty()) { + break; + } + if (!nsLayoutUtils::CSSFiltersEnabled()) { + // With CSS Filters disabled, we should only accept one SVG reference + // filter. + REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); + return false; + } + cur->mNext = new nsCSSValueList; + cur = cur->mNext; + } + } + AppendValue(eCSSProperty_filter, value); + return true; +} + bool CSSParserImpl::ParseTransitionProperty() { diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index 836f14fa5b91..6355573f1430 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -3327,9 +3327,9 @@ CSS_PROP_SVGRESET( filter, filter, Filter, - CSS_PROPERTY_PARSE_VALUE, + CSS_PROPERTY_PARSE_FUNCTION, "", - VARIANT_HUO, + 0, nullptr, CSS_PROP_NO_OFFSET, eStyleAnimType_None) diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h index d8418ac53557..8f0860b0c053 100644 --- a/layout/style/nsCSSProps.h +++ b/layout/style/nsCSSProps.h @@ -89,8 +89,9 @@ #define VARIANT_UK (VARIANT_URL | VARIANT_KEYWORD) #define VARIANT_UO (VARIANT_URL | VARIANT_NONE) #define VARIANT_ANGLE_OR_ZERO (VARIANT_ANGLE | VARIANT_ZERO_ANGLE) -#define VARIANT_LPCALC (VARIANT_LENGTH | VARIANT_CALC | VARIANT_PERCENT) -#define VARIANT_LNCALC (VARIANT_LENGTH | VARIANT_CALC | VARIANT_NUMBER) +#define VARIANT_LCALC (VARIANT_LENGTH | VARIANT_CALC) +#define VARIANT_LPCALC (VARIANT_LCALC | VARIANT_PERCENT) +#define VARIANT_LNCALC (VARIANT_LCALC | VARIANT_NUMBER) #define VARIANT_LPNCALC (VARIANT_LNCALC | VARIANT_PERCENT) #define VARIANT_IMAGE (VARIANT_URL | VARIANT_NONE | VARIANT_GRADIENT | \ VARIANT_IMAGE_RECT | VARIANT_ELEMENT) diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index f466d98aeeda..741853d6bfff 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -4468,19 +4468,96 @@ nsComputedDOMStyle::DoGetClipPath() return val; } +void +nsComputedDOMStyle::SetCssTextToCoord(nsAString& aCssText, + const nsStyleCoord& aCoord) +{ + nsROCSSPrimitiveValue* value = new nsROCSSPrimitiveValue; + bool clampNegativeCalc = true; + SetValueToCoord(value, aCoord, clampNegativeCalc); + value->GetCssText(aCssText); + delete value; +} + +static void +GetFilterFunctionName(nsAString& aString, nsStyleFilter::Type mType) +{ + switch (mType) { + case nsStyleFilter::Type::eBlur: + aString.AssignLiteral("blur("); + break; + case nsStyleFilter::Type::eBrightness: + aString.AssignLiteral("brightness("); + break; + case nsStyleFilter::Type::eContrast: + aString.AssignLiteral("contrast("); + break; + case nsStyleFilter::Type::eGrayscale: + aString.AssignLiteral("grayscale("); + break; + case nsStyleFilter::Type::eInvert: + aString.AssignLiteral("invert("); + break; + case nsStyleFilter::Type::eOpacity: + aString.AssignLiteral("opacity("); + break; + case nsStyleFilter::Type::eSaturate: + aString.AssignLiteral("saturate("); + break; + case nsStyleFilter::Type::eSepia: + aString.AssignLiteral("sepia("); + break; + default: + NS_NOTREACHED("unrecognized filter type"); + } +} + +nsROCSSPrimitiveValue* +nsComputedDOMStyle::CreatePrimitiveValueForStyleFilter( + const nsStyleFilter& aStyleFilter) +{ + nsROCSSPrimitiveValue* value = new nsROCSSPrimitiveValue; + + // Handle url(). + if (nsStyleFilter::Type::eURL == aStyleFilter.mType) { + value->SetURI(aStyleFilter.mURL); + return value; + } + + // Filter function name and opening parenthesis. + nsAutoString filterFunctionString; + GetFilterFunctionName(filterFunctionString, aStyleFilter.mType); + + // Filter function argument. + nsAutoString argumentString; + SetCssTextToCoord(argumentString, aStyleFilter.mCoord); + filterFunctionString.Append(argumentString); + + // Filter function closing parenthesis. + filterFunctionString.AppendLiteral(")"); + + value->SetString(filterFunctionString); + return value; +} + CSSValue* nsComputedDOMStyle::DoGetFilter() { - nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; + const nsTArray& filters = StyleSVGReset()->mFilters; - const nsStyleSVGReset* svg = StyleSVGReset(); + if (filters.IsEmpty()) { + nsROCSSPrimitiveValue* value = new nsROCSSPrimitiveValue; + value->SetIdent(eCSSKeyword_none); + return value; + } - if (svg->mFilter) - val->SetURI(svg->mFilter); - else - val->SetIdent(eCSSKeyword_none); - - return val; + nsDOMCSSValueList* valueList = GetROCSSValueList(false); + for(uint32_t i = 0; i < filters.Length(); i++) { + nsROCSSPrimitiveValue* value = + CreatePrimitiveValueForStyleFilter(filters[i]); + valueList->AppendCSSValue(value); + } + return valueList; } CSSValue* diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h index 4ce1d7d0993c..0b0f47ca6a43 100644 --- a/layout/style/nsComputedDOMStyle.h +++ b/layout/style/nsComputedDOMStyle.h @@ -491,6 +491,11 @@ private: bool GetFrameBorderRectWidth(nscoord& aWidth); bool GetFrameBorderRectHeight(nscoord& aHeight); + /* Helper functions for computing the filter property style. */ + void SetCssTextToCoord(nsAString& aCssText, const nsStyleCoord& aCoord); + nsROCSSPrimitiveValue* CreatePrimitiveValueForStyleFilter( + const nsStyleFilter& aStyleFilter); + struct ComputedStyleMapEntry { // Create a pointer-to-member-function type. diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index e507ac5cd9d0..8c3afba855fa 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -7707,6 +7707,68 @@ nsRuleNode::ComputeSVGData(void* aStartStruct, COMPUTE_END_INHERITED(SVG, svg) } +static nsStyleFilter::Type +StyleFilterTypeForFunctionName(nsCSSKeyword functionName) +{ + switch (functionName) { + case eCSSKeyword_blur: + return nsStyleFilter::Type::eBlur; + case eCSSKeyword_brightness: + return nsStyleFilter::Type::eBrightness; + case eCSSKeyword_contrast: + return nsStyleFilter::Type::eContrast; + case eCSSKeyword_grayscale: + return nsStyleFilter::Type::eGrayscale; + case eCSSKeyword_invert: + return nsStyleFilter::Type::eInvert; + case eCSSKeyword_opacity: + return nsStyleFilter::Type::eOpacity; + case eCSSKeyword_saturate: + return nsStyleFilter::Type::eSaturate; + case eCSSKeyword_sepia: + return nsStyleFilter::Type::eSepia; + default: + NS_NOTREACHED("Unknown filter type."); + return nsStyleFilter::Type::eNull; + } +} + +static void +SetStyleFilterToCSSValue(nsStyleFilter* aStyleFilter, + const nsCSSValue& aValue, + nsStyleContext* aStyleContext, + nsPresContext* aPresContext, + bool& aCanStoreInRuleTree) +{ + nsCSSUnit unit = aValue.GetUnit(); + if (unit == eCSSUnit_URL) { + aStyleFilter->mType = nsStyleFilter::Type::eURL; + aStyleFilter->mURL = aValue.GetURLValue(); + return; + } + + NS_ABORT_IF_FALSE(unit == eCSSUnit_Function, "expected a filter function"); + + nsCSSValue::Array* filterFunction = aValue.GetArrayValue(); + nsCSSKeyword functionName = + (nsCSSKeyword)filterFunction->Item(0).GetIntValue(); + aStyleFilter->mType = StyleFilterTypeForFunctionName(functionName); + + int32_t mask = SETCOORD_PERCENT | SETCOORD_FACTOR; + if (aStyleFilter->mType == nsStyleFilter::Type::eBlur) + mask = SETCOORD_LENGTH | SETCOORD_STORE_CALC; + + NS_ABORT_IF_FALSE(filterFunction->Count() == 2, + "all filter functions except drop-shadow should have " + "exactly one argument"); + + nsCSSValue& arg = filterFunction->Item(1); + DebugOnly success = SetCoord(arg, aStyleFilter->mCoord, nsStyleCoord(), + mask, aStyleContext, aPresContext, + aCanStoreInRuleTree); + NS_ABORT_IF_FALSE(success, "unexpected unit"); +} + const void* nsRuleNode::ComputeSVGResetData(void* aStartStruct, const nsRuleData* aRuleData, @@ -7783,14 +7845,34 @@ nsRuleNode::ComputeSVGResetData(void* aStartStruct, // filter: url, none, inherit const nsCSSValue* filterValue = aRuleData->ValueForFilter(); - if (eCSSUnit_URL == filterValue->GetUnit()) { - svgReset->mFilter = filterValue->GetURLValue(); - } else if (eCSSUnit_None == filterValue->GetUnit() || - eCSSUnit_Initial == filterValue->GetUnit()) { - svgReset->mFilter = nullptr; - } else if (eCSSUnit_Inherit == filterValue->GetUnit()) { - canStoreInRuleTree = false; - svgReset->mFilter = parentSVGReset->mFilter; + switch (filterValue->GetUnit()) { + case eCSSUnit_Null: + break; + case eCSSUnit_None: + case eCSSUnit_Initial: + svgReset->mFilters.Clear(); + break; + case eCSSUnit_Inherit: + canStoreInRuleTree = false; + svgReset->mFilters = parentSVGReset->mFilters; + break; + case eCSSUnit_List: + case eCSSUnit_ListDep: { + svgReset->mFilters.Clear(); + const nsCSSValueList* cur = filterValue->GetListValue(); + while(cur) { + nsStyleFilter styleFilter; + SetStyleFilterToCSSValue(&styleFilter, cur->mValue, aContext, + mPresContext, canStoreInRuleTree); + NS_ABORT_IF_FALSE(styleFilter.mType != nsStyleFilter::Type::eNull, + "filter should be set"); + svgReset->mFilters.AppendElement(styleFilter); + cur = cur->mNext; + } + break; + } + default: + NS_NOTREACHED("unexpected unit"); } // mask: url, none, inherit diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 06f8f065b67b..f09d82b1bd75 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1000,6 +1000,48 @@ nsChangeHint nsStyleSVG::CalcDifference(const nsStyleSVG& aOther) const return hint; } +// -------------------- +// nsStyleFilter +// +nsStyleFilter::nsStyleFilter() + : mType(eNull) +{ + MOZ_COUNT_CTOR(nsStyleFilter); +} + +nsStyleFilter::nsStyleFilter(const nsStyleFilter& aSource) + : mType(aSource.mType) +{ + MOZ_COUNT_CTOR(nsStyleFilter); + + if (mType == eURL) { + mURL = aSource.mURL; + } else if (mType != eNull) { + mCoord = aSource.mCoord; + } +} + +nsStyleFilter::~nsStyleFilter() +{ + MOZ_COUNT_DTOR(nsStyleFilter); +} + +bool +nsStyleFilter::operator==(const nsStyleFilter& aOther) const +{ + if (mType != aOther.mType) { + return false; + } + + if (mType == eURL) { + return EqualURIs(mURL, aOther.mURL); + } else if (mType != eNull) { + return mCoord == aOther.mCoord; + } + + return true; +} + // -------------------- // nsStyleSVGReset // @@ -1010,7 +1052,6 @@ nsStyleSVGReset::nsStyleSVGReset() mFloodColor = NS_RGB(0,0,0); mLightingColor = NS_RGB(255,255,255); mClipPath = nullptr; - mFilter = nullptr; mMask = nullptr; mStopOpacity = 1.0f; mFloodOpacity = 1.0f; @@ -1031,7 +1072,7 @@ nsStyleSVGReset::nsStyleSVGReset(const nsStyleSVGReset& aSource) mFloodColor = aSource.mFloodColor; mLightingColor = aSource.mLightingColor; mClipPath = aSource.mClipPath; - mFilter = aSource.mFilter; + mFilters = aSource.mFilters; mMask = aSource.mMask; mStopOpacity = aSource.mStopOpacity; mFloodOpacity = aSource.mFloodOpacity; @@ -1045,8 +1086,8 @@ nsChangeHint nsStyleSVGReset::CalcDifference(const nsStyleSVGReset& aOther) cons nsChangeHint hint = nsChangeHint(0); if (!EqualURIs(mClipPath, aOther.mClipPath) || - !EqualURIs(mFilter, aOther.mFilter) || - !EqualURIs(mMask, aOther.mMask)) { + !EqualURIs(mMask, aOther.mMask) || + mFilters != aOther.mFilters) { NS_UpdateHint(hint, nsChangeHint_UpdateEffects); NS_UpdateHint(hint, nsChangeHint_RepaintFrame); } diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 70c71e4b9930..eda5ae8ee353 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -2268,6 +2268,32 @@ struct nsStyleSVG { } }; +struct nsStyleFilter { + nsStyleFilter(); + nsStyleFilter(const nsStyleFilter& aSource); + ~nsStyleFilter(); + + bool operator==(const nsStyleFilter& aOther) const; + + enum Type { + eNull, + eURL, + eBlur, + eBrightness, + eContrast, + eInvert, + eOpacity, + eGrayscale, + eSaturate, + eSepia, + }; + + Type mType; + nsIURI* mURL; + nsStyleCoord mCoord; + // FIXME: Add a nsCSSShadowItem when we implement drop shadow. +}; + struct nsStyleSVGReset { nsStyleSVGReset(); nsStyleSVGReset(const nsStyleSVGReset& aSource); @@ -2286,8 +2312,17 @@ struct nsStyleSVGReset { return NS_CombineHint(nsChangeHint_UpdateEffects, NS_STYLE_HINT_REFLOW); } + // The backend only supports one SVG reference right now. + // Eventually, it will support multiple chained SVG reference filters and CSS + // filter functions. + nsIURI* SingleFilter() const { + return (mFilters.Length() == 1 && + mFilters[0].mType == nsStyleFilter::Type::eURL) ? + mFilters[0].mURL : nullptr; + } + nsCOMPtr mClipPath; // [reset] - nsCOMPtr mFilter; // [reset] + nsTArray mFilters; // [reset] nsCOMPtr mMask; // [reset] nscolor mStopColor; // [reset] nscolor mFloodColor; // [reset] diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 56feb7058d41..062f8c146699 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -4411,3 +4411,180 @@ if (SpecialPowers.getBoolPref("svg.paint-order.enabled")) { invalid_values: [ "fill stroke markers fill", "fill normal" ] }; } + +if (SpecialPowers.getBoolPref("layout.css.filters.enabled")) { + gCSSProperties["filter"] = { + domProp: "filter", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + // SVG reference filters + "url(#my-filter)", + "url(#my-filter-1) url(#my-filter-2)", + + // Filter functions + "opacity(50%) saturate(1.0)", + "invert(50%) sepia(0.1) brightness(90%)", + + // Mixed SVG reference filters and filter functions + "grayscale(1) url(#my-filter-1)", + "url(#my-filter-1) brightness(50%) contrast(0.9)", + + "blur(0)", + "blur(0px)", + "blur(0.5px)", + "blur(3px)", + "blur(100px)", + "blur(0.1em)", + "blur(calc(-1px))", // Parses and becomes blur(0px). + "blur(calc(0px))", + "blur(calc(5px))", + "blur(calc(2 * 5px))", + + "brightness(0)", + "brightness(50%)", + "brightness(1)", + "brightness(1.0)", + "brightness(2)", + "brightness(350%)", + "brightness(4.567)", + + "contrast(0)", + "contrast(50%)", + "contrast(1)", + "contrast(1.0)", + "contrast(2)", + "contrast(350%)", + "contrast(4.567)", + + "grayscale(0)", + "grayscale(50%)", + "grayscale(1)", + "grayscale(1.0)", + "grayscale(2)", + "grayscale(350%)", + "grayscale(4.567)", + + "invert(0)", + "invert(50%)", + "invert(1)", + "invert(1.0)", + "invert(2)", + "invert(350%)", + "invert(4.567)", + + "opacity(0)", + "opacity(50%)", + "opacity(1)", + "opacity(1.0)", + "opacity(2)", + "opacity(350%)", + "opacity(4.567)", + + "saturate(0)", + "saturate(50%)", + "saturate(1)", + "saturate(1.0)", + "saturate(2)", + "saturate(350%)", + "saturate(4.567)", + + "sepia(0)", + "sepia(50%)", + "sepia(1)", + "sepia(1.0)", + "sepia(2)", + "sepia(350%)", + "sepia(4.567)", + ], + invalid_values: [ + // none + "none none", + "url(#my-filter) none", + "none url(#my-filter)", + "blur(2px) none url(#my-filter)", + + // Nested filters + "grayscale(invert(1.0))", + + // Comma delimited filters + "url(#my-filter),", + "invert(50%), url(#my-filter), brightness(90%)", + + // Test the following situations for each filter function: + // - Invalid number of arguments + // - Comma delimited arguments + // - Wrong argument type + // - Argument value out of range + "blur()", + "blur(3px 5px)", + "blur(3px,)", + "blur(3px, 5px)", + "blur(#my-filter)", + "blur(0.5)", + "blur(50%)", + "blur(calc(0))", // Unitless zero in calc is not a valid length. + "blur(calc(0.1))", + "blur(calc(10%))", + "blur(calc(20px - 5%))", + "blur(-3px)", + + "brightness()", + "brightness(0.5 0.5)", + "brightness(0.5,)", + "brightness(0.5, 0.5)", + "brightness(#my-filter)", + "brightness(10px)", + "brightness(-1)", + + "contrast()", + "contrast(0.5 0.5)", + "contrast(0.5,)", + "contrast(0.5, 0.5)", + "contrast(#my-filter)", + "contrast(10px)", + "contrast(-1)", + + "grayscale()", + "grayscale(0.5 0.5)", + "grayscale(0.5,)", + "grayscale(0.5, 0.5)", + "grayscale(#my-filter)", + "grayscale(10px)", + "grayscale(-1)", + + "invert()", + "invert(0.5 0.5)", + "invert(0.5,)", + "invert(0.5, 0.5)", + "invert(#my-filter)", + "invert(10px)", + "invert(-1)", + + "opacity()", + "opacity(0.5 0.5)", + "opacity(0.5,)", + "opacity(0.5, 0.5)", + "opacity(#my-filter)", + "opacity(10px)", + "opacity(-1)", + + "saturate()", + "saturate(0.5 0.5)", + "saturate(0.5,)", + "saturate(0.5, 0.5)", + "saturate(#my-filter)", + "saturate(10px)", + "saturate(-1)", + + "sepia()", + "sepia(0.5 0.5)", + "sepia(0.5,)", + "sepia(0.5, 0.5)", + "sepia(#my-filter)", + "sepia(10px)", + "sepia(-1)", + ] + }; +} diff --git a/layout/svg/nsSVGEffects.cpp b/layout/svg/nsSVGEffects.cpp index a684d65ccdfa..1306dd8f2626 100644 --- a/layout/svg/nsSVGEffects.cpp +++ b/layout/svg/nsSVGEffects.cpp @@ -450,7 +450,7 @@ nsSVGEffects::GetEffectProperties(nsIFrame *aFrame) EffectProperties result; const nsStyleSVGReset *style = aFrame->StyleSVGReset(); result.mFilter = static_cast - (GetEffectProperty(style->mFilter, aFrame, FilterProperty(), + (GetEffectProperty(style->SingleFilter(), aFrame, FilterProperty(), CreateFilterProperty)); result.mClipPath = GetPaintingProperty(style->mClipPath, aFrame, ClipPathProperty()); @@ -526,7 +526,7 @@ nsSVGEffects::UpdateEffects(nsIFrame *aFrame) // Ensure that the filter is repainted correctly // We can't do that in DoUpdate as the referenced frame may not be valid - GetEffectProperty(aFrame->StyleSVGReset()->mFilter, + GetEffectProperty(aFrame->StyleSVGReset()->SingleFilter(), aFrame, FilterProperty(), CreateFilterProperty); if (aFrame->GetType() == nsGkAtoms::svgPathGeometryFrame && @@ -547,7 +547,7 @@ nsSVGEffects::GetFilterProperty(nsIFrame *aFrame) { NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation"); - if (!aFrame->StyleSVGReset()->mFilter) + if (!aFrame->StyleSVGReset()->SingleFilter()) return nullptr; return static_cast diff --git a/layout/svg/nsSVGIntegrationUtils.cpp b/layout/svg/nsSVGIntegrationUtils.cpp index ee840d9f6e77..5db2b8272bff 100644 --- a/layout/svg/nsSVGIntegrationUtils.cpp +++ b/layout/svg/nsSVGIntegrationUtils.cpp @@ -150,7 +150,7 @@ nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) // checking the SDL prefs here, since we don't know if we're being called for // painting or hit-testing anyway. const nsStyleSVGReset *style = aFrame->StyleSVGReset(); - return (style->mFilter || style->mClipPath || style->mMask); + return (style->SingleFilter() || style->mClipPath || style->mMask); } /* static */ nsPoint diff --git a/layout/svg/nsSVGUtils.cpp b/layout/svg/nsSVGUtils.cpp index 50fdceb7680b..85cda525fbee 100644 --- a/layout/svg/nsSVGUtils.cpp +++ b/layout/svg/nsSVGUtils.cpp @@ -1269,7 +1269,7 @@ nsSVGUtils::CanOptimizeOpacity(nsIFrame *aFrame) type != nsGkAtoms::svgPathGeometryFrame) { return false; } - if (aFrame->StyleSVGReset()->mFilter) { + if (aFrame->StyleSVGReset()->SingleFilter()) { return false; } // XXX The SVG WG is intending to allow fill, stroke and markers on diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 754eb33a1fd8..bbab814fa2b4 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -1774,6 +1774,9 @@ pref("layout.css.masking.enabled", true); // Is support for the the @supports rule enabled? pref("layout.css.supports-rule.enabled", true); +// Is support for CSS Filters enabled? +pref("layout.css.filters.enabled", false); + // Is support for CSS Flexbox enabled? pref("layout.css.flexbox.enabled", true);