Bug 2032954 - Make map_origin_color not eat error for unresolvable color. r=firefox-style-system-reviewers,layout-reviewers,gerard-majax,dshin
See the test added for what's going on. Basically, we hit a color we can't resolve like light-dark() from inside a relative color, and map_origin_color will happily swallow that error, causing an assert afterwards (because of a relative color with no origin color) instead of the failure that's supposed to occur. Differential Revision: https://phabricator.services.mozilla.com/D294934
This commit is contained in:
committed by
ealvarez@mozilla.com
parent
174e4d4b8d
commit
2d64b11869
@@ -1,60 +1,61 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test InspectorUtils::ColorToRGBA</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript">
|
||||
const InspectorUtils = SpecialPowers.wrap(window).InspectorUtils;
|
||||
|
||||
testColor("red", {r:255, g:0, b:0, a:1});
|
||||
testColor("#f00", {r:255, g:0, b:0, a:1});
|
||||
testColor("#ff0000", {r:255, g:0, b:0, a:1});
|
||||
testColor("ff0000", null);
|
||||
testColor("rgb(255,0,0)", {r:255, g:0, b:0, a:1});
|
||||
testColor("rgba(255,0,0)", {r:255, g:0, b:0, a:1});
|
||||
testColor("rgb(255,0,0,0.7)", {r:255, g:0, b:0, a:0.7});
|
||||
testColor("rgba(255,0,0,0.7)", {r:255, g:0, b:0, a:0.7});
|
||||
testColor("rgb(50%,75%,60%)", {r:128, g:191, b:153, a:1});
|
||||
testColor("rgba(50%,75%,60%)", {r:128, g:191, b:153, a:1});
|
||||
testColor("rgb(100%,50%,25%,0.7)", {r:255, g:128, b:64, a:0.7});
|
||||
testColor("rgba(100%,50%,25%,0.7)", {r:255, g:128, b:64, a:0.7});
|
||||
testColor("hsl(320,30%,10%)", {r:33, g:18, b:28, a:1});
|
||||
testColor("hsla(320,30%,10%)", {r:33, g:18, b:28, a:1});
|
||||
testColor("hsl(170,60%,40%,0.9)", {r:41, g:163, b:143, a:0.9});
|
||||
testColor("hsla(170,60%,40%,0.9)", {r:41, g:163, b:143, a:0.9});
|
||||
|
||||
isnot(
|
||||
InspectorUtils.colorToRGBA("ButtonText"),
|
||||
null,
|
||||
"Should support system colors"
|
||||
);
|
||||
|
||||
function testColor(color, expected) {
|
||||
let rgb = InspectorUtils.colorToRGBA(color);
|
||||
|
||||
if (rgb === null) {
|
||||
ok(expected === null, "color: " + color + " returns null");
|
||||
return;
|
||||
}
|
||||
|
||||
let {r, g, b, a} = rgb;
|
||||
|
||||
is(r, expected.r, "color: " + color + ", red component is converted correctly");
|
||||
is(g, expected.g, "color: " + color + ", green component is converted correctly");
|
||||
is(b, expected.b, "color: " + color + ", blue component is converted correctly");
|
||||
is(Math.round(a * 10) / 10, expected.a, "color: " + color + ", alpha component is a converted correctly");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<meta charset="utf-8">
|
||||
<title>Test InspectorUtils::ColorToRGBA</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<h1>Test InspectorUtils::ColorToRGBA</h1>
|
||||
<iframe id="frame" style="display:none"></iframe>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
<pre id="test"></pre>
|
||||
<script>
|
||||
const InspectorUtils = SpecialPowers.wrap(window).InspectorUtils;
|
||||
function testColor(color, expected) {
|
||||
let rgb = InspectorUtils.colorToRGBA(color);
|
||||
|
||||
if (rgb === null) {
|
||||
ok(expected === null, "color: " + color + " returns null");
|
||||
return;
|
||||
}
|
||||
|
||||
let {r, g, b, a} = rgb;
|
||||
|
||||
is(r, expected.r, "color: " + color + ", red component is converted correctly");
|
||||
is(g, expected.g, "color: " + color + ", green component is converted correctly");
|
||||
is(b, expected.b, "color: " + color + ", blue component is converted correctly");
|
||||
is(Math.round(a * 10) / 10, expected.a, "color: " + color + ", alpha component is a converted correctly");
|
||||
}
|
||||
|
||||
testColor("red", {r:255, g:0, b:0, a:1});
|
||||
testColor("#f00", {r:255, g:0, b:0, a:1});
|
||||
testColor("#ff0000", {r:255, g:0, b:0, a:1});
|
||||
testColor("ff0000", null);
|
||||
testColor("rgb(255,0,0)", {r:255, g:0, b:0, a:1});
|
||||
testColor("rgba(255,0,0)", {r:255, g:0, b:0, a:1});
|
||||
testColor("rgb(255,0,0,0.7)", {r:255, g:0, b:0, a:0.7});
|
||||
testColor("rgba(255,0,0,0.7)", {r:255, g:0, b:0, a:0.7});
|
||||
testColor("rgb(50%,75%,60%)", {r:128, g:191, b:153, a:1});
|
||||
testColor("rgba(50%,75%,60%)", {r:128, g:191, b:153, a:1});
|
||||
testColor("rgb(100%,50%,25%,0.7)", {r:255, g:128, b:64, a:0.7});
|
||||
testColor("rgba(100%,50%,25%,0.7)", {r:255, g:128, b:64, a:0.7});
|
||||
testColor("hsl(320,30%,10%)", {r:33, g:18, b:28, a:1});
|
||||
testColor("hsla(320,30%,10%)", {r:33, g:18, b:28, a:1});
|
||||
testColor("hsl(170,60%,40%,0.9)", {r:41, g:163, b:143, a:0.9});
|
||||
testColor("hsla(170,60%,40%,0.9)", {r:41, g:163, b:143, a:0.9});
|
||||
isnot(
|
||||
InspectorUtils.colorToRGBA("ButtonText"),
|
||||
null,
|
||||
"Should support system colors"
|
||||
);
|
||||
|
||||
let frameUtils =
|
||||
SpecialPowers.wrap(document.getElementById("frame").contentWindow).InspectorUtils;
|
||||
let lightDarkDependentRelativeColor = "oklch(from light-dark(oklch(55% 0.24 260), oklch(83% 0.17 260)) l c h / 20%)";
|
||||
// If we ever make these work, switch to a sandbox / non-window global instead.
|
||||
is(
|
||||
frameUtils.colorToRGBA(lightDarkDependentRelativeColor),
|
||||
null,
|
||||
"Frame with no presShell shouldn't assert when resolving relative colors"
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -371,18 +371,25 @@ impl ColorFunction<SpecifiedColor> {
|
||||
/// contain any variables (currentcolor, color components, etc.).
|
||||
pub fn resolve_to_absolute(&self) -> Result<AbsoluteColor, ()> {
|
||||
// Map the color function to one with an absolute origin color.
|
||||
let resolvable = self.map_origin_color(|o| o.resolve_to_absolute());
|
||||
resolvable.resolve_to_absolute()
|
||||
self.map_origin_color(|o| o.resolve_to_absolute())?
|
||||
.resolve_to_absolute()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Color> ColorFunction<Color> {
|
||||
/// Map the origin color to another type. Return None from `f` if the conversion fails.
|
||||
pub fn map_origin_color<U>(&self, f: impl FnOnce(&Color) -> Option<U>) -> ColorFunction<U> {
|
||||
/// Map the origin color to another type.
|
||||
pub fn map_origin_color<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&Color) -> Result<U, ()>,
|
||||
) -> Result<ColorFunction<U>, ()> {
|
||||
macro_rules! map {
|
||||
($f:ident, $o:expr, $c0:expr, $c1:expr, $c2:expr, $alpha:expr) => {{
|
||||
ColorFunction::$f(
|
||||
$o.as_ref().and_then(f).into(),
|
||||
match $o.as_ref() {
|
||||
Some(c) => Some(f(c)?),
|
||||
None => None,
|
||||
}
|
||||
.into(),
|
||||
$c0.clone(),
|
||||
$c1.clone(),
|
||||
$c2.clone(),
|
||||
@@ -390,7 +397,7 @@ impl<Color> ColorFunction<Color> {
|
||||
)
|
||||
}};
|
||||
}
|
||||
match self {
|
||||
Ok(match self {
|
||||
ColorFunction::Rgb(o, c0, c1, c2, alpha) => map!(Rgb, o, c0, c1, c2, alpha),
|
||||
ColorFunction::Hsl(o, c0, c1, c2, alpha) => map!(Hsl, o, c0, c1, c2, alpha),
|
||||
ColorFunction::Hwb(o, c0, c1, c2, alpha) => map!(Hwb, o, c0, c1, c2, alpha),
|
||||
@@ -399,14 +406,18 @@ impl<Color> ColorFunction<Color> {
|
||||
ColorFunction::Oklab(o, c0, c1, c2, alpha) => map!(Oklab, o, c0, c1, c2, alpha),
|
||||
ColorFunction::Oklch(o, c0, c1, c2, alpha) => map!(Oklch, o, c0, c1, c2, alpha),
|
||||
ColorFunction::Color(o, c0, c1, c2, alpha, color_space) => ColorFunction::Color(
|
||||
o.as_ref().and_then(f).into(),
|
||||
match o.as_ref() {
|
||||
Some(c) => Some(f(c)?),
|
||||
None => None,
|
||||
}
|
||||
.into(),
|
||||
c0.clone(),
|
||||
c1.clone(),
|
||||
c2.clone(),
|
||||
alpha.clone(),
|
||||
color_space.clone(),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,7 +425,9 @@ impl ColorFunction<ComputedColor> {
|
||||
/// Resolve a computed color function to an absolute computed color.
|
||||
pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor {
|
||||
// Map the color function to one with an absolute origin color.
|
||||
let resolvable = self.map_origin_color(|o| Some(o.resolve_to_absolute(current_color)));
|
||||
let resolvable = self
|
||||
.map_origin_color(|o| Ok(o.resolve_to_absolute(current_color)))
|
||||
.unwrap();
|
||||
match resolvable.resolve_to_absolute() {
|
||||
Ok(color) => color,
|
||||
Err(..) => {
|
||||
|
||||
@@ -146,7 +146,9 @@ fn parse_color_function<'i, 't>(
|
||||
// Validate the channels and calc expressions by trying to resolve them against
|
||||
// transparent.
|
||||
// FIXME(emilio, bug 1925572): This could avoid cloning, or be done earlier.
|
||||
let abs = color.map_origin_color(|_| Some(AbsoluteColor::TRANSPARENT_BLACK));
|
||||
let abs = color
|
||||
.map_origin_color(|_| Ok(AbsoluteColor::TRANSPARENT_BLACK))
|
||||
.unwrap();
|
||||
if abs.resolve_to_absolute().is_err() {
|
||||
return Err(arguments.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ impl Parse for FontPaletteOverrideColor {
|
||||
// https://drafts.csswg.org/css-color-5/#absolute-color
|
||||
// so check that the specified color can be resolved without a context
|
||||
// or currentColor value.
|
||||
if color.resolve_to_absolute().is_some() {
|
||||
if color.resolve_to_absolute().is_ok() {
|
||||
// We store the specified color (not the resolved absolute color)
|
||||
// because that is what the rule exposes to authors.
|
||||
return Ok(FontPaletteOverrideColor { index, color });
|
||||
|
||||
@@ -619,7 +619,7 @@ impl Color {
|
||||
context: &ParserContext,
|
||||
input: &mut Parser,
|
||||
device: Option<&Device>,
|
||||
) -> Option<ComputedColor> {
|
||||
) -> Result<ComputedColor, ()> {
|
||||
use crate::error_reporting::ContextualParseError;
|
||||
let start = input.position();
|
||||
let result = input
|
||||
@@ -628,22 +628,22 @@ impl Color {
|
||||
let specified = match result {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
if !context.error_reporting_enabled() {
|
||||
return None;
|
||||
if context.error_reporting_enabled() {
|
||||
// Ignore other kinds of errors that might be reported, such as
|
||||
// ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken),
|
||||
// since Gecko didn't use to report those to the error console.
|
||||
//
|
||||
// TODO(emilio): Revise whether we want to keep this at all, we
|
||||
// use this only for canvas, this warnings are disabled by
|
||||
// default and not available on OffscreenCanvas anyways...
|
||||
if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
|
||||
let location = e.location.clone();
|
||||
let error =
|
||||
ContextualParseError::UnsupportedValue(input.slice_from(start), e);
|
||||
context.log_css_error(location, error);
|
||||
}
|
||||
}
|
||||
// Ignore other kinds of errors that might be reported, such as
|
||||
// ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken),
|
||||
// since Gecko didn't use to report those to the error console.
|
||||
//
|
||||
// TODO(emilio): Revise whether we want to keep this at all, we
|
||||
// use this only for canvas, this warnings are disabled by
|
||||
// default and not available on OffscreenCanvas anyways...
|
||||
if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
|
||||
let location = e.location.clone();
|
||||
let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
|
||||
context.log_css_error(location, error);
|
||||
}
|
||||
return None;
|
||||
return Err(());
|
||||
},
|
||||
};
|
||||
|
||||
@@ -733,12 +733,12 @@ impl Color {
|
||||
/// forms that are invalid in an absolute color.
|
||||
/// https://drafts.csswg.org/css-color-5/#absolute-color
|
||||
/// Returns None if the specified color is not valid as an absolute color.
|
||||
pub fn resolve_to_absolute(&self) -> Option<AbsoluteColor> {
|
||||
pub fn resolve_to_absolute(&self) -> Result<AbsoluteColor, ()> {
|
||||
use crate::values::specified::percentage::ToPercentage;
|
||||
|
||||
match self {
|
||||
Self::Absolute(c) => Some(c.color),
|
||||
Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(),
|
||||
Self::Absolute(c) => Ok(c.color),
|
||||
Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute(),
|
||||
Self::ColorMix(ref mix) => {
|
||||
use crate::color::mix;
|
||||
|
||||
@@ -750,9 +750,9 @@ impl Color {
|
||||
))
|
||||
}
|
||||
|
||||
Some(mix::mix_many(mix.interpolation, items, mix.flags))
|
||||
Ok(mix::mix_many(mix.interpolation, items, mix.flags))
|
||||
},
|
||||
_ => None,
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,7 +855,7 @@ impl Color {
|
||||
///
|
||||
/// If `context` is `None`, and the specified color requires data from
|
||||
/// the context to resolve, then `None` is returned.
|
||||
pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
|
||||
pub fn to_computed_color(&self, context: Option<&Context>) -> Result<ComputedColor, ()> {
|
||||
macro_rules! adjust_absolute_color {
|
||||
($color:expr) => {{
|
||||
// Computed lightness values can not be NaN.
|
||||
@@ -875,7 +875,7 @@ impl Color {
|
||||
}};
|
||||
}
|
||||
|
||||
Some(match *self {
|
||||
Ok(match *self {
|
||||
Color::CurrentColor => ComputedColor::CurrentColor,
|
||||
Color::Absolute(ref absolute) => {
|
||||
let mut color = absolute.color;
|
||||
@@ -891,11 +891,11 @@ impl Color {
|
||||
ComputedColor::Absolute(absolute)
|
||||
} else {
|
||||
let color_function = color_function
|
||||
.map_origin_color(|origin_color| origin_color.to_computed_color(context));
|
||||
.map_origin_color(|origin_color| origin_color.to_computed_color(context))?;
|
||||
ComputedColor::ColorFunction(Box::new(color_function))
|
||||
}
|
||||
},
|
||||
Color::LightDark(ref ld) => ld.compute(context?),
|
||||
Color::LightDark(ref ld) => ld.compute(context.ok_or(())?),
|
||||
Color::ColorMix(ref mix) => {
|
||||
use crate::values::computed::percentage::Percentage;
|
||||
|
||||
@@ -916,9 +916,9 @@ impl Color {
|
||||
Color::ContrastColor(ref c) => {
|
||||
ComputedColor::ContrastColor(Box::new(c.to_computed_color(context)?))
|
||||
},
|
||||
Color::System(system) => system.compute(context?),
|
||||
Color::System(system) => system.compute(context.ok_or(())?),
|
||||
Color::InheritFromBodyQuirk => {
|
||||
ComputedColor::Absolute(context?.device().body_text_color())
|
||||
ComputedColor::Absolute(context.ok_or(())?.device().body_text_color())
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -928,7 +928,7 @@ impl ToComputedValue for Color {
|
||||
type ComputedValue = ComputedColor;
|
||||
|
||||
fn to_computed_value(&self, context: &Context) -> ComputedColor {
|
||||
self.to_computed_color(Some(context)).unwrap_or_else(|| {
|
||||
self.to_computed_color(Some(context)).unwrap_or_else(|_| {
|
||||
debug_assert!(
|
||||
false,
|
||||
"Specified color could not be resolved to a computed color!"
|
||||
@@ -941,8 +941,9 @@ impl ToComputedValue for Color {
|
||||
match *computed {
|
||||
ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()),
|
||||
ComputedColor::ColorFunction(ref color_function) => {
|
||||
let color_function =
|
||||
color_function.map_origin_color(|o| Some(Self::from_computed_value(o)));
|
||||
let color_function = color_function
|
||||
.map_origin_color(|o| Ok(Self::from_computed_value(o)))
|
||||
.unwrap();
|
||||
Self::ColorFunction(Box::new(color_function))
|
||||
},
|
||||
ComputedColor::CurrentColor => Color::CurrentColor,
|
||||
|
||||
@@ -242,15 +242,11 @@ impl Filter {
|
||||
))),
|
||||
Filter::DropShadow(ref shadow) => {
|
||||
if cfg!(feature = "gecko") {
|
||||
let color = match shadow
|
||||
let color = shadow
|
||||
.color
|
||||
.as_ref()
|
||||
.unwrap_or(&Color::currentcolor())
|
||||
.to_computed_color(None)
|
||||
{
|
||||
Some(c) => c,
|
||||
None => return Err(()),
|
||||
};
|
||||
.to_computed_color(None)?;
|
||||
|
||||
let horizontal = ComputedCSSPixelLength::new(
|
||||
shadow
|
||||
|
||||
@@ -9021,7 +9021,7 @@ unsafe fn compute_color(
|
||||
current_color: &AbsoluteColor,
|
||||
value: &nsACString,
|
||||
loader: *mut Loader,
|
||||
) -> Option<ComputeColorResult> {
|
||||
) -> Result<ComputeColorResult, ()> {
|
||||
let mut input = ParserInput::new(value.as_str_unchecked());
|
||||
let mut input = Parser::new(&mut input);
|
||||
let reporter = loader.as_mut().and_then(|loader| {
|
||||
@@ -9054,7 +9054,7 @@ unsafe fn compute_color(
|
||||
let result_color = computed.resolve_to_absolute(current_color);
|
||||
let was_current_color = computed.is_currentcolor();
|
||||
|
||||
Some(ComputeColorResult {
|
||||
Ok(ComputeColorResult {
|
||||
result_color,
|
||||
was_current_color,
|
||||
})
|
||||
@@ -9070,7 +9070,7 @@ pub unsafe extern "C" fn Servo_ComputeColor(
|
||||
loader: *mut Loader,
|
||||
) -> bool {
|
||||
let current_color = AbsoluteColor::from_nscolor(current_color);
|
||||
let Some(result) = compute_color(raw_data, ¤t_color, value, loader) else {
|
||||
let Ok(result) = compute_color(raw_data, ¤t_color, value, loader) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -9087,7 +9087,7 @@ pub unsafe extern "C" fn Servo_ComputeAbsoluteColor(
|
||||
value: &nsACString,
|
||||
result_color: &mut AbsoluteColor,
|
||||
) -> bool {
|
||||
if let Some(color) = compute_color(raw_data, &AbsoluteColor::BLACK, value, ptr::null_mut()) {
|
||||
if let Ok(color) = compute_color(raw_data, &AbsoluteColor::BLACK, value, ptr::null_mut()) {
|
||||
*result_color = color.result_color;
|
||||
true
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user