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}