diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2017-05-17 01:21:40 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-17 01:21:40 -0500 |
commit | 5da0aa9f11b7b1c2dc8b5adf178eebafa92d1849 (patch) | |
tree | ccf0d864f7043d761a57e287a932cd325e1ba756 /components/script | |
parent | 837531992864f920342020462830b933d5ed0280 (diff) | |
parent | 3ca89204ffcdcabcf7bb1a343497bdae860c72b2 (diff) | |
download | servo-5da0aa9f11b7b1c2dc8b5adf178eebafa92d1849.tar.gz servo-5da0aa9f11b7b1c2dc8b5adf178eebafa92d1849.zip |
Auto merge of #16883 - jdm:mutationobserver, r=jdm
Mutation Observer API
Rebased from #16668.
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix (partially) #6633
- [X] There are tests for these changes
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16883)
<!-- Reviewable:end -->
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/attr.rs | 8 | ||||
-rw-r--r-- | components/script/dom/element.rs | 18 | ||||
-rw-r--r-- | components/script/dom/mutationobserver.rs | 218 | ||||
-rw-r--r-- | components/script/dom/mutationrecord.rs | 83 | ||||
-rw-r--r-- | components/script/dom/node.rs | 14 | ||||
-rw-r--r-- | components/script/dom/webidls/MutationObserver.webidl | 3 | ||||
-rw-r--r-- | components/script/dom/webidls/MutationRecord.webidl | 18 | ||||
-rw-r--r-- | components/script/microtask.rs | 5 | ||||
-rw-r--r-- | components/script/script_thread.rs | 26 |
9 files changed, 370 insertions, 23 deletions
diff --git a/components/script/dom/attr.rs b/components/script/dom/attr.rs index 3c775ffdf57..af5f0107ae4 100644 --- a/components/script/dom/attr.rs +++ b/components/script/dom/attr.rs @@ -10,6 +10,8 @@ use dom::bindings::js::{LayoutJS, MutNullableJS, Root, RootedReference}; use dom::bindings::reflector::{Reflector, reflect_dom_object}; use dom::bindings::str::DOMString; 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; @@ -170,6 +172,12 @@ impl AttrMethods for Attr { impl Attr { pub fn set_value(&self, mut value: AttrValue, owner: &Element) { + 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 }; + MutationObserver::queue_a_mutation_record(owner.upcast::<Node>(), mutation); + assert!(Some(owner) == self.owner().r()); owner.will_mutate_attr(self); self.swap_value(&mut value); diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 91ea1435302..011b87aabf1 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -63,6 +63,7 @@ use dom::htmltablerowelement::{HTMLTableRowElement, HTMLTableRowElementLayoutHel use dom::htmltablesectionelement::{HTMLTableSectionElement, HTMLTableSectionElementLayoutHelpers}; use dom::htmltemplateelement::HTMLTemplateElement; use dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; +use dom::mutationobserver::{Mutation, MutationObserver}; use dom::namednodemap::NamedNodeMap; use dom::node::{CLICK_IN_PROGRESS, ChildrenMutation, LayoutNodeHelpers, Node}; use dom::node::{NodeDamage, SEQUENTIALLY_FOCUSABLE, UnbindContext}; @@ -1003,6 +1004,12 @@ 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 }; + MutationObserver::queue_a_mutation_record(&self.node, mutation); + assert!(attr.GetOwnerElement().r() == Some(self)); self.will_mutate_attr(attr); self.attrs.borrow_mut().push(JS::from_ref(attr)); @@ -1125,13 +1132,18 @@ impl Element { } fn remove_first_matching_attribute<F>(&self, find: F) -> Option<Root<Attr>> - where F: Fn(&Attr) -> bool - { + where F: Fn(&Attr) -> bool { let idx = self.attrs.borrow().iter().position(|attr| find(&attr)); - idx.map(|idx| { let attr = Root::from_ref(&*(*self.attrs.borrow())[idx]); self.will_mutate_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, }; + MutationObserver::queue_a_mutation_record(&self.node, mutation); + self.attrs.borrow_mut().remove(idx); attr.set_owner(None); if attr.namespace() == &ns!() { diff --git a/components/script/dom/mutationobserver.rs b/components/script/dom/mutationobserver.rs index 4dc745c1ce8..b174ae4d11f 100644 --- a/components/script/dom/mutationobserver.rs +++ b/components/script/dom/mutationobserver.rs @@ -2,13 +2,22 @@ * 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::ExceptionHandling; +use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::MutationObserverBinding; use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationCallback; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; +use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverBinding::MutationObserverMethods; +use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverInit; +use dom::bindings::error::{Error, Fallible}; +use dom::bindings::js::{JS, Root}; use dom::bindings::reflector::{Reflector, reflect_dom_object}; +use dom::bindings::str::DOMString; +use dom::mutationrecord::MutationRecord; +use dom::node::Node; use dom::window::Window; use dom_struct::dom_struct; +use html5ever::{Namespace, LocalName}; +use microtask::Microtask; use script_thread::ScriptThread; use std::rc::Rc; @@ -17,6 +26,29 @@ pub struct MutationObserver { reflector_: Reflector, #[ignore_heap_size_of = "can't measure Rc values"] callback: Rc<MutationCallback>, + record_queue: DOMRefCell<Vec<Root<MutationRecord>>>, +} + +#[derive(Clone)] +pub enum Mutation { + Attribute { name: LocalName, namespace: Namespace, old_value: DOMString } +} + +#[derive(HeapSizeOf, JSTraceable)] +pub struct RegisteredObserver { + observer: Root<MutationObserver>, + options: ObserverOptions, +} + +#[derive(HeapSizeOf, JSTraceable)] +pub struct ObserverOptions { + attribute_old_value: bool, + attributes: bool, + character_data: bool, + character_data_old_value: bool, + child_list: bool, + subtree: bool, + attribute_filter: Vec<DOMString>, } impl MutationObserver { @@ -29,6 +61,7 @@ impl MutationObserver { MutationObserver { reflector_: Reflector::new(), callback: callback, + record_queue: DOMRefCell::new(vec![]), } } @@ -37,4 +70,185 @@ impl MutationObserver { ScriptThread::add_mutation_observer(&*observer); Ok(observer) } + + /// https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask + pub fn queue_mutation_observer_compound_microtask() { + // Step 1 + if ScriptThread::is_mutation_observer_compound_microtask_queued() { + return; + } + // Step 2 + ScriptThread::set_mutation_observer_compound_microtask_queued(true); + // Step 3 + ScriptThread::enqueue_microtask(Microtask::NotifyMutationObservers); + } + + /// https://dom.spec.whatwg.org/#notify-mutation-observers + pub fn notify_mutation_observers() { + // Step 1 + ScriptThread::set_mutation_observer_compound_microtask_queued(false); + // Step 2 + let notify_list = ScriptThread::get_mutation_observers(); + // TODO: steps 3-4 (slots) + // Step 5 + for mo in ¬ify_list { + let queue: Vec<Root<MutationRecord>> = mo.record_queue.borrow().clone(); + mo.record_queue.borrow_mut().clear(); + // TODO: Step 5.3 Remove all transient registered observers whose observer is mo. + if !queue.is_empty() { + let _ = mo.callback.Call_(&**mo, queue, &**mo, ExceptionHandling::Report); + } + } + // TODO: Step 6 (slot signals) + } + + /// https://dom.spec.whatwg.org/#queueing-a-mutation-record + pub fn queue_a_mutation_record(target: &Node, attr_type: Mutation) { + // Step 1 + let mut interestedObservers: Vec<(Root<MutationObserver>, Option<DOMString>)> = vec![]; + // Step 2 & 3 + for node in target.inclusive_ancestors() { + for registered in &*node.registered_mutation_observers() { + if &*node != target && !registered.options.subtree { + continue; + } + + match attr_type { + Mutation::Attribute { ref name, ref namespace, ref old_value } => { + // Step 3.1 + if !registered.options.attributes { + continue; + } + if !registered.options.attribute_filter.is_empty() { + if *namespace != ns!() { + continue; + } + if registered.options.attribute_filter.iter() + .find(|s| &**s == &**name).is_some() { + continue; + } + } + // Step 3.1.2 + let paired_string = if registered.options.attribute_old_value { + Some(old_value.clone()) + } else { + None + }; + // Step 3.1.1 + let idx = interestedObservers.iter().position(|&(ref o, _)| + &**o as *const _ == &*registered.observer as *const _); + if let Some(idx) = idx { + interestedObservers[idx].1 = paired_string; + } else { + interestedObservers.push((Root::from_ref(&*registered.observer), + paired_string)); + } + } + } + } + } + + // Step 4 + for &(ref observer, ref paired_string) in &interestedObservers { + // Steps 4.1-4.7 + let record = match attr_type { + Mutation::Attribute { ref name, ref namespace, .. } => { + let namespace = if *namespace != ns!() { + Some(namespace) + } else { + None + }; + MutationRecord::attribute_mutated(target, name, namespace, paired_string.clone()) + } + }; + // Step 4.8 + observer.record_queue.borrow_mut().push(record); + } + + // Step 5 + MutationObserver::queue_mutation_observer_compound_microtask(); + } + +} + +impl MutationObserverMethods for MutationObserver { + /// https://dom.spec.whatwg.org/#dom-mutationobserver-observe + fn Observe(&self, target: &Node, options: &MutationObserverInit) -> Fallible<()> { + let attribute_filter = options.attributeFilter.clone().unwrap_or(vec![]); + let attribute_old_value = options.attributeOldValue.unwrap_or(false); + let mut attributes = options.attributes.unwrap_or(false); + let mut character_data = options.characterData.unwrap_or(false); + let character_data_old_value = options.characterDataOldValue.unwrap_or(false); + let child_list = options.childList; + let subtree = options.subtree; + + // Step 1 + if (options.attributeOldValue.is_some() || options.attributeFilter.is_some()) && + options.attributes.is_none() { + attributes = true; + } + + // Step 2 + if options.characterDataOldValue.is_some() && options.characterData.is_none() { + character_data = true; + } + + // Step 3 + if !child_list && !attributes && !character_data { + return Err(Error::Type("One of childList, attributes, or characterData must be true".into())); + } + + // Step 4 + if attribute_old_value && !attributes { + return Err(Error::Type("attributeOldValue is true but attributes is false".into())); + } + + // Step 5 + if options.attributeFilter.is_some() && !attributes { + return Err(Error::Type("attributeFilter is present but attributes is false".into())); + } + + // Step 6 + if character_data_old_value && !character_data { + return Err(Error::Type("characterDataOldValue is true but characterData is false".into())); + } + + // Step 7 + let add_new_observer = { + let mut replaced = false; + for registered in &mut *target.registered_mutation_observers() { + if &*registered.observer as *const MutationObserver != self as *const MutationObserver { + continue; + } + // TODO: remove matching transient registered observers + registered.options.attribute_old_value = attribute_old_value; + registered.options.attributes = attributes; + registered.options.character_data = character_data; + registered.options.character_data_old_value = character_data_old_value; + registered.options.child_list = child_list; + registered.options.subtree = subtree; + registered.options.attribute_filter = attribute_filter.clone(); + replaced = true; + } + !replaced + }; + + // Step 8 + if add_new_observer { + target.registered_mutation_observers().push(RegisteredObserver { + observer: Root::from_ref(self), + options: ObserverOptions { + attributes, + attribute_old_value, + character_data, + character_data_old_value, + subtree, + attribute_filter, + child_list + }, + }); + } + + Ok(()) + } } diff --git a/components/script/dom/mutationrecord.rs b/components/script/dom/mutationrecord.rs index c39d61ef18a..439f4ae02b5 100644 --- a/components/script/dom/mutationrecord.rs +++ b/components/script/dom/mutationrecord.rs @@ -2,22 +2,54 @@ * 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::codegen::Bindings::MutationRecordBinding::MutationRecordBinding; use dom::bindings::codegen::Bindings::MutationRecordBinding::MutationRecordBinding::MutationRecordMethods; use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::Reflector; +use dom::bindings::reflector::{Reflector, reflect_dom_object}; use dom::bindings::str::DOMString; -use dom::node::Node; +use dom::node::{Node, window_from_node}; +use dom::nodelist::NodeList; use dom_struct::dom_struct; +use html5ever::{LocalName, Namespace}; #[dom_struct] pub struct MutationRecord { reflector_: Reflector, - - //property for record type record_type: DOMString, - - //property for target node target: JS<Node>, + attribute_name: Option<DOMString>, + attribute_namespace: Option<DOMString>, + old_value: Option<DOMString>, +} + +impl MutationRecord { + #[allow(unrooted_must_root)] + pub fn attribute_mutated(target: &Node, + attribute_name: &LocalName, + attribute_namespace: Option<&Namespace>, + old_value: Option<DOMString>) -> Root<MutationRecord> { + let record = box MutationRecord::new_inherited("attributes", + target, + Some(DOMString::from(&**attribute_name)), + attribute_namespace.map(|n| DOMString::from(&**n)), + old_value); + reflect_dom_object(record, &*window_from_node(target), MutationRecordBinding::Wrap) + } + + fn new_inherited(record_type: &str, + target: &Node, + attribute_name: Option<DOMString>, + attribute_namespace: Option<DOMString>, + old_value: Option<DOMString>) -> MutationRecord { + MutationRecord { + reflector_: Reflector::new(), + record_type: DOMString::from(record_type), + target: JS::from_ref(target), + attribute_name: attribute_name, + attribute_namespace: attribute_namespace, + old_value: old_value, + } + } } impl MutationRecordMethods for MutationRecord { @@ -28,7 +60,44 @@ impl MutationRecordMethods for MutationRecord { // https://dom.spec.whatwg.org/#dom-mutationrecord-target fn Target(&self) -> Root<Node> { - return Root::from_ref(&*self.target); + Root::from_ref(&*self.target) + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-attributename + fn GetAttributeName(&self) -> Option<DOMString> { + self.attribute_name.clone() + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-attributenamespace + fn GetAttributeNamespace(&self) -> Option<DOMString> { + self.attribute_namespace.clone() + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-oldvalue + fn GetOldValue(&self) -> Option<DOMString> { + self.old_value.clone() + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-addednodes + fn AddedNodes(&self) -> Root<NodeList> { + let window = window_from_node(&*self.target); + NodeList::empty(&window) + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-removednodes + fn RemovedNodes(&self) -> Root<NodeList> { + let window = window_from_node(&*self.target); + NodeList::empty(&window) + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-previoussibling + fn GetPreviousSibling(&self) -> Option<Root<Node>> { + None + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-previoussibling + fn GetNextSibling(&self) -> Option<Root<Node>> { + None } } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index bec7a34d63f..8c8831801ef 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -7,6 +7,7 @@ use app_units::Au; use devtools_traits::NodeInfo; use document_loader::DocumentLoader; +use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; @@ -46,6 +47,7 @@ use dom::htmllinkelement::HTMLLinkElement; use dom::htmlmetaelement::HTMLMetaElement; use dom::htmlstyleelement::HTMLStyleElement; use dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; +use dom::mutationobserver::RegisteredObserver; use dom::nodelist::NodeList; use dom::processinginstruction::ProcessingInstruction; use dom::range::WeakRangeVec; @@ -72,7 +74,7 @@ use selectors::matching::matches_selector_list; use selectors::parser::SelectorList; use servo_url::ServoUrl; use std::borrow::ToOwned; -use std::cell::{Cell, UnsafeCell}; +use std::cell::{Cell, UnsafeCell, RefMut}; use std::cmp::max; use std::default::Default; use std::iter; @@ -138,6 +140,9 @@ pub struct Node { /// node is finalized. style_and_layout_data: Cell<Option<OpaqueStyleAndLayoutData>>, + /// Registered observers for this node. + mutation_observers: DOMRefCell<Vec<RegisteredObserver>>, + unique_id: UniqueId, } @@ -363,6 +368,11 @@ impl Node { } } + /// Return all registered mutation observers for this node. + pub fn registered_mutation_observers(&self) -> RefMut<Vec<RegisteredObserver>> { + self.mutation_observers.borrow_mut() + } + /// Dumps the subtree rooted at this node, for debugging. pub fn dump(&self) { self.dump_indent(0); @@ -1411,6 +1421,8 @@ impl Node { style_and_layout_data: Cell::new(None), + mutation_observers: Default::default(), + unique_id: UniqueId::new(), } } diff --git a/components/script/dom/webidls/MutationObserver.webidl b/components/script/dom/webidls/MutationObserver.webidl index dbcfa945d4a..738c711d8e7 100644 --- a/components/script/dom/webidls/MutationObserver.webidl +++ b/components/script/dom/webidls/MutationObserver.webidl @@ -9,7 +9,8 @@ // https://dom.spec.whatwg.org/#mutationobserver [Pref="dom.mutation_observer.enabled", Constructor(MutationCallback callback)] interface MutationObserver { - //void observe(Node target, optional MutationObserverInit options); + [Throws] + void observe(Node target, optional MutationObserverInit options); //void disconnect(); //sequence<MutationRecord> takeRecords(); }; diff --git a/components/script/dom/webidls/MutationRecord.webidl b/components/script/dom/webidls/MutationRecord.webidl index 286801bf2ab..1cd364091fc 100644 --- a/components/script/dom/webidls/MutationRecord.webidl +++ b/components/script/dom/webidls/MutationRecord.webidl @@ -12,13 +12,13 @@ interface MutationRecord { readonly attribute DOMString type; [SameObject] readonly attribute Node target; - //[SameObject] - //readonly attribute NodeList addedNodes; - //[SameObject] - //readonly attribute NodeList removedNodes; - //readonly attribute Node? previousSibling; - //readonly attribute Node? nextSibling; - //readonly attribute DOMString? attributeName; - //readonly attribute DOMString? attributeNamespace; - //readonly attribute DOMString? oldValue; + [SameObject] + readonly attribute NodeList addedNodes; + [SameObject] + readonly attribute NodeList removedNodes; + readonly attribute Node? previousSibling; + readonly attribute Node? nextSibling; + readonly attribute DOMString? attributeName; + readonly attribute DOMString? attributeNamespace; + readonly attribute DOMString? oldValue; }; diff --git a/components/script/microtask.rs b/components/script/microtask.rs index da04d1ed6ab..7fadf111ddb 100644 --- a/components/script/microtask.rs +++ b/components/script/microtask.rs @@ -11,6 +11,7 @@ use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback; use dom::bindings::js::Root; use dom::globalscope::GlobalScope; +use dom::mutationobserver::MutationObserver; use msg::constellation_msg::PipelineId; use std::cell::Cell; use std::mem; @@ -28,6 +29,7 @@ pub struct MicrotaskQueue { #[derive(JSTraceable, HeapSizeOf)] pub enum Microtask { Promise(EnqueuedPromiseCallback), + NotifyMutationObservers, } /// A promise callback scheduled to run during the next microtask checkpoint (#4283). @@ -71,6 +73,9 @@ impl MicrotaskQueue { let _ = job.callback.Call_(&*target, ExceptionHandling::Report); } } + Microtask::NotifyMutationObservers => { + MutationObserver::notify_mutation_observers(); + } } } } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 9ab6fd301bc..a8b20b6cf98 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -481,6 +481,9 @@ pub struct ScriptThread { microtask_queue: MicrotaskQueue, + /// Microtask Queue for adding support for mutation observer microtasks + mutation_observer_compound_microtask_queued: Cell<bool>, + /// The unit of related similar-origin browsing contexts' list of MutationObserver objects mutation_observers: DOMRefCell<Vec<JS<MutationObserver>>>, @@ -589,6 +592,20 @@ impl ScriptThread { }) } + pub fn set_mutation_observer_compound_microtask_queued(value: bool) { + SCRIPT_THREAD_ROOT.with(|root| { + let script_thread = unsafe { &*root.get().unwrap() }; + script_thread.mutation_observer_compound_microtask_queued.set(value); + }) + } + + pub fn is_mutation_observer_compound_microtask_queued() -> bool { + SCRIPT_THREAD_ROOT.with(|root| { + let script_thread = unsafe { &*root.get().unwrap() }; + return script_thread.mutation_observer_compound_microtask_queued.get(); + }) + } + pub fn add_mutation_observer(observer: &MutationObserver) { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.get().unwrap() }; @@ -598,6 +615,13 @@ impl ScriptThread { }) } + pub fn get_mutation_observers() -> Vec<Root<MutationObserver>> { + SCRIPT_THREAD_ROOT.with(|root| { + let script_thread = unsafe { &*root.get().unwrap() }; + script_thread.mutation_observers.borrow().iter().map(|o| Root::from_ref(&**o)).collect() + }) + } + pub fn mark_document_with_no_blocked_loads(doc: &Document) { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.get().unwrap() }; @@ -750,6 +774,8 @@ impl ScriptThread { microtask_queue: MicrotaskQueue::default(), + mutation_observer_compound_microtask_queued: Default::default(), + mutation_observers: Default::default(), layout_to_constellation_chan: state.layout_to_constellation_chan, |