aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
authorConnor Brewster <connor.brewster@eagles.oc.edu>2017-06-21 14:40:34 -0600
committerConnor Brewster <connor.brewster@eagles.oc.edu>2017-07-17 22:23:45 -0600
commit46659915036bb44e73e7ef2696ea9f35105f1659 (patch)
tree7713715250a9ff9f695062fdceec0acf40f10488 /components/script
parent596ed557d2a44174572eec28b9945f317f1310de (diff)
downloadservo-46659915036bb44e73e7ef2696ea9f35105f1659.tar.gz
servo-46659915036bb44e73e7ef2696ea9f35105f1659.zip
Support custom element callback reactions
Diffstat (limited to 'components/script')
-rw-r--r--components/script/dom/attr.rs15
-rw-r--r--components/script/dom/create.rs5
-rw-r--r--components/script/dom/customelementregistry.rs202
-rw-r--r--components/script/dom/element.rs58
-rw-r--r--components/script/dom/node.rs29
-rw-r--r--components/script/script_thread.rs15
6 files changed, 310 insertions, 14 deletions
diff --git a/components/script/dom/attr.rs b/components/script/dom/attr.rs
index af5f0107ae4..bf2ecd6e910 100644
--- a/components/script/dom/attr.rs
+++ b/components/script/dom/attr.rs
@@ -9,6 +9,7 @@ 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;
@@ -16,6 +17,7 @@ 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;
@@ -175,9 +177,20 @@ 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);
+ if owner.get_custom_element_definition().is_some() {
+ let reaction = CallbackReaction::AttributeChanged(name, Some(old_value), Some(new_value), namespace);
+ ScriptThread::enqueue_callback_reaction(owner, reaction);
+ }
+
assert!(Some(owner) == self.owner().r());
owner.will_mutate_attr(self);
self.swap_value(&mut value);
diff --git a/components/script/dom/create.rs b/components/script/dom/create.rs
index e22e28a886e..5f03a3adb98 100644
--- a/components/script/dom/create.rs
+++ b/components/script/dom/create.rs
@@ -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());
diff --git a/components/script/dom/customelementregistry.rs b/components/script/dom/customelementregistry.rs
index 6da43780082..6077130c6c4 100644
--- a/components/script/dom/customelementregistry.rs
+++ b/components/script/dom/customelementregistry.rs
@@ -2,7 +2,7 @@
* 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;
@@ -24,13 +24,14 @@ 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::collections::{HashMap, VecDeque};
+use std::ops::Deref;
use std::ptr;
use std::rc::Rc;
@@ -449,6 +450,197 @@ impl CustomElementDefinition {
}
}
+#[derive(HeapSizeOf, JSTraceable)]
+#[must_root]
+pub enum CustomElementReaction {
+ // TODO: Support upgrade reactions
+ 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: &Element) {
+ // Step 2.1
+ match *self {
+ CustomElementReaction::Callback(ref callback, ref arguments) => {
+ let arguments = arguments.iter().map(|arg| arg.handle()).collect();
+ let _ = callback.Call_(&*element, arguments, ExceptionHandling::Report);
+ }
+ }
+ }
+}
+
+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
+ /// Step 4
+ pub fn invoke_backup_element_queue(&self) {
+ // 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-an-element-on-the-appropriate-element-queue
+ pub fn enqueue_element(&self, element: &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: &Element, reaction: CallbackReaction) {
+ // Step 1
+ let definition = match element.get_custom_element_definition() {
+ Some(definition) => definition,
+ None => return,
+ };
+
+ // 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 args = vec![Heap::default(), Heap::default()];
+ args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
+ args[1].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 args = vec![Heap::default(), Heap::default(), Heap::default(), Heap::default()];
+ args[0].set(name_value.get());
+ args[1].set(old_value.get());
+ args[2].set(value.get());
+ args[3].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_callback_reaction(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<VecDeque<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
+ while let Some(element) = self.next_element() {
+ element.invoke_reactions()
+ }
+ }
+
+ fn next_element(&self) -> Option<Root<Element>> {
+ self.queue.borrow_mut().pop_front().as_ref().map(JS::deref).map(Root::from_ref)
+ }
+
+ fn append_element(&self, element: &Element) {
+ self.queue.borrow_mut().push_back(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:
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs
index a78520426e0..067549421af 100644
--- a/components/script/dom/element.rs
+++ b/components/script/dom/element.rs
@@ -13,6 +13,7 @@ use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::ElementBinding;
use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
+use dom::bindings::codegen::Bindings::FunctionBinding::Function;
use dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions};
@@ -30,6 +31,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;
@@ -80,11 +82,12 @@ use html5ever::serialize;
use html5ever::serialize::SerializeOpts;
use html5ever::serialize::TraversalScope;
use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
-use js::jsapi::{HandleValue, JSAutoCompartment};
+use js::jsapi::{HandleValue, Heap, JSAutoCompartment};
+use js::jsval::JSVal;
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};
@@ -141,6 +144,11 @@ pub struct Element {
/// when it has exclusive access to the element.
#[ignore_heap_size_of = "bitflags defined in rust-selectors"]
selector_flags: Cell<ElementSelectorFlags>,
+ /// https://html.spec.whatwg.org/multipage/#custom-element-reaction-queue
+ custom_element_reaction_queue: DOMRefCell<Vec<CustomElementReaction>>,
+ /// https://dom.spec.whatwg.org/#concept-element-custom-element-definition
+ #[ignore_heap_size_of = "Rc"]
+ custom_element_definition: DOMRefCell<Option<Rc<CustomElementDefinition>>>,
}
impl fmt::Debug for Element {
@@ -244,6 +252,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: Default::default(),
}
}
@@ -278,6 +288,26 @@ 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_callback_reaction(&self, function: Rc<Function>, args: Box<[Heap<JSVal>]>) {
+ self.custom_element_reaction_queue.borrow_mut().push(CustomElementReaction::Callback(function, args));
+ }
+
+ pub fn invoke_reactions(&self) {
+ let mut reaction_queue = self.custom_element_reaction_queue.borrow_mut();
+ for reaction in reaction_queue.iter() {
+ reaction.invoke(self);
+ }
+ reaction_queue.clear();
+ }
+
// 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
@@ -1036,10 +1066,20 @@ 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);
+ if self.get_custom_element_definition().is_some() {
+ let reaction = CallbackReaction::AttributeChanged(name, None, Some(value), namespace);
+ ScriptThread::enqueue_callback_reaction(self, reaction);
+ }
+
assert!(attr.GetOwnerElement().r() == Some(self));
self.will_mutate_attr(attr);
self.attrs.borrow_mut().push(JS::from_ref(attr));
@@ -1171,9 +1211,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(self, reaction);
+
self.attrs.borrow_mut().remove(idx);
attr.set_owner(None);
if attr.namespace() == &ns!() {
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index bf24682ede7..03a4ac59f16 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -30,6 +30,7 @@ use dom::bindings::str::{DOMString, USVString};
use dom::bindings::xmlname::namespace_from_domstring;
use dom::characterdata::{CharacterData, LayoutCharacterDataHelpers};
use dom::cssstylesheet::CSSStyleSheet;
+use dom::customelementregistry::CallbackReaction;
use dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument};
use dom::documentfragment::DocumentFragment;
use dom::documenttype::DocumentType;
@@ -66,6 +67,7 @@ use ref_slice::ref_slice;
use script_layout_interface::{HTMLCanvasData, OpaqueStyleAndLayoutData, SVGSVGData};
use script_layout_interface::{LayoutElementType, LayoutNodeType, TrustedNodeAddress};
use script_layout_interface::message::Msg;
+use script_thread::ScriptThread;
use script_traits::DocumentActivity;
use script_traits::UntrustedNodeAddress;
use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode};
@@ -313,6 +315,10 @@ impl Node {
// e.g. when removing a <form>.
vtable_for(&&*node).unbind_from_tree(&context);
node.style_and_layout_data.get().map(|d| node.dispose(d));
+ // https://dom.spec.whatwg.org/#concept-node-remove step 14
+ if let Some(element) = node.as_custom_element() {
+ ScriptThread::enqueue_callback_reaction(&*element, CallbackReaction::Disconnected);
+ }
}
self.owner_doc().content_and_heritage_changed(self, NodeDamage::OtherNodeDamage);
@@ -322,6 +328,15 @@ impl Node {
pub fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
UntrustedNodeAddress(self.reflector().get_jsobject().get() as *const c_void)
}
+
+ pub fn as_custom_element(&self) -> Option<Root<Element>> {
+ self.downcast::<Element>()
+ .and_then(|element| if element.get_custom_element_definition().is_some() {
+ Some(Root::from_ref(element))
+ } else {
+ None
+ })
+ }
}
pub struct QuerySelectorIterator {
@@ -1401,13 +1416,20 @@ impl Node {
let old_doc = node.owner_doc();
// Step 2.
node.remove_self();
+ // Step 3.
if &*old_doc != document {
- // Step 3.
+ // Step 3.1.
for descendant in node.traverse_preorder() {
descendant.set_owner_doc(document);
}
- // Step 4.
for descendant in node.traverse_preorder() {
+ // Step 3.2.
+ if let Some(element) = node.as_custom_element() {
+ ScriptThread::enqueue_callback_reaction(&*element,
+ CallbackReaction::Adopted(old_doc.clone(), Root::from_ref(document)));
+ }
+
+ // Step 3.3.
vtable_for(&descendant).adopting_steps(&old_doc);
}
}
@@ -1615,6 +1637,9 @@ impl Node {
// Step 7.1.
parent.add_child(*kid, child);
// Step 7.2: insertion steps.
+ if let Some(element) = kid.as_custom_element() {
+ ScriptThread::enqueue_callback_reaction(&*element, CallbackReaction::Connected);
+ }
}
if let SuppressObserver::Unsuppressed = suppress_observers {
vtable_for(&parent).children_changed(
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index 641d761b373..1953f5691bd 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -39,6 +39,7 @@ use dom::bindings::str::DOMString;
use dom::bindings::structuredclone::StructuredCloneData;
use dom::bindings::trace::JSTraceable;
use dom::bindings::utils::WRAP_CALLBACKS;
+use dom::customelementregistry::{CallbackReaction, CustomElementReactionStack};
use dom::document::{Document, DocumentSource, FocusType, HasBrowsingContext, IsHTMLDocument, TouchEventResult};
use dom::element::Element;
use dom::event::{Event, EventBubbles, EventCancelable};
@@ -510,6 +511,9 @@ pub struct ScriptThread {
/// A list of nodes with in-progress CSS transitions, which roots them for the duration
/// of the transition.
transitioning_nodes: DOMRefCell<Vec<JS<Node>>>,
+
+ /// https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack
+ custom_element_reaction_stack: CustomElementReactionStack,
}
/// In the event of thread panic, all data on the stack runs its destructor. However, there
@@ -742,6 +746,15 @@ impl ScriptThread {
let _ = window.layout_chan().send(msg);
}
+ pub fn enqueue_callback_reaction(element:&Element, reaction: CallbackReaction) {
+ SCRIPT_THREAD_ROOT.with(|root| {
+ if let Some(script_thread) = root.get() {
+ let script_thread = unsafe { &*script_thread };
+ script_thread.custom_element_reaction_stack.enqueue_callback_reaction(element, reaction);
+ }
+ })
+ }
+
/// Creates a new script thread.
pub fn new(state: InitialScriptState,
port: Receiver<MainThreadScriptMsg>,
@@ -827,6 +840,8 @@ impl ScriptThread {
docs_with_no_blocking_loads: Default::default(),
transitioning_nodes: Default::default(),
+
+ custom_element_reaction_stack: CustomElementReactionStack::new(),
}
}