stdweb/webapi/mutation_observer.rs
1use std;
2use webcore::value::{Reference, Value, ConversionError};
3use webcore::mutfn::Mut;
4use webapi::node_list::NodeList;
5use webcore::try_from::{TryFrom, TryInto};
6use webapi::node::{INode, Node};
7use private::TODO;
8
9/// Provides a way to receive notifications about changes to the DOM.
10///
11/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
12// https://dom.spec.whatwg.org/#mutationobserver
13#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
14#[reference(instance_of = "MutationObserver")]
15pub struct MutationObserver( Reference );
16
17/// Specifies which changes should be observed for the target.
18///
19/// This is only used with the [`MutationObserver::observe`](struct.MutationObserver.html#method.observe) method.
20///
21/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#MutationObserverInit)
22#[ derive( Debug, Clone ) ]
23pub struct MutationObserverInit< 'a > {
24 /// If `true` it will observe all inserts and removals of the target's children (including text nodes).
25 ///
26 /// This is **not** recursive, it will only observe immediate children
27 /// (unless [`subtree`](#structfield.subtree) is `true` in which case it will
28 /// observe **all** children and sub-children recursively).
29 pub child_list: bool,
30
31 /// If `true` it will observe all changes to the target's attributes.
32 pub attributes: bool,
33
34 /// If `true` it will observe all changes to the `CharacterData`'s data.
35 pub character_data: bool,
36
37 /// If `true` it will observe all changes to the target, the target's children, and the target's sub-children.
38 ///
39 /// This is recursive, so it causes **all** children and sub-children to be observed.
40 pub subtree: bool,
41
42 /// If `true` it will store the target's old attribute value in [`old_value`](enum.MutationRecord.html#variant.Attribute.field.old_value).
43 pub attribute_old_value: bool,
44
45 /// If `true` it will store the `CharacterData`'s old data in [`old_data`](enum.MutationRecord.html#variant.CharacterData.field.old_data).
46 pub character_data_old_value: bool,
47
48 /// If `Some` it will only observe the specified attributes. The attributes should be specified without a namespace.
49 ///
50 /// If `None` it will observe all attributes.
51 pub attribute_filter: Option< &'a [ &'a str ] >,
52}
53
54
55impl MutationObserver {
56 /// Returns a new [`MutationObserverHandle`](struct.MutationObserverHandle.html) with the given callback.
57 ///
58 /// The callback will be called with the following arguments when the observed DOM nodes change:
59 ///
60 /// 1. A vector of changes to the observed DOM nodes.
61 ///
62 /// 2. The `MutationObserver`.
63 ///
64 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#Constructor)
65 // https://dom.spec.whatwg.org/#ref-for-dom-mutationobserver-mutationobserver
66 pub fn new< F >( callback: F ) -> MutationObserverHandle
67 where F: FnMut( Vec< MutationRecord >, Self ) + 'static {
68 let callback_reference: Reference = js! ( return @{Mut(callback)}; ).try_into().unwrap();
69
70 MutationObserverHandle {
71 callback_reference: callback_reference.clone(),
72
73 mutation_observer: js! (
74 return new MutationObserver( @{callback_reference} );
75 ).try_into().unwrap(),
76 }
77 }
78
79 /// Starts observing changes to the `target`.
80 ///
81 /// When the `target` is changed, the `MutationObserver` is notified with a vector of [`MutationRecord`](enum.MutationRecord.html).
82 ///
83 /// The `options` specifies which changes should be observed.
84 ///
85 /// Multiple different targets can be observed simultaneously (with the same or different `options`).
86 ///
87 /// If you call `observe` on the same `target` multiple times, it will replace the old `options`
88 /// with the new `options`. It will **not** notify multiple times for the same change to the same
89 /// `target`.
90 ///
91 /// # Panics
92 ///
93 /// * At least one of
94 /// [`child_list`](struct.MutationObserverInit.html#structfield.child_list),
95 /// [`attributes`](struct.MutationObserverInit.html#structfield.attributes), or
96 /// [`character_data`](struct.MutationObserverInit.html#structfield.character_data) must be `true`.
97 ///
98 /// * If [`attribute_old_value`](struct.MutationObserverInit.html#structfield.attribute_old_value) is `true`, then
99 /// [`attributes`](struct.MutationObserverInit.html#structfield.attributes) must be `true`.
100 ///
101 /// * If [`character_data_old_value`](struct.MutationObserverInit.html#structfield.character_data_old_value) is `true`, then
102 /// [`character_data`](struct.MutationObserverInit.html#structfield.character_data) must be `true`.
103 ///
104 /// * If [`attribute_filter`](struct.MutationObserverInit.html#structfield.attribute_filter) is `Some`, then
105 /// [`attributes`](struct.MutationObserverInit.html#structfield.attributes) must be `true`.
106 ///
107 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#observe())
108 // https://dom.spec.whatwg.org/#ref-for-dom-mutationobserver-observe
109 pub fn observe< T: INode >( &self, target: &T, options: MutationObserverInit ) -> Result< (), TODO > {
110 let attribute_filter = options.attribute_filter
111 .map( |val| val.into() )
112 // This must compile to JavaScript `undefined`, NOT `null`
113 .unwrap_or( Value::Undefined );
114
115 js! { @(no_return)
116 @{self.as_ref()}.observe( @{target.as_ref()}, {
117 childList: @{options.child_list},
118 attributes: @{options.attributes},
119 characterData: @{options.character_data},
120 subtree: @{options.subtree},
121 attributeOldValue: @{options.attribute_old_value},
122 characterDataOldValue: @{options.character_data_old_value},
123 attributeFilter: @{attribute_filter}
124 } );
125 }
126
127 Ok(())
128 }
129
130 /// Stops observing all targets.
131 ///
132 /// Until the [`observe`](#method.observe) method is called again,
133 /// the `MutationObserver` will not be notified of any changes.
134 ///
135 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#disconnect())
136 // https://dom.spec.whatwg.org/#ref-for-dom-mutationobserver-disconnect
137 pub fn disconnect( &self ) {
138 js! { @(no_return)
139 @{self.as_ref()}.disconnect();
140 }
141 }
142
143 /// Empties the `MutationObserver`'s record queue and returns what was in there.
144 ///
145 /// This method is generally not needed, instead use the [`MutationObserver`](struct.MutationObserver.html#method.new)
146 /// callback to respond to changes.
147 ///
148 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#takeRecords())
149 // https://dom.spec.whatwg.org/#ref-for-dom-mutationobserver-takerecords
150 pub fn take_records( &self ) -> Vec< MutationRecord > {
151 js!(
152 return @{self.as_ref()}.takeRecords();
153 ).try_into().unwrap()
154 }
155}
156
157
158/// A wrapper which ensures that memory is properly cleaned up when it's no longer needed.
159///
160/// This is created by the [`MutationObserver::new`](struct.MutationObserver.html#method.new) method, and
161/// it can use the same methods as [`MutationObserver`](struct.MutationObserver.html).
162///
163/// When the `MutationObserverHandle` is dropped, the [`disconnect`](#method.disconnect)
164/// method will automatically be called.
165#[ derive( Debug ) ]
166pub struct MutationObserverHandle {
167 mutation_observer: MutationObserver,
168 callback_reference: Reference,
169}
170
171impl std::ops::Deref for MutationObserverHandle {
172 type Target = MutationObserver;
173
174 #[inline]
175 fn deref( &self ) -> &Self::Target {
176 &self.mutation_observer
177 }
178}
179
180impl Drop for MutationObserverHandle {
181 #[inline]
182 fn drop( &mut self ) {
183 self.disconnect();
184
185 js! { @(no_return)
186 @{&self.callback_reference}.drop();
187 }
188 }
189}
190
191
192/// Contains information about an individual change to the DOM.
193///
194/// It is passed to the [`MutationObserver`](struct.MutationObserver.html)'s callback.
195///
196/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord)
197// https://dom.spec.whatwg.org/#mutationrecord
198#[ derive( Debug, Clone ) ]
199pub enum MutationRecord {
200 /// One of the target's attributes was changed.
201 Attribute {
202 /// The [`Node`](struct.Node.html) whose attribute changed.
203 target: Node,
204
205 /// The name of the changed attribute.
206 name: String,
207
208 /// The namespace of the changed attribute.
209 namespace: Option< String >,
210
211 /// The value of the changed attribute before the change.
212 old_value: Option< String >,
213 },
214
215 /// The target's data was changed.
216 CharacterData {
217 /// The `CharacterData` node whose data changed.
218 target: Node,
219
220 /// The data of the target before the change.
221 old_data: Option< String >,
222 },
223
224 /// The children of the target were changed.
225 ChildList {
226 /// The [`Node`](struct.Node.html) whose children changed.
227 target: Node,
228
229 /// The nodes which were inserted. Will be an empty [`NodeList`](struct.NodeList.html) if no nodes were inserted.
230 inserted_nodes: NodeList,
231
232 /// The nodes which were removed. Will be an empty [`NodeList`](struct.NodeList.html) if no nodes were removed.
233 removed_nodes: NodeList,
234
235 /// The previous sibling of the inserted or removed nodes, or `None`.
236 previous_sibling: Option< Node >,
237
238 /// The next sibling of the inserted or removed nodes, or `None`.
239 next_sibling: Option< Node >,
240 },
241}
242
243// TODO create a MutationRecord Reference and use instanceof to verify it
244impl TryFrom< Value > for MutationRecord {
245 type Error = ConversionError;
246
247 fn try_from( v: Value ) -> Result< Self, Self::Error > {
248 match v {
249 Value::Reference( ref r ) => {
250 let kind: String = js!( return @{r}.type; ).try_into()?;
251 let target: Node = js!( return @{r}.target; ).try_into()?;
252
253 match kind.as_str() {
254 "attributes" => Ok( MutationRecord::Attribute {
255 target: target,
256 name: js!( return @{r}.attributeName; ).try_into()?,
257 namespace: js!( return @{r}.attributeNamespace; ).try_into()?,
258 old_value: js!( return @{r}.oldValue; ).try_into()?,
259 } ),
260
261 "characterData" => Ok( MutationRecord::CharacterData {
262 target: target,
263 old_data: js!( return @{r}.oldValue; ).try_into()?,
264 } ),
265
266 "childList" => Ok( MutationRecord::ChildList {
267 target: target,
268 inserted_nodes: js!( return @{r}.addedNodes; ).try_into()?,
269 removed_nodes: js!( return @{r}.removedNodes; ).try_into()?,
270 previous_sibling: js!( return @{r}.previousSibling; ).try_into()?,
271 next_sibling: js!( return @{r}.nextSibling; ).try_into()?,
272 } ),
273
274 other => Err( ConversionError::Custom( format!( "Unknown MutationRecord type: {:?}", other ) ) ),
275 }
276 },
277 other => Err( ConversionError::Custom( format!( "Expected MutationRecord but got: {:?}", other ) ) ),
278 }
279 }
280}
281
282
283#[ cfg( all( test, feature = "web_test" ) ) ]
284mod tests {
285 use super::*;
286 use webapi::document::document;
287
288 #[ test ]
289 fn test_observe() {
290 let observer = MutationObserver::new( |_, _| {} );
291
292 // TODO replace with document.body
293 observer.observe( &document(), MutationObserverInit {
294 child_list: true,
295 attributes: true,
296 character_data: true,
297 subtree: true,
298 attribute_old_value: true,
299 character_data_old_value: true,
300 attribute_filter: Some( &[ "foo", "bar", "qux" ] ),
301 }).unwrap();
302 }
303}