mirror of
https://github.com/servo/servo.git
synced 2025-06-12 18:34:39 +00:00
Make external script sources load asynchronously, yet still block further parsing. Hook up document loading to async networking events.
This commit is contained in:
parent
e52197d126
commit
8082df7d0d
32 changed files with 441 additions and 276 deletions
|
@ -132,8 +132,6 @@ pub struct PendingAsyncLoad {
|
||||||
resource_task: ResourceTask,
|
resource_task: ResourceTask,
|
||||||
url: Url,
|
url: Url,
|
||||||
pipeline: Option<PipelineId>,
|
pipeline: Option<PipelineId>,
|
||||||
input_sender: Sender<LoadResponse>,
|
|
||||||
input_receiver: Receiver<LoadResponse>,
|
|
||||||
guard: PendingLoadGuard,
|
guard: PendingLoadGuard,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,13 +154,10 @@ impl Drop for PendingLoadGuard {
|
||||||
impl PendingAsyncLoad {
|
impl PendingAsyncLoad {
|
||||||
pub fn new(resource_task: ResourceTask, url: Url, pipeline: Option<PipelineId>)
|
pub fn new(resource_task: ResourceTask, url: Url, pipeline: Option<PipelineId>)
|
||||||
-> PendingAsyncLoad {
|
-> PendingAsyncLoad {
|
||||||
let (sender, receiver) = channel();
|
|
||||||
PendingAsyncLoad {
|
PendingAsyncLoad {
|
||||||
resource_task: resource_task,
|
resource_task: resource_task,
|
||||||
url: url,
|
url: url,
|
||||||
pipeline: pipeline,
|
pipeline: pipeline,
|
||||||
input_sender: sender,
|
|
||||||
input_receiver: receiver,
|
|
||||||
guard: PendingLoadGuard { loaded: false, },
|
guard: PendingLoadGuard { loaded: false, },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,9 +166,18 @@ impl PendingAsyncLoad {
|
||||||
pub fn load(mut self) -> Receiver<LoadResponse> {
|
pub fn load(mut self) -> Receiver<LoadResponse> {
|
||||||
self.guard.neuter();
|
self.guard.neuter();
|
||||||
let load_data = LoadData::new(self.url, self.pipeline);
|
let load_data = LoadData::new(self.url, self.pipeline);
|
||||||
let consumer = LoadConsumer::Channel(self.input_sender);
|
let (sender, receiver) = channel();
|
||||||
|
let consumer = LoadConsumer::Channel(sender);
|
||||||
|
self.resource_task.send(ControlMsg::Load(load_data, consumer)).unwrap();
|
||||||
|
receiver
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initiate the network request associated with this pending load, using the provided target.
|
||||||
|
pub fn load_async(mut self, listener: Box<AsyncResponseTarget + Send>) {
|
||||||
|
self.guard.neuter();
|
||||||
|
let load_data = LoadData::new(self.url, self.pipeline);
|
||||||
|
let consumer = LoadConsumer::Listener(listener);
|
||||||
self.resource_task.send(ControlMsg::Load(load_data, consumer)).unwrap();
|
self.resource_task.send(ControlMsg::Load(load_data, consumer)).unwrap();
|
||||||
self.input_receiver
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,12 @@
|
||||||
|
|
||||||
use script_task::{ScriptMsg, ScriptChan};
|
use script_task::{ScriptMsg, ScriptChan};
|
||||||
use msg::constellation_msg::{PipelineId};
|
use msg::constellation_msg::{PipelineId};
|
||||||
use net_traits::{LoadResponse, Metadata, load_whole_resource, ResourceTask, PendingAsyncLoad};
|
use net_traits::{Metadata, load_whole_resource, ResourceTask, PendingAsyncLoad};
|
||||||
|
use net_traits::AsyncResponseTarget;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use std::sync::mpsc::Receiver;
|
|
||||||
|
|
||||||
#[jstraceable]
|
#[jstraceable]
|
||||||
#[derive(PartialEq, Clone)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub enum LoadType {
|
pub enum LoadType {
|
||||||
Image(Url),
|
Image(Url),
|
||||||
Script(Url),
|
Script(Url),
|
||||||
|
@ -75,9 +74,9 @@ impl DocumentLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create and initiate a new network request.
|
/// Create and initiate a new network request.
|
||||||
pub fn load_async(&mut self, load: LoadType) -> Receiver<LoadResponse> {
|
pub fn load_async(&mut self, load: LoadType, listener: Box<AsyncResponseTarget + Send>) {
|
||||||
let pending = self.prepare_async_load(load);
|
let pending = self.prepare_async_load(load);
|
||||||
pending.load()
|
pending.load_async(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create, initiate, and await the response for a new network request.
|
/// Create, initiate, and await the response for a new network request.
|
||||||
|
@ -91,7 +90,7 @@ impl DocumentLoader {
|
||||||
/// Mark an in-progress network request complete.
|
/// Mark an in-progress network request complete.
|
||||||
pub fn finish_load(&mut self, load: LoadType) {
|
pub fn finish_load(&mut self, load: LoadType) {
|
||||||
let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == load);
|
let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == load);
|
||||||
self.blocking_loads.remove(idx.expect("unknown completed load"));
|
self.blocking_loads.remove(idx.expect(&format!("unknown completed load {:?}", load)));
|
||||||
|
|
||||||
if let Some(NotifierData { ref script_chan, pipeline }) = self.notifier_data {
|
if let Some(NotifierData { ref script_chan, pipeline }) = self.notifier_data {
|
||||||
if !self.is_blocked() {
|
if !self.is_blocked() {
|
||||||
|
|
|
@ -61,6 +61,7 @@ use dom::nodelist::NodeList;
|
||||||
use dom::text::Text;
|
use dom::text::Text;
|
||||||
use dom::processinginstruction::ProcessingInstruction;
|
use dom::processinginstruction::ProcessingInstruction;
|
||||||
use dom::range::Range;
|
use dom::range::Range;
|
||||||
|
use dom::servohtmlparser::ServoHTMLParser;
|
||||||
use dom::treewalker::TreeWalker;
|
use dom::treewalker::TreeWalker;
|
||||||
use dom::uievent::UIEvent;
|
use dom::uievent::UIEvent;
|
||||||
use dom::window::{Window, WindowHelpers, ReflowReason};
|
use dom::window::{Window, WindowHelpers, ReflowReason};
|
||||||
|
@ -73,7 +74,7 @@ use msg::constellation_msg::{ConstellationChan, FocusType, Key, KeyState, KeyMod
|
||||||
use msg::constellation_msg::{SUPER, ALT, SHIFT, CONTROL};
|
use msg::constellation_msg::{SUPER, ALT, SHIFT, CONTROL};
|
||||||
use net_traits::CookieSource::NonHTTP;
|
use net_traits::CookieSource::NonHTTP;
|
||||||
use net_traits::ControlMsg::{SetCookiesForUrl, GetCookiesForUrl};
|
use net_traits::ControlMsg::{SetCookiesForUrl, GetCookiesForUrl};
|
||||||
use net_traits::{Metadata, LoadResponse, PendingAsyncLoad};
|
use net_traits::{Metadata, PendingAsyncLoad, AsyncResponseTarget};
|
||||||
use script_task::Runnable;
|
use script_task::Runnable;
|
||||||
use script_traits::{MouseButton, UntrustedNodeAddress};
|
use script_traits::{MouseButton, UntrustedNodeAddress};
|
||||||
use util::opts;
|
use util::opts;
|
||||||
|
@ -96,7 +97,7 @@ use std::ascii::AsciiExt;
|
||||||
use std::cell::{Cell, Ref, RefMut, RefCell};
|
use std::cell::{Cell, Ref, RefMut, RefCell};
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::mpsc::{Receiver, channel};
|
use std::sync::mpsc::channel;
|
||||||
use time;
|
use time;
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
|
@ -145,6 +146,8 @@ pub struct Document {
|
||||||
animation_frame_list: RefCell<HashMap<i32, Box<Fn(f64)>>>,
|
animation_frame_list: RefCell<HashMap<i32, Box<Fn(f64)>>>,
|
||||||
/// Tracks all outstanding loads related to this document.
|
/// Tracks all outstanding loads related to this document.
|
||||||
loader: DOMRefCell<DocumentLoader>,
|
loader: DOMRefCell<DocumentLoader>,
|
||||||
|
/// The current active HTML parser, to allow resuming after interruptions.
|
||||||
|
current_parser: MutNullableHeap<JS<ServoHTMLParser>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentDerived for EventTarget {
|
impl DocumentDerived for EventTarget {
|
||||||
|
@ -263,9 +266,11 @@ pub trait DocumentHelpers<'a> {
|
||||||
/// http://w3c.github.io/animation-timing/#dfn-invoke-callbacks-algorithm
|
/// http://w3c.github.io/animation-timing/#dfn-invoke-callbacks-algorithm
|
||||||
fn invoke_animation_callbacks(self);
|
fn invoke_animation_callbacks(self);
|
||||||
fn prepare_async_load(self, load: LoadType) -> PendingAsyncLoad;
|
fn prepare_async_load(self, load: LoadType) -> PendingAsyncLoad;
|
||||||
fn load_async(self, load: LoadType) -> Receiver<LoadResponse>;
|
fn load_async(self, load: LoadType, listener: Box<AsyncResponseTarget + Send>);
|
||||||
fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec<u8>), String>;
|
fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec<u8>), String>;
|
||||||
fn finish_load(self, load: LoadType);
|
fn finish_load(self, load: LoadType);
|
||||||
|
fn set_current_parser(self, script: Option<JSRef<ServoHTMLParser>>);
|
||||||
|
fn get_current_parser(self) -> Option<Temporary<ServoHTMLParser>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
|
impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
|
||||||
|
@ -892,9 +897,9 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
|
||||||
loader.prepare_async_load(load)
|
loader.prepare_async_load(load)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_async(self, load: LoadType) -> Receiver<LoadResponse> {
|
fn load_async(self, load: LoadType, listener: Box<AsyncResponseTarget + Send>) {
|
||||||
let mut loader = self.loader.borrow_mut();
|
let mut loader = self.loader.borrow_mut();
|
||||||
loader.load_async(load)
|
loader.load_async(load, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec<u8>), String> {
|
fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec<u8>), String> {
|
||||||
|
@ -906,6 +911,14 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
|
||||||
let mut loader = self.loader.borrow_mut();
|
let mut loader = self.loader.borrow_mut();
|
||||||
loader.finish_load(load);
|
loader.finish_load(load);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_current_parser(self, script: Option<JSRef<ServoHTMLParser>>) {
|
||||||
|
self.current_parser.set(script.map(JS::from_rooted));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_parser(self) -> Option<Temporary<ServoHTMLParser>> {
|
||||||
|
self.current_parser.get().map(Temporary::from_rooted)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MouseEventType {
|
pub enum MouseEventType {
|
||||||
|
@ -914,6 +927,7 @@ pub enum MouseEventType {
|
||||||
MouseUp,
|
MouseUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum DocumentSource {
|
pub enum DocumentSource {
|
||||||
FromParser,
|
FromParser,
|
||||||
|
@ -987,6 +1001,7 @@ impl Document {
|
||||||
animation_frame_ident: Cell::new(0),
|
animation_frame_ident: Cell::new(0),
|
||||||
animation_frame_list: RefCell::new(HashMap::new()),
|
animation_frame_list: RefCell::new(HashMap::new()),
|
||||||
loader: DOMRefCell::new(doc_loader),
|
loader: DOMRefCell::new(doc_loader),
|
||||||
|
current_parser: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ use dom::bindings::utils::{Reflector, reflect_dom_object};
|
||||||
use dom::document::{Document, DocumentHelpers, IsHTMLDocument};
|
use dom::document::{Document, DocumentHelpers, IsHTMLDocument};
|
||||||
use dom::document::DocumentSource;
|
use dom::document::DocumentSource;
|
||||||
use dom::window::{Window, WindowHelpers};
|
use dom::window::{Window, WindowHelpers};
|
||||||
use parse::html::{HTMLInput, parse_html};
|
use parse::html::{ParseContext, parse_html};
|
||||||
use util::str::DOMString;
|
use util::str::DOMString;
|
||||||
|
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
|
@ -64,7 +64,7 @@ impl<'a> DOMParserMethods for JSRef<'a, DOMParser> {
|
||||||
None,
|
None,
|
||||||
DocumentSource::FromParser,
|
DocumentSource::FromParser,
|
||||||
loader).root();
|
loader).root();
|
||||||
parse_html(document.r(), HTMLInput::InputString(s), &url, None);
|
parse_html(document.r(), s, &url, ParseContext::Owner(None));
|
||||||
document.r().set_ready_state(DocumentReadyState::Complete);
|
document.r().set_ready_state(DocumentReadyState::Complete);
|
||||||
Ok(Temporary::from_rooted(document.r()))
|
Ok(Temporary::from_rooted(document.r()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,17 +28,21 @@ use dom::event::{Event, EventBubbles, EventCancelable, EventHelpers};
|
||||||
use dom::element::ElementTypeId;
|
use dom::element::ElementTypeId;
|
||||||
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
|
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
|
||||||
use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node, CloneChildrenFlag};
|
use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node, CloneChildrenFlag};
|
||||||
|
use dom::servohtmlparser::ServoHTMLParserHelpers;
|
||||||
use dom::virtualmethods::VirtualMethods;
|
use dom::virtualmethods::VirtualMethods;
|
||||||
use dom::window::{WindowHelpers, ScriptHelpers};
|
use dom::window::{WindowHelpers, ScriptHelpers};
|
||||||
use script_task::{ScriptMsg, Runnable};
|
use network_listener::{NetworkListener, PreInvoke};
|
||||||
|
use script_task::{ScriptChan, ScriptMsg, Runnable};
|
||||||
|
|
||||||
use encoding::all::UTF_8;
|
use encoding::all::UTF_8;
|
||||||
use encoding::label::encoding_from_whatwg_label;
|
use encoding::label::encoding_from_whatwg_label;
|
||||||
use encoding::types::{Encoding, EncodingRef, DecoderTrap};
|
use encoding::types::{Encoding, EncodingRef, DecoderTrap};
|
||||||
use net_traits::Metadata;
|
use net_traits::{Metadata, AsyncResponseListener};
|
||||||
use util::str::{DOMString, HTML_SPACE_CHARACTERS, StaticStringVec};
|
use util::str::{DOMString, HTML_SPACE_CHARACTERS, StaticStringVec};
|
||||||
use std::borrow::ToOwned;
|
use html5ever::tree_builder::NextParserState;
|
||||||
use std::cell::Cell;
|
use std::cell::{RefCell, Cell};
|
||||||
|
use std::mem;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use string_cache::Atom;
|
use string_cache::Atom;
|
||||||
use url::{Url, UrlParser};
|
use url::{Url, UrlParser};
|
||||||
|
|
||||||
|
@ -99,7 +103,7 @@ impl HTMLScriptElement {
|
||||||
|
|
||||||
pub trait HTMLScriptElementHelpers {
|
pub trait HTMLScriptElementHelpers {
|
||||||
/// Prepare a script (<https://www.whatwg.org/html/#prepare-a-script>)
|
/// Prepare a script (<https://www.whatwg.org/html/#prepare-a-script>)
|
||||||
fn prepare(self);
|
fn prepare(self) -> NextParserState;
|
||||||
|
|
||||||
/// [Execute a script block]
|
/// [Execute a script block]
|
||||||
/// (https://html.spec.whatwg.org/multipage/#execute-the-script-block)
|
/// (https://html.spec.whatwg.org/multipage/#execute-the-script-block)
|
||||||
|
@ -153,12 +157,52 @@ pub enum ScriptOrigin {
|
||||||
External(Result<(Metadata, Vec<u8>), String>),
|
External(Result<(Metadata, Vec<u8>), String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The context required for asynchronously loading an external script source.
|
||||||
|
struct ScriptContext {
|
||||||
|
/// The element that initiated the request.
|
||||||
|
elem: Trusted<HTMLScriptElement>,
|
||||||
|
/// The response body received to date.
|
||||||
|
data: RefCell<Vec<u8>>,
|
||||||
|
/// The response metadata received to date.
|
||||||
|
metadata: RefCell<Option<Metadata>>,
|
||||||
|
/// Whether the owning document's parser should resume once the response completes.
|
||||||
|
resume_on_completion: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncResponseListener for ScriptContext {
|
||||||
|
fn headers_available(&self, metadata: Metadata) {
|
||||||
|
*self.metadata.borrow_mut() = Some(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_available(&self, payload: Vec<u8>) {
|
||||||
|
self.data.borrow_mut().extend(payload.into_iter());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_complete(&self, status: Result<(), String>) {
|
||||||
|
let load = status.map(|_| {
|
||||||
|
let data = mem::replace(&mut *self.data.borrow_mut(), vec!());
|
||||||
|
let metadata = self.metadata.borrow_mut().take().unwrap();
|
||||||
|
(metadata, data)
|
||||||
|
});
|
||||||
|
let elem = self.elem.to_temporary().root();
|
||||||
|
|
||||||
|
elem.r().execute(ScriptOrigin::External(load));
|
||||||
|
|
||||||
|
if self.resume_on_completion {
|
||||||
|
let document = document_from_node(elem.r()).root();
|
||||||
|
document.r().get_current_parser().unwrap().root().r().resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreInvoke for ScriptContext {}
|
||||||
|
|
||||||
impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
|
impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
|
||||||
fn prepare(self) {
|
fn prepare(self) -> NextParserState {
|
||||||
// https://html.spec.whatwg.org/multipage/#prepare-a-script
|
// https://html.spec.whatwg.org/multipage/#prepare-a-script
|
||||||
// Step 1.
|
// Step 1.
|
||||||
if self.already_started.get() {
|
if self.already_started.get() {
|
||||||
return;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
// Step 2.
|
// Step 2.
|
||||||
let was_parser_inserted = self.parser_inserted.get();
|
let was_parser_inserted = self.parser_inserted.get();
|
||||||
|
@ -172,16 +216,16 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
|
||||||
// Step 4.
|
// Step 4.
|
||||||
let text = self.Text();
|
let text = self.Text();
|
||||||
if text.len() == 0 && !element.has_attribute(&atom!("src")) {
|
if text.len() == 0 && !element.has_attribute(&atom!("src")) {
|
||||||
return;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
// Step 5.
|
// Step 5.
|
||||||
let node: JSRef<Node> = NodeCast::from_ref(self);
|
let node: JSRef<Node> = NodeCast::from_ref(self);
|
||||||
if !node.is_in_doc() {
|
if !node.is_in_doc() {
|
||||||
return;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
// Step 6, 7.
|
// Step 6, 7.
|
||||||
if !self.is_javascript() {
|
if !self.is_javascript() {
|
||||||
return;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
// Step 8.
|
// Step 8.
|
||||||
if was_parser_inserted {
|
if was_parser_inserted {
|
||||||
|
@ -195,12 +239,12 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
|
||||||
let document_from_node_ref = document_from_node(self).root();
|
let document_from_node_ref = document_from_node(self).root();
|
||||||
let document_from_node_ref = document_from_node_ref.r();
|
let document_from_node_ref = document_from_node_ref.r();
|
||||||
if self.parser_inserted.get() && self.parser_document.root().r() != document_from_node_ref {
|
if self.parser_inserted.get() && self.parser_document.root().r() != document_from_node_ref {
|
||||||
return;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 11.
|
// Step 11.
|
||||||
if !document_from_node_ref.is_scripting_enabled() {
|
if !document_from_node_ref.is_scripting_enabled() {
|
||||||
return;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 12.
|
// Step 12.
|
||||||
|
@ -212,13 +256,13 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
|
||||||
.to_ascii_lowercase();
|
.to_ascii_lowercase();
|
||||||
let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
|
let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
|
||||||
if for_value != "window" {
|
if for_value != "window" {
|
||||||
return;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let event_value = event_attribute.Value().to_ascii_lowercase();
|
let event_value = event_attribute.Value().to_ascii_lowercase();
|
||||||
let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
|
let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
|
||||||
if event_value != "onload" && event_value != "onload()" {
|
if event_value != "onload" && event_value != "onload()" {
|
||||||
return;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(_, _) => (),
|
(_, _) => (),
|
||||||
|
@ -245,7 +289,7 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
|
||||||
// Step 14.2
|
// Step 14.2
|
||||||
if src.is_empty() {
|
if src.is_empty() {
|
||||||
self.queue_error_event();
|
self.queue_error_event();
|
||||||
return;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 14.3
|
// Step 14.3
|
||||||
|
@ -254,7 +298,7 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
|
||||||
// Step 14.4
|
// Step 14.4
|
||||||
error!("error parsing URL for script {}", src);
|
error!("error parsing URL for script {}", src);
|
||||||
self.queue_error_event();
|
self.queue_error_event();
|
||||||
return;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
Ok(url) => {
|
Ok(url) => {
|
||||||
// Step 14.5
|
// Step 14.5
|
||||||
|
@ -263,8 +307,28 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
|
||||||
// the origin of the script element's node document, and the default origin
|
// the origin of the script element's node document, and the default origin
|
||||||
// behaviour set to taint.
|
// behaviour set to taint.
|
||||||
let doc = document_from_node(self).root();
|
let doc = document_from_node(self).root();
|
||||||
let contents = doc.r().load_sync(LoadType::Script(url));
|
|
||||||
ScriptOrigin::External(contents)
|
let script_chan = window.script_chan();
|
||||||
|
let elem = Trusted::new(window.get_cx(), self, script_chan.clone());
|
||||||
|
|
||||||
|
let context = Arc::new(Mutex::new(ScriptContext {
|
||||||
|
elem: elem,
|
||||||
|
data: RefCell::new(vec!()),
|
||||||
|
metadata: RefCell::new(None),
|
||||||
|
resume_on_completion: self.parser_inserted.get(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let listener = box NetworkListener {
|
||||||
|
context: context,
|
||||||
|
script_chan: script_chan,
|
||||||
|
};
|
||||||
|
|
||||||
|
doc.r().load_async(LoadType::Script(url), listener);
|
||||||
|
|
||||||
|
if self.parser_inserted.get() {
|
||||||
|
doc.r().get_current_parser().unwrap().root().r().suspend();
|
||||||
|
}
|
||||||
|
return NextParserState::Suspend;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -275,6 +339,7 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
|
||||||
// TODO: Add support for the `defer` and `async` attributes. (For now, we fetch all
|
// TODO: Add support for the `defer` and `async` attributes. (For now, we fetch all
|
||||||
// scripts synchronously and execute them immediately.)
|
// scripts synchronously and execute them immediately.)
|
||||||
self.execute(load);
|
self.execute(load);
|
||||||
|
NextParserState::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute(self, load: ScriptOrigin) {
|
fn execute(self, load: ScriptOrigin) {
|
||||||
|
|
|
@ -5,24 +5,35 @@
|
||||||
//! The bulk of the HTML parser integration is in `script::parse::html`.
|
//! The bulk of the HTML parser integration is in `script::parse::html`.
|
||||||
//! This module is mostly about its interaction with DOM memory management.
|
//! This module is mostly about its interaction with DOM memory management.
|
||||||
|
|
||||||
|
use document_loader::LoadType;
|
||||||
use dom::bindings::cell::DOMRefCell;
|
use dom::bindings::cell::DOMRefCell;
|
||||||
use dom::bindings::codegen::Bindings::ServoHTMLParserBinding;
|
use dom::bindings::codegen::Bindings::ServoHTMLParserBinding;
|
||||||
use dom::bindings::global::GlobalRef;
|
use dom::bindings::global::GlobalRef;
|
||||||
use dom::bindings::trace::JSTraceable;
|
use dom::bindings::trace::JSTraceable;
|
||||||
use dom::bindings::js::{JS, JSRef, Rootable, Temporary};
|
use dom::bindings::js::{JS, JSRef, Rootable, Temporary};
|
||||||
|
use dom::bindings::refcounted::Trusted;
|
||||||
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
|
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
|
||||||
use dom::document::{Document, DocumentHelpers};
|
use dom::document::{Document, DocumentHelpers};
|
||||||
use dom::node::Node;
|
use dom::node::{window_from_node, Node};
|
||||||
|
use dom::window::Window;
|
||||||
|
use network_listener::PreInvoke;
|
||||||
use parse::Parser;
|
use parse::Parser;
|
||||||
|
use script_task::{ScriptTask, ScriptChan};
|
||||||
|
|
||||||
use util::task_state;
|
use msg::constellation_msg::{PipelineId, SubpageId};
|
||||||
|
use net_traits::{Metadata, AsyncResponseListener};
|
||||||
|
|
||||||
|
use encoding::all::UTF_8;
|
||||||
|
use encoding::types::{Encoding, DecoderTrap};
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use js::jsapi::JSTracer;
|
use js::jsapi::JSTracer;
|
||||||
use html5ever::tokenizer;
|
use html5ever::tokenizer;
|
||||||
use html5ever::tree_builder;
|
use html5ever::tree_builder;
|
||||||
use html5ever::tree_builder::{TreeBuilder, TreeBuilderOpts};
|
use html5ever::tree_builder::{TreeBuilder, TreeBuilderOpts};
|
||||||
|
use hyper::header::ContentType;
|
||||||
|
use hyper::mime::{Mime, TopLevel, SubLevel};
|
||||||
|
|
||||||
#[must_root]
|
#[must_root]
|
||||||
#[jstraceable]
|
#[jstraceable]
|
||||||
|
@ -41,6 +52,110 @@ pub struct FragmentContext<'a> {
|
||||||
|
|
||||||
pub type Tokenizer = tokenizer::Tokenizer<TreeBuilder<JS<Node>, Sink>>;
|
pub type Tokenizer = tokenizer::Tokenizer<TreeBuilder<JS<Node>, Sink>>;
|
||||||
|
|
||||||
|
/// The context required for asynchronously fetching a document and parsing it progressively.
|
||||||
|
pub struct ParserContext {
|
||||||
|
/// The parser that initiated the request.
|
||||||
|
parser: RefCell<Option<Trusted<ServoHTMLParser>>>,
|
||||||
|
/// Is this document a synthesized document for a single image?
|
||||||
|
is_image_document: Cell<bool>,
|
||||||
|
/// The pipeline associated with this document.
|
||||||
|
id: PipelineId,
|
||||||
|
/// The subpage associated with this document.
|
||||||
|
subpage: Option<SubpageId>,
|
||||||
|
/// The target event loop for the response notifications.
|
||||||
|
script_chan: Box<ScriptChan+Send>,
|
||||||
|
/// The URL for this document.
|
||||||
|
url: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParserContext {
|
||||||
|
pub fn new(id: PipelineId, subpage: Option<SubpageId>, script_chan: Box<ScriptChan+Send>,
|
||||||
|
url: Url) -> ParserContext {
|
||||||
|
ParserContext {
|
||||||
|
parser: RefCell::new(None),
|
||||||
|
is_image_document: Cell::new(false),
|
||||||
|
id: id,
|
||||||
|
subpage: subpage,
|
||||||
|
script_chan: script_chan,
|
||||||
|
url: url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncResponseListener for ParserContext {
|
||||||
|
fn headers_available(&self, metadata: Metadata) {
|
||||||
|
let content_type = metadata.content_type.clone();
|
||||||
|
|
||||||
|
let parser = ScriptTask::page_fetch_complete(self.id.clone(), self.subpage.clone(),
|
||||||
|
metadata);
|
||||||
|
let parser = match parser {
|
||||||
|
Some(parser) => parser,
|
||||||
|
None => return,
|
||||||
|
}.root();
|
||||||
|
|
||||||
|
let parser = parser.r();
|
||||||
|
let win = parser.window().root();
|
||||||
|
*self.parser.borrow_mut() = Some(Trusted::new(win.r().get_cx(), parser,
|
||||||
|
self.script_chan.clone()));
|
||||||
|
|
||||||
|
match content_type {
|
||||||
|
Some(ContentType(Mime(TopLevel::Image, _, _))) => {
|
||||||
|
self.is_image_document.set(true);
|
||||||
|
let page = format!("<html><body><img src='{}' /></body></html>",
|
||||||
|
self.url.serialize());
|
||||||
|
parser.pending_input.borrow_mut().push(page);
|
||||||
|
parser.parse_sync();
|
||||||
|
}
|
||||||
|
Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => {
|
||||||
|
// FIXME: When servo/html5ever#109 is fixed remove <plaintext> usage and
|
||||||
|
// replace with fix from that issue.
|
||||||
|
|
||||||
|
// text/plain documents require setting the tokenizer into PLAINTEXT mode.
|
||||||
|
// This is done by using a <plaintext> element as the html5ever tokenizer
|
||||||
|
// provides no other way to change to that state.
|
||||||
|
// Spec for text/plain handling is:
|
||||||
|
// https://html.spec.whatwg.org/multipage/#read-text
|
||||||
|
let page = format!("<pre>\u{000A}<plaintext>");
|
||||||
|
parser.pending_input.borrow_mut().push(page);
|
||||||
|
parser.parse_sync();
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_available(&self, payload: Vec<u8>) {
|
||||||
|
if !self.is_image_document.get() {
|
||||||
|
// FIXME: use Vec<u8> (html5ever #34)
|
||||||
|
let data = UTF_8.decode(&payload, DecoderTrap::Replace).unwrap();
|
||||||
|
let parser = match self.parser.borrow().as_ref() {
|
||||||
|
Some(parser) => parser.to_temporary(),
|
||||||
|
None => return,
|
||||||
|
}.root();
|
||||||
|
parser.r().parse_chunk(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_complete(&self, status: Result<(), String>) {
|
||||||
|
let parser = match self.parser.borrow().as_ref() {
|
||||||
|
Some(parser) => parser.to_temporary(),
|
||||||
|
None => return,
|
||||||
|
}.root();
|
||||||
|
let doc = parser.r().document.root();
|
||||||
|
doc.r().finish_load(LoadType::PageSource(self.url.clone()));
|
||||||
|
|
||||||
|
if let Err(err) = status {
|
||||||
|
debug!("Failed to load page URL {}, error: {}", self.url.serialize(), err);
|
||||||
|
// TODO(Savago): we should send a notification to callers #5463.
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.r().last_chunk_received.set(true);
|
||||||
|
parser.r().parse_sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreInvoke for ParserContext {
|
||||||
|
}
|
||||||
|
|
||||||
// NB: JSTraceable is *not* auto-derived.
|
// NB: JSTraceable is *not* auto-derived.
|
||||||
// You must edit the impl below if you add fields!
|
// You must edit the impl below if you add fields!
|
||||||
#[must_root]
|
#[must_root]
|
||||||
|
@ -48,20 +163,46 @@ pub type Tokenizer = tokenizer::Tokenizer<TreeBuilder<JS<Node>, Sink>>;
|
||||||
pub struct ServoHTMLParser {
|
pub struct ServoHTMLParser {
|
||||||
reflector_: Reflector,
|
reflector_: Reflector,
|
||||||
tokenizer: DOMRefCell<Tokenizer>,
|
tokenizer: DOMRefCell<Tokenizer>,
|
||||||
|
/// Input chunks received but not yet passed to the parser.
|
||||||
|
pending_input: DOMRefCell<Vec<String>>,
|
||||||
|
/// The document associated with this parser.
|
||||||
|
document: JS<Document>,
|
||||||
|
/// True if this parser should avoid passing any further data to the tokenizer.
|
||||||
|
suspended: Cell<bool>,
|
||||||
|
/// Whether to expect any further input from the associated network request.
|
||||||
|
last_chunk_received: Cell<bool>,
|
||||||
|
/// The pipeline associated with this parse, unavailable if this parse does not
|
||||||
|
/// correspond to a page load.
|
||||||
|
pipeline: Option<PipelineId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parser for ServoHTMLParser{
|
impl<'a> Parser for JSRef<'a, ServoHTMLParser> {
|
||||||
fn parse_chunk(&self, input: String) {
|
fn parse_chunk(self, input: String) {
|
||||||
self.tokenizer().borrow_mut().feed(input);
|
self.document.root().r().set_current_parser(Some(self));
|
||||||
|
self.pending_input.borrow_mut().push(input);
|
||||||
|
self.parse_sync();
|
||||||
}
|
}
|
||||||
fn finish(&self){
|
|
||||||
|
fn finish(self) {
|
||||||
|
assert!(!self.suspended.get());
|
||||||
|
assert!(self.pending_input.borrow().is_empty());
|
||||||
|
|
||||||
self.tokenizer().borrow_mut().end();
|
self.tokenizer().borrow_mut().end();
|
||||||
|
debug!("finished parsing");
|
||||||
|
|
||||||
|
let document = self.document.root();
|
||||||
|
document.r().set_current_parser(None);
|
||||||
|
|
||||||
|
if let Some(pipeline) = self.pipeline {
|
||||||
|
ScriptTask::parsing_complete(pipeline);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServoHTMLParser {
|
impl ServoHTMLParser {
|
||||||
#[allow(unrooted_must_root)]
|
#[allow(unrooted_must_root)]
|
||||||
pub fn new(base_url: Option<Url>, document: JSRef<Document>) -> Temporary<ServoHTMLParser> {
|
pub fn new(base_url: Option<Url>, document: JSRef<Document>, pipeline: Option<PipelineId>)
|
||||||
|
-> Temporary<ServoHTMLParser> {
|
||||||
let window = document.window().root();
|
let window = document.window().root();
|
||||||
let sink = Sink {
|
let sink = Sink {
|
||||||
base_url: base_url,
|
base_url: base_url,
|
||||||
|
@ -78,6 +219,11 @@ impl ServoHTMLParser {
|
||||||
let parser = ServoHTMLParser {
|
let parser = ServoHTMLParser {
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
tokenizer: DOMRefCell::new(tok),
|
tokenizer: DOMRefCell::new(tok),
|
||||||
|
pending_input: DOMRefCell::new(vec!()),
|
||||||
|
document: JS::from_rooted(document),
|
||||||
|
suspended: Cell::new(false),
|
||||||
|
last_chunk_received: Cell::new(false),
|
||||||
|
pipeline: pipeline,
|
||||||
};
|
};
|
||||||
|
|
||||||
reflect_dom_object(box parser, GlobalRef::Window(window.r()),
|
reflect_dom_object(box parser, GlobalRef::Window(window.r()),
|
||||||
|
@ -111,6 +257,11 @@ impl ServoHTMLParser {
|
||||||
let parser = ServoHTMLParser {
|
let parser = ServoHTMLParser {
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
tokenizer: DOMRefCell::new(tok),
|
tokenizer: DOMRefCell::new(tok),
|
||||||
|
pending_input: DOMRefCell::new(vec!()),
|
||||||
|
document: JS::from_rooted(document),
|
||||||
|
suspended: Cell::new(false),
|
||||||
|
last_chunk_received: Cell::new(true),
|
||||||
|
pipeline: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
reflect_dom_object(box parser, GlobalRef::Window(window.r()),
|
reflect_dom_object(box parser, GlobalRef::Window(window.r()),
|
||||||
|
@ -129,6 +280,73 @@ impl Reflectable for ServoHTMLParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait PrivateServoHTMLParserHelpers {
|
||||||
|
/// Synchronously run the tokenizer parse loop until explicitly suspended or
|
||||||
|
/// the tokenizer runs out of input.
|
||||||
|
fn parse_sync(self);
|
||||||
|
/// Retrieve the window object associated with this parser.
|
||||||
|
fn window(self) -> Temporary<Window>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PrivateServoHTMLParserHelpers for JSRef<'a, ServoHTMLParser> {
|
||||||
|
fn parse_sync(self) {
|
||||||
|
let mut first = true;
|
||||||
|
|
||||||
|
// This parser will continue to parse while there is either pending input or
|
||||||
|
// the parser remains unsuspended.
|
||||||
|
loop {
|
||||||
|
if self.suspended.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pending_input.borrow().is_empty() && !first {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pending_input = self.pending_input.borrow_mut();
|
||||||
|
if !pending_input.is_empty() {
|
||||||
|
let chunk = pending_input.remove(0);
|
||||||
|
self.tokenizer.borrow_mut().feed(chunk);
|
||||||
|
} else {
|
||||||
|
self.tokenizer.borrow_mut().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.last_chunk_received.get() {
|
||||||
|
self.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window(self) -> Temporary<Window> {
|
||||||
|
let doc = self.document.root();
|
||||||
|
window_from_node(doc.r())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ServoHTMLParserHelpers {
|
||||||
|
/// Cause the parser to interrupt next time the tokenizer reaches a quiescent state.
|
||||||
|
/// No further parsing will occur after that point until the `resume` method is called.
|
||||||
|
/// Panics if the parser is already suspended.
|
||||||
|
fn suspend(self);
|
||||||
|
/// Immediately resume a suspended parser. Panics if the parser is not suspended.
|
||||||
|
fn resume(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ServoHTMLParserHelpers for JSRef<'a, ServoHTMLParser> {
|
||||||
|
fn suspend(self) {
|
||||||
|
assert!(!self.suspended.get());
|
||||||
|
self.suspended.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resume(self) {
|
||||||
|
assert!(self.suspended.get());
|
||||||
|
self.suspended.set(false);
|
||||||
|
self.parse_sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Tracer {
|
struct Tracer {
|
||||||
trc: *mut JSTracer,
|
trc: *mut JSTracer,
|
||||||
}
|
}
|
||||||
|
@ -152,11 +370,6 @@ impl JSTraceable for ServoHTMLParser {
|
||||||
let tracer = &tracer as &tree_builder::Tracer<Handle=JS<Node>>;
|
let tracer = &tracer as &tree_builder::Tracer<Handle=JS<Node>>;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// Assertion: If the parser is mutably borrowed, we're in the
|
|
||||||
// parsing code paths.
|
|
||||||
debug_assert!(task_state::get().contains(task_state::IN_HTML_PARSER)
|
|
||||||
|| !self.tokenizer.is_mutably_borrowed());
|
|
||||||
|
|
||||||
let tokenizer = self.tokenizer.borrow_for_gc_trace();
|
let tokenizer = self.tokenizer.borrow_for_gc_trace();
|
||||||
let tree_builder = tokenizer.sink();
|
let tree_builder = tokenizer.sink();
|
||||||
tree_builder.trace_handles(tracer);
|
tree_builder.trace_handles(tracer);
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#![allow(unsafe_code, unrooted_must_root)]
|
#![allow(unsafe_code, unrooted_must_root)]
|
||||||
|
|
||||||
use document_loader::{DocumentLoader, LoadType};
|
use document_loader::DocumentLoader;
|
||||||
use dom::attr::AttrHelpers;
|
use dom::attr::AttrHelpers;
|
||||||
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
|
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
|
||||||
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
|
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
|
||||||
|
@ -32,13 +32,10 @@ use dom::servohtmlparser::{ServoHTMLParser, FragmentContext};
|
||||||
use dom::text::Text;
|
use dom::text::Text;
|
||||||
use parse::Parser;
|
use parse::Parser;
|
||||||
|
|
||||||
use encoding::all::UTF_8;
|
use encoding::types::Encoding;
|
||||||
use encoding::types::{Encoding, DecoderTrap};
|
|
||||||
|
|
||||||
use net_traits::{ProgressMsg, LoadResponse};
|
use msg::constellation_msg::PipelineId;
|
||||||
use util::str::DOMString;
|
use util::str::DOMString;
|
||||||
use util::task_state;
|
|
||||||
use util::task_state::IN_HTML_PARSER;
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -49,14 +46,6 @@ use html5ever::serialize::TraversalScope::{IncludeNode, ChildrenOnly};
|
||||||
use html5ever::tree_builder::{TreeSink, QuirksMode, NodeOrText, AppendNode, AppendText, NextParserState};
|
use html5ever::tree_builder::{TreeSink, QuirksMode, NodeOrText, AppendNode, AppendText, NextParserState};
|
||||||
use string_cache::QualName;
|
use string_cache::QualName;
|
||||||
|
|
||||||
use hyper::header::ContentType;
|
|
||||||
use hyper::mime::{Mime, TopLevel, SubLevel};
|
|
||||||
|
|
||||||
pub enum HTMLInput {
|
|
||||||
InputString(String),
|
|
||||||
InputUrl(LoadResponse),
|
|
||||||
}
|
|
||||||
|
|
||||||
trait SinkHelpers {
|
trait SinkHelpers {
|
||||||
fn get_or_create(&self, child: NodeOrText<JS<Node>>) -> Temporary<Node>;
|
fn get_or_create(&self, child: NodeOrText<JS<Node>>) -> Temporary<Node>;
|
||||||
}
|
}
|
||||||
|
@ -183,7 +172,9 @@ impl<'a> TreeSink for servohtmlparser::Sink {
|
||||||
fn complete_script(&mut self, node: JS<Node>) -> NextParserState {
|
fn complete_script(&mut self, node: JS<Node>) -> NextParserState {
|
||||||
let node: Root<Node> = node.root();
|
let node: Root<Node> = node.root();
|
||||||
let script: Option<JSRef<HTMLScriptElement>> = HTMLScriptElementCast::to_ref(node.r());
|
let script: Option<JSRef<HTMLScriptElement>> = HTMLScriptElementCast::to_ref(node.r());
|
||||||
script.map(|script| script.prepare());
|
if let Some(script) = script {
|
||||||
|
return script.prepare();
|
||||||
|
}
|
||||||
NextParserState::Continue
|
NextParserState::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,80 +263,22 @@ impl<'a> Serializable for JSRef<'a, Node> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ParseContext<'a> {
|
||||||
|
Fragment(FragmentContext<'a>),
|
||||||
|
Owner(Option<PipelineId>),
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_html(document: JSRef<Document>,
|
pub fn parse_html(document: JSRef<Document>,
|
||||||
input: HTMLInput,
|
input: String,
|
||||||
url: &Url,
|
url: &Url,
|
||||||
fragment_context: Option<FragmentContext>) {
|
context: ParseContext) {
|
||||||
let parser = match fragment_context {
|
let parser = match context {
|
||||||
None => ServoHTMLParser::new(Some(url.clone()), document).root(),
|
ParseContext::Owner(owner) =>
|
||||||
Some(fc) => ServoHTMLParser::new_for_fragment(Some(url.clone()), document, fc).root(),
|
ServoHTMLParser::new(Some(url.clone()), document, owner),
|
||||||
};
|
ParseContext::Fragment(fc) =>
|
||||||
let parser: JSRef<ServoHTMLParser> = parser.r();
|
ServoHTMLParser::new_for_fragment(Some(url.clone()), document, fc),
|
||||||
|
}.root();
|
||||||
let nested_parse = task_state::get().contains(task_state::IN_HTML_PARSER);
|
parser.r().parse_chunk(input);
|
||||||
if !nested_parse {
|
|
||||||
task_state::enter(IN_HTML_PARSER);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_progress(document: JSRef<Document>, parser: JSRef<ServoHTMLParser>, url: &Url, load_response: &LoadResponse) {
|
|
||||||
for msg in load_response.progress_port.iter() {
|
|
||||||
match msg {
|
|
||||||
ProgressMsg::Payload(data) => {
|
|
||||||
// FIXME: use Vec<u8> (html5ever #34)
|
|
||||||
let data = UTF_8.decode(&data, DecoderTrap::Replace).unwrap();
|
|
||||||
parser.parse_chunk(data);
|
|
||||||
}
|
|
||||||
ProgressMsg::Done(Err(err)) => {
|
|
||||||
debug!("Failed to load page URL {}, error: {}", url.serialize(), err);
|
|
||||||
// TODO(Savago): we should send a notification to callers #5463.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ProgressMsg::Done(Ok(())) => {
|
|
||||||
document.finish_load(LoadType::PageSource(url.clone()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match input {
|
|
||||||
HTMLInput::InputString(s) => {
|
|
||||||
parser.parse_chunk(s);
|
|
||||||
}
|
|
||||||
HTMLInput::InputUrl(load_response) => {
|
|
||||||
match load_response.metadata.content_type {
|
|
||||||
Some(ContentType(Mime(TopLevel::Image, _, _))) => {
|
|
||||||
let page = format!("<html><body><img src='{}' /></body></html>", url.serialize());
|
|
||||||
parser.parse_chunk(page);
|
|
||||||
document.finish_load(LoadType::PageSource(url.clone()));
|
|
||||||
},
|
|
||||||
Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => {
|
|
||||||
// FIXME: When servo/html5ever#109 is fixed remove <plaintext> usage and
|
|
||||||
// replace with fix from that issue.
|
|
||||||
|
|
||||||
// text/plain documents require setting the tokenizer into PLAINTEXT mode.
|
|
||||||
// This is done by using a <plaintext> element as the html5ever tokenizer
|
|
||||||
// provides no other way to change to that state.
|
|
||||||
// Spec for text/plain handling is:
|
|
||||||
// https://html.spec.whatwg.org/multipage/#read-text
|
|
||||||
let page = format!("<pre>\u{000A}<plaintext>");
|
|
||||||
parser.parse_chunk(page);
|
|
||||||
parse_progress(document, parser, url, &load_response);
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
parse_progress(document, parser, url, &load_response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.finish();
|
|
||||||
|
|
||||||
if !nested_parse {
|
|
||||||
task_state::exit(IN_HTML_PARSER);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("finished parsing");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#parsing-html-fragments
|
// https://html.spec.whatwg.org/multipage/#parsing-html-fragments
|
||||||
|
@ -376,7 +309,7 @@ pub fn parse_html_fragment(context_node: JSRef<Node>,
|
||||||
context_elem: context_node,
|
context_elem: context_node,
|
||||||
form_elem: form.r(),
|
form_elem: form.r(),
|
||||||
};
|
};
|
||||||
parse_html(document.r(), HTMLInput::InputString(input), &url, Some(fragment_context));
|
parse_html(document.r(), input, &url, ParseContext::Fragment(fragment_context));
|
||||||
|
|
||||||
// Step 14.
|
// Step 14.
|
||||||
let root_element = document.r().GetDocumentElement().expect("no document element").root();
|
let root_element = document.r().GetDocumentElement().expect("no document element").root();
|
||||||
|
|
|
@ -5,6 +5,6 @@
|
||||||
pub mod html;
|
pub mod html;
|
||||||
|
|
||||||
pub trait Parser {
|
pub trait Parser {
|
||||||
fn parse_chunk(&self,input: String);
|
fn parse_chunk(self, input: String);
|
||||||
fn finish(&self);
|
fn finish(self);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,11 +39,13 @@ use dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementHelpers};
|
||||||
use dom::uievent::UIEvent;
|
use dom::uievent::UIEvent;
|
||||||
use dom::eventtarget::EventTarget;
|
use dom::eventtarget::EventTarget;
|
||||||
use dom::node::{Node, NodeHelpers, NodeDamage, window_from_node};
|
use dom::node::{Node, NodeHelpers, NodeDamage, window_from_node};
|
||||||
|
use dom::servohtmlparser::{ServoHTMLParser, ParserContext};
|
||||||
use dom::window::{Window, WindowHelpers, ScriptHelpers, ReflowReason};
|
use dom::window::{Window, WindowHelpers, ScriptHelpers, ReflowReason};
|
||||||
use dom::worker::TrustedWorkerAddress;
|
use dom::worker::TrustedWorkerAddress;
|
||||||
use parse::html::{HTMLInput, parse_html};
|
use parse::html::{ParseContext, parse_html};
|
||||||
use layout_interface::{ScriptLayoutChan, LayoutChan, ReflowGoal, ReflowQueryType};
|
use layout_interface::{ScriptLayoutChan, LayoutChan, ReflowGoal, ReflowQueryType};
|
||||||
use layout_interface;
|
use layout_interface;
|
||||||
|
use network_listener::NetworkListener;
|
||||||
use page::{Page, IterablePage, Frame};
|
use page::{Page, IterablePage, Frame};
|
||||||
use timers::TimerId;
|
use timers::TimerId;
|
||||||
use devtools;
|
use devtools;
|
||||||
|
@ -65,13 +67,13 @@ use msg::constellation_msg::{ConstellationChan, FocusType};
|
||||||
use msg::constellation_msg::{LoadData, PipelineId, SubpageId, MozBrowserEvent, WorkerId};
|
use msg::constellation_msg::{LoadData, PipelineId, SubpageId, MozBrowserEvent, WorkerId};
|
||||||
use msg::constellation_msg::{Failure, WindowSizeData, PipelineExitType};
|
use msg::constellation_msg::{Failure, WindowSizeData, PipelineExitType};
|
||||||
use msg::constellation_msg::Msg as ConstellationMsg;
|
use msg::constellation_msg::Msg as ConstellationMsg;
|
||||||
use net_traits::{ResourceTask, LoadResponse, LoadConsumer, ControlMsg};
|
use net_traits::{ResourceTask, LoadConsumer, ControlMsg, Metadata};
|
||||||
use net_traits::LoadData as NetLoadData;
|
use net_traits::LoadData as NetLoadData;
|
||||||
use net_traits::image_cache_task::{ImageCacheChan, ImageCacheTask, ImageCacheResult};
|
use net_traits::image_cache_task::{ImageCacheChan, ImageCacheTask, ImageCacheResult};
|
||||||
use net_traits::storage_task::StorageTask;
|
use net_traits::storage_task::StorageTask;
|
||||||
use string_cache::Atom;
|
use string_cache::Atom;
|
||||||
use util::str::DOMString;
|
use util::str::DOMString;
|
||||||
use util::task::{spawn_named, spawn_named_with_send_on_failure};
|
use util::task::spawn_named_with_send_on_failure;
|
||||||
use util::task_state;
|
use util::task_state;
|
||||||
|
|
||||||
use geom::Rect;
|
use geom::Rect;
|
||||||
|
@ -92,6 +94,7 @@ use std::option::Option;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use std::sync::mpsc::{channel, Sender, Receiver, Select};
|
use std::sync::mpsc::{channel, Sender, Receiver, Select};
|
||||||
use time::Tm;
|
use time::Tm;
|
||||||
|
|
||||||
|
@ -188,8 +191,6 @@ pub enum ScriptMsg {
|
||||||
MainThreadRunnableMsg(Box<MainThreadRunnable+Send>),
|
MainThreadRunnableMsg(Box<MainThreadRunnable+Send>),
|
||||||
/// A DOM object's last pinned reference was removed (dispatched to all tasks).
|
/// A DOM object's last pinned reference was removed (dispatched to all tasks).
|
||||||
RefcountCleanup(TrustedReference),
|
RefcountCleanup(TrustedReference),
|
||||||
/// The final network response for a page has arrived.
|
|
||||||
PageFetchComplete(PipelineId, Option<SubpageId>, LoadResponse),
|
|
||||||
/// Notify a document that all pending loads are complete.
|
/// Notify a document that all pending loads are complete.
|
||||||
DocumentLoadsComplete(PipelineId),
|
DocumentLoadsComplete(PipelineId),
|
||||||
}
|
}
|
||||||
|
@ -427,6 +428,21 @@ unsafe extern "C" fn debug_gc_callback(_rt: *mut JSRuntime, status: JSGCStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScriptTask {
|
impl ScriptTask {
|
||||||
|
pub fn page_fetch_complete(id: PipelineId, subpage: Option<SubpageId>, metadata: Metadata)
|
||||||
|
-> Option<Temporary<ServoHTMLParser>> {
|
||||||
|
SCRIPT_TASK_ROOT.with(|root| {
|
||||||
|
let script_task = unsafe { &*root.borrow().unwrap() };
|
||||||
|
script_task.handle_page_fetch_complete(id, subpage, metadata)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parsing_complete(id: PipelineId) {
|
||||||
|
SCRIPT_TASK_ROOT.with(|root| {
|
||||||
|
let script_task = unsafe { &*root.borrow().unwrap() };
|
||||||
|
script_task.handle_parsing_complete(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn process_event(msg: ScriptMsg) {
|
pub fn process_event(msg: ScriptMsg) {
|
||||||
SCRIPT_TASK_ROOT.with(|root| {
|
SCRIPT_TASK_ROOT.with(|root| {
|
||||||
if let Some(script_task) = *root.borrow() {
|
if let Some(script_task) = *root.borrow() {
|
||||||
|
@ -765,8 +781,6 @@ impl ScriptTask {
|
||||||
runnable.handler(self),
|
runnable.handler(self),
|
||||||
ScriptMsg::RefcountCleanup(addr) =>
|
ScriptMsg::RefcountCleanup(addr) =>
|
||||||
LiveDOMReferences::cleanup(self.get_cx(), addr),
|
LiveDOMReferences::cleanup(self.get_cx(), addr),
|
||||||
ScriptMsg::PageFetchComplete(id, subpage, response) =>
|
|
||||||
self.handle_page_fetch_complete(id, subpage, response),
|
|
||||||
ScriptMsg::DocumentLoadsComplete(id) =>
|
ScriptMsg::DocumentLoadsComplete(id) =>
|
||||||
self.handle_loads_complete(id),
|
self.handle_loads_complete(id),
|
||||||
}
|
}
|
||||||
|
@ -1069,7 +1083,7 @@ impl ScriptTask {
|
||||||
/// We have received notification that the response associated with a load has completed.
|
/// We have received notification that the response associated with a load has completed.
|
||||||
/// Kick off the document and frame tree creation process using the result.
|
/// Kick off the document and frame tree creation process using the result.
|
||||||
fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option<SubpageId>,
|
fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option<SubpageId>,
|
||||||
response: LoadResponse) {
|
metadata: Metadata) -> Option<Temporary<ServoHTMLParser>> {
|
||||||
let idx = self.incomplete_loads.borrow().iter().position(|load| {
|
let idx = self.incomplete_loads.borrow().iter().position(|load| {
|
||||||
load.pipeline_id == id && load.parent_info.map(|info| info.1) == subpage
|
load.pipeline_id == id && load.parent_info.map(|info| info.1) == subpage
|
||||||
});
|
});
|
||||||
|
@ -1078,10 +1092,11 @@ impl ScriptTask {
|
||||||
match idx {
|
match idx {
|
||||||
Some(idx) => {
|
Some(idx) => {
|
||||||
let load = self.incomplete_loads.borrow_mut().remove(idx);
|
let load = self.incomplete_loads.borrow_mut().remove(idx);
|
||||||
self.load(response, load);
|
Some(self.load(metadata, load))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
assert!(self.closed_pipelines.borrow().contains(&id));
|
assert!(self.closed_pipelines.borrow().contains(&id));
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1148,8 +1163,8 @@ impl ScriptTask {
|
||||||
|
|
||||||
/// The entry point to document loading. Defines bindings, sets up the window and document
|
/// The entry point to document loading. Defines bindings, sets up the window and document
|
||||||
/// objects, parses HTML and CSS, and kicks off initial layout.
|
/// objects, parses HTML and CSS, and kicks off initial layout.
|
||||||
fn load(&self, response: LoadResponse, incomplete: InProgressLoad) {
|
fn load(&self, metadata: Metadata, incomplete: InProgressLoad) -> Temporary<ServoHTMLParser> {
|
||||||
let final_url = response.metadata.final_url.clone();
|
let final_url = metadata.final_url.clone();
|
||||||
debug!("ScriptTask: loading {} on page {:?}", incomplete.url.serialize(), incomplete.pipeline_id);
|
debug!("ScriptTask: loading {} on page {:?}", incomplete.url.serialize(), incomplete.pipeline_id);
|
||||||
|
|
||||||
// We should either be initializing a root page or loading a child page of an
|
// We should either be initializing a root page or loading a child page of an
|
||||||
|
@ -1249,11 +1264,11 @@ impl ScriptTask {
|
||||||
incomplete.parent_info,
|
incomplete.parent_info,
|
||||||
incomplete.window_size).root();
|
incomplete.window_size).root();
|
||||||
|
|
||||||
let last_modified: Option<DOMString> = response.metadata.headers.as_ref().and_then(|headers| {
|
let last_modified: Option<DOMString> = metadata.headers.as_ref().and_then(|headers| {
|
||||||
headers.get().map(|&LastModified(HttpDate(ref tm))| dom_last_modified(tm))
|
headers.get().map(|&LastModified(HttpDate(ref tm))| dom_last_modified(tm))
|
||||||
});
|
});
|
||||||
|
|
||||||
let content_type = match response.metadata.content_type {
|
let content_type = match metadata.content_type {
|
||||||
Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => Some("text/plain".to_owned()),
|
Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => Some("text/plain".to_owned()),
|
||||||
_ => None
|
_ => None
|
||||||
};
|
};
|
||||||
|
@ -1264,7 +1279,7 @@ impl ScriptTask {
|
||||||
};
|
};
|
||||||
let loader = DocumentLoader::new_with_task(self.resource_task.clone(),
|
let loader = DocumentLoader::new_with_task(self.resource_task.clone(),
|
||||||
Some(notifier_data),
|
Some(notifier_data),
|
||||||
Some(final_url.clone()));
|
Some(incomplete.url.clone()));
|
||||||
let document = Document::new(window.r(),
|
let document = Document::new(window.r(),
|
||||||
Some(final_url.clone()),
|
Some(final_url.clone()),
|
||||||
IsHTMLDocument::HTMLDocument,
|
IsHTMLDocument::HTMLDocument,
|
||||||
|
@ -1288,14 +1303,17 @@ impl ScriptTask {
|
||||||
let jsval = window.r().evaluate_js_on_global_with_result(evalstr);
|
let jsval = window.r().evaluate_js_on_global_with_result(evalstr);
|
||||||
let strval = FromJSValConvertible::from_jsval(self.get_cx(), jsval,
|
let strval = FromJSValConvertible::from_jsval(self.get_cx(), jsval,
|
||||||
StringificationBehavior::Empty);
|
StringificationBehavior::Empty);
|
||||||
HTMLInput::InputString(strval.unwrap_or("".to_owned()))
|
strval.unwrap_or("".to_owned())
|
||||||
} else {
|
} else {
|
||||||
HTMLInput::InputUrl(response)
|
"".to_owned()
|
||||||
};
|
};
|
||||||
|
|
||||||
parse_html(document.r(), parse_input, &final_url, None);
|
parse_html(document.r(), parse_input, &final_url,
|
||||||
self.handle_parsing_complete(incomplete.pipeline_id);
|
ParseContext::Owner(Some(incomplete.pipeline_id)));
|
||||||
|
|
||||||
page_remover.neuter();
|
page_remover.neuter();
|
||||||
|
|
||||||
|
document.r().get_current_parser().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn notify_devtools(&self, title: DOMString, url: Url, ids: (PipelineId, Option<WorkerId>)) {
|
fn notify_devtools(&self, title: DOMString, url: Url, ids: (PipelineId, Option<WorkerId>)) {
|
||||||
|
@ -1480,29 +1498,26 @@ impl ScriptTask {
|
||||||
let script_chan = self.chan.clone();
|
let script_chan = self.chan.clone();
|
||||||
let resource_task = self.resource_task.clone();
|
let resource_task = self.resource_task.clone();
|
||||||
|
|
||||||
spawn_named(format!("fetch for {:?}", load_data.url.serialize()), move || {
|
let context = Arc::new(Mutex::new(ParserContext::new(id, subpage, script_chan.clone(),
|
||||||
if load_data.url.scheme == "javascript" {
|
load_data.url.clone())));
|
||||||
load_data.url = Url::parse("about:blank").unwrap();
|
let listener = box NetworkListener {
|
||||||
}
|
context: context,
|
||||||
|
script_chan: script_chan.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
let (input_chan, input_port) = channel();
|
if load_data.url.scheme == "javascript" {
|
||||||
resource_task.send(ControlMsg::Load(NetLoadData {
|
load_data.url = Url::parse("about:blank").unwrap();
|
||||||
url: load_data.url,
|
}
|
||||||
method: load_data.method,
|
|
||||||
headers: Headers::new(),
|
|
||||||
preserved_headers: load_data.headers,
|
|
||||||
data: load_data.data,
|
|
||||||
cors: None,
|
|
||||||
pipeline_id: Some(id),
|
|
||||||
}, LoadConsumer::Channel(input_chan))).unwrap();
|
|
||||||
|
|
||||||
let load_response = input_port.recv().unwrap();
|
resource_task.send(ControlMsg::Load(NetLoadData {
|
||||||
if script_chan.send(ScriptMsg::PageFetchComplete(id, subpage, load_response)).is_err() {
|
url: load_data.url,
|
||||||
// TODO(gw): This should be handled by aborting
|
method: load_data.method,
|
||||||
// the load before the script task exits.
|
headers: Headers::new(),
|
||||||
debug!("PageFetchComplete: script channel has exited");
|
preserved_headers: load_data.headers,
|
||||||
}
|
data: load_data.data,
|
||||||
});
|
cors: None,
|
||||||
|
pipeline_id: Some(id),
|
||||||
|
}, LoadConsumer::Listener(listener))).unwrap();
|
||||||
|
|
||||||
self.incomplete_loads.borrow_mut().push(incomplete);
|
self.incomplete_loads.borrow_mut().push(incomplete);
|
||||||
}
|
}
|
||||||
|
@ -1541,6 +1556,7 @@ impl ScriptTask {
|
||||||
|
|
||||||
// Kick off the initial reflow of the page.
|
// Kick off the initial reflow of the page.
|
||||||
debug!("kicking off initial reflow of {:?}", final_url);
|
debug!("kicking off initial reflow of {:?}", final_url);
|
||||||
|
|
||||||
document.r().content_changed(NodeCast::from_ref(document.r()),
|
document.r().content_changed(NodeCast::from_ref(document.r()),
|
||||||
NodeDamage::OtherNodeDamage);
|
NodeDamage::OtherNodeDamage);
|
||||||
let window = window_from_node(document.r()).root();
|
let window = window_from_node(document.r()).root();
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[async_006.htm]
|
|
||||||
type: testharness
|
|
||||||
[dynamically created external script executes asynchronously]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[015.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: DOM added inline+external+inline script earlier in document]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[015a.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: DOM added inline+external+inline script earlier in document]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[017.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: multiple DOM added scripts later in document]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[020.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: DOM added script with data: URL ]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[022.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: DOM added script, late .src ]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[023.html]
|
|
||||||
type: testharness
|
|
||||||
expected: TIMEOUT
|
|
||||||
[ scheduler: DOM added script, even later .src ]
|
|
||||||
expected: TIMEOUT
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[024.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: DOM added script, .src set twice]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[038.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: DOM movement with appendChild, external]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[046.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: no readystatechange events when adding external scripts ]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[047.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: adding and removing external script ]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[049.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: adding external script but removeAttribute( src ) before it runs]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[050.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: adding external script that removes all scripts from document]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[051.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: interaction of parsing and script execution - script added through DOM]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[053.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: adding external script that removes itself from document when loading]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[069.html]
|
|
||||||
type: testharness
|
|
||||||
[scheduler: external files added through DOM should not block further parsing while loading]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[076.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: adding and removing external and inline scripts ]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[078.html]
|
|
||||||
type: testharness
|
|
||||||
[ adding several types of scripts through the DOM and removing some of them confuses scheduler (slow-loading scripts) ]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
[095.html]
|
||||||
|
type: testharness
|
||||||
|
[ scheduler: slow-loading script added from defer blocking load event]
|
||||||
|
expected: FAIL
|
|
@ -0,0 +1,4 @@
|
||||||
|
[105.html]
|
||||||
|
type: testharness
|
||||||
|
[ scheduler: adding async attribute at runtime]
|
||||||
|
expected: FAIL
|
|
@ -0,0 +1,4 @@
|
||||||
|
[123.html]
|
||||||
|
type: testharness
|
||||||
|
[scheduler: altering the type attribute and adding/removing external script with async=false ]
|
||||||
|
expected: FAIL
|
|
@ -0,0 +1,4 @@
|
||||||
|
[126.html]
|
||||||
|
type: testharness
|
||||||
|
[scheduler: altering the type attribute and changing script data external script async=false ]
|
||||||
|
expected: FAIL
|
|
@ -1,5 +0,0 @@
|
||||||
[134.html]
|
|
||||||
type: testharness
|
|
||||||
[scheduler: external HTML script added by SVG script ]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue