Skip to content

Commit

Permalink
Support custom element callback reactions
Browse files Browse the repository at this point in the history
  • Loading branch information
cbrewster committed Jul 7, 2017
1 parent 3c385ef commit 20c8a58
Show file tree
Hide file tree
Showing 21 changed files with 303 additions and 253 deletions.
13 changes: 12 additions & 1 deletion components/script/dom/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ use dom::bindings::inheritance::Castable;
use dom::bindings::js::{LayoutJS, MutNullableJS, Root, RootedReference};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::customelementregistry::CallbackReaction;
use dom::element::{AttributeMutation, Element};
use dom::mutationobserver::{Mutation, MutationObserver};
use dom::node::Node;
use dom::virtualmethods::vtable_for;
use dom::window::Window;
use dom_struct::dom_struct;
use html5ever::{Prefix, LocalName, Namespace};
use script_thread::ScriptThread;
use servo_atoms::Atom;
use std::borrow::ToOwned;
use std::cell::Ref;
Expand Down Expand Up @@ -175,9 +177,18 @@ impl Attr {
let name = self.local_name().clone();
let namespace = self.namespace().clone();
let old_value = DOMString::from(&**self.value());
let mutation = Mutation::Attribute { name, namespace, old_value };
let new_value = DOMString::from(&*value);
let mutation = Mutation::Attribute {
name: name.clone(),
namespace: namespace.clone(),
old_value: old_value.clone(),
};

MutationObserver::queue_a_mutation_record(owner.upcast::<Node>(), mutation);

let reaction = CallbackReaction::AttributeChanged(name, Some(old_value), Some(new_value), namespace);
ScriptThread::enqueue_callback_reaction(self.owner().unwrap(), reaction);

assert!(Some(owner) == self.owner().r());
owner.will_mutate_attr(self);
self.swap_value(&mut value);
Expand Down
5 changes: 4 additions & 1 deletion components/script/dom/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,10 @@ fn create_html_element(name: QualName,
CustomElementCreationMode::Synchronous => {
let local_name = name.local.clone();
return match definition.create_element(document, prefix.clone()) {
Ok(element) => element,
Ok(element) => {
element.set_custom_element_definition(definition.clone());
element
},
Err(error) => {
// Step 6. Recovering from exception.
let global = GlobalScope::current().unwrap_or_else(|| document.global());
Expand Down
202 changes: 196 additions & 6 deletions components/script/dom/customelementregistry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
* 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::callback::{CallbackContainer, ExceptionHandling};
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::CustomElementRegistryBinding;
use dom::bindings::codegen::Bindings::CustomElementRegistryBinding::CustomElementRegistryMethods;
use dom::bindings::codegen::Bindings::CustomElementRegistryBinding::ElementDefinitionOptions;
use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, StringificationBehavior};
use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::error::{Error, ErrorResult, Fallible, report_pending_exception, throw_dom_exception};
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
Expand All @@ -24,11 +24,11 @@ use dom::node::Node;
use dom::promise::Promise;
use dom::window::Window;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use html5ever::{LocalName, Namespace, Prefix};
use js::conversions::ToJSValConvertible;
use js::jsapi::{Construct1, IsCallable, IsConstructor, HandleValueArray, HandleObject, MutableHandleValue};
use js::jsapi::{JS_GetProperty, JSAutoCompartment, JSContext};
use js::jsval::{JSVal, ObjectValue, UndefinedValue};
use js::jsapi::{Heap, JS_GetProperty, JSAutoCompartment, JSContext};
use js::jsval::{JSVal, NullValue, ObjectValue, UndefinedValue};
use std::cell::Cell;
use std::collections::HashMap;
use std::ptr;
Expand Down Expand Up @@ -179,7 +179,7 @@ impl CustomElementRegistry {
let observed_attributes = match conversion {
Ok(ConversionResult::Success(attributes)) => attributes,
Ok(ConversionResult::Failure(error)) => {
return Err(Error::Type(error));
return Err(Error::Type(error.into()));
},
_ => return Err(Error::JSFailed),
};
Expand Down Expand Up @@ -456,6 +456,196 @@ impl CustomElementDefinition {
}
}

#[derive(HeapSizeOf, JSTraceable)]
pub enum CustomElementReaction {
Upgrade,
Callback(
#[ignore_heap_size_of = "Rc"]
Rc<Function>,
Box<[Heap<JSVal>]>
),
}

impl CustomElementReaction {
/// https://html.spec.whatwg.org/multipage/#invoke-custom-element-reactions
#[allow(unsafe_code)]
pub fn invoke(self, element: Root<Element>) {
// Step 2.1
match self {
// TODO: Implement upgrade reactions
CustomElementReaction::Upgrade => {},
CustomElementReaction::Callback(callback, arguments) => {
let arguments = arguments.iter().map(|arg| arg.handle()).collect();
if let Err(error) = callback.Call_(&*element, arguments, ExceptionHandling::Report) {
let global = GlobalScope::current().expect("No current global.");
let cx = global.get_cx();

unsafe {
let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
throw_dom_exception(cx, &global, error);
report_pending_exception(cx, true);
}
}
}
}
}
}

pub enum CallbackReaction {
Connected,
Disconnected,
Adopted(Root<Document>, Root<Document>),
AttributeChanged(LocalName, Option<DOMString>, Option<DOMString>, Namespace),
}

/// https://html.spec.whatwg.org/multipage/#processing-the-backup-element-queue
#[derive(HeapSizeOf, JSTraceable, Eq, PartialEq, Clone, Copy)]
enum BackupElementQueueFlag {
Processing,
NotProcessing,
}

/// https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack
#[derive(HeapSizeOf, JSTraceable)]
#[must_root]
pub struct CustomElementReactionStack {
backup_queue: ElementQueue,
processing_backup_element_queue: Cell<BackupElementQueueFlag>,
}

impl CustomElementReactionStack {
pub fn new() -> CustomElementReactionStack {
CustomElementReactionStack {
backup_queue: ElementQueue::new(),
processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
}
}

/// https://html.spec.whatwg.org/multipage/#enqueue-an-element-on-the-appropriate-element-queue
pub fn enqueue_element(&self, element: Root<Element>) {
// TODO: Steps 1 - 2
// Support multiple queues

// Step 1.1
self.backup_queue.append_element(element);

// Step 1.2
if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
return;
}

// Step 1.3
self.processing_backup_element_queue.set(BackupElementQueueFlag::Processing);

// Step 4
// TODO: Invoke Microtask

// Step 4.1
self.backup_queue.invoke_reactions();

// Step 4.2
self.processing_backup_element_queue.set(BackupElementQueueFlag::NotProcessing);
}

/// https://html.spec.whatwg.org/multipage/#enqueue-a-custom-element-callback-reaction
#[allow(unsafe_code)]
pub fn enqueue_callback_reaction(&self, element: Root<Element>, reaction: CallbackReaction) {
// Step 1
if let Some(definition) = element.get_custom_element_definition() {
// Step 2
let (callback, args) = match reaction {
CallbackReaction::Connected => (definition.callbacks.connected_callback.clone(), Vec::new()),
CallbackReaction::Disconnected => (definition.callbacks.disconnected_callback.clone(), Vec::new()),
CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
let mut args = Vec::with_capacity(2);
args.push(Heap::default());
args.push(Heap::default());
args.get_mut(0).unwrap().set(ObjectValue(old_doc.reflector().get_jsobject().get()));
args.get_mut(1).unwrap().set(ObjectValue(new_doc.reflector().get_jsobject().get()));
(definition.callbacks.adopted_callback.clone(), args)
},
CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
// Step 4
if !definition.observed_attributes.iter().any(|attr| *attr == *local_name) {
return;
}

let cx = element.global().get_cx();

let local_name = DOMString::from(&*local_name);
rooted!(in(cx) let mut name_value = UndefinedValue());
unsafe { local_name.to_jsval(cx, name_value.handle_mut()); }

rooted!(in(cx) let mut old_value = NullValue());
if let Some(old_val) = old_val {
unsafe { old_val.to_jsval(cx, old_value.handle_mut()); }
}

rooted!(in(cx) let mut value = NullValue());
if let Some(val) = val {
unsafe { val.to_jsval(cx, value.handle_mut()); }
}

let namespace = DOMString::from(&*namespace);
rooted!(in(cx) let mut namespace_value = UndefinedValue());
unsafe { namespace.to_jsval(cx, namespace_value.handle_mut()); }

let mut args = Vec::with_capacity(4);
args.push(Heap::default());
args.push(Heap::default());
args.push(Heap::default());
args.push(Heap::default());
args.get_mut(0).unwrap().set(name_value.get());
args.get_mut(1).unwrap().set(old_value.get());
args.get_mut(2).unwrap().set(value.get());
args.get_mut(3).unwrap().set(namespace_value.get());

(definition.callbacks.attribute_changed_callback.clone(), args)
},
};

// Step 3
let callback = match callback {
Some(callback) => callback,
None => return,
};

// Step 5
element.push_reaction(CustomElementReaction::Callback(callback, args.into_boxed_slice()));

// Step 6
self.enqueue_element(element);
}
}
}

/// https://html.spec.whatwg.org/multipage/#element-queue
#[derive(HeapSizeOf, JSTraceable)]
#[must_root]
struct ElementQueue {
queue: DOMRefCell<Vec<JS<Element>>>,
}

impl ElementQueue {
fn new() -> ElementQueue {
ElementQueue {
queue: Default::default(),
}
}

/// https://html.spec.whatwg.org/multipage/#invoke-custom-element-reactions
fn invoke_reactions(&self) {
// Steps 1-2
for element in self.queue.borrow().iter() {
element.invoke_reactions()
}
}

fn append_element(&self, element: Root<Element>) {
self.queue.borrow_mut().push(JS::from_ref(&*element));
}
}

/// https://html.spec.whatwg.org/multipage/#valid-custom-element-name
fn is_valid_custom_element_name(name: &str) -> bool {
// Custom elment names must match:
Expand Down
50 changes: 46 additions & 4 deletions components/script/dom/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use dom::bindings::xmlname::{namespace_from_domstring, validate_and_extract, xml
use dom::bindings::xmlname::XMLName::InvalidXMLName;
use dom::characterdata::CharacterData;
use dom::create::create_element;
use dom::customelementregistry::{CallbackReaction, CustomElementDefinition, CustomElementReaction};
use dom::document::{Document, LayoutDocumentHelpers};
use dom::documentfragment::DocumentFragment;
use dom::domrect::DOMRect;
Expand Down Expand Up @@ -84,7 +85,7 @@ use js::jsapi::{HandleValue, JSAutoCompartment};
use net_traits::request::CorsSettings;
use ref_filter_map::ref_filter_map;
use script_layout_interface::message::ReflowQueryType;
use script_thread::Runnable;
use script_thread::{Runnable, ScriptThread};
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
Expand All @@ -94,6 +95,7 @@ use servo_atoms::Atom;
use std::ascii::AsciiExt;
use std::borrow::Cow;
use std::cell::{Cell, Ref};
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::default::Default;
use std::fmt;
Expand Down Expand Up @@ -141,6 +143,9 @@ pub struct Element {
/// when it has exclusive access to the element.
#[ignore_heap_size_of = "bitflags defined in rust-selectors"]
selector_flags: Cell<ElementSelectorFlags>,
custom_element_reaction_queue: DOMRefCell<VecDeque<CustomElementReaction>>,
#[ignore_heap_size_of = "Rc"]
custom_element_definition: DOMRefCell<Option<Rc<CustomElementDefinition>>>,
}

impl fmt::Debug for Element {
Expand Down Expand Up @@ -244,6 +249,8 @@ impl Element {
class_list: Default::default(),
state: Cell::new(state),
selector_flags: Cell::new(ElementSelectorFlags::empty()),
custom_element_reaction_queue: Default::default(),
custom_element_definition: DOMRefCell::new(None),
}
}

Expand Down Expand Up @@ -278,6 +285,25 @@ impl Element {
self.is.borrow().clone()
}

pub fn set_custom_element_definition(&self, definition: Rc<CustomElementDefinition>) {
*self.custom_element_definition.borrow_mut() = Some(definition);
}

pub fn get_custom_element_definition(&self) -> Option<Rc<CustomElementDefinition>> {
(*self.custom_element_definition.borrow()).clone()
}

pub fn push_reaction(&self, reaction: CustomElementReaction) {
self.custom_element_reaction_queue.borrow_mut().push_back(reaction);
}

pub fn invoke_reactions(&self) {
let mut reaction_queue = self.custom_element_reaction_queue.borrow_mut();
while let Some(reaction) = reaction_queue.pop_front() {
reaction.invoke(Root::from_ref(self));
}
}

// https://drafts.csswg.org/cssom-view/#css-layout-box
// Elements that have a computed value of the display property
// that is table-column or table-column-group
Expand Down Expand Up @@ -1036,10 +1062,18 @@ impl Element {
pub fn push_attribute(&self, attr: &Attr) {
let name = attr.local_name().clone();
let namespace = attr.namespace().clone();
let old_value = DOMString::from(&**attr.value());
let mutation = Mutation::Attribute { name, namespace, old_value };
let value = DOMString::from(&**attr.value());
let mutation = Mutation::Attribute {
name: name.clone(),
namespace: namespace.clone(),
old_value: value.clone(),
};

MutationObserver::queue_a_mutation_record(&self.node, mutation);

let reaction = CallbackReaction::AttributeChanged(name, None, Some(value), namespace);
ScriptThread::enqueue_callback_reaction(Root::from_ref(self), reaction);

assert!(attr.GetOwnerElement().r() == Some(self));
self.will_mutate_attr(attr);
self.attrs.borrow_mut().push(JS::from_ref(attr));
Expand Down Expand Up @@ -1171,9 +1205,17 @@ impl Element {
let name = attr.local_name().clone();
let namespace = attr.namespace().clone();
let old_value = DOMString::from(&**attr.value());
let mutation = Mutation::Attribute { name, namespace, old_value, };
let mutation = Mutation::Attribute {
name: name.clone(),
namespace: namespace.clone(),
old_value: old_value.clone(),
};

MutationObserver::queue_a_mutation_record(&self.node, mutation);

let reaction = CallbackReaction::AttributeChanged(name, Some(old_value), None, namespace);
ScriptThread::enqueue_callback_reaction(Root::from_ref(self), reaction);

self.attrs.borrow_mut().remove(idx);
attr.set_owner(None);
if attr.namespace() == &ns!() {
Expand Down
Loading

0 comments on commit 20c8a58

Please sign in to comment.