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

Improve spec-compliance of script loading and execution during document startup #7979

Merged
merged 1 commit into from Oct 26, 2015
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -8,9 +8,7 @@
use msg::constellation_msg::{PipelineId};
use net_traits::AsyncResponseTarget;
use net_traits::{Metadata, PendingAsyncLoad, ResourceTask, load_whole_resource};
use script_task::MainThreadScriptMsg;
use std::sync::Arc;
use std::sync::mpsc::Sender;
use url::Url;

#[derive(JSTraceable, PartialEq, Clone, Debug, HeapSizeOf)]
@@ -40,15 +38,9 @@ pub struct DocumentLoader {
/// are lots of iframes.
#[ignore_heap_size_of = "channels are hard"]
pub resource_task: Arc<ResourceTask>,
notifier_data: Option<NotifierData>,
pipeline: Option<PipelineId>,
blocking_loads: Vec<LoadType>,
}

#[derive(JSTraceable, HeapSizeOf)]
pub struct NotifierData {
#[ignore_heap_size_of = "trait objects are hard"]
pub script_chan: Sender<MainThreadScriptMsg>,
pub pipeline: PipelineId,
events_inhibited: bool,
}

impl DocumentLoader {
@@ -59,15 +51,16 @@ impl DocumentLoader {
/// We use an `Arc<ResourceTask>` here in order to avoid file descriptor exhaustion when there
/// are lots of iframes.
pub fn new_with_task(resource_task: Arc<ResourceTask>,
data: Option<NotifierData>,
pipeline: Option<PipelineId>,
initial_load: Option<Url>)
-> DocumentLoader {
let initial_loads = initial_load.into_iter().map(LoadType::PageSource).collect();

DocumentLoader {
resource_task: resource_task,
notifier_data: data,
pipeline: pipeline,
blocking_loads: initial_loads,
events_inhibited: false,
}
}

@@ -76,8 +69,7 @@ impl DocumentLoader {
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(), url, pipeline)
PendingAsyncLoad::new((*self.resource_task).clone(), url, self.pipeline)
}

/// Create and initiate a new network request.
@@ -98,12 +90,6 @@ impl DocumentLoader {
pub fn finish_load(&mut self, load: LoadType) {
let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == load);
self.blocking_loads.remove(idx.expect(&format!("unknown completed load {:?}", load)));

if let Some(NotifierData { ref script_chan, pipeline }) = self.notifier_data {
if !self.is_blocked() {
script_chan.send(MainThreadScriptMsg::DocumentLoadsComplete(pipeline)).unwrap();
}
}
}

pub fn is_blocked(&self) -> bool {
@@ -112,6 +98,9 @@ impl DocumentLoader {
}

pub fn inhibit_events(&mut self) {
self.notifier_data = None;
self.events_inhibited = true;
}
pub fn events_inhibited(&self) -> bool {
self.events_inhibited
}
}
@@ -53,6 +53,7 @@ use layout_interface::{LayoutChan, LayoutRPC};
use libc;
use msg::constellation_msg::ConstellationChan;
use msg::constellation_msg::{PipelineId, SubpageId, WindowSizeData, WorkerId};
use net_traits::Metadata;
use net_traits::image::base::Image;
use net_traits::image_cache_task::{ImageCacheChan, ImageCacheTask};
use net_traits::storage_task::StorageType;
@@ -277,6 +278,7 @@ no_jsmanaged_fields!(Rect<T>);
no_jsmanaged_fields!(Size2D<T>);
no_jsmanaged_fields!(Arc<T>);
no_jsmanaged_fields!(Image, ImageCacheChan, ImageCacheTask);
no_jsmanaged_fields!(Metadata);
no_jsmanaged_fields!(Atom, Namespace);
no_jsmanaged_fields!(Trusted<T: Reflectable>);
no_jsmanaged_fields!(PropertyDeclarationBlock);
@@ -81,9 +81,9 @@ use msg::constellation_msg::{ALT, CONTROL, SHIFT, SUPER};
use msg::constellation_msg::{ConstellationChan, FocusType, Key, KeyModifiers, KeyState, MozBrowserEvent, SubpageId};
use net_traits::ControlMsg::{GetCookiesForUrl, SetCookiesForUrl};
use net_traits::CookieSource::NonHTTP;
use net_traits::{AsyncResponseTarget, Metadata, PendingAsyncLoad};
use net_traits::{AsyncResponseTarget, PendingAsyncLoad};
use num::ToPrimitive;
use script_task::Runnable;
use script_task::{MainThreadScriptMsg, Runnable};
use script_traits::{MouseButton, UntrustedNodeAddress};
use std::ascii::AsciiExt;
use std::borrow::ToOwned;
@@ -107,6 +107,12 @@ pub enum IsHTMLDocument {
NonHTMLDocument,
}

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

// https://dom.spec.whatwg.org/#document
#[dom_struct]
pub struct Document {
@@ -129,12 +135,24 @@ pub struct Document {
anchors: MutNullableHeap<JS<HTMLCollection>>,
applets: MutNullableHeap<JS<HTMLCollection>>,
ready_state: Cell<DocumentReadyState>,
/// Whether the DOMContentLoaded event has already been dispatched.
domcontentloaded_dispatched: Cell<bool>,
/// The element that has most recently requested focus for itself.
possibly_focused: MutNullableHeap<JS<Element>>,
/// The element that currently has the document focus context.
focused: MutNullableHeap<JS<Element>>,
/// The script element that is currently executing.
current_script: MutNullableHeap<JS<HTMLScriptElement>>,
/// https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script
pending_parsing_blocking_script: MutNullableHeap<JS<HTMLScriptElement>>,
/// Number of stylesheets that block executing the next parser-inserted script
script_blocking_stylesheets_count: Cell<u32>,
/// https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-when-the-document-has-finished-parsing
deferred_scripts: DOMRefCell<Vec<JS<HTMLScriptElement>>>,
/// https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible
asap_in_order_scripts_list: DOMRefCell<Vec<JS<HTMLScriptElement>>>,
/// https://html.spec.whatwg.org/multipage/#set-of-scripts-that-will-execute-as-soon-as-possible
asap_scripts_set: DOMRefCell<Vec<JS<HTMLScriptElement>>>,
/// https://html.spec.whatwg.org/multipage/#concept-n-noscript
/// True if scripting is enabled for all scripts in this document
scripting_enabled: Cell<bool>,
@@ -892,6 +910,42 @@ impl Document {
self.current_script.set(script);
}

pub fn get_script_blocking_stylesheets_count(&self) -> u32 {
self.script_blocking_stylesheets_count.get()
}

pub fn increment_script_blocking_stylesheet_count(&self) {
let count_cell = &self.script_blocking_stylesheets_count;
count_cell.set(count_cell.get() + 1);
}

pub fn decrement_script_blocking_stylesheet_count(&self) {
let count_cell = &self.script_blocking_stylesheets_count;
assert!(count_cell.get() > 0);
count_cell.set(count_cell.get() - 1);
}

pub fn set_pending_parsing_blocking_script(&self, script: Option<&HTMLScriptElement>) {
assert!(self.get_pending_parsing_blocking_script().is_none() || script.is_none());
self.pending_parsing_blocking_script.set(script);
}

pub fn get_pending_parsing_blocking_script(&self) -> Option<Root<HTMLScriptElement>> {
self.pending_parsing_blocking_script.get()
}

pub fn add_deferred_script(&self, script: &HTMLScriptElement) {
self.deferred_scripts.borrow_mut().push(JS::from_ref(script));
}

pub fn add_asap_script(&self, script: &HTMLScriptElement) {
self.asap_scripts_set.borrow_mut().push(JS::from_ref(script));
}

pub fn push_asap_in_order_script(&self, script: &HTMLScriptElement) {
self.asap_in_order_scripts_list.borrow_mut().push(JS::from_ref(script));
}

pub fn trigger_mozbrowser_event(&self, event: MozBrowserEvent) {
if htmliframeelement::mozbrowser_enabled() {
if let Some((containing_pipeline_id, subpage_id)) = self.window.parent_info() {
@@ -966,14 +1020,120 @@ impl Document {
loader.load_async(load, listener)
}

pub fn load_sync(&self, load: LoadType) -> Result<(Metadata, Vec<u8>), String> {
let mut loader = self.loader.borrow_mut();
loader.load_sync(load)
pub fn finish_load(&self, load: LoadType) {
// The parser might need the loader, so restrict the lifetime of the borrow.
{
let mut loader = self.loader.borrow_mut();
loader.finish_load(load.clone());
}

if let LoadType::Script(_) = load {
self.process_deferred_scripts();
self.process_asap_scripts();
}

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

// A finished resource load can potentially unblock parsing. In that case, resume the
// parser so its loop can find out.
if let Some(parser) = self.current_parser.get_rooted() {
if parser.is_suspended() {
parser.resume();
}
}

let loader = self.loader.borrow();
if !loader.is_blocked() && !loader.events_inhibited() {
let win = self.window();
let msg = MainThreadScriptMsg::DocumentLoadsComplete(win.pipeline());
win.main_thread_script_chan().send(msg).unwrap();
}
}

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

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

/// https://html.spec.whatwg.org/multipage/#the-end step 3
pub fn process_deferred_scripts(&self) {
if self.ready_state.get() != DocumentReadyState::Interactive {
return;
}
// Part of substep 1.
if self.script_blocking_stylesheets_count.get() > 0 {
return;
}
let mut deferred_scripts = self.deferred_scripts.borrow_mut();
while !deferred_scripts.is_empty() {
let script = deferred_scripts[0].root();
// Part of substep 1.
if !script.is_ready_to_be_executed() {
return;
}
// Substep 2.
script.execute();
// Substep 3.
deferred_scripts.remove(0);
// Substep 4 (implicit).
}
// https://html.spec.whatwg.org/multipage/#the-end step 4.
self.maybe_dispatch_dom_content_loaded();
}

/// https://html.spec.whatwg.org/multipage/#the-end step 5 and the latter parts of
/// https://html.spec.whatwg.org/multipage/#prepare-a-script 15.d and 15.e.
pub fn process_asap_scripts(&self) {
// Execute the first in-order asap-executed script if it's ready, repeat as required.
// Re-borrowing the list for each step because it can also be borrowed under execute.
while self.asap_in_order_scripts_list.borrow().len() > 0 {
let script = self.asap_in_order_scripts_list.borrow()[0].root();
if !script.r().is_ready_to_be_executed() {
break;
}
script.r().execute();
self.asap_in_order_scripts_list.borrow_mut().remove(0);
}

let mut idx = 0;
// Re-borrowing the set for each step because it can also be borrowed under execute.
while idx < self.asap_scripts_set.borrow().len() {
let script = self.asap_scripts_set.borrow()[idx].root();
if !script.r().is_ready_to_be_executed() {
idx += 1;
continue;
}
script.r().execute();
self.asap_scripts_set.borrow_mut().swap_remove(idx);
}
}

pub fn maybe_dispatch_dom_content_loaded(&self) {
if self.domcontentloaded_dispatched.get() {
return;
}
self.domcontentloaded_dispatched.set(true);
let event = Event::new(GlobalRef::Window(self.window()), "DOMContentLoaded".to_owned(),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable);
let doctarget = self.upcast::<EventTarget>();
let _ = doctarget.DispatchEvent(event.r());
self.window().reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, ReflowReason::DOMContentLoaded);
}

pub fn notify_constellation_load(&self) {
@@ -1039,10 +1199,10 @@ impl Document {
doc_loader: DocumentLoader) -> Document {
let url = url.unwrap_or_else(|| Url::parse("about:blank").unwrap());

let ready_state = if source == DocumentSource::FromParser {
DocumentReadyState::Loading
let (ready_state, domcontentloaded_dispatched) = if source == DocumentSource::FromParser {
(DocumentReadyState::Loading, false)
} else {
DocumentReadyState::Complete
(DocumentReadyState::Complete, true)
};

Document {
@@ -1075,9 +1235,15 @@ impl Document {
anchors: Default::default(),
applets: Default::default(),
ready_state: Cell::new(ready_state),
domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched),
possibly_focused: Default::default(),
focused: Default::default(),
current_script: Default::default(),
pending_parsing_blocking_script: Default::default(),
script_blocking_stylesheets_count: Cell::new(0u32),
deferred_scripts: DOMRefCell::new(vec!()),
asap_in_order_scripts_list: DOMRefCell::new(vec!()),
asap_scripts_set: DOMRefCell::new(vec!()),
scripting_enabled: Cell::new(true),
animation_frame_ident: Cell::new(0),
animation_frame_list: RefCell::new(HashMap::new()),
@@ -1875,7 +2041,6 @@ fn is_scheme_host_port_tuple(url: &Url) -> bool {

#[derive(HeapSizeOf)]
pub enum DocumentProgressTask {
DOMContentLoaded,
Load,
}

@@ -1892,20 +2057,6 @@ impl DocumentProgressHandler {
}
}

fn dispatch_dom_content_loaded(&self) {
let document = self.addr.root();
let window = document.r().window();
let event = Event::new(GlobalRef::Window(window), "DOMContentLoaded".to_owned(),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable);
let doctarget = document.upcast::<EventTarget>();
let _ = doctarget.DispatchEvent(event.r());

window.reflow(ReflowGoal::ForDisplay,
ReflowQueryType::NoQuery,
ReflowReason::DOMContentLoaded);
}

fn set_ready_state_complete(&self) {
let document = self.addr.root();
document.r().set_ready_state(DocumentReadyState::Complete);
@@ -1949,9 +2100,6 @@ impl Runnable for DocumentProgressHandler {
let window = document.r().window();
if window.is_alive() {
match self.task {
DocumentProgressTask::DOMContentLoaded => {
self.dispatch_dom_content_loaded();
}
DocumentProgressTask::Load => {
self.set_ready_state_complete();
self.dispatch_load();
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.