From 0cb09468a0e39b7797f3cd540b8d47e0e1ba55a0 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Fri, 6 Jan 2017 16:16:48 +0100 Subject: [PATCH] Properly insert text in document when parsing If the last child of a node is a Text child and we are inserting text in that node, we need to append it to that Text child. Doing that means that HTMLStyleElement::children_changed gets called less frequently, but that's not a problem during parsing thanks to the pop hook. --- components/script/dom/htmlstyleelement.rs | 36 ++++++++++++++----- components/script/dom/htmltextareaelement.rs | 7 ++++ components/script/dom/servoparser/html.rs | 18 +++++++--- components/script/dom/servoparser/xml.rs | 6 ++++ components/script/dom/virtualmethods.rs | 8 +++++ .../open-url-javascript-window-2.htm.ini | 2 +- .../open-url-javascript-window.htm.ini | 2 +- .../document-write/051.html.ini | 5 --- ...invoke-insert-source-networkState.html.ini | 2 +- ...oke-insert-source-not-in-document.html.ini | 2 +- ...ce-selection-invoke-insert-source.html.ini | 2 +- .../resource-selection-remove-source.html.ini | 2 +- .../resource-selection-source-media.html.ini | 2 +- .../html/syntax/parsing/math-parse01.html.ini | 3 -- .../compile-error-cross-origin.html.ini | 2 +- .../runtime-error-cross-origin.html.ini | 2 +- .../tests/mozilla/iframe_hierarchy.html | 2 +- 17 files changed, 72 insertions(+), 31 deletions(-) delete mode 100644 tests/wpt/metadata/html/dom/dynamic-markup-insertion/document-write/051.html.ini diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index 2507f7bee35..97fd6591de8 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -36,6 +36,7 @@ pub struct HTMLStyleElement { cssom_stylesheet: MutNullableJS, /// https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts parser_inserted: Cell, + in_stack_of_open_elements: Cell, pending_loads: Cell, any_failed_load: Cell, } @@ -50,6 +51,7 @@ impl HTMLStyleElement { stylesheet: DOMRefCell::new(None), cssom_stylesheet: MutNullableJS::new(None), parser_inserted: Cell::new(creator == ElementCreator::ParserCreated), + in_stack_of_open_elements: Cell::new(creator == ElementCreator::ParserCreated), pending_loads: Cell::new(0), any_failed_load: Cell::new(false), } @@ -124,20 +126,38 @@ impl VirtualMethods for HTMLStyleElement { } fn children_changed(&self, mutation: &ChildrenMutation) { - if let Some(ref s) = self.super_type() { - s.children_changed(mutation); - } - if self.upcast::().is_in_doc() { + self.super_type().unwrap().children_changed(mutation); + + // https://html.spec.whatwg.org/multipage/#update-a-style-block + // Handles the case when: + // "The element is not on the stack of open elements of an HTML parser or XML parser, + // and one of its child nodes is modified by a script." + // TODO: Handle Text child contents being mutated. + if self.upcast::().is_in_doc() && !self.in_stack_of_open_elements.get() { self.parse_own_css(); } } fn bind_to_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); - } + self.super_type().unwrap().bind_to_tree(tree_in_doc); - if tree_in_doc { + // https://html.spec.whatwg.org/multipage/#update-a-style-block + // Handles the case when: + // "The element is not on the stack of open elements of an HTML parser or XML parser, + // and it becomes connected or disconnected." + if tree_in_doc && !self.in_stack_of_open_elements.get() { + self.parse_own_css(); + } + } + + fn pop(&self) { + self.super_type().unwrap().pop(); + + // https://html.spec.whatwg.org/multipage/#update-a-style-block + // Handles the case when: + // "The element is popped off the stack of open elements of an HTML parser or XML parser." + self.in_stack_of_open_elements.set(false); + if self.upcast::().is_in_doc() { self.parse_own_css(); } } diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 2582b13514a..c6fd4d6b170 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -425,6 +425,13 @@ impl VirtualMethods for HTMLTextAreaElement { } } } + + fn pop(&self) { + self.super_type().unwrap().pop(); + + // https://html.spec.whatwg.org/multipage/#the-textarea-element:stack-of-open-elements + self.reset(); + } } impl FormControl for HTMLTextAreaElement {} diff --git a/components/script/dom/servoparser/html.rs b/components/script/dom/servoparser/html.rs index 28f8735891e..4a8f01d639e 100644 --- a/components/script/dom/servoparser/html.rs +++ b/components/script/dom/servoparser/html.rs @@ -20,6 +20,7 @@ use dom::htmltemplateelement::HTMLTemplateElement; use dom::node::Node; use dom::processinginstruction::ProcessingInstruction; use dom::text::Text; +use dom::virtualmethods::vtable_for; use html5ever::Attribute; use html5ever::serialize::{AttrRef, Serializable, Serializer}; use html5ever::serialize::TraversalScope; @@ -124,7 +125,7 @@ struct Sink { document: JS, } -impl<'a> TreeSink for Sink { +impl TreeSink for Sink { type Output = Self; fn finish(self) -> Self { self } @@ -233,7 +234,11 @@ impl<'a> TreeSink for Sink { while let Some(ref child) = node.GetFirstChild() { new_parent.AppendChild(&child).unwrap(); } + } + fn pop(&mut self, node: JS) { + let node = Root::from_ref(&*node); + vtable_for(&node).pop(); } } @@ -243,10 +248,13 @@ fn insert(parent: &Node, reference_child: Option<&Node>, child: NodeOrText { - // FIXME(ajeffrey): convert directly from tendrils to DOMStrings - let s: String = t.into(); - let text = Text::new(DOMString::from(s), &parent.owner_doc()); - assert!(parent.InsertBefore(text.upcast(), reference_child).is_ok()); + if let Some(text) = parent.GetLastChild().and_then(Root::downcast::) { + text.upcast::().append_data(&t); + } else { + let s: String = t.into(); + let text = Text::new(DOMString::from(s), &parent.owner_doc()); + parent.InsertBefore(text.upcast(), reference_child).unwrap(); + } } } } diff --git a/components/script/dom/servoparser/xml.rs b/components/script/dom/servoparser/xml.rs index 74b49fedd1c..6bab196d7ee 100644 --- a/components/script/dom/servoparser/xml.rs +++ b/components/script/dom/servoparser/xml.rs @@ -17,6 +17,7 @@ use dom::htmlscriptelement::HTMLScriptElement; use dom::node::Node; use dom::processinginstruction::ProcessingInstruction; use dom::text::Text; +use dom::virtualmethods::vtable_for; use html5ever::tokenizer::buffer_queue::BufferQueue; use html5ever_atoms::{Prefix, QualName}; use js::jsapi::JSTracer; @@ -196,4 +197,9 @@ impl TreeSink for Sink { NextParserState::Continue } } + + fn pop(&mut self, node: Self::Handle) { + let node = Root::from_ref(&*node); + vtable_for(&node).pop(); + } } diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index e10e48e8ab8..d6b77b31256 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -124,6 +124,14 @@ pub trait VirtualMethods { s.cloning_steps(copy, maybe_doc, clone_children); } } + + /// Called on an element when it is popped off the stack of open elements + /// of a parser. + fn pop(&self) { + if let Some(ref s) = self.super_type() { + s.pop(); + } + } } /// Obtain a VirtualMethods instance for a given Node-derived object. Any diff --git a/tests/wpt/metadata/XMLHttpRequest/open-url-javascript-window-2.htm.ini b/tests/wpt/metadata/XMLHttpRequest/open-url-javascript-window-2.htm.ini index f48486261eb..8f9dc0ea819 100644 --- a/tests/wpt/metadata/XMLHttpRequest/open-url-javascript-window-2.htm.ini +++ b/tests/wpt/metadata/XMLHttpRequest/open-url-javascript-window-2.htm.ini @@ -1,6 +1,6 @@ [open-url-javascript-window-2.htm] type: testharness expected: TIMEOUT - [XMLHttpRequest: open() - resolving URLs (javascript: ] + [XMLHttpRequest: open() - resolving URLs (javascript: