1use std::cell::Cell;
6use std::collections::HashMap;
7use std::rc::Rc;
8
9use base::id::{OffscreenCanvasId, OffscreenCanvasIndex};
10use constellation_traits::{BlobImpl, TransferableOffscreenCanvas};
11use dom_struct::dom_struct;
12use euclid::default::Size2D;
13use js::rust::{HandleObject, HandleValue};
14use pixels::{EncodedImageType, Snapshot};
15use script_bindings::weakref::WeakRef;
16
17use crate::canvas_context::{CanvasContext, OffscreenRenderingContext};
18use crate::dom::bindings::cell::{DomRefCell, Ref};
19use crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::{
20 ImageEncodeOptions, OffscreenCanvasMethods,
21 OffscreenRenderingContext as RootedOffscreenRenderingContext,
22};
23use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas;
24use crate::dom::bindings::error::{Error, Fallible};
25use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
26use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
27use crate::dom::bindings::root::{Dom, DomRoot};
28use crate::dom::bindings::str::DOMString;
29use crate::dom::bindings::structuredclone::StructuredData;
30use crate::dom::bindings::transferable::Transferable;
31use crate::dom::blob::Blob;
32use crate::dom::eventtarget::EventTarget;
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::html::htmlcanvaselement::HTMLCanvasElement;
35use crate::dom::imagebitmap::ImageBitmap;
36use crate::dom::imagebitmaprenderingcontext::ImageBitmapRenderingContext;
37use crate::dom::offscreencanvasrenderingcontext2d::OffscreenCanvasRenderingContext2D;
38use crate::dom::promise::Promise;
39use crate::realms::{AlreadyInRealm, InRealm};
40use crate::script_runtime::{CanGc, JSContext};
41
42#[dom_struct]
44pub(crate) struct OffscreenCanvas {
45 eventtarget: EventTarget,
46 width: Cell<u64>,
47 height: Cell<u64>,
48
49 context: DomRefCell<Option<OffscreenRenderingContext>>,
54
55 placeholder: Option<WeakRef<HTMLCanvasElement>>,
57}
58
59impl OffscreenCanvas {
60 pub(crate) fn new_inherited(
61 width: u64,
62 height: u64,
63 placeholder: Option<WeakRef<HTMLCanvasElement>>,
64 ) -> OffscreenCanvas {
65 OffscreenCanvas {
66 eventtarget: EventTarget::new_inherited(),
67 width: Cell::new(width),
68 height: Cell::new(height),
69 context: DomRefCell::new(None),
70 placeholder,
71 }
72 }
73
74 pub(crate) fn new(
75 global: &GlobalScope,
76 proto: Option<HandleObject>,
77 width: u64,
78 height: u64,
79 placeholder: Option<WeakRef<HTMLCanvasElement>>,
80 can_gc: CanGc,
81 ) -> DomRoot<OffscreenCanvas> {
82 reflect_dom_object_with_proto(
83 Box::new(OffscreenCanvas::new_inherited(width, height, placeholder)),
84 global,
85 proto,
86 can_gc,
87 )
88 }
89
90 pub(crate) fn get_size(&self) -> Size2D<u32> {
91 Size2D::new(
92 self.Width().try_into().unwrap_or(u32::MAX),
93 self.Height().try_into().unwrap_or(u32::MAX),
94 )
95 }
96
97 pub(crate) fn origin_is_clean(&self) -> bool {
98 match *self.context.borrow() {
99 Some(ref context) => context.origin_is_clean(),
100 _ => true,
101 }
102 }
103
104 pub(crate) fn context(&self) -> Option<Ref<'_, OffscreenRenderingContext>> {
105 Ref::filter_map(self.context.borrow(), |ctx| ctx.as_ref()).ok()
106 }
107
108 pub(crate) fn get_image_data(&self) -> Option<Snapshot> {
109 match self.context.borrow().as_ref() {
110 Some(context) => context.get_image_data(),
111 None => {
112 let size = self.get_size();
113 if size.is_empty() ||
114 pixels::compute_rgba8_byte_length_if_within_limit(
115 size.width as usize,
116 size.height as usize,
117 )
118 .is_none()
119 {
120 None
121 } else {
122 Some(Snapshot::cleared(size))
123 }
124 },
125 }
126 }
127
128 pub(crate) fn get_or_init_2d_context(
129 &self,
130 can_gc: CanGc,
131 ) -> Option<DomRoot<OffscreenCanvasRenderingContext2D>> {
132 if let Some(ctx) = self.context() {
133 return match *ctx {
134 OffscreenRenderingContext::Context2d(ref ctx) => Some(DomRoot::from_ref(ctx)),
135 _ => None,
136 };
137 }
138 let context =
139 OffscreenCanvasRenderingContext2D::new(&self.global(), self, self.get_size(), can_gc)?;
140 *self.context.borrow_mut() = Some(OffscreenRenderingContext::Context2d(Dom::from_ref(
141 &*context,
142 )));
143 Some(context)
144 }
145
146 pub(crate) fn get_or_init_bitmaprenderer_context(
148 &self,
149 can_gc: CanGc,
150 ) -> Option<DomRoot<ImageBitmapRenderingContext>> {
151 if let Some(ctx) = self.context() {
154 return match *ctx {
155 OffscreenRenderingContext::BitmapRenderer(ref ctx) => Some(DomRoot::from_ref(ctx)),
156 _ => None,
157 };
158 }
159
160 let canvas =
164 RootedHTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(DomRoot::from_ref(self));
165
166 let context = ImageBitmapRenderingContext::new(&self.global(), &canvas, can_gc);
167
168 *self.context.borrow_mut() = Some(OffscreenRenderingContext::BitmapRenderer(
170 Dom::from_ref(&*context),
171 ));
172
173 Some(context)
175 }
176
177 pub(crate) fn placeholder(&self) -> Option<DomRoot<HTMLCanvasElement>> {
178 self.placeholder
179 .as_ref()
180 .and_then(|placeholder| placeholder.root())
181 }
182}
183
184impl Transferable for OffscreenCanvas {
185 type Index = OffscreenCanvasIndex;
186 type Data = TransferableOffscreenCanvas;
187
188 fn transfer(&self) -> Fallible<(OffscreenCanvasId, TransferableOffscreenCanvas)> {
190 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
195 return Err(Error::DataClone(None));
196 }
197
198 if !self.context.borrow().is_none() {
201 return Err(Error::InvalidState);
202 }
203
204 if self.placeholder.is_some() {
206 return Err(Error::InvalidState);
207 }
208
209 *self.context.borrow_mut() = Some(OffscreenRenderingContext::Detached);
211
212 let width = self.width.replace(0);
215 let height = self.height.replace(0);
216
217 let transferred = TransferableOffscreenCanvas { width, height };
230
231 Ok((OffscreenCanvasId::new(), transferred))
232 }
233
234 fn transfer_receive(
236 owner: &GlobalScope,
237 _: OffscreenCanvasId,
238 transferred: TransferableOffscreenCanvas,
239 ) -> Result<DomRoot<Self>, ()> {
240 Ok(OffscreenCanvas::new(
253 owner,
254 None,
255 transferred.width,
256 transferred.height,
257 None,
258 CanGc::note(),
259 ))
260 }
261
262 fn serialized_storage<'a>(
263 data: StructuredData<'a, '_>,
264 ) -> &'a mut Option<HashMap<OffscreenCanvasId, Self::Data>> {
265 match data {
266 StructuredData::Reader(r) => &mut r.offscreen_canvases,
267 StructuredData::Writer(w) => &mut w.offscreen_canvases,
268 }
269 }
270}
271
272impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas {
273 fn Constructor(
275 global: &GlobalScope,
276 proto: Option<HandleObject>,
277 can_gc: CanGc,
278 width: u64,
279 height: u64,
280 ) -> Fallible<DomRoot<OffscreenCanvas>> {
281 Ok(OffscreenCanvas::new(
282 global, proto, width, height, None, can_gc,
283 ))
284 }
285
286 fn GetContext(
288 &self,
289 _cx: JSContext,
290 id: DOMString,
291 _options: HandleValue,
292 can_gc: CanGc,
293 ) -> Fallible<Option<RootedOffscreenRenderingContext>> {
294 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
297 return Err(Error::InvalidState);
298 }
299
300 match &*id {
301 "2d" => Ok(self
302 .get_or_init_2d_context(can_gc)
303 .map(RootedOffscreenRenderingContext::OffscreenCanvasRenderingContext2D)),
304 "bitmaprenderer" => Ok(self
305 .get_or_init_bitmaprenderer_context(can_gc)
306 .map(RootedOffscreenRenderingContext::ImageBitmapRenderingContext)),
307 _ => Err(Error::Type(String::from(
314 "Unrecognized OffscreenCanvas context type",
315 ))),
316 }
317 }
318
319 fn Width(&self) -> u64 {
321 self.width.get()
322 }
323
324 fn SetWidth(&self, value: u64, can_gc: CanGc) {
326 self.width.set(value);
327
328 if let Some(canvas_context) = self.context() {
329 canvas_context.resize();
330 }
331
332 if let Some(canvas) = self.placeholder() {
333 canvas.set_natural_width(value as _, can_gc)
334 }
335 }
336
337 fn Height(&self) -> u64 {
339 self.height.get()
340 }
341
342 fn SetHeight(&self, value: u64, can_gc: CanGc) {
344 self.height.set(value);
345
346 if let Some(canvas_context) = self.context() {
347 canvas_context.resize();
348 }
349
350 if let Some(canvas) = self.placeholder() {
351 canvas.set_natural_height(value as _, can_gc)
352 }
353 }
354
355 fn TransferToImageBitmap(&self, can_gc: CanGc) -> Fallible<DomRoot<ImageBitmap>> {
357 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
361 return Err(Error::InvalidState);
362 }
363
364 if self.context.borrow().is_none() {
367 return Err(Error::InvalidState);
368 }
369
370 let Some(snapshot) = self.get_image_data() else {
374 return Err(Error::InvalidState);
375 };
376
377 let image_bitmap = ImageBitmap::new(&self.global(), snapshot, can_gc);
378 image_bitmap.set_origin_clean(self.origin_is_clean());
379
380 if let Some(canvas_context) = self.context() {
385 canvas_context.reset_bitmap();
386 }
387
388 Ok(image_bitmap)
390 }
391
392 fn ConvertToBlob(&self, options: &ImageEncodeOptions, can_gc: CanGc) -> Rc<Promise> {
394 let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
396 let promise = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
397
398 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
402 promise.reject_error(Error::InvalidState, can_gc);
403 return promise;
404 }
405
406 if !self.origin_is_clean() {
410 promise.reject_error(Error::Security, can_gc);
411 return promise;
412 }
413
414 if self.Width() == 0 || self.Height() == 0 {
418 promise.reject_error(Error::IndexSize, can_gc);
419 return promise;
420 }
421
422 let Some(mut snapshot) = self.get_image_data() else {
424 promise.reject_error(Error::InvalidState, can_gc);
425 return promise;
426 };
427
428 let trusted_this = Trusted::new(self);
434 let trusted_promise = TrustedPromise::new(promise.clone());
435
436 let image_type = EncodedImageType::from(options.type_.to_string());
437 let quality = options.quality;
438
439 self.global()
440 .task_manager()
441 .canvas_blob_task_source()
442 .queue(task!(convert_to_blob: move || {
443 let this = trusted_this.root();
444 let promise = trusted_promise.root();
445
446 let mut encoded: Vec<u8> = vec![];
447
448 if snapshot.encode_for_mime_type(&image_type, quality, &mut encoded).is_err() {
449 promise.reject_error(Error::Encoding, CanGc::note());
452 return;
453 };
454
455 let blob_impl = BlobImpl::new_from_bytes(encoded, image_type.as_mime_type());
458 let blob = Blob::new(&this.global(), blob_impl, CanGc::note());
459
460 promise.resolve_native(&blob, CanGc::note());
461 }));
462
463 promise
465 }
466}