diff options
author | Anthony Ramine <n.oxyde@gmail.com> | 2016-11-25 16:19:19 +0100 |
---|---|---|
committer | Anthony Ramine <n.oxyde@gmail.com> | 2016-11-28 23:05:56 +0100 |
commit | 4d93ee134c84d72f73d7a5556bd2dcaf36e5ca99 (patch) | |
tree | 7f57f6c9a027c9742db9ebce25e30fe654e5a94d /components/script/dom | |
parent | 708ebdceee50547d03713d32c5207c1fa870f902 (diff) | |
download | servo-4d93ee134c84d72f73d7a5556bd2dcaf36e5ca99.tar.gz servo-4d93ee134c84d72f73d7a5556bd2dcaf36e5ca99.zip |
Implement document.write (fixes #3704)
This is a bit crude because of some missing utility methods on BufferQueue.
Diffstat (limited to 'components/script/dom')
-rw-r--r-- | components/script/dom/document.rs | 77 | ||||
-rw-r--r-- | components/script/dom/htmlscriptelement.rs | 20 | ||||
-rw-r--r-- | components/script/dom/servoparser/mod.rs | 227 | ||||
-rw-r--r-- | components/script/dom/webidls/Document.webidl | 6 |
4 files changed, 225 insertions, 105 deletions
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 6a30721166a..6df47b9ecdc 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -281,6 +281,8 @@ pub struct Document { /// https://w3c.github.io/uievents/#event-type-dblclick #[ignore_heap_size_of = "Defined in std"] last_click_info: DOMRefCell<Option<(Instant, Point2D<f32>)>>, + /// https://html.spec.whatwg.org/multipage/#ignore-destructive-writes-counter + ignore_destructive_writes_counter: Cell<u32>, } #[derive(JSTraceable, HeapSizeOf)] @@ -372,15 +374,16 @@ impl Document { self.trigger_mozbrowser_event(MozBrowserEvent::SecurityChange(https_state)); } + // https://html.spec.whatwg.org/multipage/#active-document + pub fn is_active(&self) -> bool { + self.browsing_context().map_or(false, |context| { + self == &*context.active_document() + }) + } + // https://html.spec.whatwg.org/multipage/#fully-active pub fn is_fully_active(&self) -> bool { - let browsing_context = match self.browsing_context() { - Some(browsing_context) => browsing_context, - None => return false, - }; - let active_document = browsing_context.active_document(); - - if self != &*active_document { + if !self.is_active() { return false; } // FIXME: It should also check whether the browser context is top-level or not @@ -1877,6 +1880,7 @@ impl Document { referrer_policy: Cell::new(referrer_policy), target_element: MutNullableHeap::new(None), last_click_info: DOMRefCell::new(None), + ignore_destructive_writes_counter: Default::default(), } } @@ -2053,6 +2057,16 @@ impl Document { ReflowQueryType::NoQuery, ReflowReason::ElementStateChanged); } + + pub fn incr_ignore_destructive_writes_counter(&self) { + self.ignore_destructive_writes_counter.set( + self.ignore_destructive_writes_counter.get() + 1); + } + + pub fn decr_ignore_destructive_writes_counter(&self) { + self.ignore_destructive_writes_counter.set( + self.ignore_destructive_writes_counter.get() - 1); + } } @@ -3019,6 +3033,55 @@ impl DocumentMethods for Document { elements } + // https://html.spec.whatwg.org/multipage/#dom-document-write + fn Write(&self, text: Vec<DOMString>) -> ErrorResult { + if !self.is_html_document() { + // Step 1. + return Err(Error::InvalidState); + } + + // Step 2. + // TODO: handle throw-on-dynamic-markup-insertion counter. + + if !self.is_active() { + // Step 3. + return Ok(()); + } + + let parser = self.get_current_parser(); + let parser = match parser.as_ref() { + Some(parser) if parser.script_nesting_level() > 0 => parser, + _ => { + // Either there is no parser, which means the parsing ended; + // or script nesting level is 0, which means the method was + // called from outside a parser-executed script. + if self.ignore_destructive_writes_counter.get() > 0 { + // Step 4. + // TODO: handle ignore-opens-during-unload counter. + return Ok(()); + } + // Step 5. + // TODO: call document.open(). + return Err(Error::InvalidState); + } + }; + + // Step 7. + // TODO: handle reload override buffer. + + // Steps 6-8. + parser.write(text); + + // Step 9. + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-document-writeln + fn Writeln(&self, mut text: Vec<DOMString>) -> ErrorResult { + text.push("\n".into()); + self.Write(text) + } + // https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers document_and_element_event_handlers!(); } diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 18a9a89b433..255137a939d 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -469,19 +469,20 @@ impl HTMLScriptElement { Ok(script) => script, }; - if script.external { - debug!("loading external script, url = {}", script.url); - } - // TODO(#12446): beforescriptexecute. if self.dispatch_before_script_execute_event() == EventStatus::Canceled { return; } // Step 3. - // TODO: If the script is from an external file, then increment the - // ignore-destructive-writes counter of the script element's node - // document. Let neutralised doc be that Document. + let neutralized_doc = if script.external { + debug!("loading external script, url = {}", script.url); + let doc = document_from_node(self); + doc.incr_ignore_destructive_writes_counter(); + Some(doc) + } else { + None + }; // Step 4. let document = document_from_node(self); @@ -500,8 +501,9 @@ impl HTMLScriptElement { document.set_current_script(old_script.r()); // Step 7. - // TODO: Decrement the ignore-destructive-writes counter of neutralised - // doc, if it was incremented in the earlier step. + if let Some(doc) = neutralized_doc { + doc.decr_ignore_destructive_writes_counter(); + } // TODO(#12446): afterscriptexecute. self.dispatch_after_script_execute_event(); diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 895c8a73b08..ff90674c1d2 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -33,12 +33,25 @@ use profile_traits::time::{TimerMetadataReflowType, ProfilerCategory, profile}; use script_thread::ScriptThread; use servo_url::ServoUrl; use std::cell::Cell; +use std::mem; use util::resource_files::read_resource_file; mod html; mod xml; #[dom_struct] +/// The parser maintains two input streams: one for input from script through +/// document.write(), and one for input from network. +/// +/// There is no concrete representation of the insertion point, instead it +/// always points to just before the next character from the network input, +/// with all of the script input before itself. +/// +/// ```text +/// ... script input ... | ... network input ... +/// ^ +/// insertion point +/// ``` pub struct ServoParser { reflector: Reflector, /// The document associated with this parser. @@ -46,9 +59,12 @@ pub struct ServoParser { /// The pipeline associated with this parse, unavailable if this parse /// does not correspond to a page load. pipeline: Option<PipelineId>, - /// Input chunks received but not yet passed to the parser. + /// Input received from network. #[ignore_heap_size_of = "Defined in html5ever"] - pending_input: DOMRefCell<BufferQueue>, + network_input: DOMRefCell<BufferQueue>, + /// Input received from script. Used only to support document.write(). + #[ignore_heap_size_of = "Defined in html5ever"] + script_input: DOMRefCell<BufferQueue>, /// The tokenizer of this parser. tokenizer: DOMRefCell<Tokenizer>, /// Whether to expect any further input from the associated network request. @@ -140,6 +156,80 @@ impl ServoParser { self.script_nesting_level.get() } + /// Corresponds to the latter part of the "Otherwise" branch of the 'An end + /// tag whose tag name is "script"' of + /// https://html.spec.whatwg.org/multipage/#parsing-main-incdata + /// + /// This first moves everything from the script input to the beginning of + /// the network input, effectively resetting the insertion point to just + /// before the next character to be consumed. + /// + /// + /// ```text + /// | ... script input ... network input ... + /// ^ + /// insertion point + /// ``` + pub fn resume_with_pending_parsing_blocking_script(&self, script: &HTMLScriptElement) { + assert!(self.suspended.get()); + self.suspended.set(false); + + mem::swap(&mut *self.script_input.borrow_mut(), &mut *self.network_input.borrow_mut()); + while let Some(chunk) = self.script_input.borrow_mut().pop_front() { + self.network_input.borrow_mut().push_back(chunk); + } + + let script_nesting_level = self.script_nesting_level.get(); + assert_eq!(script_nesting_level, 0); + + self.script_nesting_level.set(script_nesting_level + 1); + script.execute(); + self.script_nesting_level.set(script_nesting_level); + + if !self.suspended.get() { + self.parse_sync(); + } + } + + /// Steps 6-8 of https://html.spec.whatwg.org/multipage/#document.write() + pub fn write(&self, text: Vec<DOMString>) { + assert!(self.script_nesting_level.get() > 0); + + if self.document.get_pending_parsing_blocking_script().is_some() { + // There is already a pending parsing blocking script so the + // parser is suspended, we just append everything to the + // script input and abort these steps. + for chunk in text { + self.script_input.borrow_mut().push_back(String::from(chunk).into()); + } + return; + } + + // There is no pending parsing blocking script, so all previous calls + // to document.write() should have seen their entire input tokenized + // and process, with nothing pushed to the parser script input. + assert!(self.script_input.borrow().is_empty()); + + let mut input = BufferQueue::new(); + for chunk in text { + input.push_back(String::from(chunk).into()); + } + + self.tokenize(|tokenizer| tokenizer.feed(&mut input)); + + if self.suspended.get() { + // Parser got suspended, insert remaining input at end of + // script input, following anything written by scripts executed + // reentrantly during this call. + while let Some(chunk) = input.pop_front() { + self.script_input.borrow_mut().push_back(chunk); + } + return; + } + + assert!(input.is_empty()); + } + #[allow(unrooted_must_root)] fn new_inherited( document: &Document, @@ -151,7 +241,8 @@ impl ServoParser { reflector: Reflector::new(), document: JS::from_ref(document), pipeline: pipeline, - pending_input: DOMRefCell::new(BufferQueue::new()), + network_input: DOMRefCell::new(BufferQueue::new()), + script_input: DOMRefCell::new(BufferQueue::new()), tokenizer: DOMRefCell::new(tokenizer), last_chunk_received: Cell::new(last_chunk_state == LastChunkState::Received), suspended: Default::default(), @@ -172,85 +263,59 @@ impl ServoParser { ServoParserBinding::Wrap) } - pub fn document(&self) -> &Document { - &self.document - } - - pub fn pipeline(&self) -> Option<PipelineId> { - self.pipeline - } - - fn has_pending_input(&self) -> bool { - !self.pending_input.borrow().is_empty() - } - fn push_input_chunk(&self, chunk: String) { - self.pending_input.borrow_mut().push_back(chunk.into()); - } - - fn last_chunk_received(&self) -> bool { - self.last_chunk_received.get() - } - - fn mark_last_chunk_received(&self) { - self.last_chunk_received.set(true) - } - - fn set_plaintext_state(&self) { - self.tokenizer.borrow_mut().set_plaintext_state() - } - - pub fn suspend(&self) { - assert!(!self.suspended.get()); - self.suspended.set(true); - } - - pub fn resume(&self) { - assert!(self.suspended.get()); - self.suspended.set(false); - self.parse_sync(); - } - - pub fn is_suspended(&self) -> bool { - self.suspended.get() - } - - pub fn resume_with_pending_parsing_blocking_script(&self, script: &HTMLScriptElement) { - assert!(self.suspended.get()); - self.suspended.set(false); - - let script_nesting_level = self.script_nesting_level.get(); - assert_eq!(script_nesting_level, 0); - - self.script_nesting_level.set(script_nesting_level + 1); - script.execute(); - self.script_nesting_level.set(script_nesting_level); - - if !self.suspended.get() { - self.parse_sync(); - } + self.network_input.borrow_mut().push_back(chunk.into()); } fn parse_sync(&self) { let metadata = TimerMetadata { - url: self.document().url().as_str().into(), + url: self.document.url().as_str().into(), iframe: TimerMetadataFrameType::RootWindow, incremental: TimerMetadataReflowType::FirstReflow, }; let profiler_category = self.tokenizer.borrow().profiler_category(); profile(profiler_category, Some(metadata), - self.document().window().upcast::<GlobalScope>().time_profiler_chan().clone(), + self.document.window().upcast::<GlobalScope>().time_profiler_chan().clone(), || self.do_parse_sync()) } fn do_parse_sync(&self) { + assert!(self.script_input.borrow().is_empty()); + // This parser will continue to parse while there is either pending input or // the parser remains unsuspended. + + self.tokenize(|tokenizer| tokenizer.feed(&mut *self.network_input.borrow_mut())); + + if self.suspended.get() { + return; + } + + assert!(self.network_input.borrow().is_empty()); + + if self.last_chunk_received.get() { + self.finish(); + } + } + + fn parse_chunk(&self, input: String) { + self.document.set_current_parser(Some(self)); + self.push_input_chunk(input); + if !self.suspended.get() { + self.parse_sync(); + } + } + + fn tokenize<F>(&self, mut feed: F) + where F: FnMut(&mut Tokenizer) -> Result<(), Root<HTMLScriptElement>> + { loop { - self.document().reflow_if_reflow_timer_expired(); - let script = match self.tokenizer.borrow_mut().feed(&mut *self.pending_input.borrow_mut()) { - Ok(()) => break, + assert!(!self.suspended.get()); + + self.document.reflow_if_reflow_timer_expired(); + let script = match feed(&mut *self.tokenizer.borrow_mut()) { + Ok(()) => return, Err(script) => script, }; @@ -261,36 +326,24 @@ impl ServoParser { self.script_nesting_level.set(script_nesting_level); if self.document.get_pending_parsing_blocking_script().is_some() { - self.suspend(); + self.suspended.set(true); return; } - - assert!(!self.suspended.get()); - } - - if self.last_chunk_received() { - self.finish(); - } - } - - fn parse_chunk(&self, input: String) { - self.document().set_current_parser(Some(self)); - self.push_input_chunk(input); - if !self.is_suspended() { - self.parse_sync(); } } fn finish(&self) { assert!(!self.suspended.get()); - assert!(!self.has_pending_input()); + assert!(self.last_chunk_received.get()); + assert!(self.script_input.borrow().is_empty()); + assert!(self.network_input.borrow().is_empty()); self.tokenizer.borrow_mut().end(); debug!("finished parsing"); - self.document().set_current_parser(None); + self.document.set_current_parser(None); - if let Some(pipeline) = self.pipeline() { + if let Some(pipeline) = self.pipeline { ScriptThread::parsing_complete(pipeline); } } @@ -398,7 +451,7 @@ impl FetchResponseListener for ParserContext { parser.push_input_chunk(page); parser.parse_sync(); - let doc = parser.document(); + let doc = &parser.document; let doc_body = Root::upcast::<Node>(doc.GetBody().unwrap()); let img = HTMLImageElement::new(local_name!("img"), None, doc); img.SetSrc(DOMString::from(self.url.to_string())); @@ -410,7 +463,7 @@ impl FetchResponseListener for ParserContext { let page = "<pre>\n".into(); parser.push_input_chunk(page); parser.parse_sync(); - parser.set_plaintext_state(); + parser.tokenizer.borrow_mut().set_plaintext_state(); }, Some(ContentType(Mime(TopLevel::Text, SubLevel::Html, _))) => { // Handle text/html if let Some(reason) = ssl_error { @@ -475,11 +528,11 @@ impl FetchResponseListener for ParserContext { debug!("Failed to load page URL {}, error: {:?}", self.url, err); } - parser.document() + parser.document .finish_load(LoadType::PageSource(self.url.clone())); - parser.mark_last_chunk_received(); - if !parser.is_suspended() { + parser.last_chunk_received.set(true); + if !parser.suspended.get() { parser.parse_sync(); } } diff --git a/components/script/dom/webidls/Document.webidl b/components/script/dom/webidls/Document.webidl index 192c3d03714..1e8725d8b86 100644 --- a/components/script/dom/webidls/Document.webidl +++ b/components/script/dom/webidls/Document.webidl @@ -114,8 +114,10 @@ partial /*sealed*/ interface Document { // Document open(optional DOMString type = "text/html", optional DOMString replace = ""); // WindowProxy open(DOMString url, DOMString name, DOMString features, optional boolean replace = false); // void close(); - // void write(DOMString... text); - // void writeln(DOMString... text); + [Throws] + void write(DOMString... text); + [Throws] + void writeln(DOMString... text); // user interaction readonly attribute Window?/*Proxy?*/ defaultView; |