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(&self) -> Fallible<(OffscreenCanvasId, TransferableOffscreenCanvas)> {
191 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
196 return Err(Error::DataClone(None));
197 }
198
199 if !self.context.borrow().is_none() {
202 return Err(Error::InvalidState(None));
203 }
204
205 if self.placeholder.is_some() {
207 return Err(Error::InvalidState(None));
208 }
209
210 *self.context.borrow_mut() = Some(OffscreenRenderingContext::Detached);
212
213 let width = self.width.replace(0);
216 let height = self.height.replace(0);
217
218 let transferred = TransferableOffscreenCanvas { width, height };
231
232 Ok((OffscreenCanvasId::new(), transferred))
233 }
234
235 fn transfer_receive(
237 owner: &GlobalScope,
238 _: OffscreenCanvasId,
239 transferred: TransferableOffscreenCanvas,
240 ) -> Result<DomRoot<Self>, ()> {
241 Ok(OffscreenCanvas::new(
254 owner,
255 None,
256 transferred.width,
257 transferred.height,
258 None,
259 CanGc::note(),
260 ))
261 }
262
263 fn serialized_storage<'a>(
264 data: StructuredData<'a, '_>,
265 ) -> &'a mut Option<FxHashMap<OffscreenCanvasId, Self::Data>> {
266 match data {
267 StructuredData::Reader(r) => &mut r.offscreen_canvases,
268 StructuredData::Writer(w) => &mut w.offscreen_canvases,
269 }
270 }
271}
272
273impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas {
274 fn Constructor(
276 global: &GlobalScope,
277 proto: Option<HandleObject>,
278 can_gc: CanGc,
279 width: u64,
280 height: u64,
281 ) -> Fallible<DomRoot<OffscreenCanvas>> {
282 Ok(OffscreenCanvas::new(
283 global, proto, width, height, None, can_gc,
284 ))
285 }
286
287 fn GetContext(
289 &self,
290 _cx: JSContext,
291 id: DOMString,
292 _options: HandleValue,
293 can_gc: CanGc,
294 ) -> Fallible<Option<RootedOffscreenRenderingContext>> {
295 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
298 return Err(Error::InvalidState(None));
299 }
300
301 match_domstring_ascii!(id,
302 "2d" => Ok(self
303 .get_or_init_2d_context(can_gc)
304 .map(RootedOffscreenRenderingContext::OffscreenCanvasRenderingContext2D)),
305 "bitmaprenderer" => Ok(self
306 .get_or_init_bitmaprenderer_context(can_gc)
307 .map(RootedOffscreenRenderingContext::ImageBitmapRenderingContext)),
308 _ => Err(Error::Type(String::from(
315 "Unrecognized OffscreenCanvas context type",
316 ))),
317 )
318 }
319
320 fn Width(&self) -> u64 {
322 self.width.get()
323 }
324
325 fn SetWidth(&self, value: u64, can_gc: CanGc) {
327 self.width.set(value);
328
329 if let Some(canvas_context) = self.context() {
330 canvas_context.resize();
331 }
332
333 if let Some(canvas) = self.placeholder() {
334 canvas.set_natural_width(value as _, can_gc)
335 }
336 }
337
338 fn Height(&self) -> u64 {
340 self.height.get()
341 }
342
343 fn SetHeight(&self, value: u64, can_gc: CanGc) {
345 self.height.set(value);
346
347 if let Some(canvas_context) = self.context() {
348 canvas_context.resize();
349 }
350
351 if let Some(canvas) = self.placeholder() {
352 canvas.set_natural_height(value as _, can_gc)
353 }
354 }
355
356 fn TransferToImageBitmap(&self, can_gc: CanGc) -> Fallible<DomRoot<ImageBitmap>> {
358 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
362 return Err(Error::InvalidState(None));
363 }
364
365 if self.context.borrow().is_none() {
368 return Err(Error::InvalidState(None));
369 }
370
371 let Some(snapshot) = self.get_image_data() else {
375 return Err(Error::InvalidState(None));
376 };
377
378 let image_bitmap = ImageBitmap::new(&self.global(), snapshot, can_gc);
379 image_bitmap.set_origin_clean(self.origin_is_clean());
380
381 if let Some(canvas_context) = self.context() {
386 canvas_context.reset_bitmap();
387 }
388
389 Ok(image_bitmap)
391 }
392
393 fn ConvertToBlob(&self, options: &ImageEncodeOptions, can_gc: CanGc) -> Rc<Promise> {
395 let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
397 let promise = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
398
399 if let Some(OffscreenRenderingContext::Detached) = *self.context.borrow() {
403 promise.reject_error(Error::InvalidState(None), can_gc);
404 return promise;
405 }
406
407 if !self.origin_is_clean() {
411 promise.reject_error(Error::Security, can_gc);
412 return promise;
413 }
414
415 if self.Width() == 0 || self.Height() == 0 {
419 promise.reject_error(Error::IndexSize(None), can_gc);
420 return promise;
421 }
422
423 let Some(mut snapshot) = self.get_image_data() else {
425 promise.reject_error(Error::InvalidState(None), can_gc);
426 return promise;
427 };
428
429 let trusted_this = Trusted::new(self);
435 let trusted_promise = TrustedPromise::new(promise.clone());
436
437 let image_type = EncodedImageType::from(options.type_.to_string());
438 let quality = options.quality;
439
440 self.global()
441 .task_manager()
442 .canvas_blob_task_source()
443 .queue(task!(convert_to_blob: move || {
444 let this = trusted_this.root();
445 let promise = trusted_promise.root();
446
447 let mut encoded: Vec<u8> = vec![];
448
449 if snapshot.encode_for_mime_type(&image_type, quality, &mut encoded).is_err() {
450 promise.reject_error(Error::Encoding, CanGc::note());
453 return;
454 };
455
456 let blob_impl = BlobImpl::new_from_bytes(encoded, image_type.as_mime_type());
459 let blob = Blob::new(&this.global(), blob_impl, CanGc::note());
460
461 promise.resolve_native(&blob, CanGc::note());
462 }));
463
464 promise
466 }
467}