canvas: Unify retrieving image data from the HTMLImageElement (#37809)

Currently, HTMLImageElement uses as an image source
(ImageBitmapSource, CanvasImageSource, TexImageSource)
in various canvas2D/WebGL operations, and there is a small
inconsistency in how we get the image data of the 'img' element:
usability checking and retrieving the image data from the image cache.

To simplify and avoid state inconsistency between the window's image
cache and the 'img' element, let's retrieve the image data (as a raster)
from the HTMLImageElement itself.

Testing: No expected changes in testing results, except the
'drawimage_svg_image_with_foreign_object_does_not_taint.html'
which is 'false' passed because drawing of the non supported vector
image
is silently skip instead of throwing the 'InvalidState' exception
anymore.

Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
Andrei Volykhin 2025-07-02 11:13:30 +03:00 committed by GitHub
parent d5b6160119
commit d0579256bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 118 additions and 140 deletions

View file

@ -53,9 +53,10 @@ use crate::dom::canvasgradient::{CanvasGradient, CanvasGradientStyle, ToFillOrSt
use crate::dom::canvaspattern::CanvasPattern;
use crate::dom::dommatrix::DOMMatrix;
use crate::dom::dommatrixreadonly::dommatrix2dinit_to_matrix;
use crate::dom::element::{Element, cors_setting_for_element};
use crate::dom::element::Element;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlcanvaselement::HTMLCanvasElement;
use crate::dom::htmlimageelement::HTMLImageElement;
use crate::dom::htmlvideoelement::HTMLVideoElement;
use crate::dom::imagebitmap::ImageBitmap;
use crate::dom::imagedata::ImageData;
@ -444,6 +445,17 @@ impl CanvasState {
}
let result = match image {
CanvasImageSource::HTMLImageElement(ref image) => {
// https://html.spec.whatwg.org/multipage/#drawing-images
// 2. Let usability be the result of checking the usability of image.
// 3. If usability is bad, then return (without drawing anything).
if !image.is_usable()? {
return Ok(());
}
self.draw_html_image_element(image, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh);
Ok(())
},
CanvasImageSource::HTMLVideoElement(ref video) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
// Step 2. Let usability be the result of checking the usability of image.
@ -480,34 +492,6 @@ impl CanvasState {
self.draw_offscreen_canvas(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh)
},
CanvasImageSource::HTMLImageElement(ref image) => {
// https://html.spec.whatwg.org/multipage/#drawing-images
// 2. Let usability be the result of checking the usability of image.
// 3. If usability is bad, then return (without drawing anything).
if !image.is_usable()? {
return Ok(());
}
// TODO(pylbrecht): is it possible for image.get_url() to return None after the usability check?
// https://html.spec.whatwg.org/multipage/#img-error
// If the image argument is an HTMLImageElement object that is in the broken state,
// then throw an InvalidStateError exception
let url = image.get_url().ok_or(Error::InvalidState)?;
let cors_setting = cors_setting_for_element(image.upcast());
self.fetch_and_draw_image_data(
htmlcanvas,
url,
cors_setting,
sx,
sy,
sw,
sh,
dx,
dy,
dw,
dh,
)
},
CanvasImageSource::CSSStyleValue(ref value) => {
let url = value
.get_url(self.base_url.clone())
@ -524,6 +508,52 @@ impl CanvasState {
result
}
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage>
#[allow(clippy::too_many_arguments)]
fn draw_html_image_element(
&self,
image: &HTMLImageElement,
canvas: Option<&HTMLCanvasElement>,
sx: f64,
sy: f64,
sw: Option<f64>,
sh: Option<f64>,
dx: f64,
dy: f64,
dw: Option<f64>,
dh: Option<f64>,
) {
let Some(snapshot) = image.get_raster_image_data() else {
return;
};
// Step 4. Establish the source and destination rectangles.
let image_size = snapshot.size();
let dw = dw.unwrap_or(image_size.width as f64);
let dh = dh.unwrap_or(image_size.height as f64);
let sw = sw.unwrap_or(image_size.width as f64);
let sh = sh.unwrap_or(image_size.height as f64);
let (source_rect, dest_rect) =
self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
// Step 5. If one of the sw or sh arguments is zero, then return. Nothing is painted.
if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
return;
}
let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
snapshot.as_ipc(),
dest_rect,
source_rect,
smoothing_enabled,
));
self.mark_as_dirty(canvas);
}
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage>
#[allow(clippy::too_many_arguments)]
fn draw_html_video_element(
@ -1099,12 +1129,7 @@ impl CanvasState {
return Ok(None);
}
image
.get_url()
.and_then(|url| {
self.fetch_image_data(url, cors_setting_for_element(image.upcast()))
})
.ok_or(Error::InvalidState)?
image.get_raster_image_data().ok_or(Error::InvalidState)?
},
CanvasImageSource::HTMLVideoElement(ref video) => {
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>