servo/components/script/dom/mutationobserver.rs
Josh Matthews c94ac5bccb
Move various reflector types and traits to script_bindings (#35279)
* script: Move Reflector to script_bindings.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* script: Extract global() helper from DomObject into new trait. Move DomObject and related traits to script_bindings.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

---------

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
2025-02-04 06:58:08 +00:00

402 lines
15 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::rc::Rc;
use dom_struct::dom_struct;
use html5ever::{namespace_url, ns, LocalName, Namespace};
use js::rust::HandleObject;
use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserver_Binding::MutationObserverMethods;
use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::{
MutationCallback, MutationObserverInit,
};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomGlobal, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::eventtarget::EventTarget;
use crate::dom::mutationrecord::MutationRecord;
use crate::dom::node::{Node, ShadowIncluding};
use crate::dom::window::Window;
use crate::microtask::Microtask;
use crate::script_runtime::CanGc;
use crate::script_thread::ScriptThread;
#[dom_struct]
pub(crate) struct MutationObserver {
reflector_: Reflector,
#[ignore_malloc_size_of = "can't measure Rc values"]
callback: Rc<MutationCallback>,
record_queue: DomRefCell<Vec<DomRoot<MutationRecord>>>,
node_list: DomRefCell<Vec<DomRoot<Node>>>,
}
pub(crate) enum Mutation<'a> {
Attribute {
name: LocalName,
namespace: Namespace,
old_value: Option<DOMString>,
},
CharacterData {
old_value: DOMString,
},
ChildList {
added: Option<&'a [&'a Node]>,
removed: Option<&'a [&'a Node]>,
prev: Option<&'a Node>,
next: Option<&'a Node>,
},
}
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct RegisteredObserver {
pub(crate) observer: DomRoot<MutationObserver>,
options: ObserverOptions,
}
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) 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 {
fn new_with_proto(
global: &Window,
proto: Option<HandleObject>,
callback: Rc<MutationCallback>,
can_gc: CanGc,
) -> DomRoot<MutationObserver> {
let boxed_observer = Box::new(MutationObserver::new_inherited(callback));
reflect_dom_object_with_proto(boxed_observer, global, proto, can_gc)
}
fn new_inherited(callback: Rc<MutationCallback>) -> MutationObserver {
MutationObserver {
reflector_: Reflector::new(),
callback,
record_queue: DomRefCell::new(vec![]),
node_list: DomRefCell::new(vec![]),
}
}
/// <https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask>
pub(crate) fn queue_mutation_observer_microtask() {
// Step 1. If the surrounding agents mutation observer microtask queued is true, then return.
if ScriptThread::is_mutation_observer_microtask_queued() {
return;
}
// Step 2. Set the surrounding agents mutation observer microtask queued to true.
ScriptThread::set_mutation_observer_microtask_queued(true);
// Step 3. Queue a microtask to notify mutation observers.
ScriptThread::enqueue_microtask(Microtask::NotifyMutationObservers);
}
/// <https://dom.spec.whatwg.org/#notify-mutation-observers>
pub(crate) fn notify_mutation_observers(can_gc: CanGc) {
// Step 1. Set the surrounding agents mutation observer microtask queued to false.
ScriptThread::set_mutation_observer_microtask_queued(false);
// Step 2. Let notifySet be a clone of the surrounding agents pending mutation observers.
// TODO Step 3. Empty the surrounding agents pending mutation observers.
let notify_list = ScriptThread::get_mutation_observers();
// Step 4. Let signalSet be a clone of the surrounding agents signal slots.
// Step 5. Empty the surrounding agents signal slots.
let signal_set = ScriptThread::take_signal_slots();
// Step 6. For each mo of notifySet:
for mo in &notify_list {
// Step 6.1 Let records be a clone of mos record queue.
let queue: Vec<DomRoot<MutationRecord>> = mo.record_queue.borrow().clone();
// Step 6.2 Empty mos record queue.
mo.record_queue.borrow_mut().clear();
// TODO Step 6.3 For each node of mos node list, remove all transient registered observers
// whose observer is mo from nodes registered observer list.
// Step 6.4 If records is not empty, then invoke mos callback with « records,
// mo » and "report", and with callback this value mo.
if !queue.is_empty() {
let _ = mo
.callback
.Call_(&**mo, queue, mo, ExceptionHandling::Report);
}
}
// Step 6. For each slot of signalSet, fire an event named slotchange,
// with its bubbles attribute set to true, at slot.
for slot in signal_set {
slot.upcast::<EventTarget>()
.fire_event(atom!("slotchange"), can_gc);
}
}
/// <https://dom.spec.whatwg.org/#queueing-a-mutation-record>
pub(crate) fn queue_a_mutation_record(target: &Node, attr_type: Mutation) {
if !target.global().as_window().get_exists_mut_observer() {
return;
}
// Step 1
let mut interested_observers: Vec<(DomRoot<MutationObserver>, Option<DOMString>)> = vec![];
// Step 2 & 3
for node in target.inclusive_ancestors(ShadowIncluding::No) {
let registered = node.registered_mutation_observers();
if registered.is_none() {
continue;
}
for registered in &*registered.unwrap() {
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()
.any(|s| **s == **name)
{
continue;
}
}
// Step 3.1.2
let paired_string = if registered.options.attribute_old_value {
old_value.clone()
} else {
None
};
// Step 3.1.1
let idx = interested_observers
.iter()
.position(|(o, _)| std::ptr::eq(&**o, &*registered.observer));
if let Some(idx) = idx {
interested_observers[idx].1 = paired_string;
} else {
interested_observers
.push((DomRoot::from_ref(&*registered.observer), paired_string));
}
},
Mutation::CharacterData { ref old_value } => {
if !registered.options.character_data {
continue;
}
// Step 3.1.2
let paired_string = if registered.options.character_data_old_value {
Some(old_value.clone())
} else {
None
};
// Step 3.1.1
let idx = interested_observers
.iter()
.position(|(o, _)| std::ptr::eq(&**o, &*registered.observer));
if let Some(idx) = idx {
interested_observers[idx].1 = paired_string;
} else {
interested_observers
.push((DomRoot::from_ref(&*registered.observer), paired_string));
}
},
Mutation::ChildList { .. } => {
if !registered.options.child_list {
continue;
}
interested_observers.push((DomRoot::from_ref(&*registered.observer), None));
},
}
}
}
// Step 4
for (observer, paired_string) in interested_observers {
// 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)
},
Mutation::CharacterData { .. } => {
MutationRecord::character_data_mutated(target, paired_string)
},
Mutation::ChildList {
ref added,
ref removed,
ref next,
ref prev,
} => MutationRecord::child_list_mutated(target, *added, *removed, *next, *prev),
};
// Step 4.8
observer.record_queue.borrow_mut().push(record);
}
// Step 5
MutationObserver::queue_mutation_observer_microtask();
}
}
impl MutationObserverMethods<crate::DomTypeHolder> for MutationObserver {
/// <https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver>
fn Constructor(
global: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
callback: Rc<MutationCallback>,
) -> Fallible<DomRoot<MutationObserver>> {
global.set_exists_mut_observer();
let observer = MutationObserver::new_with_proto(global, proto, callback, can_gc);
ScriptThread::add_mutation_observer(&observer);
Ok(observer)
}
/// <https://dom.spec.whatwg.org/#dom-mutationobserver-observe>
fn Observe(&self, target: &Node, options: &MutationObserverInit) -> Fallible<()> {
let attribute_filter = options.attributeFilter.clone().unwrap_or_default();
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_mut() {
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
.clone_from(&attribute_filter);
replaced = true;
}
!replaced
};
// Step 8
if add_new_observer {
target.add_mutation_observer(RegisteredObserver {
observer: DomRoot::from_ref(self),
options: ObserverOptions {
attributes,
attribute_old_value,
character_data,
character_data_old_value,
subtree,
attribute_filter,
child_list,
},
});
self.node_list.borrow_mut().push(DomRoot::from_ref(target));
}
Ok(())
}
/// <https://dom.spec.whatwg.org/#dom-mutationobserver-takerecords>
fn TakeRecords(&self) -> Vec<DomRoot<MutationRecord>> {
let records: Vec<DomRoot<MutationRecord>> = self.record_queue.borrow().clone();
self.record_queue.borrow_mut().clear();
records
}
/// <https://dom.spec.whatwg.org/#dom-mutationobserver-disconnect>
fn Disconnect(&self) {
// Step 1
let mut nodes = self.node_list.borrow_mut();
for node in nodes.drain(..) {
node.remove_mutation_observer(self);
}
// Step 2
self.record_queue.borrow_mut().clear();
}
}