From 87475b11d344c2bfdfbed4d48495ecedec34bac8 Mon Sep 17 00:00:00 2001 From: Connor Brewster Date: Mon, 27 Nov 2017 13:55:46 -0600 Subject: [PATCH 1/4] Implement the create an element for token algorithm --- .../script/dom/servoparser/async_html.rs | 31 +++-- components/script/dom/servoparser/html.rs | 6 +- components/script/dom/servoparser/mod.rs | 127 +++++++++++++++--- components/script/dom/servoparser/xml.rs | 3 +- 4 files changed, 127 insertions(+), 40 deletions(-) diff --git a/components/script/dom/servoparser/async_html.rs b/components/script/dom/servoparser/async_html.rs index c918a4b3605..bcf3e25b767 100644 --- a/components/script/dom/servoparser/async_html.rs +++ b/components/script/dom/servoparser/async_html.rs @@ -12,14 +12,15 @@ use dom::bindings::str::DOMString; use dom::comment::Comment; use dom::document::Document; use dom::documenttype::DocumentType; -use dom::element::{CustomElementCreationMode, Element, ElementCreator}; +use dom::element::{Element, ElementCreator}; use dom::htmlformelement::{FormControlElementHelpers, HTMLFormElement}; use dom::htmlscriptelement::HTMLScriptElement; use dom::htmltemplateelement::HTMLTemplateElement; use dom::node::Node; use dom::processinginstruction::ProcessingInstruction; +use dom::servoparser::{ElementAttribute, create_element_for_token, ParsingAlgorithm}; use dom::virtualmethods::vtable_for; -use html5ever::{Attribute as HtmlAttribute, ExpandedName, LocalName, QualName}; +use html5ever::{Attribute as HtmlAttribute, ExpandedName, QualName}; use html5ever::buffer_queue::BufferQueue; use html5ever::tendril::{SendTendril, StrTendril, Tendril}; use html5ever::tendril::fmt::UTF8; @@ -335,20 +336,18 @@ impl Tokenizer { self.insert_node(contents, Dom::from_ref(template.Content().upcast())); } ParseOperation::CreateElement { node, name, attrs, current_line } => { - let is = attrs.iter() - .find(|attr| attr.name.local.eq_str_ignore_ascii_case("is")) - .map(|attr| LocalName::from(&*attr.value)); - - let elem = Element::create(name, - is, - &*self.document, - ElementCreator::ParserCreated(current_line), - CustomElementCreationMode::Synchronous); - for attr in attrs { - elem.set_attribute_from_parser(attr.name, DOMString::from(attr.value), None); - } - - self.insert_node(node, Dom::from_ref(elem.upcast())); + let attrs = attrs + .into_iter() + .map(|attr| ElementAttribute::new(attr.name, DOMString::from(attr.value))) + .collect(); + let element = create_element_for_token( + name, + attrs, + &*self.document, + ElementCreator::ParserCreated(current_line), + ParsingAlgorithm::Normal + ); + self.insert_node(node, Dom::from_ref(element.upcast())); } ParseOperation::CreateComment { text, node } => { let comment = Comment::new(DOMString::from(text), document); diff --git a/components/script/dom/servoparser/html.rs b/components/script/dom/servoparser/html.rs index 53dcc3fc7c6..78ea868cac1 100644 --- a/components/script/dom/servoparser/html.rs +++ b/components/script/dom/servoparser/html.rs @@ -16,7 +16,7 @@ use dom::htmlscriptelement::HTMLScriptElement; use dom::htmltemplateelement::HTMLTemplateElement; use dom::node::Node; use dom::processinginstruction::ProcessingInstruction; -use dom::servoparser::Sink; +use dom::servoparser::{ParsingAlgorithm, Sink}; use html5ever::QualName; use html5ever::buffer_queue::BufferQueue; use html5ever::serialize::{AttrRef, Serialize, Serializer}; @@ -39,13 +39,15 @@ impl Tokenizer { pub fn new( document: &Document, url: ServoUrl, - fragment_context: Option) + fragment_context: Option, + parsing_algorithm: ParsingAlgorithm) -> Self { let sink = Sink { base_url: url, document: Dom::from_ref(document), current_line: 1, script: Default::default(), + parsing_algorithm: parsing_algorithm, }; let options = TreeBuilderOpts { diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 88dd2a740f5..988e549d309 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -101,6 +101,26 @@ enum LastChunkState { NotReceived, } +pub struct ElementAttribute { + name: QualName, + value: DOMString +} + +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] +pub enum ParsingAlgorithm { + Normal, + Fragment, +} + +impl ElementAttribute { + pub fn new(name: QualName, value: DOMString) -> ElementAttribute { + ElementAttribute { + name: name, + value: value + } + } +} + impl ServoParser { pub fn parser_is_not_active(&self) -> bool { self.can_write() || self.tokenizer.try_borrow_mut().is_ok() @@ -114,7 +134,7 @@ impl ServoParser { ParserKind::Normal) } else { ServoParser::new(document, - Tokenizer::Html(self::html::Tokenizer::new(document, url, None)), + Tokenizer::Html(self::html::Tokenizer::new(document, url, None, ParsingAlgorithm::Normal)), LastChunkState::NotReceived, ParserKind::Normal) }; @@ -160,7 +180,8 @@ impl ServoParser { let parser = ServoParser::new(&document, Tokenizer::Html(self::html::Tokenizer::new(&document, url, - Some(fragment_context))), + Some(fragment_context), + ParsingAlgorithm::Fragment)), LastChunkState::Received, ParserKind::Normal); parser.parse_string_chunk(String::from(input)); @@ -173,10 +194,17 @@ impl ServoParser { } pub fn parse_html_script_input(document: &Document, url: ServoUrl, type_: &str) { - let parser = ServoParser::new(document, - Tokenizer::Html(self::html::Tokenizer::new(document, url, None)), - LastChunkState::NotReceived, - ParserKind::ScriptCreated); + let parser = ServoParser::new( + document, + Tokenizer::Html(self::html::Tokenizer::new( + document, + url, + None, + ParsingAlgorithm::Normal, + )), + LastChunkState::NotReceived, + ParserKind::ScriptCreated, + ); document.set_current_parser(Some(&parser)); if !type_.eq_ignore_ascii_case("text/html") { parser.parse_string_chunk("
\n".to_owned());
@@ -748,6 +776,7 @@ pub struct Sink {
     document: Dom,
     current_line: u64,
     script: MutNullableDom,
+    parsing_algorithm: ParsingAlgorithm,
 }
 
 impl Sink {
@@ -795,21 +824,18 @@ impl TreeSink for Sink {
 
     fn create_element(&mut self, name: QualName, attrs: Vec, _flags: ElementFlags)
             -> Dom {
-        let is = attrs.iter()
-                      .find(|attr| attr.name.local.eq_str_ignore_ascii_case("is"))
-                      .map(|attr| LocalName::from(&*attr.value));
-
-        let elem = Element::create(name,
-                                   is,
-                                   &*self.document,
-                                   ElementCreator::ParserCreated(self.current_line),
-                                   CustomElementCreationMode::Synchronous);
-
-        for attr in attrs {
-            elem.set_attribute_from_parser(attr.name, DOMString::from(String::from(attr.value)), None);
-        }
-
-        Dom::from_ref(elem.upcast())
+        let attrs = attrs
+            .into_iter()
+            .map(|attr| ElementAttribute::new(attr.name, DOMString::from(String::from(attr.value))))
+            .collect();
+        let element = create_element_for_token(
+            name,
+            attrs,
+            &*self.document,
+            ElementCreator::ParserCreated(self.current_line),
+            self.parsing_algorithm,
+        );
+        Dom::from_ref(element.upcast())
     }
 
     fn create_comment(&mut self, text: StrTendril) -> Dom {
@@ -950,3 +976,62 @@ impl TreeSink for Sink {
         vtable_for(&node).pop();
     }
 }
+
+/// https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
+fn create_element_for_token(
+    name: QualName,
+    attrs: Vec,
+    document: &Document,
+    creator: ElementCreator,
+    parsing_algorithm: ParsingAlgorithm,
+) -> DomRoot {
+    // Step 3.
+    let is = attrs.iter()
+        .find(|attr| attr.name.local.eq_str_ignore_ascii_case("is"))
+        .map(|attr| LocalName::from(&*attr.value));
+
+    // Step 4.
+    let definition = document.lookup_custom_element_definition(&name.ns, &name.local, is.as_ref());
+
+    // Step 5.
+    let will_execute_script = definition.is_some() && parsing_algorithm != ParsingAlgorithm::Fragment;
+
+    // Step 6.
+    if will_execute_script {
+        // Step 6.1.
+        // TODO: handle throw-on-dynamic-markup-insertion counter.
+        // Step 6.2
+        // TODO: If the JavaScript execution context stack is empty, then perform a microtask checkpoint.
+        // Step 6.3
+        ScriptThread::push_new_element_queue()
+    }
+
+    // Step 7.
+    let creation_mode = if will_execute_script {
+        CustomElementCreationMode::Synchronous
+    } else {
+        CustomElementCreationMode::Asynchronous
+    };
+    let element = Element::create(name, is, document, creator, creation_mode);
+
+    // Step 8.
+    for attr in attrs {
+        element.set_attribute_from_parser(attr.name, attr.value, None);
+    }
+
+    // Step 9.
+    if will_execute_script {
+        // Steps 9.1 - 9.2.
+        ScriptThread::pop_current_element_queue()
+        // Step 9.3.
+        // TODO: handle throw-on-dynamic-markup-insertion counter.
+    }
+
+    // TODO: Step 10.
+    // TODO: Step 11.
+
+    // Step 12 is handled in `associate_with_form`.
+
+    // Step 13.
+    element
+}
diff --git a/components/script/dom/servoparser/xml.rs b/components/script/dom/servoparser/xml.rs
index d34792b56c3..62ebe351a23 100644
--- a/components/script/dom/servoparser/xml.rs
+++ b/components/script/dom/servoparser/xml.rs
@@ -9,7 +9,7 @@ use dom::bindings::trace::JSTraceable;
 use dom::document::Document;
 use dom::htmlscriptelement::HTMLScriptElement;
 use dom::node::Node;
-use dom::servoparser::Sink;
+use dom::servoparser::{ParsingAlgorithm, Sink};
 use js::jsapi::JSTracer;
 use servo_url::ServoUrl;
 use xml5ever::buffer_queue::BufferQueue;
@@ -30,6 +30,7 @@ impl Tokenizer {
             document: Dom::from_ref(document),
             current_line: 1,
             script: Default::default(),
+            parsing_algorithm: ParsingAlgorithm::Normal,
         };
 
         let tb = XmlTreeBuilder::new(sink, Default::default());

From 43526c80bb09ff2f2ea152dfd99ce6e0314a4a46 Mon Sep 17 00:00:00 2001
From: Connor Brewster 
Date: Sat, 2 Dec 2017 18:18:35 -0600
Subject: [PATCH 2/4] Add a check for when the js execution stack is empty

---
 components/script/dom/bindings/settings_stack.rs | 6 ++++++
 components/script/dom/servoparser/mod.rs         | 5 ++++-
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/components/script/dom/bindings/settings_stack.rs b/components/script/dom/bindings/settings_stack.rs
index b540b989bc4..f9438f17065 100644
--- a/components/script/dom/bindings/settings_stack.rs
+++ b/components/script/dom/bindings/settings_stack.rs
@@ -35,6 +35,12 @@ pub unsafe fn trace(tracer: *mut JSTracer) {
     })
 }
 
+pub fn is_execution_stack_empty() -> bool {
+    STACK.with(|stack| {
+        stack.borrow().is_empty()
+    })
+}
+
 /// RAII struct that pushes and pops entries from the script settings stack.
 pub struct AutoEntryScript {
     global: DomRoot,
diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs
index 988e549d309..25539925e17 100644
--- a/components/script/dom/servoparser/mod.rs
+++ b/components/script/dom/servoparser/mod.rs
@@ -13,6 +13,7 @@ use dom::bindings::inheritance::Castable;
 use dom::bindings::refcounted::Trusted;
 use dom::bindings::reflector::{Reflector, reflect_dom_object};
 use dom::bindings::root::{Dom, DomRoot, MutNullableDom, RootedReference};
+use dom::bindings::settings_stack::is_execution_stack_empty;
 use dom::bindings::str::DOMString;
 use dom::characterdata::CharacterData;
 use dom::comment::Comment;
@@ -1001,7 +1002,9 @@ fn create_element_for_token(
         // Step 6.1.
         // TODO: handle throw-on-dynamic-markup-insertion counter.
         // Step 6.2
-        // TODO: If the JavaScript execution context stack is empty, then perform a microtask checkpoint.
+        if is_execution_stack_empty() {
+            document.window().upcast::().perform_a_microtask_checkpoint();
+        }
         // Step 6.3
         ScriptThread::push_new_element_queue()
     }

From 4593195b493e819b56b8f34376b1058a80210c53 Mon Sep 17 00:00:00 2001
From: Connor Brewster 
Date: Sat, 2 Dec 2017 18:35:39 -0600
Subject: [PATCH 3/4] Implement `throw-on-dynamic-markup-insertion-counter`

---
 components/script/dom/document.rs        | 26 +++++++++++++++++++++---
 components/script/dom/servoparser/mod.rs |  6 +++---
 2 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 75a54e5c6b9..bb1c4b53ff7 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -364,6 +364,8 @@ pub struct Document {
     tti_window: DomRefCell,
     /// RAII canceller for Fetch
     canceller: FetchCanceller,
+    /// https://html.spec.whatwg.org/multipage/#throw-on-dynamic-markup-insertion-counter
+    throw_on_dynamic_markup_insertion_counter: Cell,
 }
 
 #[derive(JSTraceable, MallocSizeOf)]
@@ -2053,6 +2055,16 @@ impl Document {
         let global_scope = self.window.upcast::();
         global_scope.script_to_constellation_chan().send(msg).unwrap();
     }
+
+    pub fn increment_throw_on_dynamic_markup_insertion_counter(&self) {
+        let counter = self.throw_on_dynamic_markup_insertion_counter.get();
+        self.throw_on_dynamic_markup_insertion_counter.set(counter + 1);
+    }
+
+    pub fn decrement_throw_on_dynamic_markup_insertion_counter(&self) {
+        let counter = self.throw_on_dynamic_markup_insertion_counter.get();
+        self.throw_on_dynamic_markup_insertion_counter.set(counter - 1);
+    }
 }
 
 #[derive(MallocSizeOf, PartialEq)]
@@ -2294,6 +2306,7 @@ impl Document {
             interactive_time: DomRefCell::new(interactive_time),
             tti_window: DomRefCell::new(InteractiveWindow::new()),
             canceller: canceller,
+            throw_on_dynamic_markup_insertion_counter: Cell::new(0),
         }
     }
 
@@ -3717,7 +3730,9 @@ impl DocumentMethods for Document {
         }
 
         // Step 2.
-        // TODO: handle throw-on-dynamic-markup-insertion counter.
+        if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
+            return Err(Error::InvalidState);
+        }
 
         if !self.is_active() {
             // Step 3.
@@ -3863,7 +3878,10 @@ impl DocumentMethods for Document {
         }
 
         // Step 2.
-        // TODO: handle throw-on-dynamic-markup-insertion counter.
+        if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
+            return Err(Error::InvalidState);
+        }
+
         if !self.is_active() {
             // Step 3.
             return Ok(());
@@ -3910,7 +3928,9 @@ impl DocumentMethods for Document {
         }
 
         // Step 2.
-        // TODO: handle throw-on-dynamic-markup-insertion counter.
+        if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
+            return Err(Error::InvalidState);
+        }
 
         let parser = match self.get_current_parser() {
             Some(ref parser) if parser.is_script_created() => DomRoot::from_ref(&**parser),
diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs
index 25539925e17..7cadab84d04 100644
--- a/components/script/dom/servoparser/mod.rs
+++ b/components/script/dom/servoparser/mod.rs
@@ -1000,7 +1000,7 @@ fn create_element_for_token(
     // Step 6.
     if will_execute_script {
         // Step 6.1.
-        // TODO: handle throw-on-dynamic-markup-insertion counter.
+        document.increment_throw_on_dynamic_markup_insertion_counter();
         // Step 6.2
         if is_execution_stack_empty() {
             document.window().upcast::().perform_a_microtask_checkpoint();
@@ -1025,9 +1025,9 @@ fn create_element_for_token(
     // Step 9.
     if will_execute_script {
         // Steps 9.1 - 9.2.
-        ScriptThread::pop_current_element_queue()
+        ScriptThread::pop_current_element_queue();
         // Step 9.3.
-        // TODO: handle throw-on-dynamic-markup-insertion counter.
+        document.decrement_throw_on_dynamic_markup_insertion_counter();
     }
 
     // TODO: Step 10.

From c55f25b3aaeea7719f0864b1e6c1e71af7f05340 Mon Sep 17 00:00:00 2001
From: Josh Matthews 
Date: Thu, 11 Jan 2018 11:11:51 -0500
Subject: [PATCH 4/4] Take throw-on-dynamic-markup-insertion-counter into
 account when check script safety.

---
 components/script/dom/document.rs                          | 7 ++++++-
 .../custom-elements/microtasks-and-constructors.html.ini   | 3 ---
 .../parser/parser-fallsback-to-unknown-element.html.ini    | 3 ---
 3 files changed, 6 insertions(+), 7 deletions(-)
 delete mode 100644 tests/wpt/metadata/custom-elements/microtasks-and-constructors.html.ini
 delete mode 100644 tests/wpt/metadata/custom-elements/parser/parser-fallsback-to-unknown-element.html.ini

diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index bb1c4b53ff7..aecc0b0d0e7 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -1896,7 +1896,12 @@ impl Document {
 
     pub fn can_invoke_script(&self) -> bool {
         match self.get_current_parser() {
-            Some(parser) => parser.parser_is_not_active(),
+            Some(parser) => {
+                // It is safe to run script if the parser is not actively parsing,
+                // or if it is impossible to interact with the token stream.
+                parser.parser_is_not_active() ||
+                self.throw_on_dynamic_markup_insertion_counter.get() > 0
+            }
             None => true,
         }
     }
diff --git a/tests/wpt/metadata/custom-elements/microtasks-and-constructors.html.ini b/tests/wpt/metadata/custom-elements/microtasks-and-constructors.html.ini
deleted file mode 100644
index 22111c6cb3d..00000000000
--- a/tests/wpt/metadata/custom-elements/microtasks-and-constructors.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[microtasks-and-constructors.html]
-  expected: CRASH
-  bug: https://github.com/servo/servo/issues/19392
diff --git a/tests/wpt/metadata/custom-elements/parser/parser-fallsback-to-unknown-element.html.ini b/tests/wpt/metadata/custom-elements/parser/parser-fallsback-to-unknown-element.html.ini
deleted file mode 100644
index 9f8fb480445..00000000000
--- a/tests/wpt/metadata/custom-elements/parser/parser-fallsback-to-unknown-element.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[parser-fallsback-to-unknown-element.html]
-  expected: CRASH
-  bug: https://github.com/servo/servo/issues/19392