Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions html5ever/examples/arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,21 +339,6 @@ impl<'arena> TreeSink for Sink<'arena> {
new_parent.append(child)
}
}

fn clone_subtree(&self, node: &Self::Handle) -> Self::Handle {
// Allocate the new node in the arena using Clone
let cloned_node = self.arena.alloc(Node::new(node.data.clone()));

// Clone all children and append them
let mut child = node.first_child.get();
while let Some(current_child) = child {
let cloned_child = self.clone_subtree(&current_child);
cloned_node.append(cloned_child);
child = current_child.next_sibling.get();
}

cloned_node
}
}

/// In this example an "arena" is created and filled with the DOM nodes.
Expand Down
5 changes: 0 additions & 5 deletions html5ever/examples/noop-tree-builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,6 @@ impl TreeSink for Sink {
fn remove_from_parent(&self, _target: &usize) {}
fn reparent_children(&self, _node: &usize, _new_parent: &usize) {}
fn mark_script_already_started(&self, _node: &usize) {}

fn clone_subtree(&self, _node: &Self::Handle) -> Self::Handle {
// For this noop example, just return a new placeholder ID
self.get_id()
}
}

/// In this example we implement the TreeSink trait which takes each parsed elements and insert
Expand Down
6 changes: 0 additions & 6 deletions html5ever/examples/print-tree-actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,6 @@ impl TreeSink for Sink {
fn pop(&self, elem: &usize) {
println!("Popped element {elem}");
}

fn clone_subtree(&self, node: &Self::Handle) -> Self::Handle {
println!("Clone subtree for node {node}");
// For this example, just return a new placeholder ID
self.get_id()
}
}

/// Same example as the "noop-tree-builder", but this time every function implemented in our
Expand Down
30 changes: 1 addition & 29 deletions html5ever/src/tree_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,6 @@ pub struct TreeBuilder<Handle, Sink> {
/// Form element pointer.
form_elem: RefCell<Option<Handle>>,

/// selectedcontent element pointer.
selectedcontent_elem: RefCell<Option<Handle>>,
//§ END
/// Frameset-ok flag.
frameset_ok: Cell<bool>,

Expand Down Expand Up @@ -166,7 +163,6 @@ where
active_formatting: Default::default(),
head_elem: Default::default(),
form_elem: Default::default(),
selectedcontent_elem: Default::default(),
frameset_ok: Cell::new(true),
ignore_lf: Default::default(),
foster_parenting: Default::default(),
Expand Down Expand Up @@ -207,7 +203,6 @@ where
active_formatting: Default::default(),
head_elem: Default::default(),
form_elem: RefCell::new(form_elem),
selectedcontent_elem: Default::default(),
frameset_ok: Cell::new(true),
ignore_lf: Default::default(),
foster_parenting: Default::default(),
Expand Down Expand Up @@ -290,10 +285,6 @@ where
tracer.trace_handle(form_elem);
}

if let Some(selectedcontent_elem) = self.selectedcontent_elem.borrow().as_ref() {
tracer.trace_handle(selectedcontent_elem);
}

if let Some(context_elem) = self.context_elem.borrow().as_ref() {
tracer.trace_handle(context_elem);
}
Expand Down Expand Up @@ -1360,7 +1351,7 @@ where
// FIXME: application cache selection algorithm
}

