diff --git a/.stylelintrc.js b/.stylelintrc.js index 5f4858bdc116..15d1a55a2673 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -52,6 +52,7 @@ module.exports = { ignoreFunctions: [ "light-dark" /* Used for color-scheme dependent colors */, "add" /* Used in mathml.css */, + "-moz-symbolic-icon" /* Used for GTK icons */, ], }, ], diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css index 0afb804fb7fe..5f789ef71589 100644 --- a/browser/themes/linux/browser.css +++ b/browser/themes/linux/browser.css @@ -171,6 +171,7 @@ .titlebar-button { appearance: none; + color: inherit; align-items: center; padding: 0; padding-inline: calc(env(-moz-gtk-csd-titlebar-button-spacing) / 2); @@ -205,32 +206,14 @@ .titlebar-max { order: env(-moz-gtk-csd-maximize-button-position); > .toolbarbutton-icon { - background-image: image-set( - url(moz-icon://stock/window-maximize-symbolic?dark=0), - url(moz-icon://stock/window-maximize-symbolic?dark=0&scale=2) 2x - ); - toolbar[brighttext] & { - background-image: image-set( - url(moz-icon://stock/window-maximize-symbolic?dark=1), - url(moz-icon://stock/window-maximize-symbolic?dark=1&scale=2) 2x - ); - } + background-image: -moz-symbolic-icon(window-maximize-symbolic); } } .titlebar-restore { order: env(-moz-gtk-csd-maximize-button-position); > .toolbarbutton-icon { - background-image: image-set( - url(moz-icon://stock/window-restore-symbolic?dark=0), - url(moz-icon://stock/window-restore-symbolic?dark=0&scale=2) 2x - ); - toolbar[brighttext] & { - background-image: image-set( - url(moz-icon://stock/window-restore-symbolic?dark=1), - url(moz-icon://stock/window-restore-symbolic?dark=1&scale=2) 2x - ); - } + background-image: -moz-symbolic-icon(window-restore-symbolic); } } @@ -245,16 +228,7 @@ order: env(-moz-gtk-csd-close-button-position); > .toolbarbutton-icon { - background-image: image-set( - url(moz-icon://stock/window-close-symbolic?dark=0), - url(moz-icon://stock/window-close-symbolic?dark=0&scale=2) 2x - ); - toolbar[brighttext] & { - background-image: image-set( - url(moz-icon://stock/window-close-symbolic?dark=1), - url(moz-icon://stock/window-close-symbolic?dark=1&scale=2) 2x - ); - } + background-image: -moz-symbolic-icon(window-close-symbolic); } @media not (-moz-gtk-csd-close-button) { @@ -266,16 +240,7 @@ order: env(-moz-gtk-csd-minimize-button-position); > .toolbarbutton-icon { - background-image: image-set( - url(moz-icon://stock/window-minimize-symbolic?dark=0), - url(moz-icon://stock/window-minimize-symbolic?dark=0&scale=2) 2x - ); - toolbar[brighttext] & { - background-image: image-set( - url(moz-icon://stock/window-minimize-symbolic?dark=1), - url(moz-icon://stock/window-minimize-symbolic?dark=1&scale=2) 2x - ); - } + background-image: -moz-symbolic-icon(window-minimize-symbolic); } @media not (-moz-gtk-csd-minimize-button) { diff --git a/image/decoders/icon/gtk/nsIconChannel.cpp b/image/decoders/icon/gtk/nsIconChannel.cpp index ff26d7c098d8..c45d436f90d1 100644 --- a/image/decoders/icon/gtk/nsIconChannel.cpp +++ b/image/decoders/icon/gtk/nsIconChannel.cpp @@ -8,7 +8,11 @@ #include #include +#include "ErrorList.h" #include "gdk/gdk.h" +#include "mozilla/AlreadyAddRefed.h" +#include "nsIIconURI.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" #include "mozilla/DebugOnly.h" #include "mozilla/EndianUtils.h" #include "mozilla/NullPrincipal.h" @@ -40,24 +44,48 @@ #include "nsIAsyncInputStream.h" #include "nsIAsyncOutputStream.h" #include "prlink.h" +#include "gfxUtils.h" #include "gfxPlatform.h" -using mozilla::CheckedInt32; +using namespace mozilla; using mozilla::ipc::ByteBuf; NS_IMPL_ISUPPORTS(nsIconChannel, nsIRequest, nsIChannel) -static nsresult MozGdkPixbufToByteBuf(GdkPixbuf* aPixbuf, gint aScale, - ByteBuf* aByteBuf) { +static bool IsValidRGBAPixbuf(GdkPixbuf* aPixbuf) { + return gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB && + gdk_pixbuf_get_bits_per_sample(aPixbuf) == 8 && + gdk_pixbuf_get_has_alpha(aPixbuf) && + gdk_pixbuf_get_n_channels(aPixbuf) == 4; +} + +static already_AddRefed MozGdkPixbufToDataSurface( + GdkPixbuf* aPixbuf) { + if (NS_WARN_IF(!IsValidRGBAPixbuf(aPixbuf))) { + return nullptr; + } + int width = gdk_pixbuf_get_width(aPixbuf); + int height = gdk_pixbuf_get_height(aPixbuf); + if (!width || !height) { + return nullptr; + } + guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf); + const int stride = gdk_pixbuf_get_rowstride(aPixbuf); + RefPtr wrapper = gfx::Factory::CreateWrappingDataSourceSurface( + pixels, stride, gfx::IntSize(width, height), + gfx::SurfaceFormat::R8G8B8A8); + if (NS_WARN_IF(!wrapper)) { + return nullptr; + } + return gfxUtils::CreatePremultipliedDataSurface(wrapper); +} + +static nsresult MozGdkPixbufToByteBuf(GdkPixbuf* aPixbuf, ByteBuf* aByteBuf) { int width = gdk_pixbuf_get_width(aPixbuf); int height = gdk_pixbuf_get_height(aPixbuf); NS_ENSURE_TRUE(height < 256 && width < 256 && height > 0 && width > 0 && - gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB && - gdk_pixbuf_get_bits_per_sample(aPixbuf) == 8 && - gdk_pixbuf_get_has_alpha(aPixbuf) && - gdk_pixbuf_get_n_channels(aPixbuf) == 4, + IsValidRGBAPixbuf(aPixbuf), NS_ERROR_UNEXPECTED); - const int n_channels = 4; CheckedInt32 buf_size = 4 + n_channels * CheckedInt32(height) * CheckedInt32(width); @@ -112,7 +140,17 @@ static nsresult ByteBufToStream(ByteBuf&& aBuf, nsIInputStream** aStream) { return NS_OK; } -static GdkRGBA GetForegroundColor(nsIMozIconURI* aIconURI) { +static GdkRGBA GeckoColorToGdk(nscolor aColor) { + auto ToGdk = [](uint8_t aGecko) { return aGecko / 255.0; }; + return GdkRGBA{ + .red = ToGdk(NS_GET_R(aColor)), + .green = ToGdk(NS_GET_G(aColor)), + .blue = ToGdk(NS_GET_B(aColor)), + .alpha = ToGdk(NS_GET_A(aColor)), + }; +} + +static nscolor GetForegroundColor(nsIMozIconURI* aIconURI) { auto scheme = [&] { bool dark = false; if (NS_FAILED(aIconURI->GetImageDark(&dark))) { @@ -120,21 +158,12 @@ static GdkRGBA GetForegroundColor(nsIMozIconURI* aIconURI) { } return dark ? mozilla::ColorScheme::Dark : mozilla::ColorScheme::Light; }(); - auto color = mozilla::LookAndFeel::Color( - mozilla::LookAndFeel::ColorID::Windowtext, scheme, - mozilla::LookAndFeel::UseStandins::No); - auto ToGdk = [](uint8_t aGecko) { return aGecko / 255.0; }; - return GdkRGBA{ - .red = ToGdk(NS_GET_R(color)), - .green = ToGdk(NS_GET_G(color)), - .blue = ToGdk(NS_GET_B(color)), - .alpha = ToGdk(NS_GET_A(color)), - }; + return mozilla::LookAndFeel::Color(mozilla::LookAndFeel::ColorID::Windowtext, + scheme, + mozilla::LookAndFeel::UseStandins::No); } -/* static */ -nsresult nsIconChannel::GetIconWithGIO(nsIMozIconURI* aIconURI, - ByteBuf* aDataOut) { +static nsresult GetIconWithGIO(nsIMozIconURI* aIconURI, ByteBuf* aDataOut) { RefPtr icon; nsCOMPtr fileURI; @@ -218,14 +247,31 @@ nsresult nsIconChannel::GetIconWithGIO(nsIMozIconURI* aIconURI, } // Create a GdkPixbuf buffer containing icon and scale it - const auto fg = GetForegroundColor(aIconURI); + const auto fg = GeckoColorToGdk(GetForegroundColor(aIconURI)); RefPtr pixbuf = dont_AddRef(gtk_icon_info_load_symbolic( iconInfo, &fg, nullptr, nullptr, nullptr, nullptr, nullptr)); if (!pixbuf) { return NS_ERROR_UNEXPECTED; } - return MozGdkPixbufToByteBuf(pixbuf, scale, aDataOut); + return MozGdkPixbufToByteBuf(pixbuf, aDataOut); +} + +static already_AddRefed GetSymbolicIconPixbuf(const nsCString& aName, + int aIconSize, + int aScale, + nscolor aFgColor) { + GtkIconTheme* theme = gtk_icon_theme_get_default(); + RefPtr iconInfo = + dont_AddRef(gtk_icon_theme_lookup_icon_for_scale( + theme, aName.get(), aIconSize, aScale, GtkIconLookupFlags(0))); + if (!iconInfo) { + return nullptr; + } + const auto fg = GeckoColorToGdk(aFgColor); + RefPtr pixbuf = dont_AddRef(gtk_icon_info_load_symbolic( + iconInfo, &fg, nullptr, nullptr, nullptr, nullptr, nullptr)); + return pixbuf.forget(); } /* static */ @@ -247,27 +293,23 @@ nsresult nsIconChannel::GetIcon(nsIURI* aURI, ByteBuf* aDataOut) { return GetIconWithGIO(iconURI, aDataOut); } - GtkIconTheme* theme = gtk_icon_theme_get_default(); const gint iconSize = iconURI->GetImageSize(); const gint scale = iconURI->GetImageScale(); - RefPtr iconInfo = - dont_AddRef(gtk_icon_theme_lookup_icon_for_scale( - theme, stockIcon.get(), iconSize, scale, GtkIconLookupFlags(0))); - if (!iconInfo) { - return NS_ERROR_NOT_AVAILABLE; - } - const auto fg = GetForegroundColor(iconURI); - RefPtr pixbuf = dont_AddRef(gtk_icon_info_load_symbolic( - iconInfo, &fg, nullptr, nullptr, nullptr, nullptr, nullptr)); - - // According to documentation, gtk_icon_set_render_icon() never returns - // nullptr, but it does return nullptr when we have the problem reported - // here: https://bugzilla.gnome.org/show_bug.cgi?id=629878#c13 + const nscolor fg = GetForegroundColor(iconURI); + RefPtr pixbuf = GetSymbolicIconPixbuf(stockIcon, iconSize, scale, fg); if (!pixbuf) { return NS_ERROR_NOT_AVAILABLE; } + return MozGdkPixbufToByteBuf(pixbuf, aDataOut); +} - return MozGdkPixbufToByteBuf(pixbuf, scale, aDataOut); +already_AddRefed nsIconChannel::GetSymbolicIcon( + const nsCString& aName, int aIconSize, int aScale, nscolor aFgColor) { + RefPtr pixbuf = GetSymbolicIconPixbuf(aName, aIconSize, aScale, aFgColor); + if (!pixbuf) { + return nullptr; + } + return MozGdkPixbufToDataSurface(pixbuf); } nsresult nsIconChannel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) { diff --git a/image/decoders/icon/gtk/nsIconChannel.h b/image/decoders/icon/gtk/nsIconChannel.h index 943392735f5d..54dfb174edcb 100644 --- a/image/decoders/icon/gtk/nsIconChannel.h +++ b/image/decoders/icon/gtk/nsIconChannel.h @@ -10,12 +10,16 @@ #include "nsIChannel.h" #include "nsIURI.h" -#include "nsIIconURI.h" #include "nsCOMPtr.h" -namespace mozilla::ipc { +namespace mozilla { +namespace ipc { class ByteBuf; } +namespace gfx { +class DataSourceSurface; +} +} // namespace mozilla /// This class is the GTK implementation of nsIconChannel. It asks /// GTK for the icon, translates the pixel data in-memory into @@ -27,7 +31,7 @@ class nsIconChannel final : public nsIChannel { NS_FORWARD_NSIREQUEST(mRealChannel->) NS_FORWARD_NSICHANNEL(mRealChannel->) - nsIconChannel() {} + nsIconChannel() = default; static void Shutdown(); @@ -39,15 +43,14 @@ class nsIconChannel final : public nsIChannel { /// Obtains an icon, in nsIconDecoder format, as a ByteBuf instead /// of a channel. For use with IPC. static nsresult GetIcon(nsIURI* aURI, mozilla::ipc::ByteBuf* aDataOut); + static already_AddRefed GetSymbolicIcon( + const nsCString& aName, int aIconSize, int aScale, nscolor aFgColor); private: - ~nsIconChannel() {} + ~nsIconChannel() = default; /// The input stream channel which will yield the image. /// Will always be non-null after a successful Init. nsCOMPtr mRealChannel; - - static nsresult GetIconWithGIO(nsIMozIconURI* aIconURI, - mozilla::ipc::ByteBuf* aDataOut); }; #endif // mozilla_image_decoders_icon_gtk_nsIconChannel_h diff --git a/layout/painting/nsImageRenderer.cpp b/layout/painting/nsImageRenderer.cpp index 67165464d77d..d2d94022c30e 100644 --- a/layout/painting/nsImageRenderer.cpp +++ b/layout/painting/nsImageRenderer.cpp @@ -10,6 +10,9 @@ #include "mozilla/webrender/WebRenderAPI.h" +#ifdef MOZ_WIDGET_GTK +# include "nsIconChannel.h" +#endif #include "gfxContext.h" #include "gfxDrawable.h" #include "ImageOps.h" @@ -64,6 +67,57 @@ nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame, const StyleImage* aImage, mExtendMode(ExtendMode::CLAMP), mMaskOp(StyleMaskMode::MatchSource) {} +using SymbolicImageKey = std::tuple, int, nscolor>; +struct SymbolicImageEntry { + SymbolicImageKey mKey; + nsCOMPtr mImage; +}; +struct SymbolicImageCache final + : public mozilla::MruCache { + static HashNumber Hash(const KeyType& aKey) { + return AddToHash(std::get<0>(aKey)->hash(), + HashGeneric(std::get<1>(aKey), std::get<2>(aKey))); + } + static bool Match(const KeyType& aKey, const ValueType& aVal) { + return aVal.mKey == aKey; + } +}; + +NS_DECLARE_FRAME_PROPERTY_DELETABLE(SymbolicImageCacheProp, SymbolicImageCache); + +static already_AddRefed GetSymbolicIconImage(nsAtom* aName, + int aScale, + nsIFrame* aFrame) { + if (NS_WARN_IF(!XRE_IsParentProcess())) { + return nullptr; + } + const auto fg = aFrame->StyleText()->mColor.ToColor(); + auto key = std::make_tuple(aName, aScale, fg); + auto* cache = aFrame->GetProperty(SymbolicImageCacheProp()); + if (!cache) { + cache = new SymbolicImageCache(); + aFrame->SetProperty(SymbolicImageCacheProp(), cache); + } + auto lookup = cache->Lookup(key); + if (lookup) { + return do_AddRef(lookup.Data().mImage); + } + RefPtr surface; +#ifdef MOZ_WIDGET_GTK + surface = + nsIconChannel::GetSymbolicIcon(nsAtomCString(aName), 16, aScale, fg); +#endif + if (NS_WARN_IF(!surface)) { + return nullptr; + } + RefPtr drawable = new gfxSurfaceDrawable(surface, surface->GetSize()); + nsCOMPtr container = ImageOps::CreateFromDrawable(drawable); + MOZ_ASSERT(container); + lookup.Set(SymbolicImageEntry{std::move(key), std::move(container)}); + return do_AddRef(lookup.Data().mImage); +} + bool nsImageRenderer::PrepareImage() { if (mImage->IsNone()) { mPrepareResult = ImgDrawResult::BAD_IMAGE; @@ -182,6 +236,17 @@ bool nsImageRenderer::PrepareImage() { mPaintServerFrame = paintServerFrame; } + mPrepareResult = ImgDrawResult::SUCCESS; + } else if (mImage->IsMozSymbolicIcon()) { + auto deviceScale = + std::ceil(mForFrame->PresContext()->CSSToDevPixelScale().scale); + mImageResolution.ScaleBy(deviceScale); + mImageContainer = GetSymbolicIconImage(mImage->AsMozSymbolicIcon().AsAtom(), + int(deviceScale), mForFrame); + if (!mImageContainer) { + mPrepareResult = ImgDrawResult::BAD_IMAGE; + return false; + } mPrepareResult = ImgDrawResult::SUCCESS; } else if (mImage->IsCrossFade()) { // See bug 546052 - cross-fade implementation still being worked @@ -202,6 +267,7 @@ CSSSizeOrRatio nsImageRenderer::ComputeIntrinsicSize() { CSSSizeOrRatio result; switch (mType) { + case StyleImage::Tag::MozSymbolicIcon: case StyleImage::Tag::Url: { bool haveWidth, haveHeight; CSSIntSize imageIntSize; @@ -489,6 +555,7 @@ ImgDrawResult nsImageRenderer::Draw(nsPresContext* aPresContext, } switch (mType) { + case StyleImage::Tag::MozSymbolicIcon: case StyleImage::Tag::Url: { result = nsLayoutUtils::DrawBackgroundImage( *ctx, mForFrame, aPresContext, mImageContainer, samplingFilter, aDest, @@ -590,6 +657,7 @@ ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItems( !aItem->BackfaceIsHidden(), aOpacity); break; } + case StyleImage::Tag::MozSymbolicIcon: case StyleImage::Tag::Url: { ExtendMode extendMode = mExtendMode; if (aDest.Contains(aFill)) { @@ -889,10 +957,8 @@ ImgDrawResult nsImageRenderer::DrawBorderImageComponent( return ImgDrawResult::SUCCESS; } - const bool isRequestBacked = mType == StyleImage::Tag::Url; - MOZ_ASSERT(isRequestBacked == mImage->IsImageRequestType()); - - if (isRequestBacked || mType == StyleImage::Tag::Element) { + const bool hasImage = !!mImageContainer; + if (hasImage || mType == StyleImage::Tag::Element) { nsCOMPtr subImage; // To draw one portion of an image into a border component, we stretch that @@ -911,10 +977,10 @@ ImgDrawResult nsImageRenderer::DrawBorderImageComponent( } // Retrieve or create the subimage we'll draw. nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height); - if (isRequestBacked) { + if (hasImage) { subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize); } else { - // This path, for eStyleImageType_Element, is currently slower than it + // This path, for Element, is currently slower than it // needs to be because we don't cache anything. (In particular, if we have // to draw to a temporary surface inside ClippedImage, we don't cache that // temporary surface since we immediately throw the ClippedImage we create @@ -1027,7 +1093,7 @@ ImgDrawResult nsImageRenderer::DrawShapeImage(nsPresContext* aPresContext, return ImgDrawResult::BAD_IMAGE; } -bool nsImageRenderer::IsRasterImage() { +bool nsImageRenderer::IsRasterImage() const { return mImageContainer && mImageContainer->GetType() == imgIContainer::TYPE_RASTER; } diff --git a/layout/painting/nsImageRenderer.h b/layout/painting/nsImageRenderer.h index 260155ab5038..711ce821b5f2 100644 --- a/layout/painting/nsImageRenderer.h +++ b/layout/painting/nsImageRenderer.h @@ -240,7 +240,7 @@ class nsImageRenderer { ImgDrawResult DrawShapeImage(nsPresContext* aPresContext, gfxContext& aRenderingContext); - bool IsRasterImage(); + bool IsRasterImage() const; /// Retrieves the image associated with this nsImageRenderer, if there is one. already_AddRefed GetImage(); diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index b30e47d48a16..354b4289fcfd 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1583,7 +1583,7 @@ bool StyleImage::IsOpaque() const { return AsGradient()->IsOpaque(); } - if (IsElement()) { + if (IsElement() || IsMozSymbolicIcon()) { return false; } @@ -1604,6 +1604,7 @@ bool StyleImage::IsComplete() const { return false; case Tag::Gradient: case Tag::Element: + case Tag::MozSymbolicIcon: return true; case Tag::Url: { if (!IsResolved()) { @@ -1638,6 +1639,7 @@ bool StyleImage::IsSizeAvailable() const { return false; case Tag::Gradient: case Tag::Element: + case Tag::MozSymbolicIcon: return true; case Tag::Url: { imgRequestProxy* req = GetImageRequest(); diff --git a/servo/components/style/values/computed/image.rs b/servo/components/style/values/computed/image.rs index 7d8328ee0d9b..c55824127c65 100644 --- a/servo/components/style/values/computed/image.rs +++ b/servo/components/style/values/computed/image.rs @@ -217,6 +217,7 @@ impl ToComputedValue for specified::Image { Self::Gradient(g) => Image::Gradient(g.to_computed_value(context)), #[cfg(feature = "gecko")] Self::Element(e) => Image::Element(e.to_computed_value(context)), + Self::MozSymbolicIcon(e) => Image::MozSymbolicIcon(e.to_computed_value(context)), #[cfg(feature = "servo")] Self::PaintWorklet(w) => Image::PaintWorklet(w.to_computed_value(context)), Self::CrossFade(f) => Image::CrossFade(f.to_computed_value(context)), @@ -232,6 +233,7 @@ impl ToComputedValue for specified::Image { Image::Gradient(g) => Self::Gradient(ToComputedValue::from_computed_value(g)), #[cfg(feature = "gecko")] Image::Element(e) => Self::Element(ToComputedValue::from_computed_value(e)), + Image::MozSymbolicIcon(e) => Self::MozSymbolicIcon(ToComputedValue::from_computed_value(e)), #[cfg(feature = "servo")] Image::PaintWorklet(w) => Self::PaintWorklet(ToComputedValue::from_computed_value(w)), Image::CrossFade(f) => Self::CrossFade(ToComputedValue::from_computed_value(f)), diff --git a/servo/components/style/values/generics/image.rs b/servo/components/style/values/generics/image.rs index a6442091b688..8524e277c719 100644 --- a/servo/components/style/values/generics/image.rs +++ b/servo/components/style/values/generics/image.rs @@ -25,6 +25,7 @@ use style_traits::{CssWriter, ToCss}; pub enum GenericImage { /// `none` variant. None, + /// A `` image. Url(ImageUrl), @@ -37,6 +38,12 @@ pub enum GenericImage { #[css(function = "-moz-element")] Element(Atom), + /// A `-moz-symbolic-icon()` + /// NOTE(emilio): #[css(skip)] only really affects SpecifiedValueInfo, which we want because + /// this is chrome-only. + #[css(function, skip)] + MozSymbolicIcon(Atom), + /// A paint worklet image. /// #[cfg(feature = "servo")] @@ -427,6 +434,11 @@ where serialize_atom_identifier(selector, dest)?; dest.write_char(')') }, + Image::MozSymbolicIcon(ref id) => { + dest.write_str("-moz-symbolic-icon(")?; + serialize_atom_identifier(id, dest)?; + dest.write_char(')') + }, Image::ImageSet(ref is) => is.to_css(dest), Image::CrossFade(ref cf) => cf.to_css(dest), Image::LightDark(ref ld) => ld.to_css(dest), diff --git a/servo/components/style/values/specified/image.rs b/servo/components/style/values/specified/image.rs index 6c0c4c5f6457..72e5ccef2571 100644 --- a/servo/components/style/values/specified/image.rs +++ b/servo/components/style/values/specified/image.rs @@ -253,6 +253,7 @@ impl Image { })?)), #[cfg(feature = "gecko")] "-moz-element" => Self::Element(Self::parse_element(input)?), + "-moz-symbolic-icon" if context.chrome_rules_enabled() => Self::MozSymbolicIcon(input.expect_ident()?.as_ref().into()), _ => return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function))), })) }