From 7f0706ed42b33ec1da6bf9741811ca97d566ed45 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Mon, 13 Oct 2014 08:20:06 -0400 Subject: [PATCH 1/4] Implement a DocumentLoader type that tracks pending loads and notifies the script task when the queue is empty. Dispatch the document load event based on the DocumentLoader's notification. --- components/script/document_loader.rs | 106 +++++++++++++++++++++ components/script/dom/bindings/global.rs | 9 +- components/script/dom/document.rs | 55 +++++++++-- components/script/dom/domimplementation.rs | 13 ++- components/script/dom/domparser.rs | 11 ++- components/script/dom/htmlscriptelement.rs | 7 +- components/script/dom/node.rs | 4 +- components/script/lib.rs | 1 + components/script/page.rs | 4 + components/script/parse/html.rs | 22 +++-- components/script/script_task.rs | 49 +++++++--- 11 files changed, 243 insertions(+), 38 deletions(-) create mode 100644 components/script/document_loader.rs diff --git a/components/script/document_loader.rs b/components/script/document_loader.rs new file mode 100644 index 00000000000..bf759788c57 --- /dev/null +++ b/components/script/document_loader.rs @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Tracking of pending loads in a document. +//! https://html.spec.whatwg.org/multipage/#the-end + +use script_task::{ScriptMsg, ScriptChan}; +use msg::constellation_msg::{PipelineId}; +use net_traits::{LoadResponse, Metadata, load_whole_resource, ResourceTask}; +use net_traits::{ControlMsg, LoadData, LoadConsumer}; +use url::Url; + +use std::sync::mpsc::{Receiver, channel}; + +#[jstraceable] +#[derive(PartialEq, Clone)] +pub enum LoadType { + Image(Url), + Script(Url), + Subframe(Url), + Stylesheet(Url), + PageSource(Url), +} + +impl LoadType { + fn url(&self) -> &Url { + match *self { + LoadType::Image(ref url) | + LoadType::Script(ref url) | + LoadType::Subframe(ref url) | + LoadType::Stylesheet(ref url) | + LoadType::PageSource(ref url) => url, + } + } +} + +#[jstraceable] +pub struct DocumentLoader { + pub resource_task: ResourceTask, + notifier_data: Option, + blocking_loads: Vec, +} + +#[jstraceable] +pub struct NotifierData { + pub script_chan: Box, + pub pipeline: PipelineId, +} + +impl DocumentLoader { + pub fn new(existing: &DocumentLoader) -> DocumentLoader { + DocumentLoader::new_with_task(existing.resource_task.clone(), None, None) + } + + pub fn new_with_task(resource_task: ResourceTask, + data: Option, + initial_load: Option,) + -> DocumentLoader { + let mut initial_loads = vec!(); + if let Some(load) = initial_load { + initial_loads.push(LoadType::PageSource(load)); + } + + DocumentLoader { + resource_task: resource_task, + notifier_data: data, + blocking_loads: initial_loads, + } + } + + pub fn load_async(&mut self, load: LoadType) -> Receiver { + let (tx, rx) = channel(); + self.blocking_loads.push(load.clone()); + let pipeline = self.notifier_data.as_ref().map(|data| data.pipeline); + let load_data = LoadData::new(load.url().clone(), pipeline); + self.resource_task.send(ControlMsg::Load(load_data, LoadConsumer::Channel(tx))).unwrap(); + rx + } + + pub fn load_sync(&mut self, load: LoadType) -> Result<(Metadata, Vec), String> { + self.blocking_loads.push(load.clone()); + let result = load_whole_resource(&self.resource_task, load.url().clone()); + self.finish_load(load); + result + } + + pub fn finish_load(&mut self, load: LoadType) { + let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == load); + self.blocking_loads.remove(idx.expect("unknown completed load")); + + if let Some(NotifierData { ref script_chan, pipeline }) = self.notifier_data { + if !self.is_blocked() { + script_chan.send(ScriptMsg::DocumentLoadsComplete(pipeline)).unwrap(); + } + } + } + + pub fn is_blocked(&self) -> bool { + !self.blocking_loads.is_empty() + } + + pub fn inhibit_events(&mut self) { + self.notifier_data = None; + } +} diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs index 1b34844fa53..d1294173dd1 100644 --- a/components/script/dom/bindings/global.rs +++ b/components/script/dom/bindings/global.rs @@ -7,9 +7,11 @@ //! This module contains smart pointers to global scopes, to simplify writing //! code that works in workers as well as window scopes. +use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::conversions::native_from_reflector_jsmanaged; use dom::bindings::js::{JS, JSRef, Rootable, Root, Unrooted}; use dom::bindings::utils::{Reflectable, Reflector}; +use dom::document::DocumentHelpers; use dom::workerglobalscope::{WorkerGlobalScope, WorkerGlobalScopeHelpers}; use dom::window::{self, WindowHelpers}; use devtools_traits::DevtoolsControlChan; @@ -101,7 +103,12 @@ impl<'a> GlobalRef<'a> { /// Get the `ResourceTask` for this global scope. pub fn resource_task(&self) -> ResourceTask { match *self { - GlobalRef::Window(ref window) => window.resource_task().clone(), + GlobalRef::Window(ref window) => { + let doc = window.Document().root(); + let doc = doc.r(); + let loader = doc.loader(); + loader.resource_task.clone() + } GlobalRef::Worker(ref worker) => worker.resource_task().clone(), } } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 9c3b0b00511..411cd1fefe7 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use document_loader::{DocumentLoader, LoadType}; use dom::attr::{Attr, AttrHelpers, AttrValue}; use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::DocumentBinding; @@ -71,6 +72,7 @@ use msg::constellation_msg::{ConstellationChan, FocusType, Key, KeyState, KeyMod use msg::constellation_msg::{SUPER, ALT, SHIFT, CONTROL}; use net_traits::CookieSource::NonHTTP; use net_traits::ControlMsg::{SetCookiesForUrl, GetCookiesForUrl}; +use net_traits::{Metadata, LoadResponse}; use script_task::Runnable; use script_traits::{MouseButton, UntrustedNodeAddress}; use util::opts; @@ -90,9 +92,9 @@ use std::borrow::ToOwned; use std::collections::HashMap; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::ascii::AsciiExt; -use std::cell::{Cell, Ref, RefCell}; +use std::cell::{Cell, Ref, RefMut, RefCell}; use std::default::Default; -use std::sync::mpsc::channel; +use std::sync::mpsc::{Receiver, channel}; use time; #[derive(PartialEq)] @@ -139,6 +141,8 @@ pub struct Document { /// https://html.spec.whatwg.org/multipage/#list-of-animation-frame-callbacks /// List of animation frame callbacks animation_frame_list: RefCell>>, + /// Tracks all outstanding loads related to this document. + loader: DOMRefCell, } impl DocumentDerived for EventTarget { @@ -205,6 +209,8 @@ impl CollectionFilter for AppletsFilter { } pub trait DocumentHelpers<'a> { + fn loader(&self) -> Ref; + fn mut_loader(&self) -> RefMut; fn window(self) -> Temporary; fn encoding_name(self) -> Ref<'a, DOMString>; fn is_html_document(self) -> bool; @@ -254,9 +260,22 @@ pub trait DocumentHelpers<'a> { fn cancel_animation_frame(self, ident: i32); /// http://w3c.github.io/animation-timing/#dfn-invoke-callbacks-algorithm fn invoke_animation_callbacks(self); + fn load_async(self, load: LoadType) -> Receiver; + fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec), String>; + fn finish_load(self, load: LoadType); } impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { + #[inline] + fn loader(&self) -> Ref { + self.loader.borrow() + } + + #[inline] + fn mut_loader(&self) -> RefMut { + self.loader.borrow_mut() + } + #[inline] fn window(self) -> Temporary { Temporary::from_rooted(self.window) @@ -864,6 +883,21 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { callback(*performance.Now()); } } + + fn load_async(self, load: LoadType) -> Receiver { + let mut loader = self.loader.borrow_mut(); + loader.load_async(load) + } + + fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec), String> { + let mut loader = self.loader.borrow_mut(); + loader.load_sync(load) + } + + fn finish_load(self, load: LoadType) { + let mut loader = self.loader.borrow_mut(); + loader.finish_load(load); + } } pub enum MouseEventType { @@ -898,7 +932,8 @@ impl Document { is_html_document: IsHTMLDocument, content_type: Option, last_modified: Option, - source: DocumentSource) -> Document { + source: DocumentSource, + doc_loader: DocumentLoader) -> Document { let url = url.unwrap_or_else(|| Url::parse("about:blank").unwrap()); let ready_state = if source == DocumentSource::FromParser { @@ -943,14 +978,19 @@ impl Document { scripting_enabled: Cell::new(true), animation_frame_ident: Cell::new(0), animation_frame_list: RefCell::new(HashMap::new()), + loader: DOMRefCell::new(doc_loader), } } // https://dom.spec.whatwg.org/#dom-document pub fn Constructor(global: GlobalRef) -> Fallible> { - Ok(Document::new(global.as_window(), None, + let win = global.as_window(); + let doc = win.Document().root(); + let doc = doc.r(); + let docloader = DocumentLoader::new(&*doc.loader()); + Ok(Document::new(win, None, IsHTMLDocument::NonHTMLDocument, None, - None, DocumentSource::NotFromParser)) + None, DocumentSource::NotFromParser, docloader)) } pub fn new(window: JSRef, @@ -958,10 +998,11 @@ impl Document { doctype: IsHTMLDocument, content_type: Option, last_modified: Option, - source: DocumentSource) -> Temporary { + source: DocumentSource, + doc_loader: DocumentLoader) -> Temporary { let document = reflect_dom_object(box Document::new_inherited(window, url, doctype, content_type, last_modified, - source), + source, doc_loader), GlobalRef::Window(window), DocumentBinding::Wrap).root(); diff --git a/components/script/dom/domimplementation.rs b/components/script/dom/domimplementation.rs index 3400c1d3ea3..d64ad103aa1 100644 --- a/components/script/dom/domimplementation.rs +++ b/components/script/dom/domimplementation.rs @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use document_loader::DocumentLoader; use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::DOMImplementationBinding; use dom::bindings::codegen::Bindings::DOMImplementationBinding::DOMImplementationMethods; @@ -63,11 +64,13 @@ impl<'a> DOMImplementationMethods for JSRef<'a, DOMImplementation> { fn CreateDocument(self, namespace: Option, qname: DOMString, maybe_doctype: Option>) -> Fallible> { let doc = self.document.root(); - let win = doc.r().window().root(); + let doc = doc.r(); + let win = doc.window().root(); + let loader = DocumentLoader::new(&*doc.loader()); // Step 1. let doc = Document::new(win.r(), None, IsHTMLDocument::NonHTMLDocument, - None, None, DocumentSource::NotFromParser).root(); + None, None, DocumentSource::NotFromParser, loader).root(); // Step 2-3. let maybe_elem = if qname.is_empty() { None @@ -109,11 +112,13 @@ impl<'a> DOMImplementationMethods for JSRef<'a, DOMImplementation> { // https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument fn CreateHTMLDocument(self, title: Option) -> Temporary { let document = self.document.root(); - let win = document.r().window().root(); + let document = document.r(); + let win = document.window().root(); + let loader = DocumentLoader::new(&*document.loader()); // Step 1-2. let doc = Document::new(win.r(), None, IsHTMLDocument::HTMLDocument, None, None, - DocumentSource::NotFromParser).root(); + DocumentSource::NotFromParser, loader).root(); let doc_node: JSRef = NodeCast::from_ref(doc.r()); { diff --git a/components/script/dom/domparser.rs b/components/script/dom/domparser.rs index 65afb15e5fa..5685c3f324f 100644 --- a/components/script/dom/domparser.rs +++ b/components/script/dom/domparser.rs @@ -2,10 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use document_loader::DocumentLoader; use dom::bindings::codegen::Bindings::DocumentBinding::DocumentReadyState; use dom::bindings::codegen::Bindings::DOMParserBinding; use dom::bindings::codegen::Bindings::DOMParserBinding::DOMParserMethods; use dom::bindings::codegen::Bindings::DOMParserBinding::SupportedType::{Text_html, Text_xml}; +use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::error::Fallible; use dom::bindings::global::GlobalRef; use dom::bindings::js::{JS, JSRef, Rootable, Temporary}; @@ -51,13 +53,17 @@ impl<'a> DOMParserMethods for JSRef<'a, DOMParser> { let window = self.window.root(); let url = window.r().get_url(); let content_type = DOMParserBinding::SupportedTypeValues::strings[ty as usize].to_owned(); + let doc = window.r().Document().root(); + let doc = doc.r(); + let loader = DocumentLoader::new(&*doc.loader()); match ty { Text_html => { let document = Document::new(window.r(), Some(url.clone()), IsHTMLDocument::HTMLDocument, Some(content_type), None, - DocumentSource::FromParser).root(); + DocumentSource::FromParser, + loader).root(); parse_html(document.r(), HTMLInput::InputString(s), &url, None); document.r().set_ready_state(DocumentReadyState::Complete); Ok(Temporary::from_rooted(document.r())) @@ -68,7 +74,8 @@ impl<'a> DOMParserMethods for JSRef<'a, DOMParser> { IsHTMLDocument::NonHTMLDocument, Some(content_type), None, - DocumentSource::NotFromParser)) + DocumentSource::NotFromParser, + loader)) } } } diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index aaf36f95cb4..2f7a52ec272 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -4,6 +4,7 @@ use std::ascii::AsciiExt; +use document_loader::LoadType; use dom::attr::Attr; use dom::attr::AttrHelpers; use dom::bindings::cell::DOMRefCell; @@ -34,7 +35,7 @@ use script_task::{ScriptMsg, Runnable}; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::types::{Encoding, EncodingRef, DecoderTrap}; -use net_traits::{load_whole_resource, Metadata}; +use net_traits::Metadata; use util::str::{DOMString, HTML_SPACE_CHARACTERS, StaticStringVec}; use std::borrow::ToOwned; use std::cell::Cell; @@ -261,7 +262,9 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> { // state of the element's `crossorigin` content attribute, the origin being // the origin of the script element's node document, and the default origin // behaviour set to taint. - ScriptOrigin::External(load_whole_resource(&window.resource_task(), url)) + let doc = document_from_node(self).root(); + let contents = doc.r().load_sync(LoadType::Script(url)); + ScriptOrigin::External(contents) } } }, diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 048ee28b3a9..170ff22e5a1 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -4,6 +4,7 @@ //! The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements. +use document_loader::DocumentLoader; use dom::attr::{Attr, AttrHelpers}; use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; @@ -1711,9 +1712,10 @@ impl Node { false => IsHTMLDocument::NonHTMLDocument, }; let window = document.window().root(); + let loader = DocumentLoader::new(&*document.loader()); let document = Document::new(window.r(), Some(document.url()), is_html_doc, None, - None, DocumentSource::NotFromParser); + None, DocumentSource::NotFromParser, loader); NodeCast::from_temporary(document) }, NodeTypeId::Element(..) => { diff --git a/components/script/lib.rs b/components/script/lib.rs index 09340e7446e..403d53fccbe 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -54,6 +54,7 @@ extern crate string_cache; extern crate webdriver_traits; pub mod cors; +pub mod document_loader; #[macro_use] pub mod dom; diff --git a/components/script/page.rs b/components/script/page.rs index 67da1a9fc61..404cf20de6b 100644 --- a/components/script/page.rs +++ b/components/script/page.rs @@ -71,6 +71,10 @@ impl Page { } } + pub fn pipeline(&self) -> PipelineId { + self.id + } + pub fn window(&self) -> Temporary { Temporary::from_rooted(self.frame.borrow().as_ref().unwrap().window.clone()) } diff --git a/components/script/parse/html.rs b/components/script/parse/html.rs index c870fe8c616..80885acf300 100644 --- a/components/script/parse/html.rs +++ b/components/script/parse/html.rs @@ -4,6 +4,7 @@ #![allow(unsafe_code, unrooted_must_root)] +use document_loader::{DocumentLoader, LoadType}; use dom::attr::AttrHelpers; use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; @@ -286,7 +287,7 @@ pub fn parse_html(document: JSRef, task_state::enter(IN_HTML_PARSER); } - fn parse_progress(parser: JSRef, url: &Url, load_response: &LoadResponse) { + fn parse_progress(document: JSRef, parser: JSRef, url: &Url, load_response: &LoadResponse) { for msg in load_response.progress_port.iter() { match msg { ProgressMsg::Payload(data) => { @@ -299,7 +300,10 @@ pub fn parse_html(document: JSRef, // TODO(Savago): we should send a notification to callers #5463. break; } - ProgressMsg::Done(Ok(())) => break, + ProgressMsg::Done(Ok(())) => { + document.finish_load(LoadType::PageSource(url.clone())); + break; + } } } }; @@ -313,6 +317,7 @@ pub fn parse_html(document: JSRef, Some(ContentType(Mime(TopLevel::Image, _, _))) => { let page = format!("", 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 usage and @@ -325,10 +330,10 @@ pub fn parse_html(document: JSRef<Document>, // https://html.spec.whatwg.org/multipage/#read-text let page = format!("<pre>\u{000A}<plaintext>"); parser.parse_chunk(page); - parse_progress(parser, url, &load_response); + parse_progress(document, parser, url, &load_response); }, _ => { - parse_progress(parser, url, &load_response); + parse_progress(document, parser, url, &load_response); } } } @@ -349,16 +354,19 @@ pub fn parse_html_fragment(context_node: JSRef<Node>, output: &mut RootedVec<JS<Node>>) { let window = window_from_node(context_node).root(); let context_document = document_from_node(context_node).root(); - let url = context_document.r().url(); + let context_document = context_document.r(); + let url = context_document.url(); // Step 1. + let loader = DocumentLoader::new(&*context_document.loader()); let document = Document::new(window.r(), Some(url.clone()), IsHTMLDocument::HTMLDocument, None, None, - DocumentSource::FromParser).root(); + DocumentSource::FromParser, + loader).root(); // Step 2. - document.r().set_quirks_mode(context_document.r().quirks_mode()); + document.r().set_quirks_mode(context_document.quirks_mode()); // Step 11. let form = context_node.inclusive_ancestors() diff --git a/components/script/script_task.rs b/components/script/script_task.rs index b7fc3198511..93b3c794234 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -19,6 +19,7 @@ #![allow(unsafe_code)] +use document_loader::{DocumentLoader, NotifierData}; use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState}; use dom::bindings::codegen::InheritTypes::{ElementCast, EventTargetCast, HTMLIFrameElementCast, NodeCast, EventCast}; @@ -65,7 +66,7 @@ use msg::constellation_msg::{ConstellationChan, FocusType}; use msg::constellation_msg::{LoadData, PipelineId, SubpageId, MozBrowserEvent, WorkerId}; use msg::constellation_msg::{Failure, WindowSizeData, PipelineExitType}; use msg::constellation_msg::Msg as ConstellationMsg; -use net_traits::{ResourceTask, ControlMsg, LoadResponse, LoadConsumer}; +use net_traits::{ResourceTask, LoadResponse, LoadConsumer, ControlMsg}; use net_traits::LoadData as NetLoadData; use net_traits::image_cache_task::{ImageCacheChan, ImageCacheTask, ImageCacheResult}; use net_traits::storage_task::StorageTask; @@ -190,6 +191,8 @@ pub enum ScriptMsg { 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. + DocumentLoadsComplete(PipelineId), } /// A cloneable interface for communicating with an event loop. @@ -753,6 +756,8 @@ impl ScriptTask { LiveDOMReferences::cleanup(self.get_cx(), addr), ScriptMsg::PageFetchComplete(id, subpage, response) => self.handle_page_fetch_complete(id, subpage, response), + ScriptMsg::DocumentLoadsComplete(id) => + self.handle_loads_complete(id), } } @@ -858,6 +863,25 @@ impl ScriptTask { self.start_page_load(new_load, load_data); } + fn handle_loads_complete(&self, pipeline: PipelineId) { + let page = get_page(&self.root_page(), pipeline); + let doc = page.document().root(); + let doc = doc.r(); + if doc.loader().is_blocked() { + return; + } + + doc.mut_loader().inhibit_events(); + + // https://html.spec.whatwg.org/multipage/#the-end step 7 + let addr: Trusted<Document> = Trusted::new(self.get_cx(), doc, self.chan.clone()); + let handler = box DocumentProgressHandler::new(addr.clone(), DocumentProgressTask::Load); + self.chan.send(ScriptMsg::RunnableMsg(handler)).unwrap(); + + let ConstellationChan(ref chan) = self.constellation_chan; + chan.send(ConstellationMsg::LoadComplete).unwrap(); + } + /// Handles a timer that fired. fn handle_fire_timer_msg(&self, id: PipelineId, timer_id: TimerId) { let page = self.root_page(); @@ -1142,12 +1166,20 @@ impl ScriptTask { _ => None }; + let notifier_data = NotifierData { + script_chan: self.chan.clone(), + pipeline: page.pipeline(), + }; + let loader = DocumentLoader::new_with_task(self.resource_task.clone(), + Some(notifier_data), + Some(final_url.clone())); let document = Document::new(window.r(), Some(final_url.clone()), IsHTMLDocument::HTMLDocument, content_type, last_modified, - DocumentSource::FromParser).root(); + DocumentSource::FromParser, + loader).root(); let frame_element = frame_element.r().map(|elem| ElementCast::from_ref(elem)); window.r().init_browser_context(document.r(), frame_element); @@ -1422,22 +1454,11 @@ impl ScriptTask { // https://html.spec.whatwg.org/multipage/#the-end step 4 let addr: Trusted<Document> = Trusted::new(self.get_cx(), document.r(), self.chan.clone()); - let handler = box DocumentProgressHandler::new(addr.clone(), DocumentProgressTask::DOMContentLoaded); - self.chan.send(ScriptMsg::RunnableMsg(handler)).unwrap(); - - // We have no concept of a document loader right now, so just dispatch the - // "load" event as soon as we've finished executing all scripts parsed during - // the initial load. - - // https://html.spec.whatwg.org/multipage/#the-end step 7 - let handler = box DocumentProgressHandler::new(addr, DocumentProgressTask::Load); + let handler = box DocumentProgressHandler::new(addr, DocumentProgressTask::DOMContentLoaded); self.chan.send(ScriptMsg::RunnableMsg(handler)).unwrap(); window.r().set_fragment_name(final_url.fragment.clone()); - let ConstellationChan(ref chan) = self.constellation_chan; - chan.send(ConstellationMsg::LoadComplete).unwrap(); - // Notify devtools that a new script global exists. self.notify_devtools(document.r().Title(), final_url, (id, None)); } From 32a89c945593917f0d7d3c75231b920cf218f2b3 Mon Sep 17 00:00:00 2001 From: Josh Matthews <josh@joshmatthews.net> Date: Fri, 17 Oct 2014 11:11:46 -0400 Subject: [PATCH 2/4] Make stylesheets block page load. --- components/compositing/pipeline.rs | 1 - components/layout/layout_task.rs | 23 +++++----- components/layout_traits/lib.rs | 2 - components/net_traits/lib.rs | 56 ++++++++++++++++++++++-- components/script/document_loader.rs | 22 ++++++---- components/script/dom/document.rs | 8 +++- components/script/dom/htmllinkelement.rs | 8 +++- components/script/layout_interface.rs | 3 +- components/script/script_task.rs | 10 ++++- components/script_traits/lib.rs | 3 ++ 10 files changed, 106 insertions(+), 30 deletions(-) diff --git a/components/compositing/pipeline.rs b/components/compositing/pipeline.rs index 06d85a2ca2a..82250d44b65 100644 --- a/components/compositing/pipeline.rs +++ b/components/compositing/pipeline.rs @@ -153,7 +153,6 @@ impl Pipeline { failure, script_chan.clone(), paint_chan.clone(), - resource_task, image_cache_task, font_cache_task, time_profiler_chan, diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 324c765280e..38a2e11c3a6 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -45,7 +45,7 @@ use msg::constellation_msg::{ConstellationChan, Failure, PipelineExitType, Pipel use profile_traits::mem::{self, Report, ReportsChan}; use profile_traits::time::{self, ProfilerMetadata, profile}; use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType}; -use net_traits::{load_bytes_iter, ResourceTask}; +use net_traits::{load_bytes_iter, PendingAsyncLoad}; use net_traits::image_cache_task::{ImageCacheTask, ImageCacheResult, ImageCacheChan}; use script::dom::bindings::js::LayoutJS; use script::dom::node::{LayoutData, Node}; @@ -170,8 +170,8 @@ pub struct LayoutTask { /// The name used for the task's memory reporter. pub reporter_name: String, - /// The channel on which messages can be sent to the resource task. - pub resource_task: ResourceTask, + /// The channel on which messages can be sent to the image cache. + pub image_cache_task: ImageCacheTask, /// Public interface to the font cache task. pub font_cache_task: FontCacheTask, @@ -198,7 +198,6 @@ impl LayoutTaskFactory for LayoutTask { failure_msg: Failure, script_chan: ScriptControlChan, paint_chan: PaintChan, - resource_task: ResourceTask, image_cache_task: ImageCacheTask, font_cache_task: FontCacheTask, time_profiler_chan: time::ProfilerChan, @@ -217,7 +216,6 @@ impl LayoutTaskFactory for LayoutTask { constellation_chan, script_chan, paint_chan, - resource_task, image_cache_task, font_cache_task, time_profiler_chan, @@ -270,7 +268,6 @@ impl LayoutTask { constellation_chan: ConstellationChan, script_chan: ScriptControlChan, paint_chan: PaintChan, - resource_task: ResourceTask, image_cache_task: ImageCacheTask, font_cache_task: FontCacheTask, time_profiler_chan: time::ProfilerChan, @@ -311,7 +308,7 @@ impl LayoutTask { time_profiler_chan: time_profiler_chan, mem_profiler_chan: mem_profiler_chan, reporter_name: reporter_name, - resource_task: resource_task, + image_cache_task: image_cache_task.clone(), font_cache_task: font_cache_task, first_reflow: Cell::new(true), image_cache_receiver: image_cache_receiver, @@ -489,8 +486,8 @@ impl LayoutTask { Msg::AddStylesheet(sheet, mq) => { self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data) } - Msg::LoadStylesheet(url, mq) => { - self.handle_load_stylesheet(url, mq, possibly_locked_rw_data) + Msg::LoadStylesheet(url, mq, pending) => { + self.handle_load_stylesheet(url, mq, pending, possibly_locked_rw_data) } Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data), Msg::GetRPC(response_chan) => { @@ -595,13 +592,14 @@ impl LayoutTask { fn handle_load_stylesheet<'a>(&'a self, url: Url, mq: MediaQueryList, + pending: PendingAsyncLoad, possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) { // TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding let environment_encoding = UTF_8 as EncodingRef; // TODO we don't really even need to load this if mq does not match - let (metadata, iter) = load_bytes_iter(&self.resource_task, url); + let (metadata, iter) = load_bytes_iter(pending); let protocol_encoding_label = metadata.charset.as_ref().map(|s| &**s); let final_url = metadata.final_url; @@ -610,6 +608,11 @@ impl LayoutTask { protocol_encoding_label, Some(environment_encoding), Origin::Author); + + //TODO: mark critical subresources as blocking load as well + let ScriptControlChan(ref chan) = self.script_chan; + chan.send(ConstellationControlMsg::StylesheetLoadComplete(self.id, url)).unwrap(); + self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data); } diff --git a/components/layout_traits/lib.rs b/components/layout_traits/lib.rs index f37cfa194bb..47732f36285 100644 --- a/components/layout_traits/lib.rs +++ b/components/layout_traits/lib.rs @@ -20,7 +20,6 @@ use gfx::paint_task::PaintChan; use msg::constellation_msg::{ConstellationChan, Failure, PipelineId, PipelineExitType}; use profile_traits::mem; use profile_traits::time; -use net_traits::ResourceTask; use net_traits::image_cache_task::ImageCacheTask; use url::Url; use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel}; @@ -49,7 +48,6 @@ pub trait LayoutTaskFactory { failure_msg: Failure, script_chan: ScriptControlChan, paint_chan: PaintChan, - resource_task: ResourceTask, image_cache_task: ImageCacheTask, font_cache_task: FontCacheTask, time_profiler_chan: time::ProfilerChan, diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs index c5f5fb884e8..533e4fc42fb 100644 --- a/components/net_traits/lib.rs +++ b/components/net_traits/lib.rs @@ -125,6 +125,56 @@ pub enum ControlMsg { Exit } +/// Initialized but unsent request. Encapsulates everything necessary to instruct +/// the resource task to make a new request. +pub struct PendingAsyncLoad { + resource_task: ResourceTask, + url: Url, + pipeline: Option<PipelineId>, + input_chan: Sender<LoadResponse>, + input_port: Receiver<LoadResponse>, + guard: PendingLoadGuard, +} + +struct PendingLoadGuard { + loaded: bool, +} + +impl PendingLoadGuard { + fn neuter(&mut self) { + self.loaded = true; + } +} + +impl Drop for PendingLoadGuard { + fn drop(&mut self) { + assert!(self.loaded) + } +} + +impl PendingAsyncLoad { + pub fn new(resource_task: ResourceTask, url: Url, pipeline: Option<PipelineId>) + -> PendingAsyncLoad { + let (tx, rx) = channel(); + PendingAsyncLoad { + resource_task: resource_task, + url: url, + pipeline: pipeline, + input_chan: tx, + input_port: rx, + guard: PendingLoadGuard { loaded: false, }, + } + } + + /// Initiate the network request associated with this pending load. + pub fn load(mut self) -> Receiver<LoadResponse> { + self.guard.neuter(); + let load_data = LoadData::new(self.url, self.pipeline); + self.resource_task.send(ControlMsg::Load(load_data, LoadConsumer::Channel(self.input_chan))).unwrap(); + self.input_port + } +} + /// Message sent in response to `Load`. Contains metadata, and a port /// for receiving the data. /// @@ -230,10 +280,8 @@ pub fn load_whole_resource(resource_task: &ResourceTask, url: Url) } /// Load a URL asynchronously and iterate over chunks of bytes from the response. -pub fn load_bytes_iter(resource_task: &ResourceTask, url: Url) -> (Metadata, ProgressMsgPortIterator) { - let (input_chan, input_port) = channel(); - resource_task.send(ControlMsg::Load(LoadData::new(url, None), LoadConsumer::Channel(input_chan))).unwrap(); - +pub fn load_bytes_iter(pending: PendingAsyncLoad) -> (Metadata, ProgressMsgPortIterator) { + let input_port = pending.load(); let response = input_port.recv().unwrap(); let iter = ProgressMsgPortIterator { progress_port: response.progress_port }; (response.metadata, iter) diff --git a/components/script/document_loader.rs b/components/script/document_loader.rs index bf759788c57..bd144ed6547 100644 --- a/components/script/document_loader.rs +++ b/components/script/document_loader.rs @@ -7,11 +7,10 @@ use script_task::{ScriptMsg, ScriptChan}; use msg::constellation_msg::{PipelineId}; -use net_traits::{LoadResponse, Metadata, load_whole_resource, ResourceTask}; -use net_traits::{ControlMsg, LoadData, LoadConsumer}; +use net_traits::{LoadResponse, Metadata, load_whole_resource, ResourceTask, PendingAsyncLoad}; use url::Url; -use std::sync::mpsc::{Receiver, channel}; +use std::sync::mpsc::Receiver; #[jstraceable] #[derive(PartialEq, Clone)] @@ -69,15 +68,21 @@ impl DocumentLoader { } } - pub fn load_async(&mut self, load: LoadType) -> Receiver<LoadResponse> { - let (tx, rx) = channel(); + /// Create a new pending network request, which can be initiated at some point in + /// the future. + pub fn prep_async_load(&mut self, load: LoadType) -> PendingAsyncLoad { self.blocking_loads.push(load.clone()); let pipeline = self.notifier_data.as_ref().map(|data| data.pipeline); - let load_data = LoadData::new(load.url().clone(), pipeline); - self.resource_task.send(ControlMsg::Load(load_data, LoadConsumer::Channel(tx))).unwrap(); - rx + PendingAsyncLoad::new(self.resource_task.clone(), load.url().clone(), pipeline) } + /// Create and initiate a new network request. + pub fn load_async(&mut self, load: LoadType) -> Receiver<LoadResponse> { + let pending = self.prep_async_load(load); + pending.load() + } + + /// Create, initiate, and await the response for a new network request. pub fn load_sync(&mut self, load: LoadType) -> Result<(Metadata, Vec<u8>), String> { self.blocking_loads.push(load.clone()); let result = load_whole_resource(&self.resource_task, load.url().clone()); @@ -85,6 +90,7 @@ impl DocumentLoader { result } + /// Mark an in-progress network request complete. pub fn finish_load(&mut self, load: LoadType) { let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == load); self.blocking_loads.remove(idx.expect("unknown completed load")); diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 411cd1fefe7..2c11b33b04c 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -72,7 +72,7 @@ use msg::constellation_msg::{ConstellationChan, FocusType, Key, KeyState, KeyMod use msg::constellation_msg::{SUPER, ALT, SHIFT, CONTROL}; use net_traits::CookieSource::NonHTTP; use net_traits::ControlMsg::{SetCookiesForUrl, GetCookiesForUrl}; -use net_traits::{Metadata, LoadResponse}; +use net_traits::{Metadata, LoadResponse, PendingAsyncLoad}; use script_task::Runnable; use script_traits::{MouseButton, UntrustedNodeAddress}; use util::opts; @@ -260,6 +260,7 @@ pub trait DocumentHelpers<'a> { fn cancel_animation_frame(self, ident: i32); /// http://w3c.github.io/animation-timing/#dfn-invoke-callbacks-algorithm fn invoke_animation_callbacks(self); + fn prep_async_load(self, load: LoadType) -> PendingAsyncLoad; fn load_async(self, load: LoadType) -> Receiver<LoadResponse>; fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec<u8>), String>; fn finish_load(self, load: LoadType); @@ -884,6 +885,11 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { } } + fn prep_async_load(self, load: LoadType) -> PendingAsyncLoad { + let mut loader = self.loader.borrow_mut(); + loader.prep_async_load(load) + } + fn load_async(self, load: LoadType) -> Receiver<LoadResponse> { let mut loader = self.loader.borrow_mut(); loader.load_async(load) diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 1189b21af46..49e9bf42b1d 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -2,15 +2,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use document_loader::LoadType; use dom::attr::{Attr, AttrValue}; use dom::attr::AttrHelpers; use dom::bindings::codegen::Bindings::HTMLLinkElementBinding; use dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods; +use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::codegen::InheritTypes::HTMLLinkElementDerived; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; use dom::bindings::js::{JS, JSRef, MutNullableHeap, Rootable, Temporary}; use dom::bindings::js::{OptionalRootable, RootedReference}; -use dom::document::Document; +use dom::document::{Document, DocumentHelpers}; use dom::domtokenlist::DOMTokenList; use dom::element::{AttributeHandlers, Element}; use dom::eventtarget::{EventTarget, EventTargetTypeId}; @@ -149,8 +151,10 @@ impl<'a> PrivateHTMLLinkElementHelpers for JSRef<'a, HTMLLinkElement> { let mut css_parser = CssParser::new(&mq_str); let media = parse_media_query_list(&mut css_parser); + let doc = window.Document().root(); + let pending = doc.r().prep_async_load(LoadType::Stylesheet(url.clone())); let LayoutChan(ref layout_chan) = window.layout_chan(); - layout_chan.send(Msg::LoadStylesheet(url, media)).unwrap(); + layout_chan.send(Msg::LoadStylesheet(url, media, pending)).unwrap(); } Err(e) => debug!("Parsing url {} failed: {}", href, e) } diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs index 34d21e7e788..22f11462db1 100644 --- a/components/script/layout_interface.rs +++ b/components/script/layout_interface.rs @@ -12,6 +12,7 @@ use geom::point::Point2D; use geom::rect::Rect; use libc::uintptr_t; use msg::constellation_msg::{PipelineExitType, WindowSizeData}; +use net_traits::PendingAsyncLoad; use profile_traits::mem::{Reporter, ReportsChan}; use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel, UntrustedNodeAddress}; use std::any::Any; @@ -30,7 +31,7 @@ pub enum Msg { AddStylesheet(Stylesheet, MediaQueryList), /// Adds the given stylesheet to the document. - LoadStylesheet(Url, MediaQueryList), + LoadStylesheet(Url, MediaQueryList, PendingAsyncLoad), /// Puts a document into quirks mode, causing the quirks mode stylesheet to be loaded. SetQuirksMode, diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 93b3c794234..3f6ce8c94fb 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -19,7 +19,7 @@ #![allow(unsafe_code)] -use document_loader::{DocumentLoader, NotifierData}; +use document_loader::{LoadType, DocumentLoader, NotifierData}; use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState}; use dom::bindings::codegen::InheritTypes::{ElementCast, EventTargetCast, HTMLIFrameElementCast, NodeCast, EventCast}; @@ -731,6 +731,8 @@ impl ScriptTask { self.handle_webdriver_msg(pipeline_id, msg), ConstellationControlMsg::TickAllAnimations(pipeline_id) => self.handle_tick_all_animations(pipeline_id), + ConstellationControlMsg::StylesheetLoadComplete(id, url) => + self.handle_resource_loaded(id, LoadType::Stylesheet(url)), } } @@ -839,6 +841,12 @@ impl ScriptTask { } /// Handle a request to load a page in a new child frame of an existing page. + fn handle_resource_loaded(&self, pipeline: PipelineId, load: LoadType) { + let page = get_page(&self.root_page(), pipeline); + let doc = page.document().root(); + doc.r().finish_load(load); + } + fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) { let NewLayoutInfo { containing_pipeline_id, diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 62143373f0a..c77ebe97c72 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -29,6 +29,7 @@ use net_traits::storage_task::StorageTask; use std::any::Any; use std::sync::mpsc::{Sender, Receiver}; use webdriver_traits::WebDriverScriptCommand; +use url::Url; use geom::point::Point2D; use geom::rect::Rect; @@ -89,6 +90,8 @@ pub enum ConstellationControlMsg { WebDriverCommand(PipelineId, WebDriverScriptCommand), /// Notifies script task that all animations are done TickAllAnimations(PipelineId), + /// Notifies script that a stylesheet has finished loading. + StylesheetLoadComplete(PipelineId, Url), } /// The mouse button involved in the event. From f3cdba6b8bbd51eedb05bdb4c21073f4fe41e9a8 Mon Sep 17 00:00:00 2001 From: Josh Matthews <josh@joshmatthews.net> Date: Wed, 22 Apr 2015 20:12:24 -0400 Subject: [PATCH 3/4] Make link elements fire a load event. --- components/layout/layout_task.rs | 9 ++--- components/script/dom/htmllinkelement.rs | 35 +++++++++++++++++-- components/script/layout_interface.rs | 3 +- components/script/script_task.rs | 6 ++-- components/script_traits/lib.rs | 6 +++- tests/wpt/mozilla/meta/MANIFEST.json | 6 ++++ .../mozilla/tests/mozilla/resources/style.css | 3 ++ .../tests/mozilla/stylesheet_load.html | 20 +++++++++++ 8 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 tests/wpt/mozilla/tests/mozilla/resources/style.css create mode 100644 tests/wpt/mozilla/tests/mozilla/stylesheet_load.html diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 38a2e11c3a6..c29f421f80a 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -54,7 +54,7 @@ use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC}; use script::layout_interface::{MouseOverResponse, Msg, Reflow, ReflowGoal, ReflowQueryType}; use script::layout_interface::{ScriptLayoutChan, ScriptReflow, TrustedNodeAddress}; use script_traits::{ConstellationControlMsg, OpaqueScriptLayoutChannel}; -use script_traits::ScriptControlChan; +use script_traits::{ScriptControlChan, StylesheetLoadResponder}; use std::borrow::ToOwned; use std::cell::Cell; use std::mem::transmute; @@ -486,8 +486,8 @@ impl LayoutTask { Msg::AddStylesheet(sheet, mq) => { self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data) } - Msg::LoadStylesheet(url, mq, pending) => { - self.handle_load_stylesheet(url, mq, pending, possibly_locked_rw_data) + Msg::LoadStylesheet(url, mq, pending, link_element) => { + self.handle_load_stylesheet(url, mq, pending, link_element, possibly_locked_rw_data) } Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data), Msg::GetRPC(response_chan) => { @@ -593,6 +593,7 @@ impl LayoutTask { url: Url, mq: MediaQueryList, pending: PendingAsyncLoad, + responder: Box<StylesheetLoadResponder+Send>, possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) { // TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding @@ -611,7 +612,7 @@ impl LayoutTask { //TODO: mark critical subresources as blocking load as well let ScriptControlChan(ref chan) = self.script_chan; - chan.send(ConstellationControlMsg::StylesheetLoadComplete(self.id, url)).unwrap(); + chan.send(ConstellationControlMsg::StylesheetLoadComplete(self.id, url, responder)).unwrap(); self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data); } diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 49e9bf42b1d..fd2d3471a61 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -8,13 +8,16 @@ use dom::attr::AttrHelpers; use dom::bindings::codegen::Bindings::HTMLLinkElementBinding; use dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::codegen::InheritTypes::HTMLLinkElementDerived; +use dom::bindings::codegen::InheritTypes::{EventTargetCast, HTMLLinkElementDerived}; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; +use dom::bindings::global::GlobalRef; use dom::bindings::js::{JS, JSRef, MutNullableHeap, Rootable, Temporary}; use dom::bindings::js::{OptionalRootable, RootedReference}; +use dom::bindings::refcounted::Trusted; use dom::document::{Document, DocumentHelpers}; use dom::domtokenlist::DOMTokenList; use dom::element::{AttributeHandlers, Element}; +use dom::event::{EventBubbles, EventCancelable, Event, EventHelpers}; use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; @@ -22,6 +25,7 @@ use dom::node::{Node, NodeHelpers, NodeTypeId, window_from_node}; use dom::virtualmethods::VirtualMethods; use dom::window::WindowHelpers; use layout_interface::{LayoutChan, Msg}; +use script_traits::StylesheetLoadResponder; use util::str::{DOMString, HTML_SPACE_CHARACTERS}; use style::media_queries::parse_media_query_list; use style::node::TElement; @@ -152,9 +156,12 @@ impl<'a> PrivateHTMLLinkElementHelpers for JSRef<'a, HTMLLinkElement> { let media = parse_media_query_list(&mut css_parser); let doc = window.Document().root(); + let link_element = Trusted::new(window.get_cx(), self, window.script_chan().clone()); + let load_dispatcher = StylesheetLoadDispatcher::new(link_element); + let pending = doc.r().prep_async_load(LoadType::Stylesheet(url.clone())); let LayoutChan(ref layout_chan) = window.layout_chan(); - layout_chan.send(Msg::LoadStylesheet(url, media, pending)).unwrap(); + layout_chan.send(Msg::LoadStylesheet(url, media, pending, box load_dispatcher)).unwrap(); } Err(e) => debug!("Parsing url {} failed: {}", href, e) } @@ -183,3 +190,27 @@ impl<'a> HTMLLinkElementMethods for JSRef<'a, HTMLLinkElement> { }) } } + +pub struct StylesheetLoadDispatcher { + elem: Trusted<HTMLLinkElement>, +} + +impl StylesheetLoadDispatcher { + pub fn new(elem: Trusted<HTMLLinkElement>) -> StylesheetLoadDispatcher { + StylesheetLoadDispatcher { + elem: elem, + } + } +} + +impl StylesheetLoadResponder for StylesheetLoadDispatcher { + fn respond(self: Box<StylesheetLoadDispatcher>) { + let elem = self.elem.to_temporary().root(); + let window = window_from_node(elem.r()).root(); + let event = Event::new(GlobalRef::Window(window.r()), "load".to_owned(), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable).root(); + let target = EventTargetCast::from_ref(elem.r()); + event.r().fire(target); + } +} diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs index 22f11462db1..6fde5c4312f 100644 --- a/components/script/layout_interface.rs +++ b/components/script/layout_interface.rs @@ -15,6 +15,7 @@ use msg::constellation_msg::{PipelineExitType, WindowSizeData}; use net_traits::PendingAsyncLoad; use profile_traits::mem::{Reporter, ReportsChan}; use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel, UntrustedNodeAddress}; +use script_traits::StylesheetLoadResponder; use std::any::Any; use std::sync::mpsc::{channel, Receiver, Sender}; use style::animation::PropertyAnimation; @@ -31,7 +32,7 @@ pub enum Msg { AddStylesheet(Stylesheet, MediaQueryList), /// Adds the given stylesheet to the document. - LoadStylesheet(Url, MediaQueryList, PendingAsyncLoad), + LoadStylesheet(Url, MediaQueryList, PendingAsyncLoad, Box<StylesheetLoadResponder+Send>), /// Puts a document into quirks mode, causing the quirks mode stylesheet to be loaded. SetQuirksMode, diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 3f6ce8c94fb..34faad42ec4 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -731,8 +731,10 @@ impl ScriptTask { self.handle_webdriver_msg(pipeline_id, msg), ConstellationControlMsg::TickAllAnimations(pipeline_id) => self.handle_tick_all_animations(pipeline_id), - ConstellationControlMsg::StylesheetLoadComplete(id, url) => - self.handle_resource_loaded(id, LoadType::Stylesheet(url)), + ConstellationControlMsg::StylesheetLoadComplete(id, url, responder) => { + self.handle_resource_loaded(id, LoadType::Stylesheet(url)); + responder.respond(); + } } } diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index c77ebe97c72..2ed5685a00d 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -56,6 +56,10 @@ pub struct NewLayoutInfo { pub load_data: LoadData, } +pub trait StylesheetLoadResponder { + fn respond(self: Box<Self>); +} + /// Messages sent from the constellation to the script task pub enum ConstellationControlMsg { /// Gives a channel and ID to a layout task, as well as the ID of that layout's parent @@ -91,7 +95,7 @@ pub enum ConstellationControlMsg { /// Notifies script task that all animations are done TickAllAnimations(PipelineId), /// Notifies script that a stylesheet has finished loading. - StylesheetLoadComplete(PipelineId, Url), + StylesheetLoadComplete(PipelineId, Url, Box<StylesheetLoadResponder+Send>), } /// The mouse button involved in the event. diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 841aa8f1c5b..37b28df50ad 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -521,6 +521,12 @@ "url": "/_mozilla/mozilla/storage.html" } ], + "mozilla/stylesheet_load.html": [ + { + "path": "mozilla/stylesheet_load.html", + "url": "/_mozilla/mozilla/stylesheet_load.html" + } + ], "mozilla/textcontent.html": [ { "path": "mozilla/textcontent.html", diff --git a/tests/wpt/mozilla/tests/mozilla/resources/style.css b/tests/wpt/mozilla/tests/mozilla/resources/style.css new file mode 100644 index 00000000000..d48115e5652 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/resources/style.css @@ -0,0 +1,3 @@ +body { + background-color: white; +} \ No newline at end of file diff --git a/tests/wpt/mozilla/tests/mozilla/stylesheet_load.html b/tests/wpt/mozilla/tests/mozilla/stylesheet_load.html new file mode 100644 index 00000000000..bf095725a5c --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/stylesheet_load.html @@ -0,0 +1,20 @@ +<html> +<head> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link href="resources/style.css" rel="stylesheet" id="style_test"></link> +<script> +var saw_link_onload = false; +var t = async_test(); +document.getElementById('style_test').onload = t.step_func(function() { + saw_link_onload = true; +}); +window.addEventListener('load', function() { + t.step_func(function() { + assert_true(saw_link_onload); + }); + t.done(); +}, false); +</script> +</head> +</html> From a1ae53a230cf8643dc768669b52b386e62882253 Mon Sep 17 00:00:00 2001 From: Josh Matthews <josh@joshmatthews.net> Date: Thu, 23 Apr 2015 10:45:57 -0400 Subject: [PATCH 4/4] Delay stylesheet load in test to increase confidence. --- components/compositing/pipeline.rs | 2 +- components/layout/layout_task.rs | 2 +- components/net_traits/lib.rs | 18 +-- components/script/document_loader.rs | 14 +- components/script/dom/document.rs | 6 +- components/script/dom/htmllinkelement.rs | 2 +- components/script/script_task.rs | 2 +- tests/wpt/metadata/MANIFEST.json | 6 +- .../relevant-mutations.html.ini | 131 +++++++----------- tests/wpt/mozilla/meta/MANIFEST.json | 6 - .../the-link-element/link-load-event.html} | 9 +- .../the-link-element}/style.css | 0 12 files changed, 82 insertions(+), 116 deletions(-) rename tests/wpt/{mozilla/tests/mozilla/stylesheet_load.html => web-platform-tests/html/semantics/document-metadata/the-link-element/link-load-event.html} (53%) rename tests/wpt/{mozilla/tests/mozilla/resources => web-platform-tests/html/semantics/document-metadata/the-link-element}/style.css (100%) diff --git a/components/compositing/pipeline.rs b/components/compositing/pipeline.rs index 82250d44b65..2402ed23f1f 100644 --- a/components/compositing/pipeline.rs +++ b/components/compositing/pipeline.rs @@ -108,7 +108,7 @@ impl Pipeline { script_port, constellation_chan.clone(), failure.clone(), - resource_task.clone(), + resource_task, storage_task.clone(), image_cache_task.clone(), devtools_chan, diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index c29f421f80a..a7c89ded388 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -610,7 +610,7 @@ impl LayoutTask { Some(environment_encoding), Origin::Author); - //TODO: mark critical subresources as blocking load as well + //TODO: mark critical subresources as blocking load as well (#5974) let ScriptControlChan(ref chan) = self.script_chan; chan.send(ConstellationControlMsg::StylesheetLoadComplete(self.id, url, responder)).unwrap(); diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs index 533e4fc42fb..46fcc476843 100644 --- a/components/net_traits/lib.rs +++ b/components/net_traits/lib.rs @@ -126,13 +126,14 @@ pub enum ControlMsg { } /// Initialized but unsent request. Encapsulates everything necessary to instruct -/// the resource task to make a new request. +/// the resource task to make a new request. The `load` method *must* be called before +/// destruction or the task will panic. pub struct PendingAsyncLoad { resource_task: ResourceTask, url: Url, pipeline: Option<PipelineId>, - input_chan: Sender<LoadResponse>, - input_port: Receiver<LoadResponse>, + input_sender: Sender<LoadResponse>, + input_receiver: Receiver<LoadResponse>, guard: PendingLoadGuard, } @@ -155,13 +156,13 @@ impl Drop for PendingLoadGuard { impl PendingAsyncLoad { pub fn new(resource_task: ResourceTask, url: Url, pipeline: Option<PipelineId>) -> PendingAsyncLoad { - let (tx, rx) = channel(); + let (sender, receiver) = channel(); PendingAsyncLoad { resource_task: resource_task, url: url, pipeline: pipeline, - input_chan: tx, - input_port: rx, + input_sender: sender, + input_receiver: receiver, guard: PendingLoadGuard { loaded: false, }, } } @@ -170,8 +171,9 @@ impl PendingAsyncLoad { pub fn load(mut self) -> Receiver<LoadResponse> { self.guard.neuter(); let load_data = LoadData::new(self.url, self.pipeline); - self.resource_task.send(ControlMsg::Load(load_data, LoadConsumer::Channel(self.input_chan))).unwrap(); - self.input_port + let consumer = LoadConsumer::Channel(self.input_sender); + self.resource_task.send(ControlMsg::Load(load_data, consumer)).unwrap(); + self.input_receiver } } diff --git a/components/script/document_loader.rs b/components/script/document_loader.rs index bd144ed6547..6b8dfcea801 100644 --- a/components/script/document_loader.rs +++ b/components/script/document_loader.rs @@ -56,10 +56,7 @@ impl DocumentLoader { data: Option<NotifierData>, initial_load: Option<Url>,) -> DocumentLoader { - let mut initial_loads = vec!(); - if let Some(load) = initial_load { - initial_loads.push(LoadType::PageSource(load)); - } + let initial_loads = initial_load.into_iter().map(LoadType::PageSource).collect(); DocumentLoader { resource_task: resource_task, @@ -70,15 +67,16 @@ impl DocumentLoader { /// Create a new pending network request, which can be initiated at some point in /// the future. - pub fn prep_async_load(&mut self, load: LoadType) -> PendingAsyncLoad { - self.blocking_loads.push(load.clone()); + pub fn prepare_async_load(&mut self, load: LoadType) -> PendingAsyncLoad { + let url = load.url().clone(); + self.blocking_loads.push(load); let pipeline = self.notifier_data.as_ref().map(|data| data.pipeline); - PendingAsyncLoad::new(self.resource_task.clone(), load.url().clone(), pipeline) + PendingAsyncLoad::new(self.resource_task.clone(), url, pipeline) } /// Create and initiate a new network request. pub fn load_async(&mut self, load: LoadType) -> Receiver<LoadResponse> { - let pending = self.prep_async_load(load); + let pending = self.prepare_async_load(load); pending.load() } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 2c11b33b04c..e08e47f85fe 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -260,7 +260,7 @@ pub trait DocumentHelpers<'a> { fn cancel_animation_frame(self, ident: i32); /// http://w3c.github.io/animation-timing/#dfn-invoke-callbacks-algorithm fn invoke_animation_callbacks(self); - fn prep_async_load(self, load: LoadType) -> PendingAsyncLoad; + fn prepare_async_load(self, load: LoadType) -> PendingAsyncLoad; fn load_async(self, load: LoadType) -> Receiver<LoadResponse>; fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec<u8>), String>; fn finish_load(self, load: LoadType); @@ -885,9 +885,9 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { } } - fn prep_async_load(self, load: LoadType) -> PendingAsyncLoad { + fn prepare_async_load(self, load: LoadType) -> PendingAsyncLoad { let mut loader = self.loader.borrow_mut(); - loader.prep_async_load(load) + loader.prepare_async_load(load) } fn load_async(self, load: LoadType) -> Receiver<LoadResponse> { diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index fd2d3471a61..b8dc06f8080 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -159,7 +159,7 @@ impl<'a> PrivateHTMLLinkElementHelpers for JSRef<'a, HTMLLinkElement> { let link_element = Trusted::new(window.get_cx(), self, window.script_chan().clone()); let load_dispatcher = StylesheetLoadDispatcher::new(link_element); - let pending = doc.r().prep_async_load(LoadType::Stylesheet(url.clone())); + let pending = doc.r().prepare_async_load(LoadType::Stylesheet(url.clone())); let LayoutChan(ref layout_chan) = window.layout_chan(); layout_chan.send(Msg::LoadStylesheet(url, media, pending, box load_dispatcher)).unwrap(); } diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 34faad42ec4..775e45b29a8 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -732,8 +732,8 @@ impl ScriptTask { ConstellationControlMsg::TickAllAnimations(pipeline_id) => self.handle_tick_all_animations(pipeline_id), ConstellationControlMsg::StylesheetLoadComplete(id, url, responder) => { - self.handle_resource_loaded(id, LoadType::Stylesheet(url)); responder.respond(); + self.handle_resource_loaded(id, LoadType::Stylesheet(url)); } } } diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index 3a6c2845cd5..1ac47eda7ca 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -12553,6 +12553,10 @@ "path": "html/semantics/document-metadata/the-base-element/base_multiple.html", "url": "/html/semantics/document-metadata/the-base-element/base_multiple.html" }, + { + "path": "html/semantics/document-metadata/the-link-element/link-load-event.html", + "url": "/html/semantics/document-metadata/the-link-element/link-load-event.html" + }, { "path": "html/semantics/document-metadata/the-link-element/link-rellist.html", "url": "/html/semantics/document-metadata/the-link-element/link-rellist.html" @@ -25924,4 +25928,4 @@ "rev": "fef3eb9bbb033d1d7150f4c70ecc1a5f59bcf115", "url_base": "/", "version": 2 -} \ No newline at end of file +} diff --git a/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/relevant-mutations.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/relevant-mutations.html.ini index 97f48f3e0f8..7e20c7aa0eb 100644 --- a/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/relevant-mutations.html.ini +++ b/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/relevant-mutations.html.ini @@ -2,7 +2,55 @@ type: testharness expected: TIMEOUT [src removed] - expected: FAIL + expected: TIMEOUT + + [inserted into picture] + expected: TIMEOUT + + [removed from picture] + expected: TIMEOUT + + [parent is picture, previous source inserted] + expected: TIMEOUT + + [parent is picture, previous source removed] + expected: TIMEOUT + + [parent is picture, previous source has srcset set] + expected: TIMEOUT + + [parent is picture, previous source has srcset changed] + expected: TIMEOUT + + [parent is picture, previous source has srcset removed] + expected: TIMEOUT + + [parent is picture, previous source has sizes set] + expected: TIMEOUT + + [parent is picture, previous source has sizes changed] + expected: TIMEOUT + + [parent is picture, previous source has sizes removed] + expected: TIMEOUT + + [parent is picture, previous source has media set] + expected: TIMEOUT + + [parent is picture, previous source has media changed] + expected: TIMEOUT + + [parent is picture, previous source has media removed] + expected: TIMEOUT + + [parent is picture, previous source has type set] + expected: TIMEOUT + + [parent is picture, previous source has type changed] + expected: TIMEOUT + + [parent is picture, previous source has type removed] + expected: TIMEOUT [srcset set] expected: TIMEOUT @@ -52,84 +100,3 @@ [crossorigin use-credentials to anonymous] expected: TIMEOUT - [crossorigin state not changed: absent, removeAttribute] - expected: FAIL - - [crossorigin state not changed: empty to anonymous] - expected: FAIL - - [crossorigin state not changed: anonymous to foobar] - expected: FAIL - - [crossorigin state not changed: use-credentials to USE-CREDENTIALS] - expected: FAIL - - [inserted into picture ancestor] - expected: FAIL - - [removed from picture ancestor] - expected: FAIL - - [ancestor picture has a source inserted] - expected: FAIL - - [ancestor picture has a source removed] - expected: FAIL - - [ancestor picture; previous sibling source inserted] - expected: FAIL - - [ancestor picture; previous sibling source removed] - expected: FAIL - - [parent is picture, following sibling source inserted] - expected: FAIL - - [parent is picture, following sibling source removed] - expected: FAIL - - [parent is picture, following sibling source has srcset set] - expected: FAIL - - [media on img set] - expected: FAIL - - [type on img set] - expected: FAIL - - [class on img set] - expected: FAIL - - [alt on img set] - expected: FAIL - - [src on previous sibling source set] - expected: FAIL - - [class on previous sibling source set] - expected: FAIL - - [inserted/removed children of img] - expected: FAIL - - [picture is inserted] - expected: FAIL - - [picture is removed] - expected: FAIL - - [parent is picture, following img inserted] - expected: FAIL - - [parent is picture, following img removed] - expected: FAIL - - [parent is picture, following img has src set] - expected: FAIL - - [parent is picture, following img has srcset set] - expected: FAIL - - [parent is picture, following img has sizes set] - expected: FAIL - diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 37b28df50ad..841aa8f1c5b 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -521,12 +521,6 @@ "url": "/_mozilla/mozilla/storage.html" } ], - "mozilla/stylesheet_load.html": [ - { - "path": "mozilla/stylesheet_load.html", - "url": "/_mozilla/mozilla/stylesheet_load.html" - } - ], "mozilla/textcontent.html": [ { "path": "mozilla/textcontent.html", diff --git a/tests/wpt/mozilla/tests/mozilla/stylesheet_load.html b/tests/wpt/web-platform-tests/html/semantics/document-metadata/the-link-element/link-load-event.html similarity index 53% rename from tests/wpt/mozilla/tests/mozilla/stylesheet_load.html rename to tests/wpt/web-platform-tests/html/semantics/document-metadata/the-link-element/link-load-event.html index bf095725a5c..afe86a4210c 100644 --- a/tests/wpt/mozilla/tests/mozilla/stylesheet_load.html +++ b/tests/wpt/web-platform-tests/html/semantics/document-metadata/the-link-element/link-load-event.html @@ -1,11 +1,12 @@ -<html> -<head> +<!DOCTYPE html> +<link rel="author" title="Josh Matthews" href="mailto:josh@joshmatthews.net"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> -<link href="resources/style.css" rel="stylesheet" id="style_test"></link> +<link href="style.css?pipe=trickle(d3)" rel="stylesheet" id="style_test"></link> <script> var saw_link_onload = false; -var t = async_test(); +var t = async_test("Check if the stylesheet's load event blocks the document load event"); document.getElementById('style_test').onload = t.step_func(function() { saw_link_onload = true; }); diff --git a/tests/wpt/mozilla/tests/mozilla/resources/style.css b/tests/wpt/web-platform-tests/html/semantics/document-metadata/the-link-element/style.css similarity index 100% rename from tests/wpt/mozilla/tests/mozilla/resources/style.css rename to tests/wpt/web-platform-tests/html/semantics/document-metadata/the-link-element/style.css