// https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
/// <https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token>
fn insert_element(
&self,
push: PushFlag,
Expand Down Expand Up @@ -1405,12 +1396,6 @@ where

self.insert_at(insertion_point, AppendNode(elem.clone()));

if qname.local == local_name!("selectedcontent")
&& self.selectedcontent_elem.borrow().is_none()
{
*self.selectedcontent_elem.borrow_mut() = Some(elem.clone());
}

match push {
PushFlag::Push => self.push(&elem),
PushFlag::NoPush => (),
Expand Down Expand Up @@ -1595,19 +1580,6 @@ where
self.remove_from_stack(&node);
}

fn maybe_clone_option_into_selectedcontent(&self, option: &Handle) {
if let Some(selectedcontent) = self.selectedcontent_elem.borrow().as_ref().cloned() {
self.clone_option_into_selectedcontent(option, &selectedcontent);
}
}

fn clone_option_into_selectedcontent(&self, option: &Handle, selectedcontent: &Handle) {
self.sink
.reparent_children(selectedcontent, &self.sink.get_document());
let cloned_option = self.sink.clone_subtree(option);
self.sink.reparent_children(&cloned_option, selectedcontent);
}

//§ tree-construction
fn is_foreign(&self, token: &Token) -> bool {
if let Token::Eof = *token {
Expand Down
6 changes: 4 additions & 2 deletions html5ever/src/tree_builder/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,8 @@ where
}
ProcessResult::Done
},

// FIXME: This branch does not exist like this in the specification, because it should run for
// implicitly closed option tags too. See https://github.com/servo/html5ever/issues/712.
Token::Tag(tag @ tag!(</option>)) => {
let option_in_stack = self
.open_elems
Expand All @@ -680,7 +681,8 @@ where
.iter()
.any(|elem| self.sink.same_node(elem, &option))
{
self.maybe_clone_option_into_selectedcontent(&option);
self.sink
.maybe_clone_an_option_into_selectedcontent(&option);
}
}

Expand Down
5 changes: 0 additions & 5 deletions html5ever/tests/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,6 @@ impl TreeSink for Sink {
fn remove_from_parent(&self, _target: &usize) {}
fn reparent_children(&self, _node: &usize, _new_parent: &usize) {}
fn mark_script_already_started(&self, _node: &usize) {}

fn clone_subtree(&self, _node: &Self::Handle) -> Self::Handle {
// For this noop example, just return a new placeholder ID
self.get_id()
}
}

#[test]
Expand Down
14 changes: 11 additions & 3 deletions markup5ever/interface/tree_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,6 @@ pub trait TreeSink {
/// Remove all the children from node and append them to new_parent.
fn reparent_children(&self, node: &Self::Handle, new_parent: &Self::Handle);

/// Clone a node and all its descendants, returning the cloned node.
fn clone_subtree(&self, node: &Self::Handle) -> Self::Handle;

/// Returns true if the adjusted current node is an HTML integration point
/// and the token is a start tag.
fn is_mathml_annotation_xml_integration_point(&self, _handle: &Self::Handle) -> bool {
Expand All @@ -263,6 +260,17 @@ pub trait TreeSink {
) -> bool {
false
}

/// Implements [`maybe clone an option into selectedcontent`](https://html.spec.whatwg.org/#maybe-clone-an-option-into-selectedcontent).
///
/// The provided handle is guaranteed to be an `<option>` element.
///
/// Leaving this method unimplemented will not cause panics, but will result in a (slightly) incorrect DOM tree.
///
/// This method will never be called from `xml5ever`.
fn maybe_clone_an_option_into_selectedcontent(&self, option: &Self::Handle) {
_ = option;
}
}

/// Trace hooks for a garbage-collected DOM.
Expand Down
171 changes: 159 additions & 12 deletions rcdom/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ use markup5ever::serialize::{Serialize, Serializer};
use markup5ever::Attribute;
use markup5ever::ExpandedName;
use markup5ever::QualName;
use xml5ever::interface::ElemName;
use xml5ever::local_name;

/// The different kinds of nodes in the DOM.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -123,6 +125,136 @@ impl Node {
children: RefCell::new(Vec::new()),
})
}

/// <https://html.spec.whatwg.org/#option-element-nearest-ancestor-select>
fn get_option_element_nearest_ancestor_select(&self) -> Option<Rc<Self>> {
// Step 1. Let ancestorOptgroup be null.
// NOTE: The algorithm doesn't actually need the value, so a boolean is enough.
let mut did_see_ancestor_optgroup = false;

// Step 2. For each ancestor of option's ancestors, in reverse tree order:
let mut current = self.parent().and_then(|parent| parent.upgrade())?;
loop {
if let NodeData::Element { name, .. } = &current.data {
// Step 2.1 If ancestor is a datalist, hr, or option element, then return null.
if matches!(
name.local_name(),
&local_name!("datalist") | &local_name!("hr") | &local_name!("option")
) {
return None;
}

// Step 2.2 If ancestor is an optgroup element:
if name.local_name() == &local_name!("optgroup") {
// Step 2.2.1 If ancestorOptgroup is not null, then return null.
if did_see_ancestor_optgroup {
return None;
}

// Step 2.2.2 Set ancestorOptgroup to ancestor.
did_see_ancestor_optgroup = true;
}

// Step 2.3 If ancestor is a select, then return ancestor.
if name.local_name() == &local_name!("select") {
return Some(current);
}
};

// Move on to the next ancestor
let Some(next_ancestor) = current.parent().and_then(|parent| parent.upgrade()) else {
break;
};
current = next_ancestor;
}

// Step 3. Return null.
None
}

fn parent(&self) -> Option<Weak<Self>> {
let parent = self.parent.take();
self.parent.set(parent.clone());
parent
}

