mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
Improve spec-compliance of script loading and execution during document startup
Including proper support for async and deferred scripts.
This commit is contained in:
parent
6b95c3957b
commit
a0c5d47910
9 changed files with 298 additions and 128 deletions
|
@ -8,9 +8,7 @@
|
||||||
use msg::constellation_msg::{PipelineId};
|
use msg::constellation_msg::{PipelineId};
|
||||||
use net_traits::AsyncResponseTarget;
|
use net_traits::AsyncResponseTarget;
|
||||||
use net_traits::{Metadata, PendingAsyncLoad, ResourceTask, load_whole_resource};
|
use net_traits::{Metadata, PendingAsyncLoad, ResourceTask, load_whole_resource};
|
||||||
use script_task::MainThreadScriptMsg;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(JSTraceable, PartialEq, Clone, Debug, HeapSizeOf)]
|
#[derive(JSTraceable, PartialEq, Clone, Debug, HeapSizeOf)]
|
||||||
|
@ -40,15 +38,9 @@ pub struct DocumentLoader {
|
||||||
/// are lots of iframes.
|
/// are lots of iframes.
|
||||||
#[ignore_heap_size_of = "channels are hard"]
|
#[ignore_heap_size_of = "channels are hard"]
|
||||||
pub resource_task: Arc<ResourceTask>,
|
pub resource_task: Arc<ResourceTask>,
|
||||||
notifier_data: Option<NotifierData>,
|
pipeline: Option<PipelineId>,
|
||||||
blocking_loads: Vec<LoadType>,
|
blocking_loads: Vec<LoadType>,
|
||||||
}
|
events_inhibited: bool,
|
||||||
|
|
||||||
#[derive(JSTraceable, HeapSizeOf)]
|
|
||||||
pub struct NotifierData {
|
|
||||||
#[ignore_heap_size_of = "trait objects are hard"]
|
|
||||||
pub script_chan: Sender<MainThreadScriptMsg>,
|
|
||||||
pub pipeline: PipelineId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentLoader {
|
impl DocumentLoader {
|
||||||
|
@ -59,15 +51,16 @@ impl DocumentLoader {
|
||||||
/// We use an `Arc<ResourceTask>` here in order to avoid file descriptor exhaustion when there
|
/// We use an `Arc<ResourceTask>` here in order to avoid file descriptor exhaustion when there
|
||||||
/// are lots of iframes.
|
/// are lots of iframes.
|
||||||
pub fn new_with_task(resource_task: Arc<ResourceTask>,
|
pub fn new_with_task(resource_task: Arc<ResourceTask>,
|
||||||
data: Option<NotifierData>,
|
pipeline: Option<PipelineId>,
|
||||||
initial_load: Option<Url>)
|
initial_load: Option<Url>)
|
||||||
-> DocumentLoader {
|
-> DocumentLoader {
|
||||||
let initial_loads = initial_load.into_iter().map(LoadType::PageSource).collect();
|
let initial_loads = initial_load.into_iter().map(LoadType::PageSource).collect();
|
||||||
|
|
||||||
DocumentLoader {
|
DocumentLoader {
|
||||||
resource_task: resource_task,
|
resource_task: resource_task,
|
||||||
notifier_data: data,
|
pipeline: pipeline,
|
||||||
blocking_loads: initial_loads,
|
blocking_loads: initial_loads,
|
||||||
|
events_inhibited: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,8 +69,7 @@ impl DocumentLoader {
|
||||||
pub fn prepare_async_load(&mut self, load: LoadType) -> PendingAsyncLoad {
|
pub fn prepare_async_load(&mut self, load: LoadType) -> PendingAsyncLoad {
|
||||||
let url = load.url().clone();
|
let url = load.url().clone();
|
||||||
self.blocking_loads.push(load);
|
self.blocking_loads.push(load);
|
||||||
let pipeline = self.notifier_data.as_ref().map(|data| data.pipeline);
|
PendingAsyncLoad::new((*self.resource_task).clone(), url, self.pipeline)
|
||||||
PendingAsyncLoad::new((*self.resource_task).clone(), url, pipeline)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create and initiate a new network request.
|
/// Create and initiate a new network request.
|
||||||
|
@ -98,12 +90,6 @@ impl DocumentLoader {
|
||||||
pub fn finish_load(&mut self, load: LoadType) {
|
pub fn finish_load(&mut self, load: LoadType) {
|
||||||
let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == load);
|
let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == load);
|
||||||
self.blocking_loads.remove(idx.expect(&format!("unknown completed load {:?}", 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 {
|
pub fn is_blocked(&self) -> bool {
|
||||||
|
@ -112,6 +98,9 @@ impl DocumentLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inhibit_events(&mut self) {
|
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 libc;
|
||||||
use msg::constellation_msg::ConstellationChan;
|
use msg::constellation_msg::ConstellationChan;
|
||||||
use msg::constellation_msg::{PipelineId, SubpageId, WindowSizeData, WorkerId};
|
use msg::constellation_msg::{PipelineId, SubpageId, WindowSizeData, WorkerId};
|
||||||
|
use net_traits::Metadata;
|
||||||
use net_traits::image::base::Image;
|
use net_traits::image::base::Image;
|
||||||
use net_traits::image_cache_task::{ImageCacheChan, ImageCacheTask};
|
use net_traits::image_cache_task::{ImageCacheChan, ImageCacheTask};
|
||||||
use net_traits::storage_task::StorageType;
|
use net_traits::storage_task::StorageType;
|
||||||
|
@ -277,6 +278,7 @@ no_jsmanaged_fields!(Rect<T>);
|
||||||
no_jsmanaged_fields!(Size2D<T>);
|
no_jsmanaged_fields!(Size2D<T>);
|
||||||
no_jsmanaged_fields!(Arc<T>);
|
no_jsmanaged_fields!(Arc<T>);
|
||||||
no_jsmanaged_fields!(Image, ImageCacheChan, ImageCacheTask);
|
no_jsmanaged_fields!(Image, ImageCacheChan, ImageCacheTask);
|
||||||
|
no_jsmanaged_fields!(Metadata);
|
||||||
no_jsmanaged_fields!(Atom, Namespace);
|
no_jsmanaged_fields!(Atom, Namespace);
|
||||||
no_jsmanaged_fields!(Trusted<T: Reflectable>);
|
no_jsmanaged_fields!(Trusted<T: Reflectable>);
|
||||||
no_jsmanaged_fields!(PropertyDeclarationBlock);
|
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 msg::constellation_msg::{ConstellationChan, FocusType, Key, KeyModifiers, KeyState, MozBrowserEvent, SubpageId};
|
||||||
use net_traits::ControlMsg::{GetCookiesForUrl, SetCookiesForUrl};
|
use net_traits::ControlMsg::{GetCookiesForUrl, SetCookiesForUrl};
|
||||||
use net_traits::CookieSource::NonHTTP;
|
use net_traits::CookieSource::NonHTTP;
|
||||||
use net_traits::{AsyncResponseTarget, Metadata, PendingAsyncLoad};
|
use net_traits::{AsyncResponseTarget, PendingAsyncLoad};
|
||||||
use num::ToPrimitive;
|
use num::ToPrimitive;
|
||||||
use script_task::Runnable;
|
use script_task::{MainThreadScriptMsg, Runnable};
|
||||||
use script_traits::{MouseButton, UntrustedNodeAddress};
|
use script_traits::{MouseButton, UntrustedNodeAddress};
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
|
@ -107,6 +107,12 @@ pub enum IsHTMLDocument {
|
||||||
NonHTMLDocument,
|
NonHTMLDocument,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum ParserBlockedByScript {
|
||||||
|
Blocked,
|
||||||
|
Unblocked,
|
||||||
|
}
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#document
|
// https://dom.spec.whatwg.org/#document
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub struct Document {
|
pub struct Document {
|
||||||
|
@ -129,12 +135,24 @@ pub struct Document {
|
||||||
anchors: MutNullableHeap<JS<HTMLCollection>>,
|
anchors: MutNullableHeap<JS<HTMLCollection>>,
|
||||||
applets: MutNullableHeap<JS<HTMLCollection>>,
|
applets: MutNullableHeap<JS<HTMLCollection>>,
|
||||||
ready_state: Cell<DocumentReadyState>,
|
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.
|
/// The element that has most recently requested focus for itself.
|
||||||
possibly_focused: MutNullableHeap<JS<Element>>,
|
possibly_focused: MutNullableHeap<JS<Element>>,
|
||||||
/// The element that currently has the document focus context.
|
/// The element that currently has the document focus context.
|
||||||
focused: MutNullableHeap<JS<Element>>,
|
focused: MutNullableHeap<JS<Element>>,
|
||||||
/// The script element that is currently executing.
|
/// The script element that is currently executing.
|
||||||
current_script: MutNullableHeap<JS<HTMLScriptElement>>,
|
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
|
/// https://html.spec.whatwg.org/multipage/#concept-n-noscript
|
||||||
/// True if scripting is enabled for all scripts in this document
|
/// True if scripting is enabled for all scripts in this document
|
||||||
scripting_enabled: Cell<bool>,
|
scripting_enabled: Cell<bool>,
|
||||||
|
@ -892,6 +910,42 @@ impl Document {
|
||||||
self.current_script.set(script);
|
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) {
|
pub fn trigger_mozbrowser_event(&self, event: MozBrowserEvent) {
|
||||||
if htmliframeelement::mozbrowser_enabled() {
|
if htmliframeelement::mozbrowser_enabled() {
|
||||||
if let Some((containing_pipeline_id, subpage_id)) = self.window.parent_info() {
|
if let Some((containing_pipeline_id, subpage_id)) = self.window.parent_info() {
|
||||||
|
@ -966,14 +1020,120 @@ impl Document {
|
||||||
loader.load_async(load, listener)
|
loader.load_async(load, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_sync(&self, load: LoadType) -> Result<(Metadata, Vec<u8>), String> {
|
pub fn finish_load(&self, load: LoadType) {
|
||||||
let mut loader = self.loader.borrow_mut();
|
// The parser might need the loader, so restrict the lifetime of the borrow.
|
||||||
loader.load_sync(load)
|
{
|
||||||
|
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) {
|
/// If document parsing is blocked on a script, and that script is ready to run,
|
||||||
let mut loader = self.loader.borrow_mut();
|
/// execute it.
|
||||||
loader.finish_load(load);
|
/// 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) {
|
pub fn notify_constellation_load(&self) {
|
||||||
|
@ -1039,10 +1199,10 @@ impl Document {
|
||||||
doc_loader: DocumentLoader) -> Document {
|
doc_loader: DocumentLoader) -> Document {
|
||||||
let url = url.unwrap_or_else(|| Url::parse("about:blank").unwrap());
|
let url = url.unwrap_or_else(|| Url::parse("about:blank").unwrap());
|
||||||
|
|
||||||
let ready_state = if source == DocumentSource::FromParser {
|
let (ready_state, domcontentloaded_dispatched) = if source == DocumentSource::FromParser {
|
||||||
DocumentReadyState::Loading
|
(DocumentReadyState::Loading, false)
|
||||||
} else {
|
} else {
|
||||||
DocumentReadyState::Complete
|
(DocumentReadyState::Complete, true)
|
||||||
};
|
};
|
||||||
|
|
||||||
Document {
|
Document {
|
||||||
|
@ -1075,9 +1235,15 @@ impl Document {
|
||||||
anchors: Default::default(),
|
anchors: Default::default(),
|
||||||
applets: Default::default(),
|
applets: Default::default(),
|
||||||
ready_state: Cell::new(ready_state),
|
ready_state: Cell::new(ready_state),
|
||||||
|
domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched),
|
||||||
possibly_focused: Default::default(),
|
possibly_focused: Default::default(),
|
||||||
focused: Default::default(),
|
focused: Default::default(),
|
||||||
current_script: 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),
|
scripting_enabled: Cell::new(true),
|
||||||
animation_frame_ident: Cell::new(0),
|
animation_frame_ident: Cell::new(0),
|
||||||
animation_frame_list: RefCell::new(HashMap::new()),
|
animation_frame_list: RefCell::new(HashMap::new()),
|
||||||
|
@ -1875,7 +2041,6 @@ fn is_scheme_host_port_tuple(url: &Url) -> bool {
|
||||||
|
|
||||||
#[derive(HeapSizeOf)]
|
#[derive(HeapSizeOf)]
|
||||||
pub enum DocumentProgressTask {
|
pub enum DocumentProgressTask {
|
||||||
DOMContentLoaded,
|
|
||||||
Load,
|
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) {
|
fn set_ready_state_complete(&self) {
|
||||||
let document = self.addr.root();
|
let document = self.addr.root();
|
||||||
document.r().set_ready_state(DocumentReadyState::Complete);
|
document.r().set_ready_state(DocumentReadyState::Complete);
|
||||||
|
@ -1949,9 +2100,6 @@ impl Runnable for DocumentProgressHandler {
|
||||||
let window = document.r().window();
|
let window = document.r().window();
|
||||||
if window.is_alive() {
|
if window.is_alive() {
|
||||||
match self.task {
|
match self.task {
|
||||||
DocumentProgressTask::DOMContentLoaded => {
|
|
||||||
self.dispatch_dom_content_loaded();
|
|
||||||
}
|
|
||||||
DocumentProgressTask::Load => {
|
DocumentProgressTask::Load => {
|
||||||
self.set_ready_state_complete();
|
self.set_ready_state_complete();
|
||||||
self.dispatch_load();
|
self.dispatch_load();
|
||||||
|
|
|
@ -37,7 +37,7 @@ use network_listener::{NetworkListener, PreInvoke};
|
||||||
use script_task::ScriptTaskEventCategory::ScriptEvent;
|
use script_task::ScriptTaskEventCategory::ScriptEvent;
|
||||||
use script_task::{CommonScriptMsg, Runnable, ScriptChan};
|
use script_task::{CommonScriptMsg, Runnable, ScriptChan};
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::cell::Cell;
|
use std::cell::{Cell, RefCell};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use url::{Url, UrlParser};
|
use url::{Url, UrlParser};
|
||||||
|
@ -59,13 +59,14 @@ pub struct HTMLScriptElement {
|
||||||
non_blocking: Cell<bool>,
|
non_blocking: Cell<bool>,
|
||||||
|
|
||||||
/// https://html.spec.whatwg.org/multipage/#ready-to-be-parser-executed
|
/// https://html.spec.whatwg.org/multipage/#ready-to-be-parser-executed
|
||||||
///
|
|
||||||
/// (currently unused)
|
|
||||||
ready_to_be_parser_executed: Cell<bool>,
|
ready_to_be_parser_executed: Cell<bool>,
|
||||||
|
|
||||||
/// Document of the parser that created this element
|
/// Document of the parser that created this element
|
||||||
parser_document: JS<Document>,
|
parser_document: JS<Document>,
|
||||||
|
|
||||||
|
/// The source this script was loaded from
|
||||||
|
load: RefCell<Option<ScriptOrigin>>,
|
||||||
|
|
||||||
#[ignore_heap_size_of = "Defined in rust-encoding"]
|
#[ignore_heap_size_of = "Defined in rust-encoding"]
|
||||||
/// https://html.spec.whatwg.org/multipage/#concept-script-encoding
|
/// https://html.spec.whatwg.org/multipage/#concept-script-encoding
|
||||||
block_character_encoding: DOMRefCell<EncodingRef>,
|
block_character_encoding: DOMRefCell<EncodingRef>,
|
||||||
|
@ -82,6 +83,7 @@ impl HTMLScriptElement {
|
||||||
non_blocking: Cell::new(creator != ElementCreator::ParserCreated),
|
non_blocking: Cell::new(creator != ElementCreator::ParserCreated),
|
||||||
ready_to_be_parser_executed: Cell::new(false),
|
ready_to_be_parser_executed: Cell::new(false),
|
||||||
parser_document: JS::from_ref(document),
|
parser_document: JS::from_ref(document),
|
||||||
|
load: RefCell::new(None),
|
||||||
block_character_encoding: DOMRefCell::new(UTF_8 as EncodingRef),
|
block_character_encoding: DOMRefCell::new(UTF_8 as EncodingRef),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +118,7 @@ static SCRIPT_JS_MIMES: StaticStringVec = &[
|
||||||
"text/x-javascript",
|
"text/x-javascript",
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(HeapSizeOf)]
|
#[derive(HeapSizeOf, JSTraceable)]
|
||||||
pub enum ScriptOrigin {
|
pub enum ScriptOrigin {
|
||||||
Internal(String, Url),
|
Internal(String, Url),
|
||||||
External(Result<(Metadata, Vec<u8>), String>),
|
External(Result<(Metadata, Vec<u8>), String>),
|
||||||
|
@ -130,8 +132,6 @@ struct ScriptContext {
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
/// The response metadata received to date.
|
/// The response metadata received to date.
|
||||||
metadata: Option<Metadata>,
|
metadata: Option<Metadata>,
|
||||||
/// Whether the owning document's parser should resume once the response completes.
|
|
||||||
resume_on_completion: bool,
|
|
||||||
/// The initial URL requested.
|
/// The initial URL requested.
|
||||||
url: Url,
|
url: Url,
|
||||||
}
|
}
|
||||||
|
@ -153,23 +153,20 @@ impl AsyncResponseListener for ScriptContext {
|
||||||
(metadata, data)
|
(metadata, data)
|
||||||
});
|
});
|
||||||
let elem = self.elem.root();
|
let elem = self.elem.root();
|
||||||
|
// TODO: maybe set this to None again after script execution to save memory.
|
||||||
elem.r().execute(ScriptOrigin::External(load));
|
*elem.r().load.borrow_mut() = Some(ScriptOrigin::External(load));
|
||||||
|
elem.ready_to_be_parser_executed.set(true);
|
||||||
|
|
||||||
let document = document_from_node(elem.r());
|
let document = document_from_node(elem.r());
|
||||||
document.r().finish_load(LoadType::Script(self.url.clone()));
|
document.r().finish_load(LoadType::Script(self.url.clone()));
|
||||||
|
|
||||||
if self.resume_on_completion {
|
|
||||||
document.r().get_current_parser().unwrap().r().resume();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreInvoke for ScriptContext {}
|
impl PreInvoke for ScriptContext {}
|
||||||
|
|
||||||
impl HTMLScriptElement {
|
impl HTMLScriptElement {
|
||||||
|
/// https://html.spec.whatwg.org/multipage/#prepare-a-script
|
||||||
pub fn prepare(&self) -> NextParserState {
|
pub fn prepare(&self) -> NextParserState {
|
||||||
// https://html.spec.whatwg.org/multipage/#prepare-a-script
|
|
||||||
// Step 1.
|
// Step 1.
|
||||||
if self.already_started.get() {
|
if self.already_started.get() {
|
||||||
return NextParserState::Continue;
|
return NextParserState::Continue;
|
||||||
|
@ -180,7 +177,9 @@ impl HTMLScriptElement {
|
||||||
|
|
||||||
// Step 3.
|
// Step 3.
|
||||||
let element = self.upcast::<Element>();
|
let element = self.upcast::<Element>();
|
||||||
if was_parser_inserted && element.has_attribute(&atom!("async")) {
|
let async = element.has_attribute(&atom!("async"));
|
||||||
|
// Note: confusingly, this is done if the element does *not* have an "async" attribute.
|
||||||
|
if was_parser_inserted && !async {
|
||||||
self.non_blocking.set(true);
|
self.non_blocking.set(true);
|
||||||
}
|
}
|
||||||
// Step 4.
|
// Step 4.
|
||||||
|
@ -205,8 +204,8 @@ impl HTMLScriptElement {
|
||||||
self.already_started.set(true);
|
self.already_started.set(true);
|
||||||
|
|
||||||
// Step 10.
|
// Step 10.
|
||||||
let document_from_node_ref = document_from_node(self);
|
let doc = document_from_node(self);
|
||||||
let document_from_node_ref = document_from_node_ref.r();
|
let document_from_node_ref = doc.r();
|
||||||
if self.parser_inserted.get() && &*self.parser_document != document_from_node_ref {
|
if self.parser_inserted.get() && &*self.parser_document != document_from_node_ref {
|
||||||
return NextParserState::Continue;
|
return NextParserState::Continue;
|
||||||
}
|
}
|
||||||
|
@ -247,8 +246,9 @@ impl HTMLScriptElement {
|
||||||
let window = window_from_node(self);
|
let window = window_from_node(self);
|
||||||
let window = window.r();
|
let window = window.r();
|
||||||
let base_url = window.get_url();
|
let base_url = window.get_url();
|
||||||
|
let deferred = element.has_attribute(&atom!("defer"));
|
||||||
|
|
||||||
let load = match element.get_attribute(&ns!(""), &atom!("src")) {
|
let is_external = match element.get_attribute(&ns!(""), &atom!("src")) {
|
||||||
// Step 14.
|
// Step 14.
|
||||||
Some(ref src) => {
|
Some(ref src) => {
|
||||||
// Step 14.1
|
// Step 14.1
|
||||||
|
@ -274,8 +274,6 @@ impl HTMLScriptElement {
|
||||||
// state of the element's `crossorigin` content attribute, the origin being
|
// state of the element's `crossorigin` content attribute, the origin being
|
||||||
// the origin of the script element's node document, and the default origin
|
// the origin of the script element's node document, and the default origin
|
||||||
// behaviour set to taint.
|
// behaviour set to taint.
|
||||||
let doc = document_from_node(self);
|
|
||||||
|
|
||||||
let script_chan = window.script_chan();
|
let script_chan = window.script_chan();
|
||||||
let elem = Trusted::new(window.get_cx(), self, script_chan.clone());
|
let elem = Trusted::new(window.get_cx(), self, script_chan.clone());
|
||||||
|
|
||||||
|
@ -283,7 +281,6 @@ impl HTMLScriptElement {
|
||||||
elem: elem,
|
elem: elem,
|
||||||
data: vec!(),
|
data: vec!(),
|
||||||
metadata: None,
|
metadata: None,
|
||||||
resume_on_completion: self.parser_inserted.get(),
|
|
||||||
url: url.clone(),
|
url: url.clone(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -300,36 +297,85 @@ impl HTMLScriptElement {
|
||||||
});
|
});
|
||||||
|
|
||||||
doc.r().load_async(LoadType::Script(url), response_target);
|
doc.r().load_async(LoadType::Script(url), response_target);
|
||||||
|
|
||||||
if self.parser_inserted.get() {
|
|
||||||
doc.r().get_current_parser().unwrap().r().suspend();
|
|
||||||
}
|
|
||||||
return NextParserState::Suspend;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
true
|
||||||
},
|
},
|
||||||
None => ScriptOrigin::Internal(text, base_url),
|
None => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 15.
|
// Step 15.
|
||||||
// TODO: Add support for the `defer` and `async` attributes. (For now, we fetch all
|
// Step 15.a, has src, has defer, was parser-inserted, is not async.
|
||||||
// scripts synchronously and execute them immediately.)
|
if is_external &&
|
||||||
self.execute(load);
|
deferred &&
|
||||||
NextParserState::Continue
|
was_parser_inserted &&
|
||||||
|
!async {
|
||||||
|
doc.r().add_deferred_script(self);
|
||||||
|
// Second part implemented in Document::process_deferred_scripts.
|
||||||
|
return NextParserState::Continue;
|
||||||
|
// Step 15.b, has src, was parser-inserted, is not async.
|
||||||
|
} else if is_external &&
|
||||||
|
was_parser_inserted &&
|
||||||
|
!async {
|
||||||
|
doc.r().set_pending_parsing_blocking_script(Some(self));
|
||||||
|
// Second part implemented in the load result handler.
|
||||||
|
// Step 15.c, doesn't have src, was parser-inserted, is blocked on stylesheet.
|
||||||
|
} else if !is_external &&
|
||||||
|
was_parser_inserted &&
|
||||||
|
// TODO: check for script nesting levels.
|
||||||
|
doc.r().get_script_blocking_stylesheets_count() > 0 {
|
||||||
|
doc.r().set_pending_parsing_blocking_script(Some(self));
|
||||||
|
*self.load.borrow_mut() = Some(ScriptOrigin::Internal(text, base_url));
|
||||||
|
self.ready_to_be_parser_executed.set(true);
|
||||||
|
// Step 15.d, has src, isn't async, isn't non-blocking.
|
||||||
|
} else if is_external &&
|
||||||
|
!async &&
|
||||||
|
!self.non_blocking.get() {
|
||||||
|
doc.r().push_asap_in_order_script(self);
|
||||||
|
// Second part implemented in Document::process_asap_scripts.
|
||||||
|
// Step 15.e, has src.
|
||||||
|
} else if is_external {
|
||||||
|
doc.r().add_asap_script(self);
|
||||||
|
// Second part implemented in Document::process_asap_scripts.
|
||||||
|
// Step 15.f, otherwise.
|
||||||
|
} else {
|
||||||
|
assert!(!text.is_empty());
|
||||||
|
self.ready_to_be_parser_executed.set(true);
|
||||||
|
*self.load.borrow_mut() = Some(ScriptOrigin::Internal(text, base_url));
|
||||||
|
self.execute();
|
||||||
|
return NextParserState::Continue;
|
||||||
|
}
|
||||||
|
// TODO: make this suspension happen automatically.
|
||||||
|
if was_parser_inserted {
|
||||||
|
if let Some(parser) = doc.r().get_current_parser() {
|
||||||
|
parser.r().suspend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NextParserState::Suspend;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(&self, load: ScriptOrigin) {
|
pub fn is_ready_to_be_executed(&self) -> bool {
|
||||||
|
self.ready_to_be_parser_executed.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://html.spec.whatwg.org/multipage/#execute-the-script-block
|
||||||
|
pub fn execute(&self) {
|
||||||
|
assert!(self.ready_to_be_parser_executed.get());
|
||||||
|
|
||||||
// Step 1.
|
// Step 1.
|
||||||
// TODO: If the element is flagged as "parser-inserted", but the
|
let doc = document_from_node(self);
|
||||||
// element's node document is not the Document of the parser that
|
if self.parser_inserted.get() && doc.r() != self.parser_document.root().r() {
|
||||||
// created the element, then abort these steps.
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let load = self.load.borrow_mut().take().unwrap();
|
||||||
|
|
||||||
// Step 2.
|
// Step 2.
|
||||||
let (source, external, url) = match load {
|
let (source, external, url) = match load {
|
||||||
// Step 2.a.
|
// Step 2.a.
|
||||||
ScriptOrigin::External(Err(e)) => {
|
ScriptOrigin::External(Err(e)) => {
|
||||||
error!("error loading script {}", e);
|
error!("error loading script {}", e);
|
||||||
self.queue_error_event();
|
self.dispatch_error_event();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,9 @@ impl AsyncResponseListener for ParserContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
parser.r().last_chunk_received.set(true);
|
parser.r().last_chunk_received.set(true);
|
||||||
parser.r().parse_sync();
|
if !parser.r().is_suspended() {
|
||||||
|
parser.r().parse_sync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +190,9 @@ impl<'a> Parser for &'a ServoHTMLParser {
|
||||||
fn parse_chunk(self, input: String) {
|
fn parse_chunk(self, input: String) {
|
||||||
self.document.set_current_parser(Some(self));
|
self.document.set_current_parser(Some(self));
|
||||||
self.pending_input.borrow_mut().push(input);
|
self.pending_input.borrow_mut().push(input);
|
||||||
self.parse_sync();
|
if !self.is_suspended() {
|
||||||
|
self.parse_sync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self) {
|
fn finish(self) {
|
||||||
|
@ -282,21 +286,10 @@ impl ServoHTMLParser {
|
||||||
|
|
||||||
impl ServoHTMLParser {
|
impl ServoHTMLParser {
|
||||||
fn parse_sync(&self) {
|
fn parse_sync(&self) {
|
||||||
let mut first = true;
|
|
||||||
|
|
||||||
// This parser will continue to parse while there is either pending input or
|
// This parser will continue to parse while there is either pending input or
|
||||||
// the parser remains unsuspended.
|
// the parser remains unsuspended.
|
||||||
loop {
|
loop {
|
||||||
if self.suspended.get() {
|
self.document.reflow_if_reflow_timer_expired();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.pending_input.borrow().is_empty() && !first {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.document.reflow_if_reflow_timer_expired();
|
|
||||||
|
|
||||||
let mut pending_input = self.pending_input.borrow_mut();
|
let mut pending_input = self.pending_input.borrow_mut();
|
||||||
if !pending_input.is_empty() {
|
if !pending_input.is_empty() {
|
||||||
let chunk = pending_input.remove(0);
|
let chunk = pending_input.remove(0);
|
||||||
|
@ -305,7 +298,14 @@ impl ServoHTMLParser {
|
||||||
self.tokenizer.borrow_mut().run();
|
self.tokenizer.borrow_mut().run();
|
||||||
}
|
}
|
||||||
|
|
||||||
first = false;
|
// Document parsing is blocked on an external resource.
|
||||||
|
if self.suspended.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if pending_input.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.last_chunk_received.get() {
|
if self.last_chunk_received.get() {
|
||||||
|
@ -330,6 +330,10 @@ impl ServoHTMLParser {
|
||||||
self.suspended.set(false);
|
self.suspended.set(false);
|
||||||
self.parse_sync();
|
self.parse_sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_suspended(&self) -> bool {
|
||||||
|
self.suspended.get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Tracer {
|
struct Tracer {
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
use devtools;
|
use devtools;
|
||||||
use devtools_traits::ScriptToDevtoolsControlMsg;
|
use devtools_traits::ScriptToDevtoolsControlMsg;
|
||||||
use devtools_traits::{DevtoolScriptControlMsg, DevtoolsPageInfo};
|
use devtools_traits::{DevtoolScriptControlMsg, DevtoolsPageInfo};
|
||||||
use document_loader::{DocumentLoader, LoadType, NotifierData};
|
use document_loader::{DocumentLoader, LoadType};
|
||||||
use dom::bindings::cell::DOMRefCell;
|
use dom::bindings::cell::DOMRefCell;
|
||||||
use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
|
use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
|
||||||
use dom::bindings::conversions::{Castable, FromJSValConvertible, StringificationBehavior};
|
use dom::bindings::conversions::{Castable, FromJSValConvertible, StringificationBehavior};
|
||||||
|
@ -1614,15 +1614,8 @@ impl ScriptTask {
|
||||||
_ => None
|
_ => None
|
||||||
};
|
};
|
||||||
|
|
||||||
let notifier_data = {
|
|
||||||
let MainThreadScriptChan(ref sender) = self.chan;
|
|
||||||
NotifierData {
|
|
||||||
script_chan: sender.clone(),
|
|
||||||
pipeline: page.pipeline(),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let loader = DocumentLoader::new_with_task(self.resource_task.clone(),
|
let loader = DocumentLoader::new_with_task(self.resource_task.clone(),
|
||||||
Some(notifier_data),
|
Some(page.pipeline()),
|
||||||
Some(incomplete.url.clone()));
|
Some(incomplete.url.clone()));
|
||||||
let document = Document::new(window.r(),
|
let document = Document::new(window.r(),
|
||||||
Some(final_url.clone()),
|
Some(final_url.clone()),
|
||||||
|
@ -1935,8 +1928,11 @@ impl ScriptTask {
|
||||||
let document = page.document();
|
let document = page.document();
|
||||||
let final_url = document.r().url();
|
let final_url = document.r().url();
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#the-end step 1
|
||||||
document.r().set_ready_state(DocumentReadyState::Interactive);
|
document.r().set_ready_state(DocumentReadyState::Interactive);
|
||||||
|
|
||||||
|
// TODO: Execute step 2 here.
|
||||||
|
|
||||||
// Kick off the initial reflow of the page.
|
// Kick off the initial reflow of the page.
|
||||||
debug!("kicking off initial reflow of {:?}", final_url);
|
debug!("kicking off initial reflow of {:?}", final_url);
|
||||||
document.r().disarm_reflow_timeout();
|
document.r().disarm_reflow_timeout();
|
||||||
|
@ -1948,14 +1944,14 @@ impl ScriptTask {
|
||||||
// No more reflow required
|
// No more reflow required
|
||||||
page.set_reflow_status(false);
|
page.set_reflow_status(false);
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#the-end step 4
|
// https://html.spec.whatwg.org/multipage/#the-end steps 3-4.
|
||||||
let addr: Trusted<Document> = Trusted::new(self.get_cx(), document.r(), self.chan.clone());
|
document.r().process_deferred_scripts();
|
||||||
let handler = box DocumentProgressHandler::new(addr, DocumentProgressTask::DOMContentLoaded);
|
|
||||||
self.chan.send(CommonScriptMsg::RunnableMsg(ScriptTaskEventCategory::DocumentEvent, handler)).unwrap();
|
|
||||||
|
|
||||||
window.r().set_fragment_name(final_url.fragment.clone());
|
window.r().set_fragment_name(final_url.fragment.clone());
|
||||||
|
|
||||||
// Notify devtools that a new script global exists.
|
// Notify devtools that a new script global exists.
|
||||||
|
//TODO: should this happen as soon as the global is created, or at least once the first
|
||||||
|
// script runs?
|
||||||
self.notify_devtools(document.r().Title(), (*final_url).clone(), (id, None));
|
self.notify_devtools(document.r().Title(), (*final_url).clone(), (id, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[085.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: async script and slow-loading defer script]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[103.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: removing defer attribute at runtime]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[110.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: removing defer script at runtime]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue