mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
http://www.robohornet.org gives a score of 101.36 on master, and 102.68 with this PR. The latter is slightly better, but probably within noise level. So it looks like this PR does not affect DOM performance. This is expected since `Box::new` is defined as: ```rust impl<T> Box<T> { #[inline(always)] pub fn new(x: T) -> Box<T> { box x } } ``` With inlining, it should compile to the same as box syntax.
502 lines
22 KiB
Rust
502 lines
22 KiB
Rust
/* 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 http://mozilla.org/MPL/2.0/. */
|
|
|
|
use dom::bindings::callback::CallbackContainer;
|
|
use dom::bindings::cell::DomRefCell;
|
|
use dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding;
|
|
use dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding::PaintWorkletGlobalScopeMethods;
|
|
use dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
|
|
use dom::bindings::conversions::get_property;
|
|
use dom::bindings::conversions::get_property_jsval;
|
|
use dom::bindings::error::Error;
|
|
use dom::bindings::error::Fallible;
|
|
use dom::bindings::inheritance::Castable;
|
|
use dom::bindings::reflector::DomObject;
|
|
use dom::bindings::root::{Dom, DomRoot};
|
|
use dom::bindings::str::DOMString;
|
|
use dom::cssstylevalue::CSSStyleValue;
|
|
use dom::paintrenderingcontext2d::PaintRenderingContext2D;
|
|
use dom::paintsize::PaintSize;
|
|
use dom::stylepropertymapreadonly::StylePropertyMapReadOnly;
|
|
use dom::worklet::WorkletExecutor;
|
|
use dom::workletglobalscope::WorkletGlobalScope;
|
|
use dom::workletglobalscope::WorkletGlobalScopeInit;
|
|
use dom::workletglobalscope::WorkletTask;
|
|
use dom_struct::dom_struct;
|
|
use euclid::ScaleFactor;
|
|
use euclid::TypedSize2D;
|
|
use ipc_channel::ipc;
|
|
use js::jsapi::Call;
|
|
use js::jsapi::Construct1;
|
|
use js::jsapi::HandleValue;
|
|
use js::jsapi::HandleValueArray;
|
|
use js::jsapi::Heap;
|
|
use js::jsapi::IsCallable;
|
|
use js::jsapi::IsConstructor;
|
|
use js::jsapi::JSAutoCompartment;
|
|
use js::jsapi::JS_ClearPendingException;
|
|
use js::jsapi::JS_IsExceptionPending;
|
|
use js::jsapi::JS_NewArrayObject;
|
|
use js::jsval::JSVal;
|
|
use js::jsval::ObjectValue;
|
|
use js::jsval::UndefinedValue;
|
|
use js::rust::Runtime;
|
|
use msg::constellation_msg::PipelineId;
|
|
use net_traits::image::base::PixelFormat;
|
|
use net_traits::image_cache::ImageCache;
|
|
use script_traits::DrawAPaintImageResult;
|
|
use script_traits::Painter;
|
|
use servo_atoms::Atom;
|
|
use servo_url::ServoUrl;
|
|
use std::cell::Cell;
|
|
use std::collections::HashMap;
|
|
use std::collections::hash_map::Entry;
|
|
use std::ptr::null_mut;
|
|
use std::rc::Rc;
|
|
use std::sync::Arc;
|
|
use std::sync::Mutex;
|
|
use std::sync::mpsc;
|
|
use std::sync::mpsc::Sender;
|
|
use style_traits::CSSPixel;
|
|
use style_traits::DevicePixel;
|
|
use style_traits::SpeculativePainter;
|
|
|
|
/// https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope
|
|
#[dom_struct]
|
|
pub struct PaintWorkletGlobalScope {
|
|
/// The worklet global for this object
|
|
worklet_global: WorkletGlobalScope,
|
|
/// The image cache
|
|
#[ignore_heap_size_of = "Arc"]
|
|
image_cache: Arc<ImageCache>,
|
|
/// https://drafts.css-houdini.org/css-paint-api/#paint-definitions
|
|
paint_definitions: DomRefCell<HashMap<Atom, Box<PaintDefinition>>>,
|
|
/// https://drafts.css-houdini.org/css-paint-api/#paint-class-instances
|
|
paint_class_instances: DomRefCell<HashMap<Atom, Box<Heap<JSVal>>>>,
|
|
/// The most recent name the worklet was called with
|
|
cached_name: DomRefCell<Atom>,
|
|
/// The most recent size the worklet was drawn at
|
|
cached_size: Cell<TypedSize2D<f32, CSSPixel>>,
|
|
/// The most recent device pixel ratio the worklet was drawn at
|
|
cached_device_pixel_ratio: Cell<ScaleFactor<f32, CSSPixel, DevicePixel>>,
|
|
/// The most recent properties the worklet was drawn at
|
|
cached_properties: DomRefCell<Vec<(Atom, String)>>,
|
|
/// The most recent arguments the worklet was drawn at
|
|
cached_arguments: DomRefCell<Vec<String>>,
|
|
/// The most recent result
|
|
cached_result: DomRefCell<DrawAPaintImageResult>,
|
|
}
|
|
|
|
impl PaintWorkletGlobalScope {
|
|
#[allow(unsafe_code)]
|
|
pub fn new(runtime: &Runtime,
|
|
pipeline_id: PipelineId,
|
|
base_url: ServoUrl,
|
|
executor: WorkletExecutor,
|
|
init: &WorkletGlobalScopeInit)
|
|
-> DomRoot<PaintWorkletGlobalScope> {
|
|
debug!("Creating paint worklet global scope for pipeline {}.", pipeline_id);
|
|
let global = Box::new(PaintWorkletGlobalScope {
|
|
worklet_global: WorkletGlobalScope::new_inherited(pipeline_id, base_url, executor, init),
|
|
image_cache: init.image_cache.clone(),
|
|
paint_definitions: Default::default(),
|
|
paint_class_instances: Default::default(),
|
|
cached_name: DomRefCell::new(Atom::from("")),
|
|
cached_size: Cell::new(TypedSize2D::zero()),
|
|
cached_device_pixel_ratio: Cell::new(ScaleFactor::new(1.0)),
|
|
cached_properties: Default::default(),
|
|
cached_arguments: Default::default(),
|
|
cached_result: DomRefCell::new(DrawAPaintImageResult {
|
|
width: 0,
|
|
height: 0,
|
|
format: PixelFormat::BGRA8,
|
|
image_key: None,
|
|
missing_image_urls: Vec::new(),
|
|
}),
|
|
});
|
|
unsafe { PaintWorkletGlobalScopeBinding::Wrap(runtime.cx(), global) }
|
|
}
|
|
|
|
pub fn image_cache(&self) -> Arc<ImageCache> {
|
|
self.image_cache.clone()
|
|
}
|
|
|
|
pub fn perform_a_worklet_task(&self, task: PaintWorkletTask) {
|
|
match task {
|
|
PaintWorkletTask::DrawAPaintImage(name, size, device_pixel_ratio, properties, arguments, sender) => {
|
|
let cache_hit = (&*self.cached_name.borrow() == &name) &&
|
|
(self.cached_size.get() == size) &&
|
|
(self.cached_device_pixel_ratio.get() == device_pixel_ratio) &&
|
|
(&*self.cached_properties.borrow() == &properties) &&
|
|
(&*self.cached_arguments.borrow() == &arguments);
|
|
let result = if cache_hit {
|
|
debug!("Cache hit on paint worklet {}!", name);
|
|
self.cached_result.borrow().clone()
|
|
} else {
|
|
debug!("Cache miss on paint worklet {}!", name);
|
|
let map = StylePropertyMapReadOnly::from_iter(self.upcast(), properties.iter().cloned());
|
|
let result = self.draw_a_paint_image(&name, size, device_pixel_ratio, &*map, &*arguments);
|
|
if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
|
|
*self.cached_name.borrow_mut() = name;
|
|
self.cached_size.set(size);
|
|
self.cached_device_pixel_ratio.set(device_pixel_ratio);
|
|
*self.cached_properties.borrow_mut() = properties;
|
|
*self.cached_arguments.borrow_mut() = arguments;
|
|
*self.cached_result.borrow_mut() = result.clone();
|
|
}
|
|
result
|
|
};
|
|
let _ = sender.send(result);
|
|
}
|
|
PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments) => {
|
|
let should_speculate = (&*self.cached_name.borrow() != &name) ||
|
|
(&*self.cached_properties.borrow() != &properties) ||
|
|
(&*self.cached_arguments.borrow() != &arguments);
|
|
if should_speculate {
|
|
let size = self.cached_size.get();
|
|
let device_pixel_ratio = self.cached_device_pixel_ratio.get();
|
|
let map = StylePropertyMapReadOnly::from_iter(self.upcast(), properties.iter().cloned());
|
|
let result = self.draw_a_paint_image(&name, size, device_pixel_ratio, &*map, &*arguments);
|
|
if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) {
|
|
*self.cached_name.borrow_mut() = name;
|
|
*self.cached_properties.borrow_mut() = properties;
|
|
*self.cached_arguments.borrow_mut() = arguments;
|
|
*self.cached_result.borrow_mut() = result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image
|
|
fn draw_a_paint_image(&self,
|
|
name: &Atom,
|
|
size_in_px: TypedSize2D<f32, CSSPixel>,
|
|
device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
|
|
properties: &StylePropertyMapReadOnly,
|
|
arguments: &[String])
|
|
-> DrawAPaintImageResult
|
|
{
|
|
let size_in_dpx = size_in_px * device_pixel_ratio;
|
|
let size_in_dpx = TypedSize2D::new(size_in_dpx.width.abs() as u32, size_in_dpx.height.abs() as u32);
|
|
|
|
// TODO: Steps 1-5.
|
|
|
|
// TODO: document paint definitions.
|
|
self.invoke_a_paint_callback(name, size_in_px, size_in_dpx, device_pixel_ratio, properties, arguments)
|
|
}
|
|
|
|
/// https://drafts.css-houdini.org/css-paint-api/#invoke-a-paint-callback
|
|
#[allow(unsafe_code)]
|
|
fn invoke_a_paint_callback(&self,
|
|
name: &Atom,
|
|
size_in_px: TypedSize2D<f32, CSSPixel>,
|
|
size_in_dpx: TypedSize2D<u32, DevicePixel>,
|
|
device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
|
|
properties: &StylePropertyMapReadOnly,
|
|
arguments: &[String])
|
|
-> DrawAPaintImageResult
|
|
{
|
|
debug!("Invoking a paint callback {}({},{}) at {}.",
|
|
name, size_in_px.width, size_in_px.height, device_pixel_ratio);
|
|
|
|
let cx = self.worklet_global.get_cx();
|
|
let _ac = JSAutoCompartment::new(cx, self.worklet_global.reflector().get_jsobject().get());
|
|
|
|
// TODO: Steps 1-2.1.
|
|
// Step 2.2-5.1.
|
|
rooted!(in(cx) let mut class_constructor = UndefinedValue());
|
|
rooted!(in(cx) let mut paint_function = UndefinedValue());
|
|
let rendering_context = match self.paint_definitions.borrow().get(name) {
|
|
None => {
|
|
// Step 2.2.
|
|
warn!("Drawing un-registered paint definition {}.", name);
|
|
return self.invalid_image(size_in_dpx, vec![]);
|
|
}
|
|
Some(definition) => {
|
|
// Step 5.1
|
|
if !definition.constructor_valid_flag.get() {
|
|
debug!("Drawing invalid paint definition {}.", name);
|
|
return self.invalid_image(size_in_dpx, vec![]);
|
|
}
|
|
class_constructor.set(definition.class_constructor.get());
|
|
paint_function.set(definition.paint_function.get());
|
|
DomRoot::from_ref(&*definition.context)
|
|
}
|
|
};
|
|
|
|
// Steps 5.2-5.4
|
|
// TODO: the spec requires calling the constructor now, but we might want to
|
|
// prepopulate the paint instance in `RegisterPaint`, to avoid calling it in
|
|
// the primary worklet thread.
|
|
// https://github.com/servo/servo/issues/17377
|
|
rooted!(in(cx) let mut paint_instance = UndefinedValue());
|
|
match self.paint_class_instances.borrow_mut().entry(name.clone()) {
|
|
Entry::Occupied(entry) => paint_instance.set(entry.get().get()),
|
|
Entry::Vacant(entry) => {
|
|
// Step 5.2-5.3
|
|
let args = HandleValueArray::new();
|
|
rooted!(in(cx) let mut result = null_mut());
|
|
unsafe { Construct1(cx, class_constructor.handle(), &args, result.handle_mut()); }
|
|
paint_instance.set(ObjectValue(result.get()));
|
|
if unsafe { JS_IsExceptionPending(cx) } {
|
|
debug!("Paint constructor threw an exception {}.", name);
|
|
unsafe { JS_ClearPendingException(cx); }
|
|
self.paint_definitions.borrow_mut().get_mut(name)
|
|
.expect("Vanishing paint definition.")
|
|
.constructor_valid_flag.set(false);
|
|
return self.invalid_image(size_in_dpx, vec![]);
|
|
}
|
|
// Step 5.4
|
|
entry.insert(Box::new(Heap::default())).set(paint_instance.get());
|
|
}
|
|
};
|
|
|
|
// TODO: Steps 6-7
|
|
// Step 8
|
|
// TODO: the spec requires creating a new paint rendering context each time,
|
|
// this code recycles the same one.
|
|
rendering_context.set_bitmap_dimensions(size_in_px, device_pixel_ratio);
|
|
|
|
// Step 9
|
|
let paint_size = PaintSize::new(self, size_in_px);
|
|
|
|
// TODO: Step 10
|
|
// Steps 11-12
|
|
debug!("Invoking paint function {}.", name);
|
|
rooted_vec!(let arguments_values <- arguments.iter().cloned()
|
|
.map(|argument| CSSStyleValue::new(self.upcast(), argument)));
|
|
let arguments_value_vec: Vec<JSVal> = arguments_values.iter()
|
|
.map(|argument| ObjectValue(argument.reflector().get_jsobject().get()))
|
|
.collect();
|
|
let arguments_value_array = unsafe { HandleValueArray::from_rooted_slice(&*arguments_value_vec) };
|
|
rooted!(in(cx) let argument_object = unsafe { JS_NewArrayObject(cx, &arguments_value_array) });
|
|
|
|
let args_slice = [
|
|
ObjectValue(rendering_context.reflector().get_jsobject().get()),
|
|
ObjectValue(paint_size.reflector().get_jsobject().get()),
|
|
ObjectValue(properties.reflector().get_jsobject().get()),
|
|
ObjectValue(argument_object.get()),
|
|
];
|
|
let args = unsafe { HandleValueArray::from_rooted_slice(&args_slice) };
|
|
|
|
rooted!(in(cx) let mut result = UndefinedValue());
|
|
unsafe { Call(cx, paint_instance.handle(), paint_function.handle(), &args, result.handle_mut()); }
|
|
let missing_image_urls = rendering_context.take_missing_image_urls();
|
|
|
|
// Step 13.
|
|
if unsafe { JS_IsExceptionPending(cx) } {
|
|
debug!("Paint function threw an exception {}.", name);
|
|
unsafe { JS_ClearPendingException(cx); }
|
|
return self.invalid_image(size_in_dpx, missing_image_urls);
|
|
}
|
|
|
|
let (sender, receiver) = ipc::channel().expect("IPC channel creation.");
|
|
rendering_context.send_data(sender);
|
|
let image_key = match receiver.recv() {
|
|
Ok(data) => Some(data.image_key),
|
|
_ => None,
|
|
};
|
|
|
|
DrawAPaintImageResult {
|
|
width: size_in_dpx.width,
|
|
height: size_in_dpx.height,
|
|
format: PixelFormat::BGRA8,
|
|
image_key: image_key,
|
|
missing_image_urls: missing_image_urls,
|
|
}
|
|
}
|
|
|
|
// https://drafts.csswg.org/css-images-4/#invalid-image
|
|
fn invalid_image(&self, size: TypedSize2D<u32, DevicePixel>, missing_image_urls: Vec<ServoUrl>)
|
|
-> DrawAPaintImageResult {
|
|
debug!("Returning an invalid image.");
|
|
DrawAPaintImageResult {
|
|
width: size.width as u32,
|
|
height: size.height as u32,
|
|
format: PixelFormat::BGRA8,
|
|
image_key: None,
|
|
missing_image_urls: missing_image_urls,
|
|
}
|
|
}
|
|
|
|
fn painter(&self, name: Atom) -> Box<Painter> {
|
|
// Rather annoyingly we have to use a mutex here to make the painter Sync.
|
|
struct WorkletPainter {
|
|
name: Atom,
|
|
executor: Mutex<WorkletExecutor>,
|
|
}
|
|
impl SpeculativePainter for WorkletPainter {
|
|
fn speculatively_draw_a_paint_image(&self,
|
|
properties: Vec<(Atom, String)>,
|
|
arguments: Vec<String>) {
|
|
let name = self.name.clone();
|
|
let task = PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments);
|
|
self.executor.lock().expect("Locking a painter.")
|
|
.schedule_a_worklet_task(WorkletTask::Paint(task));
|
|
}
|
|
}
|
|
impl Painter for WorkletPainter {
|
|
fn draw_a_paint_image(&self,
|
|
size: TypedSize2D<f32, CSSPixel>,
|
|
device_pixel_ratio: ScaleFactor<f32, CSSPixel, DevicePixel>,
|
|
properties: Vec<(Atom, String)>,
|
|
arguments: Vec<String>)
|
|
-> DrawAPaintImageResult {
|
|
let name = self.name.clone();
|
|
let (sender, receiver) = mpsc::channel();
|
|
let task = PaintWorkletTask::DrawAPaintImage(name,
|
|
size,
|
|
device_pixel_ratio,
|
|
properties,
|
|
arguments,
|
|
sender);
|
|
self.executor.lock().expect("Locking a painter.")
|
|
.schedule_a_worklet_task(WorkletTask::Paint(task));
|
|
receiver.recv().expect("Worklet thread died?")
|
|
}
|
|
}
|
|
Box::new(WorkletPainter {
|
|
name: name,
|
|
executor: Mutex::new(self.worklet_global.executor()),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope {
|
|
#[allow(unsafe_code)]
|
|
#[allow(unrooted_must_root)]
|
|
/// https://drafts.css-houdini.org/css-paint-api/#dom-paintworkletglobalscope-registerpaint
|
|
fn RegisterPaint(&self, name: DOMString, paint_ctor: Rc<VoidFunction>) -> Fallible<()> {
|
|
let name = Atom::from(name);
|
|
let cx = self.worklet_global.get_cx();
|
|
rooted!(in(cx) let paint_obj = paint_ctor.callback_holder().get());
|
|
rooted!(in(cx) let paint_val = ObjectValue(paint_obj.get()));
|
|
|
|
debug!("Registering paint image name {}.", name);
|
|
|
|
// Step 1.
|
|
if name.is_empty() {
|
|
return Err(Error::Type(String::from("Empty paint name."))) ;
|
|
}
|
|
|
|
// Step 2-3.
|
|
if self.paint_definitions.borrow().contains_key(&name) {
|
|
return Err(Error::InvalidModification);
|
|
}
|
|
|
|
// Step 4-6.
|
|
let mut property_names: Vec<String> =
|
|
unsafe { get_property(cx, paint_obj.handle(), "inputProperties", ()) }?
|
|
.unwrap_or_default();
|
|
let properties = property_names.drain(..).map(Atom::from).collect();
|
|
|
|
// Step 7-9.
|
|
let input_arguments: Vec<String> =
|
|
unsafe { get_property(cx, paint_obj.handle(), "inputArguments", ()) }?
|
|
.unwrap_or_default();
|
|
|
|
// TODO: Steps 10-11.
|
|
|
|
// Steps 12-13.
|
|
let alpha: bool =
|
|
unsafe { get_property(cx, paint_obj.handle(), "alpha", ()) }?
|
|
.unwrap_or(true);
|
|
|
|
// Step 14
|
|
if unsafe { !IsConstructor(paint_obj.get()) } {
|
|
return Err(Error::Type(String::from("Not a constructor.")));
|
|
}
|
|
|
|
// Steps 15-16
|
|
rooted!(in(cx) let mut prototype = UndefinedValue());
|
|
unsafe { get_property_jsval(cx, paint_obj.handle(), "prototype", prototype.handle_mut())?; }
|
|
if !prototype.is_object() {
|
|
return Err(Error::Type(String::from("Prototype is not an object.")));
|
|
}
|
|
rooted!(in(cx) let prototype = prototype.to_object());
|
|
|
|
// Steps 17-18
|
|
rooted!(in(cx) let mut paint_function = UndefinedValue());
|
|
unsafe { get_property_jsval(cx, prototype.handle(), "paint", paint_function.handle_mut())?; }
|
|
if !paint_function.is_object() || unsafe { !IsCallable(paint_function.to_object()) } {
|
|
return Err(Error::Type(String::from("Paint function is not callable.")));
|
|
}
|
|
|
|
// Step 19.
|
|
let context = PaintRenderingContext2D::new(self);
|
|
let definition = PaintDefinition::new(paint_val.handle(),
|
|
paint_function.handle(),
|
|
alpha,
|
|
input_arguments.len(),
|
|
&*context);
|
|
|
|
// Step 20.
|
|
debug!("Registering definition {}.", name);
|
|
self.paint_definitions.borrow_mut().insert(name.clone(), definition);
|
|
|
|
// TODO: Step 21.
|
|
|
|
// Inform layout that there is a registered paint worklet.
|
|
// TODO: layout will end up getting this message multiple times.
|
|
let painter = self.painter(name.clone());
|
|
self.worklet_global.register_paint_worklet(name, properties, painter);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Tasks which can be peformed by a paint worklet
|
|
pub enum PaintWorkletTask {
|
|
DrawAPaintImage(Atom,
|
|
TypedSize2D<f32, CSSPixel>,
|
|
ScaleFactor<f32, CSSPixel, DevicePixel>,
|
|
Vec<(Atom, String)>,
|
|
Vec<String>,
|
|
Sender<DrawAPaintImageResult>),
|
|
SpeculativelyDrawAPaintImage(Atom,
|
|
Vec<(Atom, String)>,
|
|
Vec<String>),
|
|
}
|
|
|
|
/// A paint definition
|
|
/// https://drafts.css-houdini.org/css-paint-api/#paint-definition
|
|
/// This type is dangerous, because it contains uboxed `Heap<JSVal>` values,
|
|
/// which can't be moved.
|
|
#[derive(HeapSizeOf, JSTraceable)]
|
|
#[must_root]
|
|
struct PaintDefinition {
|
|
class_constructor: Heap<JSVal>,
|
|
paint_function: Heap<JSVal>,
|
|
constructor_valid_flag: Cell<bool>,
|
|
context_alpha_flag: bool,
|
|
// TODO: this should be a list of CSS syntaxes.
|
|
input_arguments_len: usize,
|
|
// TODO: the spec calls for fresh rendering contexts each time a paint image is drawn,
|
|
// but to avoid having the primary worklet thread create a new renering context,
|
|
// we recycle them.
|
|
context: Dom<PaintRenderingContext2D>,
|
|
}
|
|
|
|
impl PaintDefinition {
|
|
fn new(class_constructor: HandleValue,
|
|
paint_function: HandleValue,
|
|
alpha: bool,
|
|
input_arguments_len: usize,
|
|
context: &PaintRenderingContext2D)
|
|
-> Box<PaintDefinition>
|
|
{
|
|
let result = Box::new(PaintDefinition {
|
|
class_constructor: Heap::default(),
|
|
paint_function: Heap::default(),
|
|
constructor_valid_flag: Cell::new(true),
|
|
context_alpha_flag: alpha,
|
|
input_arguments_len: input_arguments_len,
|
|
context: Dom::from_ref(context),
|
|
});
|
|
result.class_constructor.set(class_constructor.get());
|
|
result.paint_function.set(paint_function.get());
|
|
result
|
|
}
|
|
}
|