Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement document.write (fixes #3704) #14361

Merged
merged 3 commits into from Nov 29, 2016
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -142,12 +142,6 @@ pub enum IsHTMLDocument {
NonHTMLDocument,
}

#[derive(PartialEq)]
enum ParserBlockedByScript {
Blocked,
Unblocked,
}

#[derive(JSTraceable, HeapSizeOf)]
#[must_root]
pub struct StylesheetInDocument {
@@ -287,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)]
@@ -378,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
@@ -1546,15 +1543,13 @@ impl Document {
self.process_asap_scripts();
}

if self.maybe_execute_parser_blocking_script() == ParserBlockedByScript::Blocked {
return;
}

// A finished resource load can potentially unblock parsing. In that case, resume the
// parser so its loop can find out.
if let Some(parser) = self.get_current_parser() {
if parser.is_suspended() {
parser.resume();
if let Some(script) = self.pending_parsing_blocking_script.get() {
if self.script_blocking_stylesheets_count.get() > 0 || !script.is_ready_to_be_executed() {
return;
}
self.pending_parsing_blocking_script.set(None);
parser.resume_with_pending_parsing_blocking_script(&script);
}
} else if self.reflow_timeout.get().is_none() {
// If we don't have a parser, and the reflow timer has been reset, explicitly
@@ -1577,23 +1572,6 @@ impl Document {
}
}

/// If document parsing is blocked on a script, and that script is ready to run,
/// execute it.
/// https://html.spec.whatwg.org/multipage/#ready-to-be-parser-executed
fn maybe_execute_parser_blocking_script(&self) -> ParserBlockedByScript {
let script = match self.pending_parsing_blocking_script.get() {
None => return ParserBlockedByScript::Unblocked,
Some(script) => script,
};

if self.script_blocking_stylesheets_count.get() == 0 && script.is_ready_to_be_executed() {
self.pending_parsing_blocking_script.set(None);
script.execute();
return ParserBlockedByScript::Unblocked;
}
ParserBlockedByScript::Blocked
}

/// https://html.spec.whatwg.org/multipage/#the-end step 3
pub fn process_deferred_scripts(&self) {
if self.ready_state.get() != DocumentReadyState::Interactive {
@@ -1902,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(),
}
}

@@ -2078,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);
}
}


@@ -3044,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,

This comment has been minimized.

Copy link
@jdm

jdm Nov 28, 2016

Member

So the spec's idea of an insertion point corresponds to an active parser with a script nesting level > 0 in Servo? Is there some way we can document that better?

_ => {
// 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!();
}
@@ -274,12 +274,10 @@ fn fetch_a_classic_script(script: &HTMLScriptElement,

impl HTMLScriptElement {
/// https://html.spec.whatwg.org/multipage/#prepare-a-script
///
/// Returns true if tokenization should continue, false otherwise.
pub fn prepare(&self) -> bool {
pub fn prepare(&self) {
// Step 1.
if self.already_started.get() {
return true;
return;
}

// Step 2.
@@ -297,17 +295,17 @@ impl HTMLScriptElement {
// Step 4.
let text = self.Text();
if text.is_empty() && !element.has_attribute(&local_name!("src")) {
return true;
return;
}

// Step 5.
if !self.upcast::<Node>().is_in_doc() {
return true;
return;
}

// Step 6.
if !self.is_javascript() {
return true;
return;
}

// Step 7.
@@ -322,12 +320,12 @@ impl HTMLScriptElement {
// Step 9.
let doc = document_from_node(self);
if self.parser_inserted.get() && &*self.parser_document != &*doc {
return true;
return;
}

// Step 10.
if !doc.is_scripting_enabled() {
return true;
return;
}

// TODO(#4577): Step 11: CSP.
@@ -340,13 +338,13 @@ impl HTMLScriptElement {
let for_value = for_attribute.value().to_ascii_lowercase();
let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
if for_value != "window" {
return true;
return;
}

let event_value = event_attribute.value().to_ascii_lowercase();
let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
if event_value != "onload" && event_value != "onload()" {
return true;
return;
}
},
(_, _) => (),
@@ -381,15 +379,15 @@ impl HTMLScriptElement {
// Step 18.2.
if src.is_empty() {
self.queue_error_event();
return true;
return;
}

// Step 18.4-18.5.
let url = match base_url.join(&src) {
Err(_) => {
warn!("error parsing URL for script {}", &**src);
self.queue_error_event();
return true;
return;
}
Ok(url) => url,
};
@@ -412,7 +410,6 @@ impl HTMLScriptElement {
!async {
doc.add_deferred_script(self);
// Second part implemented in Document::process_deferred_scripts.
return true;
// Step 20.b: classic, has src, was parser-inserted, is not async.
} else if is_external &&
was_parser_inserted &&
@@ -432,7 +429,7 @@ impl HTMLScriptElement {
// Step 20.e: doesn't have src, was parser-inserted, is blocked on stylesheet.
} else if !is_external &&
was_parser_inserted &&
// TODO: check for script nesting levels.
doc.get_current_parser().map_or(false, |parser| parser.script_nesting_level() <= 1) &&
doc.get_script_blocking_stylesheets_count() > 0 {
doc.set_pending_parsing_blocking_script(Some(self));
*self.load.borrow_mut() = Some(Ok(ScriptOrigin::internal(text, base_url)));
@@ -443,16 +440,7 @@ impl HTMLScriptElement {
self.ready_to_be_parser_executed.set(true);
*self.load.borrow_mut() = Some(Ok(ScriptOrigin::internal(text, base_url)));
self.execute();
return true;
}

// TODO: make this suspension happen automatically.
if was_parser_inserted {
if let Some(parser) = doc.get_current_parser() {
parser.suspend();
}
}
false
}

pub fn is_ready_to_be_executed(&self) -> bool {
@@ -481,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);
@@ -512,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();
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.