Allow currentColor in canvas context's shadowColor (#30754)

This allows unifying the parse_color function and method, aligns Servo
with other browsers, and obeys the HTML spec:
 - https://html.spec.whatwg.org/multipage/canvas.html#shadows
 - https://html.spec.whatwg.org/multipage/infrastructure.html#parsed-as-a-css-color-value
This commit is contained in:
Oriol Brufau 2023-11-20 12:03:18 +01:00 committed by GitHub
parent 334c67a3cc
commit 3543a87592
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 186 additions and 56 deletions

View file

@ -296,43 +296,6 @@ impl CanvasState {
} }
} }
fn parse_color(&self, canvas: Option<&HTMLCanvasElement>, string: &str) -> Result<RGBA, ()> {
let mut input = ParserInput::new(string);
let mut parser = Parser::new(&mut input);
let color = CSSColor::parse(&mut parser);
if parser.is_exhausted() {
match color {
Ok(CSSColor::Rgba(rgba)) => Ok(rgba),
Ok(CSSColor::CurrentColor) => {
// TODO: https://github.com/whatwg/html/issues/1099
// Reconsider how to calculate currentColor in a display:none canvas
// TODO: will need to check that the context bitmap mode is fixed
// once we implement CanvasProxy
let canvas = match canvas {
// https://drafts.css-houdini.org/css-paint-api/#2d-rendering-context
// Whenever "currentColor" is used as a color in the PaintRenderingContext2D API,
// it is treated as opaque black.
None => return Ok(RGBA::new(0, 0, 0, 1.0)),
Some(ref canvas) => &**canvas,
};
let canvas_element = canvas.upcast::<Element>();
match canvas_element.style() {
Some(ref s) if canvas_element.has_css_layout_box() => {
Ok(s.get_inherited_text().color)
},
_ => Ok(RGBA::new(0, 0, 0, 1.0)),
}
},
_ => Err(()),
}
} else {
Err(())
}
}
pub fn get_rect(&self, canvas_size: Size2D<u64>, rect: Rect<u64>) -> Vec<u8> { pub fn get_rect(&self, canvas_size: Size2D<u64>, rect: Rect<u64>) -> Vec<u8> {
assert!(self.origin_is_clean()); assert!(self.origin_is_clean());
@ -753,10 +716,10 @@ impl CanvasState {
} }
// https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor
pub fn set_shadow_color(&self, value: DOMString) { pub fn set_shadow_color(&self, canvas: Option<&HTMLCanvasElement>, value: DOMString) {
if let Ok(color) = parse_color(&value) { if let Ok(rgba) = parse_color(canvas, &value) {
self.state.borrow_mut().shadow_color = color; self.state.borrow_mut().shadow_color = rgba;
self.send_canvas_2d_msg(Canvas2dMsg::SetShadowColor(color)) self.send_canvas_2d_msg(Canvas2dMsg::SetShadowColor(rgba))
} }
} }
@ -785,7 +748,7 @@ impl CanvasState {
) { ) {
match value { match value {
StringOrCanvasGradientOrCanvasPattern::String(string) => { StringOrCanvasGradientOrCanvasPattern::String(string) => {
if let Ok(rgba) = self.parse_color(canvas, &string) { if let Ok(rgba) = parse_color(canvas, &string) {
self.state.borrow_mut().stroke_style = CanvasFillOrStrokeStyle::Color(rgba); self.state.borrow_mut().stroke_style = CanvasFillOrStrokeStyle::Color(rgba);
} }
}, },
@ -828,7 +791,7 @@ impl CanvasState {
) { ) {
match value { match value {
StringOrCanvasGradientOrCanvasPattern::String(string) => { StringOrCanvasGradientOrCanvasPattern::String(string) => {
if let Ok(rgba) = self.parse_color(canvas, &string) { if let Ok(rgba) = parse_color(canvas, &string) {
self.state.borrow_mut().fill_style = CanvasFillOrStrokeStyle::Color(rgba); self.state.borrow_mut().fill_style = CanvasFillOrStrokeStyle::Color(rgba);
} }
}, },
@ -1702,18 +1665,40 @@ impl CanvasState {
} }
} }
pub fn parse_color(string: &str) -> Result<RGBA, ()> { fn parse_color(canvas: Option<&HTMLCanvasElement>, string: &str) -> Result<RGBA, ()> {
let mut input = ParserInput::new(string); let mut input = ParserInput::new(string);
let mut parser = Parser::new(&mut input); let mut parser = Parser::new(&mut input);
match CSSColor::parse(&mut parser) { let color = CSSColor::parse(&mut parser);
Ok(CSSColor::Rgba(rgba)) => { if parser.is_exhausted() {
if parser.is_exhausted() { match color {
Ok(rgba) Ok(CSSColor::Rgba(rgba)) => Ok(rgba),
} else { Ok(CSSColor::CurrentColor) => {
Err(()) // TODO: https://github.com/whatwg/html/issues/1099
} // Reconsider how to calculate currentColor in a display:none canvas
},
_ => Err(()), // TODO: will need to check that the context bitmap mode is fixed
// once we implement CanvasProxy
let canvas = match canvas {
// https://drafts.css-houdini.org/css-paint-api/#2d-rendering-context
// Whenever "currentColor" is used as a color in the PaintRenderingContext2D API,
// it is treated as opaque black.
None => return Ok(RGBA::new(0, 0, 0, 1.0)),
Some(ref canvas) => &**canvas,
};
let canvas_element = canvas.upcast::<Element>();
match canvas_element.style() {
Some(ref s) if canvas_element.has_css_layout_box() => {
Ok(s.get_inherited_text().color)
},
_ => Ok(RGBA::new(0, 0, 0, 1.0)),
}
},
_ => Err(()),
}
} else {
Err(())
} }
} }

View file

@ -647,7 +647,8 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
// https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor
fn SetShadowColor(&self, value: DOMString) { fn SetShadowColor(&self, value: DOMString) {
self.canvas_state.set_shadow_color(value) self.canvas_state
.set_shadow_color(self.canvas.as_ref().map(|c| &**c), value)
} }
} }

View file

@ -140,7 +140,8 @@ impl OffscreenCanvasRenderingContext2DMethods for OffscreenCanvasRenderingContex
// https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor
fn SetShadowColor(&self, value: DOMString) { fn SetShadowColor(&self, value: DOMString) {
self.canvas_state.set_shadow_color(value) self.canvas_state
.set_shadow_color(self.htmlcanvas.as_ref().map(|c| &**c), value)
} }
// https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle

View file

@ -439502,7 +439502,7 @@
[] []
], ],
"shadows.yaml": [ "shadows.yaml": [
"366ac0b6072dfaa9de58abbda84525d9fe1806d1", "953ab2c555d5ad9d46d52b6c74f09d344850982c",
[] []
], ],
"text.yaml": [ "text.yaml": [
@ -621476,6 +621476,27 @@
{} {}
] ]
], ],
"2d.shadow.attributes.shadowColor.current.basic.html": [
"bfdc54d31b71e663bb2eef282c2fb4c9655af98f",
[
null,
{}
]
],
"2d.shadow.attributes.shadowColor.current.changed.html": [
"9eee2122d5efb1c56f0ad695b90e1d2df54f2c5c",
[
null,
{}
]
],
"2d.shadow.attributes.shadowColor.current.removed.html": [
"e1989cbdc80622ff1598adf0485377c7d28777b6",
[
null,
{}
]
],
"2d.shadow.attributes.shadowColor.initial.html": [ "2d.shadow.attributes.shadowColor.initial.html": [
"f4d0d33d6d9af347af413fdfc1436034a4a3c919", "f4d0d33d6d9af347af413fdfc1436034a4a3c919",
[ [

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<title>Canvas test: 2d.shadow.attributes.shadowColor.current.basic</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/html/canvas/resources/canvas-tests.js"></script>
<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
<body class="show_output">
<h1>2d.shadow.attributes.shadowColor.current.basic</h1>
<p class="desc">currentColor is computed from the canvas element</p>
<p class="output">Actual output:</p>
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
<ul id="d"></ul>
<script>
var t = async_test("currentColor is computed from the canvas element");
_addTest(function(canvas, ctx) {
canvas.style.color = '#0f0';
ctx.shadowColor = 'currentColor';
_assertSame(ctx.shadowColor, '#00ff00', "ctx.shadowColor", "'#00ff00'");
});
</script>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<title>Canvas test: 2d.shadow.attributes.shadowColor.current.changed</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/html/canvas/resources/canvas-tests.js"></script>
<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
<body class="show_output">
<h1>2d.shadow.attributes.shadowColor.current.changed</h1>
<p class="desc">currentColor is computed when the attribute is set, not when it is painted</p>
<p class="output">Actual output:</p>
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
<ul id="d"></ul>
<script>
var t = async_test("currentColor is computed when the attribute is set, not when it is painted");
_addTest(function(canvas, ctx) {
canvas.style.color = '#0f0';
ctx.shadowColor = 'currentColor';
canvas.style.color = '#f00';
_assertSame(ctx.shadowColor, '#00ff00', "ctx.shadowColor", "'#00ff00'");
});
</script>

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<title>Canvas test: 2d.shadow.attributes.shadowColor.current.removed</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/html/canvas/resources/canvas-tests.js"></script>
<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
<body class="show_output">
<h1>2d.shadow.attributes.shadowColor.current.removed</h1>
<p class="desc">currentColor is solid black when the canvas element is not in a document</p>
<p class="output">Actual output:</p>
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
<ul id="d"></ul>
<script>
var t = async_test("currentColor is solid black when the canvas element is not in a document");
_addTest(function(canvas, ctx) {
// Try not to let it undetectably incorrectly pick up opaque-black
// from other parts of the document:
document.documentElement.style.color = '#f00';
document.body.style.color = '#f00';
canvas.style.color = '#f00';
canvas.remove();
ctx.shadowColor = 'currentColor';
_assertSame(ctx.shadowColor, '#000000', "ctx.shadowColor", "'#000000'");
});
</script>

View file

@ -134,6 +134,37 @@
ctx.shadowColor = 'RGBA(0,255, 0,0)'; ctx.shadowColor = 'RGBA(0,255, 0,0)';
@assert ctx.shadowColor === 'rgba(0, 255, 0, 0)'; @assert ctx.shadowColor === 'rgba(0, 255, 0, 0)';
- name: 2d.shadow.attributes.shadowColor.current.basic
desc: currentColor is computed from the canvas element
canvasType: ['HtmlCanvas']
code: |
canvas.style.color = '#0f0';
ctx.shadowColor = 'currentColor';
@assert ctx.shadowColor === '#00ff00';
- name: 2d.shadow.attributes.shadowColor.current.changed
desc: currentColor is computed when the attribute is set, not when it is painted
canvasType: ['HtmlCanvas']
code: |
canvas.style.color = '#0f0';
ctx.shadowColor = 'currentColor';
canvas.style.color = '#f00';
@assert ctx.shadowColor === '#00ff00';
- name: 2d.shadow.attributes.shadowColor.current.removed
desc: currentColor is solid black when the canvas element is not in a document
canvasType: ['HtmlCanvas']
code: |
// Try not to let it undetectably incorrectly pick up opaque-black
// from other parts of the document:
document.documentElement.style.color = '#f00';
document.body.style.color = '#f00';
canvas.style.color = '#f00';
canvas.remove();
ctx.shadowColor = 'currentColor';
@assert ctx.shadowColor === '#000000';
- name: 2d.shadow.attributes.shadowColor.invalid - name: 2d.shadow.attributes.shadowColor.invalid
code: | code: |
ctx.shadowColor = '#00ff00'; ctx.shadowColor = '#00ff00';