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 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Next

Rewrite how parser handles script scheduling

  • Loading branch information
nox committed Nov 26, 2016
commit c801327eab30bc5047068b29fce64aec72ab99eb
@@ -142,12 +142,6 @@ pub enum IsHTMLDocument {
NonHTMLDocument,
}

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

#[derive(JSTraceable, HeapSizeOf)]
#[must_root]
pub struct StylesheetInDocument {
@@ -1546,15 +1540,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 +1569,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 {
@@ -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 {
@@ -55,6 +55,8 @@ pub struct ServoParser {
last_chunk_received: Cell<bool>,
/// Whether this parser should avoid passing any further data to the tokenizer.
suspended: Cell<bool>,
/// https://html.spec.whatwg.org/multipage/#script-nesting-level
script_nesting_level: Cell<usize>,
}

#[derive(PartialEq)]
@@ -134,6 +136,10 @@ impl ServoParser {
parser.parse_chunk(String::from(input));
}

pub fn script_nesting_level(&self) -> usize {
self.script_nesting_level.get()
}

#[allow(unrooted_must_root)]
fn new_inherited(
document: &Document,
@@ -149,6 +155,7 @@ impl ServoParser {
tokenizer: DOMRefCell::new(tokenizer),
last_chunk_received: Cell::new(last_chunk_state == LastChunkState::Received),
suspended: Default::default(),
script_nesting_level: Default::default(),
}
}

@@ -208,6 +215,22 @@ impl ServoParser {
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();
}
}

fn parse_sync(&self) {
let metadata = TimerMetadata {
url: self.document().url().as_str().into(),
@@ -226,20 +249,23 @@ impl ServoParser {
// the parser remains unsuspended.
loop {
self.document().reflow_if_reflow_timer_expired();
if let Err(script) = self.tokenizer.borrow_mut().feed(&mut *self.pending_input.borrow_mut()) {
if script.prepare() {
continue;
}
}
let script = match self.tokenizer.borrow_mut().feed(&mut *self.pending_input.borrow_mut()) {
Ok(()) => break,
Err(script) => script,
};

let script_nesting_level = self.script_nesting_level.get();

// Document parsing is blocked on an external resource.
if self.suspended.get() {
self.script_nesting_level.set(script_nesting_level + 1);
script.prepare();
self.script_nesting_level.set(script_nesting_level);

if self.document.get_pending_parsing_blocking_script().is_some() {
self.suspend();

This comment has been minimized.

return;
}

if !self.has_pending_input() {
break;
}
assert!(!self.suspended.get());
}

if self.last_chunk_received() {

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.