/// <https://html.spec.whatwg.org/#select-enabled-selectedcontent>
fn get_a_selects_enabled_selectedcontent(&self) -> Option<Rc<Self>> {
// Step 1. If select has the multiple attribute, then return null.
let NodeData::Element { name, attrs, .. } = &self.data else {
panic!("Trying to get selectedcontent of non-element");
};
debug_assert_eq!(name.local_name(), &local_name!("select"));
if attrs
.borrow()
.iter()
.any(|attribute| attribute.name.local == local_name!("multiple"))
{
return None;
}

// Step 2. Let selectedcontent be the first selectedcontent element descendant of select in tree order
// if any such element exists; otherwise return null.
// FIXME: This does not visit the nodes in tree order
let mut remaining = VecDeque::default();
remaining.extend(self.children.borrow().iter().cloned());
let mut selectedcontent = None;
while let Some(node) = remaining.pop_front() {
remaining.extend(node.children.borrow().iter().cloned());

let NodeData::Element { name, .. } = &self.data else {
continue;
};
if name.local_name() == &local_name!("selectedcontent") {
selectedcontent = Some(node);
break;
}
}
let selectedcontent = selectedcontent?;

// Step 3. If selectedcontent's disabled is true, then return null.
// FIXME: This step is unimplemented for now to reduce complexity.

// Step 4. Return selectedcontent.
Some(selectedcontent)
}

/// <https://html.spec.whatwg.org/#clone-an-option-into-a-selectedcontent>
fn clone_an_option_into_selectedcontent(&self, selectedcontent: Rc<Self>) {
// Step 1. Let documentFragment be a new DocumentFragment whose node document is option's node document.
// NOTE: We just remember the children of said fragment, thats good enough.
let mut document_fragment = Vec::new();

// Step 2. For each child of option's children:
for child in self.children.borrow().iter() {
// Step 2.1 Let childClone be the result of running clone given child with subtree set to true.
let child_clone = child.clone_with_subtree();

// Step 2.2 Append childClone to documentFragment.
document_fragment.push(child_clone);
}

// Step 3. Replace all with documentFragment within selectedcontent.
*selectedcontent.children.borrow_mut() = document_fragment;
}

/// Clones the node and all of its descendants, returning a handle to the new subtree.
///
/// This function will run into infinite recursion when the DOM tree contains cycles and it makes
/// no attempts to guard against that.
fn clone_with_subtree(&self) -> Rc<Self> {
let children = self
.children
.borrow()
.iter()
.map(|child| child.clone_with_subtree())
.collect();
Rc::new(Self {
parent: Cell::new(self.parent()),
data: self.data.clone(),
children: RefCell::new(children),
})
}
}

impl Drop for Node {
Expand Down Expand Up @@ -417,18 +549,6 @@ impl TreeSink for RcDom {
new_children.extend(mem::take(&mut *children));
}

fn clone_subtree(&self, node: &Handle) -> Handle {
let cloned_node = Node::new(node.data.clone());

// Clone all children recursively
for child in node.children.borrow().iter() {
let cloned_child = self.clone_subtree(child);
append(&cloned_node, cloned_child);
}

cloned_node
}

fn is_mathml_annotation_xml_integration_point(&self, target: &Handle) -> bool {
if let NodeData::Element {
mathml_annotation_xml_integration_point,
Expand All @@ -440,6 +560,33 @@ impl TreeSink for RcDom {
panic!("not an element!")
}
}

fn maybe_clone_an_option_into_selectedcontent(&self, option: &Self::Handle) {
let NodeData::Element { name, attrs, .. } = &option.data else {
panic!("\"maybe clone an option into selectedcontent\" called with non-element node");
};
debug_assert_eq!(name.local_name(), &local_name!("option"));

// Step 1. Let select be option's option element nearest ancestor select.
let select = option.get_option_element_nearest_ancestor_select();

// Step 2. If all of the following conditions are true:
// * select is not null;
// * option's selectedness is true; and
// * select's enabled selectedcontent is not null,
// then run clone an option into a selectedcontent given option and select's enabled selectedcontent.
if let Some(selectedcontent) =
select.and_then(|select| select.get_a_selects_enabled_selectedcontent())
{
if attrs
.borrow()
.iter()
.any(|attribute| attribute.name.local == local_name!("selected"))
{
option.clone_an_option_into_selectedcontent(selectedcontent);
}
}
}
}

impl Default for RcDom {
Expand Down
4 changes: 0 additions & 4 deletions rcdom/tests/html-tree-sink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,6 @@ impl TreeSink for LineCountingDOM {
fn set_current_line(&self, line_number: u64) {
self.current_line.set(line_number);
}

fn clone_subtree(&self, node: &Self::Handle) -> Self::Handle {
self.rcdom.clone_subtree(node)
}
}

#[test]
Expand Down