Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement document load tracking. #3714

Closed
wants to merge 4 commits into from
Closed
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Next

Implement a DocumentLoader type that tracks pending loads and notifie…

…s the script task when the queue is empty. Replace the current DOM load event with DOMContentLoaded and dispatch the real load event based on the DocumentLoader's notification.
  • Loading branch information
jdm committed Jan 10, 2015
commit 72932f90aff72d5ec8d087cf652e062dea83d670
@@ -0,0 +1,96 @@
/* 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/syntax.html#the-end

use script_task::{ScriptMsg, ScriptChan};
use servo_msg::constellation_msg::{PipelineId};
use servo_net::resource_task::{LoadResponse, Metadata, load_whole_resource, ResourceTask};
use servo_net::resource_task::{ControlMsg, LoadData};
use url::Url;

#[jstraceable]
#[deriving(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,
script_chan: Box<ScriptChan + Send>,
blocking_loads: Vec<LoadType>,
pipeline: PipelineId,
notify: bool,
}

impl DocumentLoader {
pub fn new(existing: &DocumentLoader) -> DocumentLoader {
DocumentLoader::new_with_task(existing.resource_task.clone(),
existing.script_chan.clone(),
existing.pipeline)
}

pub fn new_with_task(resource_task: ResourceTask, script_chan: Box<ScriptChan + Send>,
pipeline: PipelineId) -> DocumentLoader {
DocumentLoader {
resource_task: resource_task,
script_chan: script_chan,
pipeline: pipeline,
blocking_loads: vec!(),
notify: true,
}
}

pub fn load_async(&mut self, load: LoadType) -> Receiver<LoadResponse> {
self.load_async_with(load, |_| {})
}

pub fn load_async_with(&mut self, load: LoadType, cb: |load_data: &mut LoadData|) -> Receiver<LoadResponse> {
let (tx, rx) = channel();
self.blocking_loads.push(load.clone());
let mut load_data = LoadData::new(load.url().clone(), tx);
cb(&mut load_data);
self.resource_task.send(ControlMsg::Load(load_data));
rx
}

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());
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 !self.is_blocked() && self.notify {
self.script_chan.send(ScriptMsg::DocumentLoadsComplete(self.pipeline));
}
}

pub fn is_blocked(&self) -> bool {
!self.blocking_loads.is_empty()
}

pub fn inhibit_events(&mut self) {
self.notify = false;
}
}
@@ -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::FromJSValConvertible;
use dom::bindings::js::{JS, JSRef, Root};
use dom::bindings::utils::{Reflectable, Reflector};
use dom::document::DocumentHelpers;
use dom::workerglobalscope::{WorkerGlobalScope, WorkerGlobalScopeHelpers};
use dom::window;
use script_task::ScriptChan;
@@ -68,7 +70,10 @@ impl<'a> GlobalRef<'a> {

pub fn resource_task(&self) -> ResourceTask {
match *self {
GlobalRef::Window(ref window) => window.page().resource_task.clone(),
GlobalRef::Window(ref window) => {
let doc = window.Document().root();
doc.r().loader().resource_task.clone()
}
GlobalRef::Worker(ref worker) => worker.resource_task().clone(),
}
}
@@ -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;
@@ -55,6 +56,7 @@ use dom::range::Range;
use dom::treewalker::TreeWalker;
use dom::uievent::UIEvent;
use dom::window::{Window, WindowHelpers};
use servo_net::resource_task::{LoadResponse, Metadata, LoadData};
use servo_util::namespace;
use servo_util::str::{DOMString, split_html_space_chars};

@@ -66,7 +68,7 @@ use url::Url;
use std::collections::HashMap;
use std::collections::hash_map::{Vacant, Occupied};
use std::ascii::AsciiExt;
use std::cell::{Cell, Ref};
use std::cell::{Cell, Ref, RefMut};
use std::default::Default;
use time;

@@ -101,6 +103,8 @@ pub struct Document {
possibly_focused: MutNullableJS<Element>,
/// The element that currently has the document focus context.
focused: MutNullableJS<Element>,

loader: DOMRefCell<DocumentLoader>,
}

impl DocumentDerived for EventTarget {
@@ -167,6 +171,8 @@ impl CollectionFilter for AppletsFilter {
}

pub trait DocumentHelpers<'a> {
fn loader(&self) -> Ref<DocumentLoader>;
fn mut_loader(&self) -> RefMut<DocumentLoader>;
fn window(self) -> Temporary<Window>;
fn encoding_name(self) -> Ref<'a, DOMString>;
fn is_html_document(self) -> bool;
@@ -188,9 +194,23 @@ pub trait DocumentHelpers<'a> {
fn commit_focus_transaction(self);
fn send_title_to_compositor(self);
fn dirty_all_nodes(self);
fn load_async(self, load: LoadType) -> Receiver<LoadResponse>;
fn load_async_with(self, load: LoadType, cb: |&mut LoadData|) -> Receiver<LoadResponse>;
fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec<u8>), String>;
fn finish_load(self, load: LoadType);
}

impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
#[inline]
fn loader(&self) -> Ref<DocumentLoader> {
self.loader.borrow()
}

#[inline]
fn mut_loader(&self) -> RefMut<DocumentLoader> {
self.loader.borrow_mut()
}

#[inline]
fn window(self) -> Temporary<Window> {
Temporary::new(self.window)
@@ -382,6 +402,22 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
node.dirty(NodeDamage::OtherNodeDamage)
}
}

fn load_async(self, load: LoadType) -> Receiver<LoadResponse> {
self.load_async_with(load, |_| {})
}

fn load_async_with(self, load: LoadType, cb: |load_data: &mut LoadData|) -> Receiver<LoadResponse> {
self.loader.borrow_mut().load_async_with(load, cb)
}

fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec<u8>), String> {

This comment has been minimized.

Copy link
@hsivonen

hsivonen Nov 24, 2014

Contributor

The very existence of machinery to load something synchronously worries me. If this is mean to be sync only from the Rust task point of view and never from the JS point of view, it would be good to have some comment to that effect to make this less scary.

This comment has been minimized.

Copy link
@jdm

jdm Dec 8, 2014

Author Member

I am implementing the machinery that will enable asynchronous network responses in #4057. Until then we live in a world where everything except XHR is synchronous.

self.loader.borrow_mut().load_sync(load)
}

fn finish_load(self, load: LoadType) {
self.loader.borrow_mut().finish_load(load);
}
}

#[deriving(PartialEq)]
@@ -407,7 +443,8 @@ impl Document {
url: Option<Url>,
is_html_document: IsHTMLDocument,
content_type: Option<DOMString>,
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 {
@@ -447,23 +484,29 @@ impl Document {
ready_state: Cell::new(ready_state),
possibly_focused: Default::default(),
focused: Default::default(),
loader: DOMRefCell::new(doc_loader),
}
}

// http://dom.spec.whatwg.org/#dom-document
pub fn Constructor(global: GlobalRef) -> Fallible<Temporary<Document>> {
Ok(Document::new(global.as_window(), None,
let win = global.as_window();
let doc = win.Document().root();
let docloader = DocumentLoader::new(&*doc.loader());
Ok(Document::new(win, None,
IsHTMLDocument::NonHTMLDocument, None,
DocumentSource::NotFromParser))
DocumentSource::NotFromParser, docloader))
}

pub fn new(window: JSRef<Window>,
url: Option<Url>,
doctype: IsHTMLDocument,
content_type: Option<DOMString>,
source: DocumentSource) -> Temporary<Document> {
source: DocumentSource,
docloader: DocumentLoader) -> Temporary<Document> {
let document = reflect_dom_object(box Document::new_inherited(window, url, doctype,
content_type, source),
content_type, source,
docloader),
GlobalRef::Window(window),
DocumentBinding::Wrap).root();

@@ -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;
@@ -69,10 +70,11 @@ impl<'a> DOMImplementationMethods for JSRef<'a, DOMImplementation> {
maybe_doctype: Option<JSRef<DocumentType>>) -> Fallible<Temporary<Document>> {
let doc = self.document.root();
let win = doc.r().window().root();
let loader = DocumentLoader::new(&*doc.r().loader());

// Step 1.
let doc = Document::new(win.r(), None, IsHTMLDocument::NonHTMLDocument,
None, DocumentSource::NotFromParser).root();
None, DocumentSource::NotFromParser, loader).root();
// Step 2-3.
let maybe_elem = if qname.is_empty() {
None
@@ -115,10 +117,11 @@ impl<'a> DOMImplementationMethods for JSRef<'a, DOMImplementation> {
fn CreateHTMLDocument(self, title: Option<DOMString>) -> Temporary<Document> {
let document = self.document.root();
let win = document.r().window().root();
let loader = DocumentLoader::new(&*document.r().loader());

// Step 1-2.
let doc = Document::new(win.r(), None, IsHTMLDocument::HTMLDocument, None,
DocumentSource::NotFromParser).root();
DocumentSource::NotFromParser, loader).root();
let doc_node: JSRef<Node> = NodeCast::from_ref(doc.r());

{
@@ -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::error::Error::FailureUnknown;
use dom::bindings::global::GlobalRef;
@@ -50,12 +52,15 @@ 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 uint].into_string();
let doc = window.r().Document().root();
let loader = DocumentLoader::new(&*doc.r().loader());
match ty {
Text_html => {
let document = Document::new(window.r(), Some(url.clone()),
IsHTMLDocument::HTMLDocument,
Some(content_type),
DocumentSource::FromParser).root();
DocumentSource::FromParser,
loader).root();
parse_html(document.r(), HTMLInput::InputString(s), &url);
document.r().set_ready_state(DocumentReadyState::Complete);
Ok(Temporary::from_rooted(document.r()))
@@ -65,7 +70,8 @@ impl<'a> DOMParserMethods for JSRef<'a, DOMParser> {
Ok(Document::new(window.r(), Some(url.clone()),
IsHTMLDocument::NonHTMLDocument,
Some(content_type),
DocumentSource::NotFromParser))
DocumentSource::NotFromParser,
loader))
}
_ => {
Err(FailureUnknown)
@@ -4,6 +4,7 @@

use std::ascii::AsciiExt;

use document_loader::LoadType;
use dom::attr::Attr;
use dom::attr::AttrHelpers;
use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
@@ -15,19 +16,19 @@ use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCas
use dom::bindings::codegen::InheritTypes::EventTargetCast;
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
use dom::document::Document;
use dom::document::{Document, DocumentHelpers};
use dom::element::{Element, AttributeHandlers, ElementCreator};
use dom::eventtarget::{EventTarget, EventTargetTypeId, EventTargetHelpers};
use dom::event::{Event, EventBubbles, EventCancelable, EventHelpers};
use dom::element::ElementTypeId;
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeHelpers, NodeTypeId, window_from_node, CloneChildrenFlag};
use dom::node::document_from_node;
use dom::virtualmethods::VirtualMethods;
use dom::window::ScriptHelpers;

use encoding::all::UTF_8;
use encoding::types::{Encoding, DecoderTrap};
use servo_net::resource_task::load_whole_resource;
use servo_util::str::{DOMString, HTML_SPACE_CHARACTERS, StaticStringVec};
use std::cell::Cell;
use string_cache::Atom;
@@ -187,7 +188,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.
match load_whole_resource(&page.resource_task, url) {
let doc = document_from_node(self).root();
let contents = doc.load_sync(LoadType::Script(url));
match contents {
Ok((metadata, bytes)) => {
// TODO: use the charset from step 13.
let source = UTF_8.decode(bytes.as_slice(), DecoderTrap::Replace).unwrap();
@@ -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::CharacterDataBinding::CharacterDataMethods;
@@ -1546,9 +1547,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().clone()),
is_html_doc, None,
DocumentSource::NotFromParser);
DocumentSource::NotFromParser, loader);
NodeCast::from_temporary(document)
},
NodeTypeId::Element(..) => {
@@ -44,6 +44,7 @@ extern crate string_cache;
extern crate string_cache_macros;

pub mod cors;
pub mod document_loader;

/// The implementation of the DOM.
#[macro_escape]
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.