Skip to content

Commit

Permalink
Store lifecycle callbacks when CEs are defined
Browse files Browse the repository at this point in the history
This implements steps 10.3 - 10.4 of the CustomElementRegistry.define
steps.
  • Loading branch information
cbrewster committed Jul 13, 2017
1 parent 347cbb0 commit 901a202
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 69 deletions.
96 changes: 84 additions & 12 deletions components/script/dom/customelementregistry.rs
Expand Up @@ -26,7 +26,7 @@ use dom::window::Window;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::conversions::ToJSValConvertible;
use js::jsapi::{Construct1, IsConstructor, HandleValueArray, HandleObject};
use js::jsapi::{Construct1, IsCallable, IsConstructor, HandleValueArray, HandleObject, MutableHandleValue};
use js::jsapi::{JS_GetProperty, JSAutoCompartment, JSContext};
use js::jsval::{JSVal, ObjectValue, UndefinedValue};
use std::cell::Cell;
Expand Down Expand Up @@ -94,15 +94,14 @@ impl CustomElementRegistry {
/// https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define
/// Steps 10.1, 10.2
#[allow(unsafe_code)]
fn check_prototype(&self, constructor: HandleObject) -> ErrorResult {
fn check_prototype(&self, constructor: HandleObject, prototype: MutableHandleValue) -> ErrorResult {
let global_scope = self.window.upcast::<GlobalScope>();
rooted!(in(global_scope.get_cx()) let mut prototype = UndefinedValue());
unsafe {
// Step 10.1
if !JS_GetProperty(global_scope.get_cx(),
constructor,
b"prototype\0".as_ptr() as *const _,
prototype.handle_mut()) {
prototype) {
return Err(Error::JSFailed);
}

Expand All @@ -113,6 +112,45 @@ impl CustomElementRegistry {
}
Ok(())
}

/// https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define
/// Steps 10.3, 10.4
fn get_callbacks(&self, prototype: HandleObject) -> Fallible<LifecycleCallbacks> {
let cx = self.window.get_cx();

// Step 4
Ok(LifecycleCallbacks {
connected_callback: get_callback(cx, prototype, b"connectedCallback\0")?,
disconnected_callback: get_callback(cx, prototype, b"disconnectedCallback\0")?,
adopted_callback: get_callback(cx, prototype, b"adoptedCallback\0")?,
attribute_changed_callback: get_callback(cx, prototype, b"attributeChangedCallback\0")?,
})
}
}

/// https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define
/// Step 10.4
#[allow(unsafe_code)]
fn get_callback(cx: *mut JSContext, prototype: HandleObject, name: &[u8]) -> Fallible<Option<Rc<Function>>> {
rooted!(in(cx) let mut callback = UndefinedValue());

// Step 10.4.1
if unsafe { !JS_GetProperty(cx,
prototype,
name.as_ptr() as *const _,
callback.handle_mut()) } {
return Err(Error::JSFailed);
}

// Step 10.4.2
if !callback.is_undefined() {
if !callback.is_object() || unsafe { !IsCallable(callback.to_object()) } {
return Err(Error::Type("Lifecycle callback is not callable".to_owned()));
}
Ok(Some(Function::new(cx, callback.to_object())))
} else {
Ok(None)
}
}

impl CustomElementRegistryMethods for CustomElementRegistry {
Expand Down Expand Up @@ -173,22 +211,38 @@ impl CustomElementRegistryMethods for CustomElementRegistry {
self.element_definition_is_running.set(true);

// Steps 10.1 - 10.2
let result = {
rooted!(in(global_scope.get_cx()) let mut prototype = UndefinedValue());
{
let _ac = JSAutoCompartment::new(global_scope.get_cx(), constructor.get());
self.check_prototype(constructor.handle())
if let Err(error) = self.check_prototype(constructor.handle(), prototype.handle_mut()) {
self.element_definition_is_running.set(false);
return Err(error);
}
};

// TODO: Steps 10.3 - 10.6
// 10.3 - 10.4 Handle lifecycle callbacks
// 10.5 - 10.6 Get observed attributes from the constructor
// Steps 10.3 - 10.4
rooted!(in(global_scope.get_cx()) let proto_object = prototype.to_object());
let callbacks = {
let _ac = JSAutoCompartment::new(global_scope.get_cx(), proto_object.get());
match self.get_callbacks(proto_object.handle()) {
Ok(callbacks) => callbacks,
Err(error) => {
self.element_definition_is_running.set(false);
return Err(error);
},
}
};

// TODO: Steps 10.5 - 10.6
// Get observed attributes from the constructor

self.element_definition_is_running.set(false);
result?;

// Step 11
let definition = CustomElementDefinition::new(name.clone(),
local_name,
constructor_);
constructor_,
callbacks);

// Step 12
self.definitions.borrow_mut().insert(name.clone(), Rc::new(definition));
Expand Down Expand Up @@ -254,6 +308,21 @@ impl CustomElementRegistryMethods for CustomElementRegistry {
}
}

#[derive(HeapSizeOf, JSTraceable, Clone)]
pub struct LifecycleCallbacks {
#[ignore_heap_size_of = "Rc"]
connected_callback: Option<Rc<Function>>,

#[ignore_heap_size_of = "Rc"]
disconnected_callback: Option<Rc<Function>>,

#[ignore_heap_size_of = "Rc"]
adopted_callback: Option<Rc<Function>>,

#[ignore_heap_size_of = "Rc"]
attribute_changed_callback: Option<Rc<Function>>,
}

/// https://html.spec.whatwg.org/multipage/#custom-element-definition
#[derive(HeapSizeOf, JSTraceable, Clone)]
pub struct CustomElementDefinition {
Expand All @@ -263,14 +332,17 @@ pub struct CustomElementDefinition {

#[ignore_heap_size_of = "Rc"]
pub constructor: Rc<Function>,

pub callbacks: LifecycleCallbacks,
}

impl CustomElementDefinition {
fn new(name: LocalName, local_name: LocalName, constructor: Rc<Function>) -> CustomElementDefinition {
fn new(name: LocalName, local_name: LocalName, constructor: Rc<Function>, callbacks: LifecycleCallbacks) -> CustomElementDefinition {
CustomElementDefinition {
name: name,
local_name: local_name,
constructor: constructor,
callbacks: callbacks,
}
}

Expand Down
@@ -1,14 +1,5 @@
[CustomElementRegistry.html]
type: testharness
[customElements.define must get callbacks of the constructor prototype]
expected: FAIL

[customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype]
expected: FAIL

[customElements.define must rethrow an exception thrown while converting a callback value to Function callback type]
expected: FAIL

[customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present]
expected: FAIL

Expand Down
Expand Up @@ -3,51 +3,3 @@
[If constructor is arrow function, should throw a TypeError]
expected: FAIL

[If constructor.prototype.connectedCallback throws, should rethrow]
expected: FAIL

[If constructor.prototype.connectedCallback is null, should throw a TypeError]
expected: FAIL

[If constructor.prototype.connectedCallback is object, should throw a TypeError]
expected: FAIL

[If constructor.prototype.connectedCallback is integer, should throw a TypeError]
expected: FAIL

[If constructor.prototype.disconnectedCallback throws, should rethrow]
expected: FAIL

[If constructor.prototype.disconnectedCallback is null, should throw a TypeError]
expected: FAIL

[If constructor.prototype.disconnectedCallback is object, should throw a TypeError]
expected: FAIL

[If constructor.prototype.disconnectedCallback is integer, should throw a TypeError]
expected: FAIL

[If constructor.prototype.adoptedCallback throws, should rethrow]
expected: FAIL

[If constructor.prototype.adoptedCallback is null, should throw a TypeError]
expected: FAIL

[If constructor.prototype.adoptedCallback is object, should throw a TypeError]
expected: FAIL

[If constructor.prototype.adoptedCallback is integer, should throw a TypeError]
expected: FAIL

[If constructor.prototype.attributeChangedCallback throws, should rethrow]
expected: FAIL

[If constructor.prototype.attributeChangedCallback is null, should throw a TypeError]
expected: FAIL

[If constructor.prototype.attributeChangedCallback is object, should throw a TypeError]
expected: FAIL

[If constructor.prototype.attributeChangedCallback is integer, should throw a TypeError]
expected: FAIL

0 comments on commit 901a202

Please sign in to comment.