canvas: Reset origin clean flag on reset the context to its default state (#39768)

The canvas `origin clean` flag for 2D rendering context should be reset
on the reset the context to its default state.

See
https://html.spec.whatwg.org/multipage/#security-with-canvas-elements
    
The following operations are resetting the `origin clean` flag:
- reset() method
  https://html.spec.whatwg.org/multipage/#dom-context-2d-reset
- canvas (output bitmap) dimensions change

https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions
    
Testing: Improvements in the following tests
- html/semantics/embedded-content/the-canvas-element/security.reset.*
- html/semantics/embedded-content/the-canvas-element/security.resize.*

Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
Andrei Volykhin
2025-10-13 15:11:03 +03:00
committed by GitHub
parent b9b26cdfef
commit 08cc8bc991
9 changed files with 112 additions and 27 deletions

View File

@@ -304,8 +304,10 @@ impl CanvasState {
/// <https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions>
pub(super) fn set_bitmap_dimensions(&self, size: Size2D<u64>) {
// Step 1. Reset the rendering context to its default state.
self.reset_to_initial_state();
// Step 2. Resize the output bitmap to the new width and height.
self.size.replace(adjust_canvas_size(size));
self.ipc_renderer
@@ -316,6 +318,7 @@ impl CanvasState {
.unwrap();
}
/// <https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state>
pub(super) fn reset(&self) {
self.reset_to_initial_state();
@@ -323,15 +326,29 @@ impl CanvasState {
return;
}
// Step 1. Clear canvas's bitmap to transparent black.
self.ipc_renderer
.send(CanvasMsg::Recreate(None, self.get_canvas_id()))
.unwrap();
}
pub(super) fn reset_to_initial_state(&self) {
/// <https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state>
fn reset_to_initial_state(&self) {
// Step 2. Empty the list of subpaths in context's current default path.
*self.current_default_path.borrow_mut() = Path::new();
// Step 3. Clear the context's drawing state stack.
self.saved_states.borrow_mut().clear();
// Step 4. Reset everything that drawing state consists of to their initial values.
*self.state.borrow_mut() = CanvasContextState::new();
// <https://html.spec.whatwg.org/multipage/#security-with-canvas-elements>
// The flag can be reset in certain situations; for example, when changing the value of the
// width or the height content attribute of the canvas element to which a
// CanvasRenderingContext2D is bound, the bitmap is cleared and its origin-clean flag is
// reset.
self.set_origin_clean(true);
}
pub(super) fn reset_bitmap(&self) {
@@ -364,8 +381,8 @@ impl CanvasState {
self.origin_clean.get()
}
fn set_origin_unclean(&self) {
self.origin_clean.set(false)
fn set_origin_clean(&self, origin_clean: bool) {
self.origin_clean.set(origin_clean);
}
/// <https://html.spec.whatwg.org/multipage/#the-image-argument-is-not-origin-clean>
@@ -540,7 +557,7 @@ impl CanvasState {
};
if result.is_ok() && !self.is_origin_clean(image) {
self.set_origin_unclean()
self.set_origin_clean(false);
}
result
}
@@ -1143,7 +1160,7 @@ impl CanvasState {
self.state.borrow_mut().stroke_style =
CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern));
if !pattern.origin_is_clean() {
self.set_origin_unclean();
self.set_origin_clean(false);
}
},
}
@@ -1186,7 +1203,7 @@ impl CanvasState {
self.state.borrow_mut().fill_style =
CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern));
if !pattern.origin_is_clean() {
self.set_origin_unclean();
self.set_origin_clean(false);
}
},
}

View File

@@ -79,16 +79,6 @@ impl CanvasRenderingContext2D {
.map(|context| reflect_dom_object(Box::new(context), global, can_gc))
}
// https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state
fn reset_to_initial_state(&self) {
self.canvas_state.reset_to_initial_state();
}
/// <https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions>
pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) {
self.canvas_state.set_bitmap_dimensions(size);
}
pub(crate) fn take_missing_image_urls(&self) -> Vec<ServoUrl> {
std::mem::take(&mut self.canvas_state.get_missing_image_urls().borrow_mut())
}
@@ -133,7 +123,7 @@ impl CanvasContext for CanvasRenderingContext2D {
}
fn resize(&self) {
self.set_canvas_bitmap_dimensions(self.size().cast())
self.canvas_state.set_bitmap_dimensions(self.size().cast());
}
fn reset_bitmap(&self) {

View File

@@ -750592,14 +750592,28 @@
]
],
"security.reset.cross.html": [
"f823bbd8ac484202100ec8aa737fe46fce95faab",
"bdf784e84996ca4cbebffa4b6446b32b92a14d95",
[
null,
{}
]
],
"security.reset.redirect.html": [
"af881c5fdc3a584d5bba0dc166b91d5873249c41",
"4db862e28ca350a3ba21a22946f09d0b36f98f10",
[
null,
{}
]
],
"security.resize.cross.html": [
"1236e4a9a96db4a314bd86b2ed507995a0d57dae",
[
null,
{}
]
],
"security.resize.redirect.html": [
"fb593d7b077185c067147a1e504e003dce90e065",
[
null,
{}

View File

@@ -1,3 +0,0 @@
[security.reset.cross.html]
[Resetting the canvas state resets the origin-clean flag]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[security.reset.redirect.html]
[Resetting the canvas state resets the origin-clean flag]
expected: FAIL

View File

@@ -22,7 +22,7 @@ _addTest(function(canvas, ctx) {
canvas.width = 50;
ctx.drawImage(document.getElementById('yellow.png'), 0, 0);
assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
canvas.width = 100;
ctx.reset();
canvas.toDataURL();
ctx.getImageData(0, 0, 1, 1);
_assert(true, "true"); // okay if there was no exception

View File

@@ -22,7 +22,7 @@ _addTest(function(canvas, ctx) {
canvas.width = 50;
ctx.drawImage(document.getElementById('yellow.png'), 0, 0);
assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
canvas.width = 100;
ctx.reset();
canvas.toDataURL();
ctx.getImageData(0, 0, 1, 1);
_assert(true, "true"); // okay if there was no exception

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<title>Canvas test: security.resize.cross</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>security.resize.cross</h1>
<p class="desc">Resizing the canvas dimensions resets the origin-clean flag</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("Resizing the canvas dimensions resets the origin-clean flag");
_addTest(function(canvas, ctx) {
canvas.width = 50;
ctx.drawImage(document.getElementById('yellow.png'), 0, 0);
assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
canvas.width = 100;
canvas.toDataURL();
ctx.getImageData(0, 0, 1, 1);
_assert(true, "true"); // okay if there was no exception
});
</script>
<script src="/common/get-host-info.sub.js"></script>
<script src="data:text/javascript,addCrossOriginYellowImage()"></script>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<title>Canvas test: security.resize.redirect</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>security.resize.redirect</h1>
<p class="desc">Resizing the canvas dimensions resets the origin-clean flag</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("Resizing the canvas dimensions resets the origin-clean flag");
_addTest(function(canvas, ctx) {
canvas.width = 50;
ctx.drawImage(document.getElementById('yellow.png'), 0, 0);
assert_throws_dom("SECURITY_ERR", function() { canvas.toDataURL(); });
canvas.width = 100;
canvas.toDataURL();
ctx.getImageData(0, 0, 1, 1);
_assert(true, "true"); // okay if there was no exception
});
</script>
<script src="/common/get-host-info.sub.js"></script>
<script src="data:text/javascript,addCrossOriginRedirectYellowImage()"></script>