1use std::cell::Cell;
6use std::rc::Rc;
7
8use base::id::{OffscreenCanvasId, OffscreenCanvasIndex};
9use constellation_traits::{BlobImpl, TransferableOffscreenCanvas};
10use dom_struct::dom_struct;
11use euclid::default::Size2D;
12use js::rust::{HandleObject, HandleValue};
13use pixels::{EncodedImageType, Snapshot};
14use rustc_hash::FxHashMap;
15use script_bindings::match_domstring_ascii;
16use script_bindings::weakref::WeakRef;
17
18use crate::canvas_context::{CanvasContext, OffscreenRenderingContext};
19use crate::dom::bindings::cell::{DomRefCell, Ref};
20use crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::{
21 ImageEncodeOptions, OffscreenCanvasMethods,
22 OffscreenRenderingContext as RootedOffscreenRenderingContext,
23};
24use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas;
25use crate::dom::bindings::error::{Error, Fallible};
26use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
27use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
28use crate::dom::bindings::root::{Dom, DomRoot};
29use crate::dom::bindings::str::DOMString;
30use crate::dom::bindings::structuredclone::StructuredData;
31use crate::dom::bindings::transferable::Transferable;
32use crate::dom::blob::Blob;
33use crate::dom::eventtarget::EventTarget;
34use crate::dom::globalscope::GlobalScope;
35use crate::dom::html::htmlcanvaselement::HTMLCanvasElement;
36use crate::dom::imagebitmap::ImageBitmap;
37use crate::dom::imagebitmaprenderingcontext::ImageBitmapRenderingContext;
38use crate::dom::offscreencanvasrenderingcontext2d::OffscreenCanvasRenderingContext2D;
39use crate::dom::promise::Promise;
40use crate::realms::{AlreadyInRealm, InRealm};
41use crate::script_runtime::{CanGc, JSContext};
42
43#[dom_struct]
45pub(crate) struct OffscreenCanvas {
46 eventtarget: EventTarget,
47 width: Cell<u64>,
48 height: Cell<u64>,
49
50 context: DomRefCell<Option<OffscreenRenderingContext>>,
55
56 placeholder: Option<WeakRef<HTMLCanvasElement>>,
58}
59
60impl OffscreenCanvas {
61 pub(crate) fn new_inherited(
62 width: u64,
63 height: u64,
64 placeholder: Option<WeakRef<HTMLCanvasElement>>,
65 ) -> OffscreenCanvas {
66 OffscreenCanvas {
67 eventtarget: EventTarget::new_inherited(),
68 width: Cell::new(width),
69 height: Cell::new(height),
70 context: DomRefCell::new(None),
71 placeholder,
72 }
73 }
74
75 pub(crate) fn new(
76 global: &GlobalScope,
77 proto: Option<HandleObject>,
78 width: u64,
79 height: u64,
80 placeholder: Option<WeakRef<HTMLCanvasElement>>,
81 can_gc: CanGc,
82 ) -> DomRoot<OffscreenCanvas> {
83 reflect_dom_object_with_proto(
84 Box::new(OffscreenCanvas::new_inherited(width, height, placeholder)),
85 global,
86 proto,
87 can_gc,
88 )
89 }
90
91 pub(crate) fn get_size(&self) -> Size2D<u32> {
92 Size2D::new(
93 self.Width().try_into().unwrap_or(u32::MAX),
94 self.Height().try_into().unwrap_or(u32::MAX),
95 )
96 }
97
98 pub(crate) fn origin_is_clean(&self) -> bool {
99 match *self.context.borrow() {
100 Some(ref context) => context.origin_is_clean(),
101 _ => true,
102 }
103 }
104
105 pub(crate) fn context(&self) -> Option<Ref<'_, OffscreenRenderingContext>> {
106 Ref::filter_map(self.context.borrow(), |ctx| ctx.as_ref()).ok()
107 }
108
109 pub(crate) fn get_image_data(&self) -> Option<Snapshot> {
110 match self.context.borrow().as_ref() {
111 Some(context) => context.get_image_data(),
112 None => {
113 let size = self.get_size();
114 if size.is_empty() ||
115 pixels::compute_rgba8_byte_length_if_within_limit(
116 size.width as usize,
117 size.height as usize,
118 )
119 .is_none()
120 {
121 None
122 } else {
123 Some(Snapshot::cleared(size))
124 }
125 },
126 }
127 }
128
129 pub(crate) fn get_or_init_2d_context(
130 &self,
131 can_gc: CanGc,
132 ) -> Option<DomRoot<OffscreenCanvasRenderingContext2D>> {
133 if let Some(ctx) = self.context() {
134 return match *ctx {
135 OffscreenRenderingContext::Context2d(ref ctx) => Some(DomRoot::from_ref(ctx)),
136 _ => None,
137 };
138 }
139 let context =
140 OffscreenCanvasRenderingContext2D::new(&self.global(), self, self.get_size(), can_gc)?;
141 *self.context.borrow_mut() = Some(OffscreenRenderingContext::Context2d(Dom::from_ref(
142 &*context,
143 )));
144 Some(context)
145 }
146
147 pub(crate) fn get_or_init_bitmaprenderer_context(
149 &self,
150 can_gc: CanGc,
151 ) -> Option<DomRoot<ImageBitmapRenderingContext>> {
152 if let Some(ctx) = self.context() {
155 return match *ctx {
156 OffscreenRenderingContext::BitmapRenderer(ref ctx) => Some(DomRoot::from_ref(ctx)),
157 _ => None,
158 };
159 }
160
161 let canvas =
165 RootedHTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(DomRoot::from_ref(self));
166
167 let context = ImageBitmapRenderingContext::new(&self.global(), &canvas, can_gc);
168
169 *self.context.borrow_mut() = Some(OffscreenRenderingContext::BitmapRenderer(
171 Dom::from_ref(&*context),
172 ));
173
174 Some(context)
176 }
177
178 pub(crate) fn placeholder(&self) -> Option<DomRoot<HTMLCanvasElement>> {
179 self.placeholder
180 .as_ref()
181 .and_then(|placeholder| placeholder.root())
182 }
183}
184
185impl Transferable for OffscreenCanvas {
186 type Index = OffscreenCanvasIndex;
187 type Data = TransferableOffscreenCanvas;
188
189 fn transfer(
191 &self,
192 _cx: &mut js::context::JSContext,
193 ) -> Fallible<(OffscreenCanvasId, TransferableOffscreenCanvas)> {
194 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
199 return Err(Error::DataClone(None));
200 }
201
202 if !self.context.borrow().is_none() {
205 return Err(Error::InvalidState(None));
206 }
207
208 if self.placeholder.is_some() {
210 return Err(Error::InvalidState(None));
211 }
212
213 *self.context.borrow_mut() = Some(OffscreenRenderingContext::Detached);
215
216 let width = self.width.replace(0);
219 let height = self.height.replace(0);
220
221 let transferred = TransferableOffscreenCanvas { width, height };
234
235 Ok((OffscreenCanvasId::new(), transferred))
236 }
237
238 fn transfer_receive(
240 cx: &mut js::context::JSContext,
241 owner: &GlobalScope,
242 _: OffscreenCanvasId,
243 transferred: TransferableOffscreenCanvas,
244 ) -> Result<DomRoot<Self>, ()> {
245 Ok(OffscreenCanvas::new(
258 owner,
259 None,
260 transferred.width,
261 transferred.height,
262 None,
263 CanGc::from_cx(cx),
264 ))
265 }
266
267 fn serialized_storage<'a>(
268 data: StructuredData<'a, '_>,
269 ) -> &'a mut Option<FxHashMap<OffscreenCanvasId, Self::Data>> {
270 match data {
271 StructuredData::Reader(r) => &mut r.offscreen_canvases,
272 StructuredData::Writer(w) => &mut w.offscreen_canvases,
273 }
274 }
275}
276
277impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas {
278 fn Constructor(
280 global: &GlobalScope,
281 proto: Option<HandleObject>,
282 can_gc: CanGc,
283 width: u64,
284 height: u64,
285 ) -> Fallible<DomRoot<OffscreenCanvas>> {
286 Ok(OffscreenCanvas::new(
287 global, proto, width, height, None, can_gc,
288 ))
289 }
290
291 fn GetContext(
293 &self,
294 _cx: JSContext,
295 id: DOMString,
296 _options: HandleValue,
297 can_gc: CanGc,
298 ) -> Fallible<Option<RootedOffscreenRenderingContext>> {
299 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
302 return Err(Error::InvalidState(None));
303 }
304
305 match_domstring_ascii!(id,
306 "2d" => Ok(self
307 .get_or_init_2d_context(can_gc)
308 .map(RootedOffscreenRenderingContext::OffscreenCanvasRenderingContext2D)),
309 "bitmaprenderer" => Ok(self
310 .get_or_init_bitmaprenderer_context(can_gc)
311 .map(RootedOffscreenRenderingContext::ImageBitmapRenderingContext)),
312 _ => Err(Error::Type(c"Unrecognized OffscreenCanvas context type".to_owned())),
319 )
320 }
321
322 fn Width(&self) -> u64 {
324 self.width.get()
325 }
326
327 fn SetWidth(&self, value: u64, can_gc: CanGc) {
329 self.width.set(value);
330
331 if let Some(canvas_context) = self.context() {
332 canvas_context.resize();
333 }
334
335 if let Some(canvas) = self.placeholder() {
336 canvas.set_natural_width(value as _, can_gc)
337 }
338 }
339
340 fn Height(&self) -> u64 {
342 self.height.get()
343 }
344
345 fn SetHeight(&self, value: u64, can_gc: CanGc) {
347 self.height.set(value);
348
349 if let Some(canvas_context) = self.context() {
350 canvas_context.resize();
351 }
352
353 if let Some(canvas) = self.placeholder() {
354 canvas.set_natural_height(value as _, can_gc)
355 }
356 }
357
358 fn TransferToImageBitmap(&self, can_gc: CanGc) -> Fallible<DomRoot<ImageBitmap>> {
360 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
364 return Err(Error::InvalidState(None));
365 }
366
367 if self.context.borrow().is_none() {
370 return Err(Error::InvalidState(None));
371 }
372
373 let Some(snapshot) = self.get_image_data() else {
377 return Err(Error::InvalidState(None));
378 };
379
380 let image_bitmap = ImageBitmap::new(&self.global(), snapshot, can_gc);
381 image_bitmap.set_origin_clean(self.origin_is_clean());
382
383 if let Some(canvas_context) = self.context() {
388 canvas_context.reset_bitmap();
389 }
390
391 Ok(image_bitmap)
393 }
394
395 fn ConvertToBlob(&self, options: &ImageEncodeOptions, can_gc: CanGc) -> Rc<Promise> {
397 let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
399 let promise = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
400
401 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
405 promise.reject_error(Error::InvalidState(None), can_gc);
406 return promise;
407 }
408
409 if !self.origin_is_clean() {
413 promise.reject_error(Error::Security(None), can_gc);
414 return promise;
415 }
416
417 if self.Width() == 0 || self.Height() == 0 {
421 promise.reject_error(Error::IndexSize(None), can_gc);
422 return promise;
423 }
424
425 let Some(mut snapshot) = self.get_image_data() else {
427 promise.reject_error(Error::InvalidState(None), can_gc);
428 return promise;
429 };
430
431 let trusted_this = Trusted::new(self);
437 let trusted_promise = TrustedPromise::new(promise.clone());
438
439 let image_type = EncodedImageType::from(options.type_.to_string());
440 let quality = options.quality;
441
442 self.global()
443 .task_manager()
444 .canvas_blob_task_source()
445 .queue(task!(convert_to_blob: move || {
446 let this = trusted_this.root();
447 let promise = trusted_promise.root();
448
449 let mut encoded: Vec<u8> = vec![];
450
451 if snapshot.encode_for_mime_type(&image_type, quality, &mut encoded).is_err() {
452 promise.reject_error(Error::Encoding(None), CanGc::note());
455 return;
456 };
457
458 let blob_impl = BlobImpl::new_from_bytes(encoded, image_type.as_mime_type());
461 let blob = Blob::new(&this.global(), blob_impl, CanGc::note());
462
463 promise.resolve_native(&blob, CanGc::note());
464 }));
465
466 promise
468 }
469}