canvas: Add initial support of ImageBitmapRenderingContext (#37998)

Add support of the ImageBitmapRenderingContext as "bitmaprenderer"
canvas context mode to RenderingContext/OffscreenRenderingContext
https://html.spec.whatwg.org/multipage/#imagebitmaprenderingcontext

It is initial implementation with public interface API but without
any display presentation support for HTMLCanvasElement.

Testing: Improvements in the following tests:
-
html/canvas/element/manual/imagebitmap/createImageBitmap-origin.sub.html
- html/canvas/offscreen/manual/text/canvas.2d.offscreen*
-
html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transfer.to.imagebitmap.nocrash.html
- imagebitmap-renderingcontext/*

Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
Andrei Volykhin 2025-07-15 11:03:49 +03:00 committed by GitHub
parent ccc902eb7a
commit c817d7b9ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 365 additions and 82 deletions

View file

@ -17,8 +17,8 @@ use crate::dom::node::{Node, NodeDamage};
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
use crate::dom::types::GPUCanvasContext; use crate::dom::types::GPUCanvasContext;
use crate::dom::types::{ use crate::dom::types::{
CanvasRenderingContext2D, OffscreenCanvas, OffscreenCanvasRenderingContext2D, CanvasRenderingContext2D, ImageBitmapRenderingContext, OffscreenCanvas,
WebGL2RenderingContext, WebGLRenderingContext, OffscreenCanvasRenderingContext2D, WebGL2RenderingContext, WebGLRenderingContext,
}; };
pub(crate) trait LayoutCanvasRenderingContextHelpers { pub(crate) trait LayoutCanvasRenderingContextHelpers {
@ -113,6 +113,7 @@ impl CanvasHelpers for HTMLCanvasElementOrOffscreenCanvas {
pub(crate) enum RenderingContext { pub(crate) enum RenderingContext {
Placeholder(Dom<OffscreenCanvas>), Placeholder(Dom<OffscreenCanvas>),
Context2d(Dom<CanvasRenderingContext2D>), Context2d(Dom<CanvasRenderingContext2D>),
BitmapRenderer(Dom<ImageBitmapRenderingContext>),
WebGL(Dom<WebGLRenderingContext>), WebGL(Dom<WebGLRenderingContext>),
WebGL2(Dom<WebGL2RenderingContext>), WebGL2(Dom<WebGL2RenderingContext>),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -128,6 +129,7 @@ impl CanvasContext for RenderingContext {
match self { match self {
RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas.context()?.canvas(), RenderingContext::Placeholder(offscreen_canvas) => offscreen_canvas.context()?.canvas(),
RenderingContext::Context2d(context) => context.canvas(), RenderingContext::Context2d(context) => context.canvas(),
RenderingContext::BitmapRenderer(context) => context.canvas(),
RenderingContext::WebGL(context) => context.canvas(), RenderingContext::WebGL(context) => context.canvas(),
RenderingContext::WebGL2(context) => context.canvas(), RenderingContext::WebGL2(context) => context.canvas(),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -143,6 +145,7 @@ impl CanvasContext for RenderingContext {
} }
}, },
RenderingContext::Context2d(context) => context.resize(), RenderingContext::Context2d(context) => context.resize(),
RenderingContext::BitmapRenderer(context) => context.resize(),
RenderingContext::WebGL(context) => context.resize(), RenderingContext::WebGL(context) => context.resize(),
RenderingContext::WebGL2(context) => context.resize(), RenderingContext::WebGL2(context) => context.resize(),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -158,6 +161,7 @@ impl CanvasContext for RenderingContext {
} }
}, },
RenderingContext::Context2d(context) => context.reset_bitmap(), RenderingContext::Context2d(context) => context.reset_bitmap(),
RenderingContext::BitmapRenderer(context) => context.reset_bitmap(),
RenderingContext::WebGL(context) => context.reset_bitmap(), RenderingContext::WebGL(context) => context.reset_bitmap(),
RenderingContext::WebGL2(context) => context.reset_bitmap(), RenderingContext::WebGL2(context) => context.reset_bitmap(),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -171,6 +175,7 @@ impl CanvasContext for RenderingContext {
offscreen_canvas.context()?.get_image_data() offscreen_canvas.context()?.get_image_data()
}, },
RenderingContext::Context2d(context) => context.get_image_data(), RenderingContext::Context2d(context) => context.get_image_data(),
RenderingContext::BitmapRenderer(context) => context.get_image_data(),
RenderingContext::WebGL(context) => context.get_image_data(), RenderingContext::WebGL(context) => context.get_image_data(),
RenderingContext::WebGL2(context) => context.get_image_data(), RenderingContext::WebGL2(context) => context.get_image_data(),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -184,6 +189,7 @@ impl CanvasContext for RenderingContext {
.context() .context()
.is_none_or(|context| context.origin_is_clean()), .is_none_or(|context| context.origin_is_clean()),
RenderingContext::Context2d(context) => context.origin_is_clean(), RenderingContext::Context2d(context) => context.origin_is_clean(),
RenderingContext::BitmapRenderer(context) => context.origin_is_clean(),
RenderingContext::WebGL(context) => context.origin_is_clean(), RenderingContext::WebGL(context) => context.origin_is_clean(),
RenderingContext::WebGL2(context) => context.origin_is_clean(), RenderingContext::WebGL2(context) => context.origin_is_clean(),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -198,6 +204,7 @@ impl CanvasContext for RenderingContext {
.map(|context| context.size()) .map(|context| context.size())
.unwrap_or_default(), .unwrap_or_default(),
RenderingContext::Context2d(context) => context.size(), RenderingContext::Context2d(context) => context.size(),
RenderingContext::BitmapRenderer(context) => context.size(),
RenderingContext::WebGL(context) => context.size(), RenderingContext::WebGL(context) => context.size(),
RenderingContext::WebGL2(context) => context.size(), RenderingContext::WebGL2(context) => context.size(),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -213,6 +220,7 @@ impl CanvasContext for RenderingContext {
} }
}, },
RenderingContext::Context2d(context) => context.mark_as_dirty(), RenderingContext::Context2d(context) => context.mark_as_dirty(),
RenderingContext::BitmapRenderer(context) => context.mark_as_dirty(),
RenderingContext::WebGL(context) => context.mark_as_dirty(), RenderingContext::WebGL(context) => context.mark_as_dirty(),
RenderingContext::WebGL2(context) => context.mark_as_dirty(), RenderingContext::WebGL2(context) => context.mark_as_dirty(),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -228,6 +236,7 @@ impl CanvasContext for RenderingContext {
} }
}, },
RenderingContext::Context2d(context) => context.update_rendering(), RenderingContext::Context2d(context) => context.update_rendering(),
RenderingContext::BitmapRenderer(context) => context.update_rendering(),
RenderingContext::WebGL(context) => context.update_rendering(), RenderingContext::WebGL(context) => context.update_rendering(),
RenderingContext::WebGL2(context) => context.update_rendering(), RenderingContext::WebGL2(context) => context.update_rendering(),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -241,6 +250,7 @@ impl CanvasContext for RenderingContext {
.context() .context()
.is_some_and(|context| context.onscreen()), .is_some_and(|context| context.onscreen()),
RenderingContext::Context2d(context) => context.onscreen(), RenderingContext::Context2d(context) => context.onscreen(),
RenderingContext::BitmapRenderer(context) => context.onscreen(),
RenderingContext::WebGL(context) => context.onscreen(), RenderingContext::WebGL(context) => context.onscreen(),
RenderingContext::WebGL2(context) => context.onscreen(), RenderingContext::WebGL2(context) => context.onscreen(),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -254,6 +264,7 @@ impl CanvasContext for RenderingContext {
#[derive(Clone, JSTraceable, MallocSizeOf)] #[derive(Clone, JSTraceable, MallocSizeOf)]
pub(crate) enum OffscreenRenderingContext { pub(crate) enum OffscreenRenderingContext {
Context2d(Dom<OffscreenCanvasRenderingContext2D>), Context2d(Dom<OffscreenCanvasRenderingContext2D>),
BitmapRenderer(Dom<ImageBitmapRenderingContext>),
//WebGL(Dom<WebGLRenderingContext>), //WebGL(Dom<WebGLRenderingContext>),
//WebGL2(Dom<WebGL2RenderingContext>), //WebGL2(Dom<WebGL2RenderingContext>),
//#[cfg(feature = "webgpu")] //#[cfg(feature = "webgpu")]
@ -269,6 +280,7 @@ impl CanvasContext for OffscreenRenderingContext {
fn canvas(&self) -> Option<HTMLCanvasElementOrOffscreenCanvas> { fn canvas(&self) -> Option<HTMLCanvasElementOrOffscreenCanvas> {
match self { match self {
OffscreenRenderingContext::Context2d(context) => context.canvas(), OffscreenRenderingContext::Context2d(context) => context.canvas(),
OffscreenRenderingContext::BitmapRenderer(context) => context.canvas(),
OffscreenRenderingContext::Detached => None, OffscreenRenderingContext::Detached => None,
} }
} }
@ -276,6 +288,7 @@ impl CanvasContext for OffscreenRenderingContext {
fn resize(&self) { fn resize(&self) {
match self { match self {
OffscreenRenderingContext::Context2d(context) => context.resize(), OffscreenRenderingContext::Context2d(context) => context.resize(),
OffscreenRenderingContext::BitmapRenderer(context) => context.resize(),
OffscreenRenderingContext::Detached => {}, OffscreenRenderingContext::Detached => {},
} }
} }
@ -283,6 +296,7 @@ impl CanvasContext for OffscreenRenderingContext {
fn reset_bitmap(&self) { fn reset_bitmap(&self) {
match self { match self {
OffscreenRenderingContext::Context2d(context) => context.reset_bitmap(), OffscreenRenderingContext::Context2d(context) => context.reset_bitmap(),
OffscreenRenderingContext::BitmapRenderer(context) => context.reset_bitmap(),
OffscreenRenderingContext::Detached => {}, OffscreenRenderingContext::Detached => {},
} }
} }
@ -290,6 +304,7 @@ impl CanvasContext for OffscreenRenderingContext {
fn get_image_data(&self) -> Option<Snapshot> { fn get_image_data(&self) -> Option<Snapshot> {
match self { match self {
OffscreenRenderingContext::Context2d(context) => context.get_image_data(), OffscreenRenderingContext::Context2d(context) => context.get_image_data(),
OffscreenRenderingContext::BitmapRenderer(context) => context.get_image_data(),
OffscreenRenderingContext::Detached => None, OffscreenRenderingContext::Detached => None,
} }
} }
@ -297,6 +312,7 @@ impl CanvasContext for OffscreenRenderingContext {
fn origin_is_clean(&self) -> bool { fn origin_is_clean(&self) -> bool {
match self { match self {
OffscreenRenderingContext::Context2d(context) => context.origin_is_clean(), OffscreenRenderingContext::Context2d(context) => context.origin_is_clean(),
OffscreenRenderingContext::BitmapRenderer(context) => context.origin_is_clean(),
OffscreenRenderingContext::Detached => true, OffscreenRenderingContext::Detached => true,
} }
} }
@ -304,6 +320,7 @@ impl CanvasContext for OffscreenRenderingContext {
fn size(&self) -> Size2D<u32> { fn size(&self) -> Size2D<u32> {
match self { match self {
OffscreenRenderingContext::Context2d(context) => context.size(), OffscreenRenderingContext::Context2d(context) => context.size(),
OffscreenRenderingContext::BitmapRenderer(context) => context.size(),
OffscreenRenderingContext::Detached => Size2D::default(), OffscreenRenderingContext::Detached => Size2D::default(),
} }
} }
@ -311,6 +328,7 @@ impl CanvasContext for OffscreenRenderingContext {
fn mark_as_dirty(&self) { fn mark_as_dirty(&self) {
match self { match self {
OffscreenRenderingContext::Context2d(context) => context.mark_as_dirty(), OffscreenRenderingContext::Context2d(context) => context.mark_as_dirty(),
OffscreenRenderingContext::BitmapRenderer(context) => context.mark_as_dirty(),
OffscreenRenderingContext::Detached => {}, OffscreenRenderingContext::Detached => {},
} }
} }
@ -318,6 +336,7 @@ impl CanvasContext for OffscreenRenderingContext {
fn update_rendering(&self) { fn update_rendering(&self) {
match self { match self {
OffscreenRenderingContext::Context2d(context) => context.update_rendering(), OffscreenRenderingContext::Context2d(context) => context.update_rendering(),
OffscreenRenderingContext::BitmapRenderer(context) => context.update_rendering(),
OffscreenRenderingContext::Detached => {}, OffscreenRenderingContext::Detached => {},
} }
} }
@ -325,6 +344,7 @@ impl CanvasContext for OffscreenRenderingContext {
fn onscreen(&self) -> bool { fn onscreen(&self) -> bool {
match self { match self {
OffscreenRenderingContext::Context2d(context) => context.onscreen(), OffscreenRenderingContext::Context2d(context) => context.onscreen(),
OffscreenRenderingContext::BitmapRenderer(context) => context.onscreen(),
OffscreenRenderingContext::Detached => false, OffscreenRenderingContext::Detached => false,
} }
} }

View file

@ -36,7 +36,7 @@ use style_traits::{CssWriter, ParsingMode};
use url::Url; use url::Url;
use webrender_api::ImageKey; use webrender_api::ImageKey;
use crate::canvas_context::{OffscreenRenderingContext, RenderingContext}; use crate::canvas_context::{CanvasContext, OffscreenRenderingContext, RenderingContext};
use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{ use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{
CanvasDirection, CanvasFillRule, CanvasImageSource, CanvasLineCap, CanvasLineJoin, CanvasDirection, CanvasFillRule, CanvasImageSource, CanvasLineCap, CanvasLineJoin,
@ -623,7 +623,10 @@ impl CanvasState {
dw: Option<f64>, dw: Option<f64>,
dh: Option<f64>, dh: Option<f64>,
) -> ErrorResult { ) -> ErrorResult {
let canvas_size = canvas.get_size(); let canvas_size = canvas
.context()
.map_or_else(|| canvas.get_size(), |context| context.size());
let dw = dw.unwrap_or(canvas_size.width as f64); let dw = dw.unwrap_or(canvas_size.width as f64);
let dh = dh.unwrap_or(canvas_size.height as f64); let dh = dh.unwrap_or(canvas_size.height as f64);
let sw = sw.unwrap_or(canvas_size.width as f64); let sw = sw.unwrap_or(canvas_size.width as f64);
@ -651,6 +654,18 @@ impl CanvasState {
smoothing_enabled, smoothing_enabled,
)); ));
}, },
OffscreenRenderingContext::BitmapRenderer(ref context) => {
let Some(snapshot) = context.get_image_data() else {
return Ok(());
};
self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
snapshot.as_ipc(),
dest_rect,
source_rect,
smoothing_enabled,
));
},
OffscreenRenderingContext::Detached => return Err(Error::InvalidState), OffscreenRenderingContext::Detached => return Err(Error::InvalidState),
} }
} else { } else {
@ -679,7 +694,10 @@ impl CanvasState {
dw: Option<f64>, dw: Option<f64>,
dh: Option<f64>, dh: Option<f64>,
) -> ErrorResult { ) -> ErrorResult {
let canvas_size = canvas.get_size(); let canvas_size = canvas
.context()
.map_or_else(|| canvas.get_size(), |context| context.size());
let dw = dw.unwrap_or(canvas_size.width as f64); let dw = dw.unwrap_or(canvas_size.width as f64);
let dh = dh.unwrap_or(canvas_size.height as f64); let dh = dh.unwrap_or(canvas_size.height as f64);
let sw = sw.unwrap_or(canvas_size.width as f64); let sw = sw.unwrap_or(canvas_size.width as f64);
@ -707,6 +725,18 @@ impl CanvasState {
smoothing_enabled, smoothing_enabled,
)); ));
}, },
RenderingContext::BitmapRenderer(ref context) => {
let Some(snapshot) = context.get_image_data() else {
return Ok(());
};
self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
snapshot.as_ipc(),
dest_rect,
source_rect,
smoothing_enabled,
));
},
RenderingContext::Placeholder(ref context) => { RenderingContext::Placeholder(ref context) => {
let Some(context) = context.context() else { let Some(context) = context.context() else {
return Err(Error::InvalidState); return Err(Error::InvalidState);
@ -720,6 +750,18 @@ impl CanvasState {
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
)), )),
OffscreenRenderingContext::BitmapRenderer(ref context) => {
let Some(snapshot) = context.get_image_data() else {
return Ok(());
};
self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
snapshot.as_ipc(),
dest_rect,
source_rect,
smoothing_enabled,
));
},
OffscreenRenderingContext::Detached => return Err(Error::InvalidState), OffscreenRenderingContext::Detached => return Err(Error::InvalidState),
} }
}, },

View file

@ -51,6 +51,7 @@ use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
#[cfg(not(feature = "webgpu"))] #[cfg(not(feature = "webgpu"))]
use crate::dom::gpucanvascontext::GPUCanvasContext; use crate::dom::gpucanvascontext::GPUCanvasContext;
use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlelement::HTMLElement;
use crate::dom::imagebitmaprenderingcontext::ImageBitmapRenderingContext;
use crate::dom::mediastream::MediaStream; use crate::dom::mediastream::MediaStream;
use crate::dom::mediastreamtrack::MediaStreamTrack; use crate::dom::mediastreamtrack::MediaStreamTrack;
use crate::dom::node::{Node, NodeTraits}; use crate::dom::node::{Node, NodeTraits};
@ -164,6 +165,9 @@ impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> {
Some(RenderingContext::Context2d(context)) => { Some(RenderingContext::Context2d(context)) => {
context.to_layout().canvas_data_source() context.to_layout().canvas_data_source()
}, },
Some(RenderingContext::BitmapRenderer(context)) => {
context.to_layout().canvas_data_source()
},
Some(RenderingContext::WebGL(context)) => context.to_layout().canvas_data_source(), Some(RenderingContext::WebGL(context)) => context.to_layout().canvas_data_source(),
Some(RenderingContext::WebGL2(context)) => context.to_layout().canvas_data_source(), Some(RenderingContext::WebGL2(context)) => context.to_layout().canvas_data_source(),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
@ -207,6 +211,37 @@ impl HTMLCanvasElement {
Some(context) Some(context)
} }
/// <https://html.spec.whatwg.org/multipage/#canvas-context-bitmaprenderer>
fn get_or_init_bitmaprenderer_context(
&self,
can_gc: CanGc,
) -> Option<DomRoot<ImageBitmapRenderingContext>> {
// Return the same object as was returned the last time the method was
// invoked with this same first argument.
if let Some(ctx) = self.context() {
return match *ctx {
RenderingContext::BitmapRenderer(ref ctx) => Some(DomRoot::from_ref(ctx)),
_ => None,
};
}
// Step 1. Let context be the result of running the
// ImageBitmapRenderingContext creation algorithm given this and
// options.
let context = ImageBitmapRenderingContext::new(
&self.owner_global(),
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self)),
can_gc,
);
// Step 2. Set this's context mode to bitmaprenderer.
*self.context_mode.borrow_mut() =
Some(RenderingContext::BitmapRenderer(Dom::from_ref(&*context)));
// Step 3. Return context.
Some(context)
}
fn get_or_init_webgl_context( fn get_or_init_webgl_context(
&self, &self,
cx: JSContext, cx: JSContext,
@ -410,6 +445,9 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
"2d" => self "2d" => self
.get_or_init_2d_context(can_gc) .get_or_init_2d_context(can_gc)
.map(RootedRenderingContext::CanvasRenderingContext2D), .map(RootedRenderingContext::CanvasRenderingContext2D),
"bitmaprenderer" => self
.get_or_init_bitmaprenderer_context(can_gc)
.map(RootedRenderingContext::ImageBitmapRenderingContext),
"webgl" | "experimental-webgl" => self "webgl" | "experimental-webgl" => self
.get_or_init_webgl_context(cx, options, can_gc) .get_or_init_webgl_context(cx, options, can_gc)
.map(RootedRenderingContext::WebGLRenderingContext), .map(RootedRenderingContext::WebGLRenderingContext),

View file

@ -0,0 +1,193 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use dom_struct::dom_struct;
use euclid::default::Size2D;
use pixels::Snapshot;
use webrender_api::ImageKey;
use crate::canvas_context::{CanvasContext, CanvasHelpers, LayoutCanvasRenderingContextHelpers};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::ImageBitmapMethods;
use crate::dom::bindings::codegen::Bindings::ImageBitmapRenderingContextBinding::ImageBitmapRenderingContextMethods;
use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::root::{DomRoot, LayoutDom};
use crate::dom::globalscope::GlobalScope;
use crate::dom::imagebitmap::ImageBitmap;
use crate::script_runtime::CanGc;
/// <https://html.spec.whatwg.org/multipage/#imagebitmaprenderingcontext>
#[dom_struct]
pub(crate) struct ImageBitmapRenderingContext {
reflector_: Reflector,
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmaprenderingcontext-canvas>
canvas: HTMLCanvasElementOrOffscreenCanvas,
/// Represents both the [output bitmap] and the [bitmap mode] of the context.
/// <https://html.spec.whatwg.org/multipage/#concept-imagebitmaprenderingcontext-output-bitmap>
/// <https://html.spec.whatwg.org/multipage/#concept-imagebitmaprenderingcontext-bitmap-mode>
#[no_trace]
bitmap: DomRefCell<Option<Snapshot>>,
origin_clean: Cell<bool>,
}
impl ImageBitmapRenderingContext {
/// <https://html.spec.whatwg.org/multipage/#imagebitmaprenderingcontext-creation-algorithm>
fn new_inherited(canvas: HTMLCanvasElementOrOffscreenCanvas) -> ImageBitmapRenderingContext {
ImageBitmapRenderingContext {
reflector_: Reflector::new(),
canvas,
bitmap: DomRefCell::new(None),
origin_clean: Cell::new(true),
}
}
pub(crate) fn new(
global: &GlobalScope,
canvas: HTMLCanvasElementOrOffscreenCanvas,
can_gc: CanGc,
) -> DomRoot<ImageBitmapRenderingContext> {
reflect_dom_object(
Box::new(ImageBitmapRenderingContext::new_inherited(canvas)),
global,
can_gc,
)
}
/// <https://html.spec.whatwg.org/multipage/#set-an-imagebitmaprenderingcontext's-output-bitmap>
fn set_bitmap(&self, image_bitmap: Option<&ImageBitmap>) {
match image_bitmap {
Some(image_bitmap) => {
// Step 2.1. Set context's bitmap mode to valid.
// Step 2.2. Set context's output bitmap to refer to the same
// underlying bitmap data as bitmap, without making a copy.
*self.bitmap.borrow_mut() = image_bitmap.bitmap_data().clone();
// The origin-clean flag of bitmap is included in the bitmap
// data to be referenced by context's output bitmap.
self.origin_clean.set(image_bitmap.origin_is_clean());
},
None => {
// Step 1.1. Set context's bitmap mode to blank.
// Step 1.2. Let canvas be the canvas element to which context is bound.
// Step 1.3. Set context's output bitmap to be transparent black
// with a natural width equal to the numeric value of canvas's
// width attribute and a natural height equal to the numeric
// value of canvas's height attribute, those values being
// interpreted in CSS pixels.
*self.bitmap.borrow_mut() = None;
// Step 1.4. Set the output bitmap's origin-clean flag to true.
self.origin_clean.set(true);
},
}
}
}
impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, ImageBitmapRenderingContext> {
fn canvas_data_source(self) -> Option<ImageKey> {
None
}
}
impl CanvasContext for ImageBitmapRenderingContext {
type ID = ();
fn context_id(&self) -> Self::ID {}
fn canvas(&self) -> Option<HTMLCanvasElementOrOffscreenCanvas> {
Some(self.canvas.clone())
}
/// <https://html.spec.whatwg.org/multipage/#the-canvas-element:concept-canvas-bitmaprenderer>
fn resize(&self) {
// The absence of the bitmap is the context's blank bitmap mode so the
// steps to set output bitmap could be omitted.
}
fn reset_bitmap(&self) {
// The newly created bitmap should be of the same dimensions as the
// previous bitmap if the context's bitmap mode is valid.
if self.bitmap.borrow().is_none() {
return;
}
let size = self.bitmap.borrow().as_ref().unwrap().size();
*self.bitmap.borrow_mut() = Some(Snapshot::cleared(size));
}
fn get_image_data(&self) -> Option<Snapshot> {
match self.bitmap.borrow().as_ref() {
Some(bitmap) => Some(bitmap.clone()),
None => {
let size = self.canvas.size();
if size.is_empty() ||
pixels::compute_rgba8_byte_length_if_within_limit(
size.width as usize,
size.height as usize,
)
.is_none()
{
None
} else {
Some(Snapshot::cleared(size))
}
},
}
}
fn origin_is_clean(&self) -> bool {
self.origin_clean.get()
}
fn size(&self) -> Size2D<u32> {
self.bitmap
.borrow()
.as_ref()
.map_or_else(|| self.canvas.size(), |bitmap| bitmap.size())
}
}
impl ImageBitmapRenderingContextMethods<crate::DomTypeHolder> for ImageBitmapRenderingContext {
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmaprenderingcontext-canvas>
fn Canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas {
self.canvas.clone()
}
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmaprenderingcontext-transferfromimagebitmap>
fn TransferFromImageBitmap(&self, image_bitmap: Option<&ImageBitmap>) -> Fallible<()> {
let Some(image_bitmap) = image_bitmap else {
// Step 2. If bitmap is null, then run the steps to set an
// ImageBitmapRenderingContext's output bitmap, with
// bitmapContext as the context argument and no bitmap argument,
// then return.
self.set_bitmap(None);
return Ok(());
};
// Step 3. If the value of bitmap's [[Detached]] internal slot
// is set to true, then throw an "InvalidStateError"
// DOMException.
if image_bitmap.is_detached() {
return Err(Error::InvalidState);
}
// Step 4. Run the steps to set an ImageBitmapRenderingContext's
// output bitmap, with the context argument equal to
// bitmapContext, and the bitmap argument referring to bitmap's
// underlying bitmap data.
self.set_bitmap(Some(image_bitmap));
// Step 5. Set the value of bitmap's [[Detached]] internal slot
// to true.
// Step 6. Unset bitmap's bitmap data.
image_bitmap.Close();
Ok(())
}
}

View file

@ -429,6 +429,7 @@ pub(crate) mod idbtransaction;
pub(crate) mod idbversionchangeevent; pub(crate) mod idbversionchangeevent;
pub(crate) mod iirfilternode; pub(crate) mod iirfilternode;
pub(crate) mod imagebitmap; pub(crate) mod imagebitmap;
pub(crate) mod imagebitmaprenderingcontext;
pub(crate) mod imagedata; pub(crate) mod imagedata;
pub(crate) mod inputevent; pub(crate) mod inputevent;
pub(crate) mod intersectionobserver; pub(crate) mod intersectionobserver;

View file

@ -20,6 +20,7 @@ use crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::{
ImageEncodeOptions, OffscreenCanvasMethods, ImageEncodeOptions, OffscreenCanvasMethods,
OffscreenRenderingContext as RootedOffscreenRenderingContext, OffscreenRenderingContext as RootedOffscreenRenderingContext,
}; };
use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas;
use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto}; use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
@ -32,6 +33,7 @@ use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::htmlcanvaselement::HTMLCanvasElement;
use crate::dom::imagebitmap::ImageBitmap; use crate::dom::imagebitmap::ImageBitmap;
use crate::dom::imagebitmaprenderingcontext::ImageBitmapRenderingContext;
use crate::dom::offscreencanvasrenderingcontext2d::OffscreenCanvasRenderingContext2D; use crate::dom::offscreencanvasrenderingcontext2d::OffscreenCanvasRenderingContext2D;
use crate::dom::promise::Promise; use crate::dom::promise::Promise;
use crate::realms::{AlreadyInRealm, InRealm}; use crate::realms::{AlreadyInRealm, InRealm};
@ -140,6 +142,38 @@ impl OffscreenCanvas {
Some(context) Some(context)
} }
/// <https://html.spec.whatwg.org/multipage/#offscreen-context-type-bitmaprenderer>
pub(crate) fn get_or_init_bitmaprenderer_context(
&self,
can_gc: CanGc,
) -> Option<DomRoot<ImageBitmapRenderingContext>> {
// Return the same object as was returned the last time the method was
// invoked with this same first argument.
if let Some(ctx) = self.context() {
return match *ctx {
OffscreenRenderingContext::BitmapRenderer(ref ctx) => Some(DomRoot::from_ref(ctx)),
_ => None,
};
}
// Step 1. Let context be the result of running the
// ImageBitmapRenderingContext creation algorithm given this and
// options.
let context = ImageBitmapRenderingContext::new(
&self.global(),
HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(DomRoot::from_ref(self)),
can_gc,
);
// Step 2. Set this's context mode to bitmaprenderer.
*self.context.borrow_mut() = Some(OffscreenRenderingContext::BitmapRenderer(
Dom::from_ref(&*context),
));
// Step 3. Return context.
Some(context)
}
pub(crate) fn placeholder(&self) -> Option<DomRoot<HTMLCanvasElement>> { pub(crate) fn placeholder(&self) -> Option<DomRoot<HTMLCanvasElement>> {
self.placeholder self.placeholder
.as_ref() .as_ref()
@ -267,6 +301,9 @@ impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas {
"2d" => Ok(self "2d" => Ok(self
.get_or_init_2d_context(can_gc) .get_or_init_2d_context(can_gc)
.map(RootedOffscreenRenderingContext::OffscreenCanvasRenderingContext2D)), .map(RootedOffscreenRenderingContext::OffscreenCanvasRenderingContext2D)),
"bitmaprenderer" => Ok(self
.get_or_init_bitmaprenderer_context(can_gc)
.map(RootedOffscreenRenderingContext::ImageBitmapRenderingContext)),
/*"webgl" | "experimental-webgl" => self /*"webgl" | "experimental-webgl" => self
.get_or_init_webgl_context(cx, options) .get_or_init_webgl_context(cx, options)
.map(OffscreenRenderingContext::WebGLRenderingContext), .map(OffscreenRenderingContext::WebGLRenderingContext),

View file

@ -4,6 +4,7 @@
// https://html.spec.whatwg.org/multipage/#htmlcanvaselement // https://html.spec.whatwg.org/multipage/#htmlcanvaselement
typedef (CanvasRenderingContext2D typedef (CanvasRenderingContext2D
or ImageBitmapRenderingContext
or WebGLRenderingContext or WebGLRenderingContext
or WebGL2RenderingContext or WebGL2RenderingContext
or GPUCanvasContext) RenderingContext; or GPUCanvasContext) RenderingContext;

View file

@ -0,0 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/// <https://html.spec.whatwg.org/multipage/#imagebitmaprenderingcontext>
[Exposed=(Window,Worker)]
interface ImageBitmapRenderingContext {
readonly attribute (HTMLCanvasElement or OffscreenCanvas) canvas;
[Throws] undefined transferFromImageBitmap(ImageBitmap? bitmap);
};
dictionary ImageBitmapRenderingContextSettings {
boolean alpha = true;
};

View file

@ -3,15 +3,17 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
// https://html.spec.whatwg.org/multipage/#the-offscreencanvas-interface // https://html.spec.whatwg.org/multipage/#the-offscreencanvas-interface
typedef (OffscreenCanvasRenderingContext2D or WebGLRenderingContext or WebGL2RenderingContext) typedef (OffscreenCanvasRenderingContext2D
OffscreenRenderingContext; or ImageBitmapRenderingContext
or WebGLRenderingContext
or WebGL2RenderingContext) OffscreenRenderingContext;
dictionary ImageEncodeOptions { dictionary ImageEncodeOptions {
DOMString type = "image/png"; DOMString type = "image/png";
unrestricted double quality; unrestricted double quality;
}; };
//enum OffscreenRenderingContextId { "2d", "webgl", "webgl2" }; //enum OffscreenRenderingContextId { "2d", "bitmaprenderer", "webgl", "webgl2" };
[Exposed=(Window,Worker), Transferable, Pref="dom_offscreen_canvas_enabled"] [Exposed=(Window,Worker), Transferable, Pref="dom_offscreen_canvas_enabled"]
interface OffscreenCanvas : EventTarget { interface OffscreenCanvas : EventTarget {

View file

@ -176,6 +176,8 @@ skip: true
skip: true skip: true
[moving-between-documents] [moving-between-documents]
skip: true skip: true
[imagebitmap-renderingcontext]
skip: false
[import-maps] [import-maps]
skip: false skip: false
[IndexedDB] [IndexedDB]

View file

@ -1,27 +1,9 @@
[createImageBitmap-origin.sub.html] [createImageBitmap-origin.sub.html]
[redirected to cross-origin HTMLVideoElement: origin unclear bitmaprenderer.transferFromImageBitmap]
expected: FAIL
[unclean HTMLCanvasElement: origin unclear bitmaprenderer.transferFromImageBitmap]
expected: FAIL
[cross-origin SVGImageElement: origin unclear bitmaprenderer.transferFromImageBitmap] [cross-origin SVGImageElement: origin unclear bitmaprenderer.transferFromImageBitmap]
expected: FAIL expected: FAIL
[cross-origin HTMLVideoElement: origin unclear bitmaprenderer.transferFromImageBitmap]
expected: FAIL
[cross-origin SVGImageElement: origin unclear 2dContext.drawImage] [cross-origin SVGImageElement: origin unclear 2dContext.drawImage]
expected: FAIL expected: FAIL
[cross-origin HTMLImageElement: origin unclear bitmaprenderer.transferFromImageBitmap]
expected: FAIL
[unclean ImageBitmap: origin unclear bitmaprenderer.transferFromImageBitmap]
expected: FAIL
[redirected to same-origin HTMLVideoElement: origin unclear bitmaprenderer.transferFromImageBitmap]
expected: FAIL
[cross-origin SVGImageElement: origin unclear getImageData] [cross-origin SVGImageElement: origin unclear getImageData]
expected: FAIL expected: FAIL

View file

@ -1,2 +1,2 @@
[canvas.2d.offscreen.direction.html] [canvas.2d.offscreen.direction.html]
expected: TIMEOUT expected: FAIL

View file

@ -1,2 +1,2 @@
[canvas.2d.offscreen.lang.html] [canvas.2d.offscreen.lang.html]
expected: TIMEOUT expected: FAIL

View file

@ -1,2 +1,2 @@
[canvas.2d.offscreen.lang.inherit.html] [canvas.2d.offscreen.lang.inherit.html]
expected: TIMEOUT expected: FAIL

View file

@ -1,3 +0,0 @@
[offscreencanvas.transfer.to.imagebitmap.nocrash.html]
[offscreencanvas]
expected: FAIL

View file

@ -2,30 +2,6 @@
[Path2D interface: operation roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>))] [Path2D interface: operation roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>))]
expected: FAIL expected: FAIL
[ImageBitmapRenderingContext interface: existence and properties of interface object]
expected: FAIL
[ImageBitmapRenderingContext interface object length]
expected: FAIL
[ImageBitmapRenderingContext interface object name]
expected: FAIL
[ImageBitmapRenderingContext interface: existence and properties of interface prototype object]
expected: FAIL
[ImageBitmapRenderingContext interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL
[ImageBitmapRenderingContext interface: existence and properties of interface prototype object's @@unscopables property]
expected: FAIL
[ImageBitmapRenderingContext interface: attribute canvas]
expected: FAIL
[ImageBitmapRenderingContext interface: operation transferFromImageBitmap(ImageBitmap?)]
expected: FAIL
[OffscreenCanvas interface: attribute oncontextlost] [OffscreenCanvas interface: attribute oncontextlost]
expected: FAIL expected: FAIL

View file

@ -4346,30 +4346,6 @@
[Path2D interface: operation roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>))] [Path2D interface: operation roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>))]
expected: FAIL expected: FAIL
[ImageBitmapRenderingContext interface: existence and properties of interface object]
expected: FAIL
[ImageBitmapRenderingContext interface object length]
expected: FAIL
[ImageBitmapRenderingContext interface object name]
expected: FAIL
[ImageBitmapRenderingContext interface: existence and properties of interface prototype object]
expected: FAIL
[ImageBitmapRenderingContext interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL
[ImageBitmapRenderingContext interface: existence and properties of interface prototype object's @@unscopables property]
expected: FAIL
[ImageBitmapRenderingContext interface: attribute canvas]
expected: FAIL
[ImageBitmapRenderingContext interface: operation transferFromImageBitmap(ImageBitmap?)]
expected: FAIL
[OffscreenCanvas interface: attribute oncontextlost] [OffscreenCanvas interface: attribute oncontextlost]
expected: FAIL expected: FAIL

View file

@ -13623,14 +13623,14 @@
] ]
], ],
"interfaces.https.html": [ "interfaces.https.html": [
"8a9345525360e7a7ce69e84e394b65a4cbc0ab34", "641c5ba19d389390b7b51da7644f011b0c42f33a",
[ [
null, null,
{} {}
] ]
], ],
"interfaces.worker.js": [ "interfaces.worker.js": [
"f217fd8fb6b46144bc3576a081cc6ce5db3129d5", "463bfc25211014203a5094baae4b4e2d890bf9aa",
[ [
"mozilla/interfaces.worker.html", "mozilla/interfaces.worker.html",
{} {}

View file

@ -225,6 +225,7 @@ test_interfaces([
"ImageData", "ImageData",
"Image", "Image",
"ImageBitmap", "ImageBitmap",
"ImageBitmapRenderingContext",
"InputEvent", "InputEvent",
"IntersectionObserver", "IntersectionObserver",
"IntersectionObserverEntry", "IntersectionObserverEntry",

View file

@ -77,6 +77,7 @@ test_interfaces([
"Headers", "Headers",
"History", "History",
"ImageBitmap", "ImageBitmap",
"ImageBitmapRenderingContext",
"ImageData", "ImageData",
"MessageChannel", "MessageChannel",
"MessageEvent", "MessageEvent",