diff --git a/examples/noop-tree-builder.rs b/examples/noop-tree-builder.rs index 0860cb92..02f8f46c 100644 --- a/examples/noop-tree-builder.rs +++ b/examples/noop-tree-builder.rs @@ -57,6 +57,10 @@ impl TreeSink for Sink { x == y } + fn same_tree(&self, _x: usize, _y: usize) -> bool { + true + } + fn elem_name(&self, target: usize) -> QualName { self.names.get(&target).expect("not an element").clone() } @@ -71,13 +75,15 @@ impl TreeSink for Sink { self.get_id() } + fn has_parent_node(&self, _node: usize) -> bool { + // `node` will have a parent unless a script moved it, and we're + // not running scripts. Therefore we can aways return true. + true + } + fn append_before_sibling(&mut self, _sibling: usize, - _new_node: NodeOrText) -> Result<(), NodeOrText> { - // `sibling` will have a parent unless a script moved it, and we're - // not running scripts. Therefore we can aways return `Ok(())`. - Ok(()) - } + _new_node: NodeOrText) { } fn parse_error(&mut self, _msg: Cow<'static, str>) { } fn set_quirks_mode(&mut self, _mode: QuirksMode) { } diff --git a/examples/print-tree-actions.rs b/examples/print-tree-actions.rs index 015ce192..744290db 100644 --- a/examples/print-tree-actions.rs +++ b/examples/print-tree-actions.rs @@ -82,6 +82,12 @@ impl TreeSink for Sink { id } + fn has_parent_node(&self, _node: usize) -> bool { + // `node` will have a parent unless a script moved it, and we're + // not running scripts. Therefore we can aways return true + true + } + fn append(&mut self, parent: usize, child: NodeOrText) { match child { AppendNode(n) @@ -93,17 +99,13 @@ impl TreeSink for Sink { fn append_before_sibling(&mut self, sibling: usize, - new_node: NodeOrText) -> Result<(), NodeOrText> { + new_node: NodeOrText) { match new_node { AppendNode(n) => println!("Append node {} before {}", n, sibling), AppendText(t) => println!("Append text before {}: \"{}\"", sibling, escape_default(&t)), } - - // `sibling` will have a parent unless a script moved it, and we're - // not running scripts. Therefore we can aways return `Ok(())`. - Ok(()) } fn append_doctype_to_document(&mut self, @@ -121,6 +123,12 @@ impl TreeSink for Sink { } } + fn associate_with_form(&mut self, _target: usize, _form: usize) { + // No form owner support. Since same_tree always returns + // true we cannot be sure that this associate_with_form call is + // valid + } + fn remove_from_parent(&mut self, target: usize) { println!("Remove {} from parent", target); } diff --git a/src/rcdom.rs b/src/rcdom.rs index 82dedec0..3d00094a 100644 --- a/src/rcdom.rs +++ b/src/rcdom.rs @@ -223,6 +223,11 @@ impl TreeSink for RcDom { new_node(Comment(text)) } + fn has_parent_node(&self, node: Handle) -> bool { + let node = node.borrow(); + node.parent.is_some() + } + fn append(&mut self, parent: Handle, child: NodeOrText) { // Append to an existing Text node if we have one. match child { @@ -241,8 +246,9 @@ impl TreeSink for RcDom { fn append_before_sibling(&mut self, sibling: Handle, - child: NodeOrText) -> Result<(), NodeOrText> { - let (parent, i) = unwrap_or_return!(get_parent_and_index(&sibling), Err(child)); + child: NodeOrText) { + let (parent, i) = get_parent_and_index(&sibling) + .expect("append_before_sibling called on node without parent"); let child = match (child, i) { // No previous node. @@ -253,7 +259,7 @@ impl TreeSink for RcDom { let parent = parent.borrow(); let prev = &parent.children[i-1]; if append_to_existing_text(prev, &text) { - return Ok(()); + return; } new_node(Text(text)) } @@ -271,7 +277,6 @@ impl TreeSink for RcDom { child.borrow_mut().parent = Some(Rc::downgrade(&parent)); parent.borrow_mut().children.insert(i, child); - Ok(()) } fn append_doctype_to_document(&mut self, diff --git a/src/tree_builder/actions.rs b/src/tree_builder/actions.rs index 43e4633f..ce94676a 100644 --- a/src/tree_builder/actions.rs +++ b/src/tree_builder/actions.rs @@ -210,40 +210,8 @@ impl TreeBuilderActions // Insert at the "appropriate place for inserting a node". fn insert_appropriately(&mut self, child: NodeOrText, override_target: Option) { - declare_tag_set!(foster_target = "table" "tbody" "tfoot" "thead" "tr"); - let target = override_target.unwrap_or_else(|| self.current_node()); - if !(self.foster_parenting && self.elem_in(target.clone(), foster_target)) { - if self.html_elem_named(target.clone(), local_name!("template")) { - // No foster parenting (inside template). - let contents = self.sink.get_template_contents(target); - self.sink.append(contents, child); - } else { - // No foster parenting (the common case). - self.sink.append(target, child); - } - return; - } - - // Foster parenting - let mut iter = self.open_elems.iter().rev().peekable(); - while let Some(elem) = iter.next() { - if self.html_elem_named(elem.clone(), local_name!("template")) { - let contents = self.sink.get_template_contents(elem.clone()); - self.sink.append(contents, child); - return; - } else if self.html_elem_named(elem.clone(), local_name!("table")) { - // Try inserting "inside last table's parent node, immediately before last table" - if let Err(child) = self.sink.append_before_sibling(elem.clone(), child) { - // If last_table has no parent, we regain ownership of the child. - // Insert "inside previous element, after its last child (if any)" - let previous_element = (*iter.peek().unwrap()).clone(); - self.sink.append(previous_element, child); - } - return; - } - } - let html_elem = self.html_elem(); - self.sink.append(html_elem, child); + let insertion_point = self.appropriate_place_for_insertion(override_target); + self.insert_at(insertion_point, child); } fn adoption_agency(&mut self, subject: LocalName) { @@ -775,10 +743,40 @@ impl TreeBuilderActions // FIXME: application cache selection algorithm } + // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token fn insert_element(&mut self, push: PushFlag, ns: Namespace, name: LocalName, attrs: Vec) -> Handle { - let elem = self.sink.create_element(QualName::new(ns, name), attrs); - self.insert_appropriately(AppendNode(elem.clone()), None); + declare_tag_set!(form_associatable = + "button" "fieldset" "input" "object" + "output" "select" "textarea" "img"); + + declare_tag_set!(listed = [form_associatable] - "img"); + + // Step 7. + let qname = QualName::new(ns, name); + let elem = self.sink.create_element(qname.clone(), attrs.clone()); + + let insertion_point = self.appropriate_place_for_insertion(None); + let tree_node = match insertion_point { + LastChild(ref p) | + BeforeSibling(ref p) => p.clone() + }; + + // Step 12. + if form_associatable(qname.clone()) && + self.form_elem.is_some() && + !self.in_html_elem_named(local_name!("template")) && + !(listed(qname.clone()) && + attrs.iter().any(|a| a.name == qualname!("","form"))) { + + let form = self.form_elem.as_ref().unwrap().clone(); + if self.sink.same_tree(tree_node, form.clone()) { + self.sink.associate_with_form(elem.clone(), form) + } + } + + self.insert_at(insertion_point, AppendNode(elem.clone())); + match push { Push => self.push(&elem), NoPush => (), diff --git a/src/tree_builder/interface.rs b/src/tree_builder/interface.rs index fda279fe..1a60ff13 100644 --- a/src/tree_builder/interface.rs +++ b/src/tree_builder/interface.rs @@ -72,6 +72,11 @@ pub trait TreeSink { /// Do two handles refer to the same node? fn same_node(&self, x: Self::Handle, y: Self::Handle) -> bool; + /// Are two handles present in the same tree + fn same_tree(&self, x: Self::Handle, y: Self::Handle) -> bool { + true + } + /// What is the name of this element? /// /// Should never be called on a non-element node; @@ -93,6 +98,9 @@ pub trait TreeSink { /// Create a comment node. fn create_comment(&mut self, text: StrTendril) -> Self::Handle; + /// Does the node have a parent? + fn has_parent_node(&self, node: Self::Handle) -> bool; + /// Append a node as the last child of the given node. If this would /// produce adjacent sibling text nodes, it should concatenate the text /// instead. @@ -100,8 +108,8 @@ pub trait TreeSink { /// The child node will not already have a parent. fn append(&mut self, parent: Self::Handle, child: NodeOrText); - /// Append a node as the sibling immediately before the given node. If that node - /// has no parent, do nothing and return Err(new_node). + /// Append a node as the sibling immediately before the given node. + /// This method will only be called if has_parent_node(sibling) is true /// /// The tree builder promises that `sibling` is not a text node. However its /// old previous sibling, which would become the new node's previous sibling, @@ -111,7 +119,7 @@ pub trait TreeSink { /// NB: `new_node` may have an old parent, from which it should be removed. fn append_before_sibling(&mut self, sibling: Self::Handle, - new_node: NodeOrText) -> Result<(), NodeOrText>; + new_node: NodeOrText); /// Append a `DOCTYPE` element to the `Document` node. fn append_doctype_to_document(&mut self, @@ -124,6 +132,9 @@ pub trait TreeSink { /// with something else than an element. fn add_attrs_if_missing(&mut self, target: Self::Handle, attrs: Vec); + /// Associate the given form-associatable element with the form element + fn associate_with_form(&mut self, target: Self::Handle, form: Self::Handle) {} + /// Detach the given node from its parent. fn remove_from_parent(&mut self, target: Self::Handle); diff --git a/src/tree_builder/mod.rs b/src/tree_builder/mod.rs index 64bad05b..83b4c9d0 100644 --- a/src/tree_builder/mod.rs +++ b/src/tree_builder/mod.rs @@ -369,6 +369,53 @@ impl TreeBuilder pub fn is_fragment(&self) -> bool { self.context_elem.is_some() } + + fn appropriate_place_for_insertion(&mut self, + override_target: Option) + -> InsertionPoint { + use self::tag_sets::*; + + declare_tag_set!(foster_target = "table" "tbody" "tfoot" "thead" "tr"); + let target = override_target.unwrap_or_else(|| self.current_node()); + if !(self.foster_parenting && self.elem_in(target.clone(), foster_target)) { + if self.html_elem_named(target.clone(), local_name!("template")) { + // No foster parenting (inside template). + let contents = self.sink.get_template_contents(target); + return LastChild(contents); + } else { + // No foster parenting (the common case). + return LastChild(target); + } + } + + // Foster parenting + let mut iter = self.open_elems.iter().rev().peekable(); + while let Some(elem) = iter.next() { + if self.html_elem_named(elem.clone(), local_name!("template")) { + let contents = self.sink.get_template_contents(elem.clone()); + return LastChild(contents); + } else if self.html_elem_named(elem.clone(), local_name!("table")) { + // Try inserting "inside last table's parent node, immediately before last table" + if self.sink.has_parent_node(elem.clone()) { + return BeforeSibling(elem.clone()); + } else { + // If elem has no parent, we regain ownership of the child. + // Insert "inside previous element, after its last child (if any)" + let previous_element = (*iter.peek().unwrap()).clone(); + return LastChild(previous_element); + } + } + } + let html_elem = self.html_elem(); + LastChild(html_elem) + } + + fn insert_at(&mut self, insertion_point: InsertionPoint, child: NodeOrText) { + match insertion_point { + LastChild(parent) => self.sink.append(parent, child), + BeforeSibling(sibling) => self.sink.append_before_sibling(sibling, child) + } + } } impl TokenSink @@ -529,13 +576,18 @@ mod test { self.rcdom.create_comment(text) } + fn has_parent_node(&self, node: Handle) -> bool { + let node = node.borrow(); + node.parent.is_some() + } + fn append(&mut self, parent: Handle, child: NodeOrText) { self.rcdom.append(parent, child) } fn append_before_sibling(&mut self, sibling: Handle, - child: NodeOrText) -> Result<(), NodeOrText> { + child: NodeOrText) { self.rcdom.append_before_sibling(sibling, child) } diff --git a/src/tree_builder/types.rs b/src/tree_builder/types.rs index 8bb591af..2d8898b1 100644 --- a/src/tree_builder/types.rs +++ b/src/tree_builder/types.rs @@ -19,6 +19,7 @@ pub use self::SplitStatus::*; pub use self::Token::*; pub use self::ProcessResult::*; pub use self::FormatEntry::*; +pub use self::InsertionPoint::*; #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub enum InsertionMode { @@ -80,3 +81,13 @@ pub enum FormatEntry { Element(Handle, Tag), Marker, } + +pub enum InsertionPoint { + /// Holds the parent + LastChild(Handle), + /// Holds the sibling before which the node will be inserted + /// TODO: Is the parent node needed? Is there a problem with using + /// the sibling to find if the form element is in the same home + /// subtree? + BeforeSibling(Handle) +}