aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom
diff options
context:
space:
mode:
authorAnthony Ramine <n.oxyde@gmail.com>2016-11-25 16:19:19 +0100
committerAnthony Ramine <n.oxyde@gmail.com>2016-11-28 23:05:56 +0100
commit4d93ee134c84d72f73d7a5556bd2dcaf36e5ca99 (patch)
tree7f57f6c9a027c9742db9ebce25e30fe654e5a94d /components/script/dom
parent708ebdceee50547d03713d32c5207c1fa870f902 (diff)
downloadservo-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.rs77
-rw-r--r--components/script/dom/htmlscriptelement.rs20
-rw-r--r--components/script/dom/servoparser/mod.rs227
-rw-r--r--components/script/dom/webidls/Document.webidl6
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;