/* 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 https://mozilla.org/MPL/2.0/. */ //! The script module mod contains common traits and structs //! related to `type=module` for script thread or worker threads. use crate::document_loader::LoadType; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; use crate::dom::bindings::conversions::jsstring_to_str; use crate::dom::bindings::error::report_pending_exception; use crate::dom::bindings::error::Error; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::settings_stack::AutoIncumbentScript; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::document::Document; use crate::dom::element::Element; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlscriptelement::{HTMLScriptElement, ScriptId}; use crate::dom::htmlscriptelement::{ScriptOrigin, ScriptType, SCRIPT_JS_MIMES}; use crate::dom::node::document_from_node; use crate::dom::performanceresourcetiming::InitiatorType; use crate::dom::promise::Promise; use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; use crate::dom::window::Window; use crate::dom::worker::TrustedWorkerAddress; use crate::network_listener::{self, NetworkListener}; use crate::network_listener::{PreInvoke, ResourceTimingListener}; use crate::realms::{enter_realm, AlreadyInRealm, InRealm}; use crate::script_runtime::JSContext as SafeJSContext; use crate::task::TaskBox; use crate::task_source::TaskSourceName; use encoding_rs::UTF_8; use hyper_serde::Serde; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use js::jsapi::Handle as RawHandle; use js::jsapi::HandleObject; use js::jsapi::HandleValue as RawHandleValue; use js::jsapi::{ CompileModule, ExceptionStackBehavior, GetModuleResolveHook, JSRuntime, SetModuleResolveHook, }; use js::jsapi::{GetRequestedModules, SetModuleMetadataHook}; use js::jsapi::{GetWaitForAllPromise, ModuleEvaluate, ModuleInstantiate, SourceText}; use js::jsapi::{Heap, JSContext, JS_ClearPendingException, SetModulePrivate}; use js::jsapi::{JSAutoRealm, JSObject, JSString}; use js::jsapi::{SetModuleDynamicImportHook, SetScriptPrivateReferenceHooks}; use js::jsval::{JSVal, PrivateValue, UndefinedValue}; use js::rust::jsapi_wrapped::{GetRequestedModuleSpecifier, JS_GetPendingException}; use js::rust::jsapi_wrapped::{JS_GetArrayLength, JS_GetElement}; use js::rust::wrappers::JS_SetPendingException; use js::rust::CompileOptionsWrapper; use js::rust::IntoHandle; use js::rust::RootedObjectVectorWrapper; use js::rust::{Handle, HandleValue}; use mime::Mime; use net_traits::request::{CredentialsMode, Destination, ParserMetadata}; use net_traits::request::{Referrer, RequestBuilder, RequestMode}; use net_traits::{FetchMetadata, Metadata}; use net_traits::{FetchResponseListener, NetworkError}; use net_traits::{ResourceFetchTiming, ResourceTimingType}; use servo_url::ServoUrl; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::ffi; use std::marker::PhantomData; use std::rc::Rc; use std::str::FromStr; use std::sync::{Arc, Mutex}; use url::ParseError as UrlParseError; use indexmap::IndexSet; pub fn get_source_text(source: &[u16]) -> SourceText { SourceText { units_: source.as_ptr() as *const _, length_: source.len() as u32, ownsUnits_: false, _phantom_0: PhantomData, } } #[allow(unsafe_code)] unsafe fn gen_type_error(global: &GlobalScope, string: String) -> ModuleError { rooted!(in(*global.get_cx()) let mut thrown = UndefinedValue()); Error::Type(string).to_jsval(*global.get_cx(), &global, thrown.handle_mut()); return ModuleError::RawException(RootedTraceableBox::from_box(Heap::boxed(thrown.get()))); } #[derive(JSTraceable)] pub struct ModuleObject(Box>); impl ModuleObject { #[allow(unsafe_code)] pub fn handle(&self) -> HandleObject { unsafe { self.0.handle() } } } #[derive(JSTraceable)] pub enum ModuleError { Network(NetworkError), RawException(RootedTraceableBox>), } impl Eq for ModuleError {} impl PartialEq for ModuleError { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Network(_), Self::RawException(_)) | (Self::RawException(_), Self::Network(_)) => false, _ => true, } } } impl Ord for ModuleError { fn cmp(&self, other: &Self) -> Ordering { match (self, other) { (Self::Network(_), Self::RawException(_)) => Ordering::Greater, (Self::RawException(_), Self::Network(_)) => Ordering::Less, _ => Ordering::Equal, } } } impl PartialOrd for ModuleError { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl ModuleError { #[allow(unsafe_code)] pub fn handle(&self) -> Handle { match self { Self::Network(_) => unreachable!(), Self::RawException(exception) => exception.handle(), } } } impl Clone for ModuleError { fn clone(&self) -> Self { match self { Self::Network(network_error) => Self::Network(network_error.clone()), Self::RawException(exception) => Self::RawException(RootedTraceableBox::from_box( Heap::boxed(exception.get().clone()), )), } } } struct ModuleScript { base_url: ServoUrl, } #[derive(JSTraceable)] pub struct ModuleTree { url: ServoUrl, text: DomRefCell, record: DomRefCell>, status: DomRefCell, // The spec maintains load order for descendants, so we use an indexset for descendants and // parents. This isn't actually necessary for parents however the IndexSet APIs don't // interop with HashSet, and IndexSet isn't very expensive // (https://github.com/bluss/indexmap/issues/110) // // By default all maps in web specs are ordered maps // (https://infra.spec.whatwg.org/#ordered-map), however we can usually get away with using // stdlib maps and sets because we rarely iterate over them. parent_urls: DomRefCell>, descendant_urls: DomRefCell>, visited_urls: DomRefCell>, error: DomRefCell>, promise: DomRefCell>>, } impl ModuleTree { pub fn new(url: ServoUrl) -> Self { ModuleTree { url, text: DomRefCell::new(DOMString::new()), record: DomRefCell::new(None), status: DomRefCell::new(ModuleStatus::Initial), parent_urls: DomRefCell::new(IndexSet::new()), descendant_urls: DomRefCell::new(IndexSet::new()), visited_urls: DomRefCell::new(HashSet::new()), error: DomRefCell::new(None), promise: DomRefCell::new(None), } } pub fn get_promise(&self) -> &DomRefCell>> { &self.promise } pub fn set_promise(&self, promise: Rc) { *self.promise.borrow_mut() = Some(promise); } pub fn get_status(&self) -> ModuleStatus { self.status.borrow().clone() } pub fn set_status(&self, status: ModuleStatus) { *self.status.borrow_mut() = status; } pub fn get_record(&self) -> &DomRefCell> { &self.record } pub fn set_record(&self, record: ModuleObject) { *self.record.borrow_mut() = Some(record); } pub fn get_error(&self) -> &DomRefCell> { &self.error } pub fn set_error(&self, error: Option) { *self.error.borrow_mut() = error; } pub fn get_text(&self) -> &DomRefCell { &self.text } pub fn set_text(&self, module_text: DOMString) { *self.text.borrow_mut() = module_text; } pub fn get_parent_urls(&self) -> &DomRefCell> { &self.parent_urls } pub fn insert_parent_url(&self, parent_url: ServoUrl) { self.parent_urls.borrow_mut().insert(parent_url); } pub fn append_parent_urls(&self, parent_urls: IndexSet) { self.parent_urls.borrow_mut().extend(parent_urls); } pub fn get_descendant_urls(&self) -> &DomRefCell> { &self.descendant_urls } pub fn append_descendant_urls(&self, descendant_urls: IndexSet) { self.descendant_urls.borrow_mut().extend(descendant_urls); } /// recursively checks if all of the transitive descendants are /// in the FetchingDescendants or later status fn recursive_check_descendants( module_tree: &ModuleTree, module_map: &HashMap>, discovered_urls: &mut HashSet, ) -> bool { discovered_urls.insert(module_tree.url.clone()); let descendant_urls = module_tree.descendant_urls.borrow(); for descendant_module in descendant_urls .iter() .filter_map(|url| module_map.get(&url.clone())) { if discovered_urls.contains(&descendant_module.url) { continue; } let descendant_status = descendant_module.get_status(); if descendant_status < ModuleStatus::FetchingDescendants { return false; } let all_ready_descendants = ModuleTree::recursive_check_descendants( &descendant_module, module_map, discovered_urls, ); if !all_ready_descendants { return false; } } return true; } fn has_all_ready_descendants(&self, module_map: &HashMap>) -> bool { let mut discovered_urls = HashSet::new(); return ModuleTree::recursive_check_descendants(&self, module_map, &mut discovered_urls); } pub fn get_visited_urls(&self) -> &DomRefCell> { &self.visited_urls } pub fn append_handler(&self, owner: ModuleOwner, module_url: ServoUrl, is_top_level: bool) { let promise = self.promise.borrow(); let resolve_this = owner.clone(); let reject_this = owner.clone(); let resolved_url = module_url.clone(); let rejected_url = module_url.clone(); let handler = PromiseNativeHandler::new( &owner.global(), Some(ModuleHandler::new(Box::new( task!(fetched_resolve: move || { resolve_this.finish_module_load(Some(resolved_url), is_top_level); }), ))), Some(ModuleHandler::new(Box::new( task!(failure_reject: move || { reject_this.finish_module_load(Some(rejected_url), is_top_level); }), ))), ); let _realm = enter_realm(&*owner.global()); AlreadyInRealm::assert(&*owner.global()); let _ais = AutoIncumbentScript::new(&*owner.global()); let promise = promise.as_ref().unwrap(); promise.append_native_handler(&handler); } } #[derive(Clone, Copy, Debug, JSTraceable, PartialEq, PartialOrd)] pub enum ModuleStatus { Initial, Fetching, FetchingDescendants, FetchFailed, Ready, Finished, } impl ModuleTree { #[allow(unsafe_code)] /// https://html.spec.whatwg.org/multipage/#creating-a-module-script /// Step 7-11. fn compile_module_script( &self, global: &GlobalScope, module_script_text: DOMString, url: ServoUrl, ) -> Result { let module: Vec = module_script_text.encode_utf16().collect(); let url_cstr = ffi::CString::new(url.as_str().as_bytes()).unwrap(); let _ac = JSAutoRealm::new(*global.get_cx(), *global.reflector().get_jsobject()); let compile_options = unsafe { CompileOptionsWrapper::new(*global.get_cx(), url_cstr.as_ptr(), 1) }; let mut source = get_source_text(&module); unsafe { rooted!(in(*global.get_cx()) let mut module_script = CompileModule( *global.get_cx(), compile_options.ptr, &mut source, )); if module_script.is_null() { warn!("fail to compile module script of {}", url); rooted!(in(*global.get_cx()) let mut exception = UndefinedValue()); assert!(JS_GetPendingException( *global.get_cx(), &mut exception.handle_mut() )); JS_ClearPendingException(*global.get_cx()); return Err(ModuleError::RawException(RootedTraceableBox::from_box( Heap::boxed(exception.get()), ))); } let module_script_data = Box::new(ModuleScript { base_url: url.clone(), }); SetModulePrivate( module_script.get(), &PrivateValue(Box::into_raw(module_script_data) as *const _), ); debug!("module script of {} compile done", url); self.resolve_requested_module_specifiers( &global, module_script.handle().into_handle(), url.clone(), ) .map(|_| ModuleObject(Heap::boxed(*module_script))) } } #[allow(unsafe_code)] pub fn instantiate_module_tree( &self, global: &GlobalScope, module_record: HandleObject, ) -> Result<(), ModuleError> { let _ac = JSAutoRealm::new(*global.get_cx(), *global.reflector().get_jsobject()); unsafe { if !ModuleInstantiate(*global.get_cx(), module_record) { warn!("fail to instantiate module"); rooted!(in(*global.get_cx()) let mut exception = UndefinedValue()); assert!(JS_GetPendingException( *global.get_cx(), &mut exception.handle_mut() )); JS_ClearPendingException(*global.get_cx()); Err(ModuleError::RawException(RootedTraceableBox::from_box( Heap::boxed(exception.get()), ))) } else { debug!("module instantiated successfully"); Ok(()) } } } #[allow(unsafe_code)] pub fn execute_module( &self, global: &GlobalScope, module_record: HandleObject, ) -> Result<(), ModuleError> { let _ac = JSAutoRealm::new(*global.get_cx(), *global.reflector().get_jsobject()); unsafe { if !ModuleEvaluate(*global.get_cx(), module_record) { warn!("fail to evaluate module"); rooted!(in(*global.get_cx()) let mut exception = UndefinedValue()); assert!(JS_GetPendingException( *global.get_cx(), &mut exception.handle_mut() )); JS_ClearPendingException(*global.get_cx()); Err(ModuleError::RawException(RootedTraceableBox::from_box( Heap::boxed(exception.get()), ))) } else { debug!("module evaluated successfully"); Ok(()) } } } #[allow(unsafe_code)] pub fn report_error(&self, global: &GlobalScope) { let module_error = self.error.borrow(); if let Some(exception) = &*module_error { unsafe { let ar = enter_realm(&*global); JS_SetPendingException( *global.get_cx(), exception.handle(), ExceptionStackBehavior::Capture, ); report_pending_exception(*global.get_cx(), true, InRealm::Entered(&ar)); } } } /// https://html.spec.whatwg.org/multipage/#fetch-the-descendants-of-a-module-script /// Step 5. pub fn resolve_requested_modules( &self, global: &GlobalScope, ) -> Result, ModuleError> { let status = self.get_status(); assert_ne!(status, ModuleStatus::Initial); assert_ne!(status, ModuleStatus::Fetching); let record = self.record.borrow(); if let Some(raw_record) = &*record { let valid_specifier_urls = self.resolve_requested_module_specifiers( &global, raw_record.handle(), self.url.clone(), ); return valid_specifier_urls.map(|parsed_urls| { parsed_urls .iter() .filter_map(|parsed_url| { let mut visited = self.visited_urls.borrow_mut(); if !visited.contains(&parsed_url) { visited.insert(parsed_url.clone()); Some(parsed_url.clone()) } else { None } }) .collect::>() }); } unreachable!("Didn't have record while resolving its requested module") } #[allow(unsafe_code)] fn resolve_requested_module_specifiers( &self, global: &GlobalScope, module_object: HandleObject, base_url: ServoUrl, ) -> Result, ModuleError> { let _ac = JSAutoRealm::new(*global.get_cx(), *global.reflector().get_jsobject()); let mut specifier_urls = IndexSet::new(); unsafe { rooted!(in(*global.get_cx()) let requested_modules = GetRequestedModules(*global.get_cx(), module_object)); let mut length = 0; if !JS_GetArrayLength(*global.get_cx(), requested_modules.handle(), &mut length) { let module_length_error = gen_type_error(&global, "Wrong length of requested modules".to_owned()); return Err(module_length_error); } for index in 0..length { rooted!(in(*global.get_cx()) let mut element = UndefinedValue()); if !JS_GetElement( *global.get_cx(), requested_modules.handle(), index, &mut element.handle_mut(), ) { let get_element_error = gen_type_error(&global, "Failed to get requested module".to_owned()); return Err(get_element_error); } rooted!(in(*global.get_cx()) let specifier = GetRequestedModuleSpecifier( *global.get_cx(), element.handle() )); let url = ModuleTree::resolve_module_specifier( *global.get_cx(), &base_url, specifier.handle().into_handle(), ); if url.is_err() { let specifier_error = gen_type_error(&global, "Wrong module specifier".to_owned()); return Err(specifier_error); } specifier_urls.insert(url.unwrap()); } } Ok(specifier_urls) } /// The following module specifiers are allowed by the spec: /// - a valid absolute URL /// - a valid relative URL that starts with "/", "./" or "../" /// /// Bareword module specifiers are currently disallowed as these may be given /// special meanings in the future. /// https://html.spec.whatwg.org/multipage/#resolve-a-module-specifier #[allow(unsafe_code)] fn resolve_module_specifier( cx: *mut JSContext, url: &ServoUrl, specifier: RawHandle<*mut JSString>, ) -> Result { let specifier_str = unsafe { jsstring_to_str(cx, *specifier) }; // Step 1. if let Ok(specifier_url) = ServoUrl::parse(&specifier_str) { return Ok(specifier_url); } // Step 2. if !specifier_str.starts_with("/") && !specifier_str.starts_with("./") && !specifier_str.starts_with("../") { return Err(UrlParseError::InvalidDomainCharacter); } // Step 3. return ServoUrl::parse_with_base(Some(url), &specifier_str.clone()); } /// https://html.spec.whatwg.org/multipage/#finding-the-first-parse-error fn find_first_parse_error( global: &GlobalScope, module_tree: &ModuleTree, discovered_urls: &mut HashSet, ) -> Option { // 3. discovered_urls.insert(module_tree.url.clone()); // 4. let module_map = global.get_module_map().borrow(); let record = module_tree.get_record().borrow(); if record.is_none() { let module_error = module_tree.get_error().borrow(); return module_error.clone(); } // 5-6. let mut errors: Vec = Vec::new(); let descendant_urls = module_tree.get_descendant_urls().borrow(); for descendant_module in descendant_urls .iter() // 7. .filter_map(|url| module_map.get(&url.clone())) { // 8-2. if discovered_urls.contains(&descendant_module.url) { continue; } // 8-3. let child_parse_error = ModuleTree::find_first_parse_error(&global, &descendant_module, discovered_urls); // 8-4. if let Some(child_error) = child_parse_error { errors.push(child_error); } } // Step 9. return errors.into_iter().max(); } } #[derive(JSTraceable, MallocSizeOf)] struct ModuleHandler { #[ignore_malloc_size_of = "Measuring trait objects is hard"] task: DomRefCell>>, } impl ModuleHandler { pub fn new(task: Box) -> Box { Box::new(Self { task: DomRefCell::new(Some(task)), }) } } impl Callback for ModuleHandler { fn callback(&self, _cx: *mut JSContext, _v: HandleValue) { let task = self.task.borrow_mut().take().unwrap(); task.run_box(); } } /// The owner of the module /// It can be `worker` or `script` element #[derive(Clone)] pub enum ModuleOwner { #[allow(dead_code)] Worker(TrustedWorkerAddress), Window(Trusted), } impl ModuleOwner { pub fn global(&self) -> DomRoot { match &self { ModuleOwner::Worker(worker) => (*worker.root().clone()).global(), ModuleOwner::Window(script) => (*script.root()).global(), } } fn gen_promise_with_final_handler( &self, module_url: Option, is_top_level: bool, ) -> Rc { let resolve_this = self.clone(); let reject_this = self.clone(); let resolved_url = module_url.clone(); let rejected_url = module_url.clone(); let handler = PromiseNativeHandler::new( &self.global(), Some(ModuleHandler::new(Box::new( task!(fetched_resolve: move || { resolve_this.finish_module_load(resolved_url, is_top_level); }), ))), Some(ModuleHandler::new(Box::new( task!(failure_reject: move || { reject_this.finish_module_load(rejected_url, is_top_level); }), ))), ); let realm = enter_realm(&*self.global()); let comp = InRealm::Entered(&realm); let _ais = AutoIncumbentScript::new(&*self.global()); let promise = Promise::new_in_current_realm(&self.global(), comp); promise.append_native_handler(&handler); promise } /// https://html.spec.whatwg.org/multipage/#fetch-the-descendants-of-and-link-a-module-script /// step 4-7. pub fn finish_module_load(&self, module_url: Option, is_top_level: bool) { match &self { ModuleOwner::Worker(_) => unimplemented!(), ModuleOwner::Window(script) => { let global = self.global(); let document = document_from_node(&*script.root()); let module_map = global.get_module_map().borrow(); let (module_tree, mut load) = if let Some(script_src) = module_url.clone() { let module_tree = module_map.get(&script_src.clone()).unwrap().clone(); let load = Ok(ScriptOrigin::external( module_tree.get_text().borrow().clone(), script_src.clone(), ScriptType::Module, )); debug!( "Going to finish external script from {}", script_src.clone() ); (module_tree, load) } else { let module_tree = { let inline_module_map = global.get_inline_module_map().borrow(); inline_module_map .get(&script.root().get_script_id()) .unwrap() .clone() }; let base_url = document.base_url(); let load = Ok(ScriptOrigin::internal( module_tree.get_text().borrow().clone(), base_url.clone(), ScriptType::Module, )); debug!("Going to finish internal script from {}", base_url.clone()); (module_tree, load) }; module_tree.set_status(ModuleStatus::Finished); if !module_tree.has_all_ready_descendants(&module_map) { return; } let parent_urls = module_tree.get_parent_urls().borrow(); let parent_all_ready = parent_urls .iter() .filter_map(|parent_url| module_map.get(&parent_url.clone())) .all(|parent_tree| parent_tree.has_all_ready_descendants(&module_map)); if !parent_all_ready { return; } parent_urls .iter() .filter_map(|parent_url| module_map.get(&parent_url.clone())) .for_each(|parent_tree| { let parent_promise = parent_tree.get_promise().borrow(); if let Some(promise) = parent_promise.as_ref() { promise.resolve_native(&()); } }); let mut discovered_urls: HashSet = HashSet::new(); let module_error = ModuleTree::find_first_parse_error(&global, &module_tree, &mut discovered_urls); match module_error { None => { let module_record = module_tree.get_record().borrow(); if let Some(record) = &*module_record { let instantiated = module_tree.instantiate_module_tree(&global, record.handle()); if let Err(exception) = instantiated { module_tree.set_error(Some(exception.clone())); } } }, Some(ModuleError::RawException(exception)) => { module_tree.set_error(Some(ModuleError::RawException(exception))); }, Some(ModuleError::Network(network_error)) => { module_tree.set_error(Some(ModuleError::Network(network_error.clone()))); // Change the `result` load of the script into `network` error load = Err(network_error); }, }; if is_top_level { let r#async = script .root() .upcast::() .has_attribute(&local_name!("async")); if !r#async && (&*script.root()).get_parser_inserted() { document.deferred_script_loaded(&*script.root(), load); } else if !r#async && !(&*script.root()).get_non_blocking() { document.asap_in_order_script_loaded(&*script.root(), load); } else { document.asap_script_loaded(&*script.root(), load); }; } }, } } } /// The context required for asynchronously loading an external module script source. struct ModuleContext { /// The owner of the module that initiated the request. owner: ModuleOwner, /// The response body received to date. data: Vec, /// The response metadata received to date. metadata: Option, /// The initial URL requested. url: ServoUrl, /// Destination of current module context destination: Destination, /// Credentials Mode of current module context credentials_mode: CredentialsMode, /// Indicates whether the request failed, and why status: Result<(), NetworkError>, /// Timing object for this resource resource_timing: ResourceFetchTiming, } impl FetchResponseListener for ModuleContext { fn process_request_body(&mut self) {} // TODO(cybai): Perhaps add custom steps to perform fetch here? fn process_request_eof(&mut self) {} // TODO(cybai): Perhaps add custom steps to perform fetch here? fn process_response(&mut self, metadata: Result) { self.metadata = metadata.ok().map(|meta| match meta { FetchMetadata::Unfiltered(m) => m, FetchMetadata::Filtered { unsafe_, .. } => unsafe_, }); let status_code = self .metadata .as_ref() .and_then(|m| match m.status { Some((c, _)) => Some(c), _ => None, }) .unwrap_or(0); self.status = match status_code { 0 => Err(NetworkError::Internal( "No http status code received".to_owned(), )), 200..=299 => Ok(()), // HTTP ok status codes _ => Err(NetworkError::Internal(format!( "HTTP error code {}", status_code ))), }; } fn process_response_chunk(&mut self, mut chunk: Vec) { if self.status.is_ok() { self.data.append(&mut chunk); } } /// /// Step 9-12 #[allow(unsafe_code)] fn process_response_eof(&mut self, response: Result) { let global = self.owner.global(); if let Some(window) = global.downcast::() { window .Document() .finish_load(LoadType::Script(self.url.clone())); } // Step 9-1 & 9-2. let load = response.and(self.status.clone()).and_then(|_| { // Step 9-3. let meta = self.metadata.take().unwrap(); if let Some(content_type) = meta.content_type.map(Serde::into_inner) { if let Ok(content_type) = Mime::from_str(&content_type.to_string()) { let essence_mime = content_type.essence_str(); if !SCRIPT_JS_MIMES.contains(&essence_mime) { return Err(NetworkError::Internal(format!( "Invalid MIME type: {}", essence_mime ))); } } else { return Err(NetworkError::Internal(format!( "Failed to parse MIME type: {}", content_type.to_string() ))); } } else { return Err(NetworkError::Internal("No MIME type".into())); } // Step 10. let (source_text, _, _) = UTF_8.decode(&self.data); Ok(ScriptOrigin::external( DOMString::from(source_text), meta.final_url, ScriptType::Module, )) }); if let Err(err) = load { // Step 9. error!("Failed to fetch {} with error {:?}", self.url.clone(), err); let module_tree = { let module_map = global.get_module_map().borrow(); module_map.get(&self.url.clone()).unwrap().clone() }; module_tree.set_status(ModuleStatus::FetchFailed); module_tree.set_error(Some(ModuleError::Network(err))); let promise = module_tree.get_promise().borrow(); promise.as_ref().unwrap().resolve_native(&()); return; } // Step 12. if let Ok(ref resp_mod_script) = load { let module_tree = { let module_map = global.get_module_map().borrow(); module_map.get(&self.url.clone()).unwrap().clone() }; module_tree.set_text(resp_mod_script.text()); let compiled_module = module_tree.compile_module_script( &global, resp_mod_script.text(), self.url.clone(), ); match compiled_module { Err(exception) => { module_tree.set_error(Some(exception)); let promise = module_tree.get_promise().borrow(); promise.as_ref().unwrap().resolve_native(&()); return; }, Ok(record) => { module_tree.set_record(record); { let mut visited = module_tree.get_visited_urls().borrow_mut(); visited.insert(self.url.clone()); } let descendant_results = fetch_module_descendants_and_link( &self.owner, &module_tree, self.destination.clone(), self.credentials_mode.clone(), ); // Resolve the request of this module tree promise directly // when there's no descendant if descendant_results.is_none() { module_tree.set_status(ModuleStatus::Ready); let promise = module_tree.get_promise().borrow(); promise.as_ref().unwrap().resolve_native(&()); } }, } } } fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { &mut self.resource_timing } fn resource_timing(&self) -> &ResourceFetchTiming { &self.resource_timing } fn submit_resource_timing(&mut self) { network_listener::submit_timing(self) } } impl ResourceTimingListener for ModuleContext { fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { let initiator_type = InitiatorType::LocalName("module".to_string()); (initiator_type, self.url.clone()) } fn resource_timing_global(&self) -> DomRoot { self.owner.global() } } impl PreInvoke for ModuleContext {} #[allow(unsafe_code, non_snake_case)] /// A function to register module hooks (e.g. listening on resolving modules, /// getting module metadata, getting script private reference and resolving dynamic import) pub unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) { if GetModuleResolveHook(rt).is_some() { return; } SetModuleResolveHook(rt, Some(HostResolveImportedModule)); SetModuleMetadataHook(rt, None); SetScriptPrivateReferenceHooks(rt, None, None); SetModuleDynamicImportHook(rt, None); } #[allow(unsafe_code, non_snake_case)] /// https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule /// https://html.spec.whatwg.org/multipage/#hostresolveimportedmodule(referencingscriptormodule%2C-specifier) unsafe extern "C" fn HostResolveImportedModule( cx: *mut JSContext, reference_private: RawHandleValue, specifier: RawHandle<*mut JSString>, ) -> *mut JSObject { let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); let global_scope = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)); // Step 2. let mut base_url = global_scope.api_base_url(); // Step 3. let module_data = (reference_private.to_private() as *const ModuleScript).as_ref(); if let Some(data) = module_data { base_url = data.base_url.clone(); } // Step 5. let url = ModuleTree::resolve_module_specifier(*global_scope.get_cx(), &base_url, specifier); // Step 6. assert!(url.is_ok()); let parsed_url = url.unwrap(); // Step 4 & 7. let module_map = global_scope.get_module_map().borrow(); let module_tree = module_map.get(&parsed_url); // Step 9. assert!(module_tree.is_some()); let fetched_module_object = module_tree.unwrap().get_record().borrow(); // Step 8. assert!(fetched_module_object.is_some()); // Step 10. if let Some(record) = &*fetched_module_object { return record.handle().get(); } unreachable!() } /// https://html.spec.whatwg.org/multipage/#fetch-a-module-script-tree pub fn fetch_external_module_script( owner: ModuleOwner, url: ServoUrl, destination: Destination, integrity_metadata: String, credentials_mode: CredentialsMode, ) -> Rc { // Step 1. fetch_single_module_script( owner, url, destination, Referrer::Client, ParserMetadata::NotParserInserted, integrity_metadata, credentials_mode, None, true, ) } /// https://html.spec.whatwg.org/multipage/#fetch-a-single-module-script pub fn fetch_single_module_script( owner: ModuleOwner, url: ServoUrl, destination: Destination, referrer: Referrer, parser_metadata: ParserMetadata, integrity_metadata: String, credentials_mode: CredentialsMode, parent_url: Option, top_level_module_fetch: bool, ) -> Rc { { // Step 1. let global = owner.global(); let module_map = global.get_module_map().borrow(); debug!("Start to fetch {}", url); if let Some(module_tree) = module_map.get(&url.clone()) { let status = module_tree.get_status(); let promise = module_tree.get_promise().borrow(); debug!("Meet a fetched url {} and its status is {:?}", url, status); assert!(promise.is_some()); module_tree.append_handler(owner.clone(), url.clone(), top_level_module_fetch); let promise = promise.as_ref().unwrap(); match status { ModuleStatus::Initial => unreachable!( "We have the module in module map so its status should not be `initial`" ), // Step 2. ModuleStatus::Fetching => return promise.clone(), ModuleStatus::FetchingDescendants => { if module_tree.has_all_ready_descendants(&module_map) { promise.resolve_native(&()); } }, // Step 3. ModuleStatus::FetchFailed | ModuleStatus::Ready | ModuleStatus::Finished => { promise.resolve_native(&()); }, } return promise.clone(); } } let global = owner.global(); let module_tree = ModuleTree::new(url.clone()); module_tree.set_status(ModuleStatus::Fetching); let promise = owner.gen_promise_with_final_handler(Some(url.clone()), top_level_module_fetch); module_tree.set_promise(promise.clone()); if let Some(parent_url) = parent_url { module_tree.insert_parent_url(parent_url); } // Step 4. global.set_module_map(url.clone(), module_tree); // Step 5-6. let mode = match destination.clone() { Destination::Worker | Destination::SharedWorker if top_level_module_fetch => { RequestMode::SameOrigin }, _ => RequestMode::CorsMode, }; let document: Option> = match &owner { ModuleOwner::Worker(_) => None, ModuleOwner::Window(script) => Some(document_from_node(&*script.root())), }; // Step 7-8. let request = RequestBuilder::new(url.clone()) .destination(destination.clone()) .origin(global.origin().immutable().clone()) .referrer(Some(referrer)) .parser_metadata(parser_metadata) .integrity_metadata(integrity_metadata.clone()) .credentials_mode(credentials_mode) .mode(mode); let context = Arc::new(Mutex::new(ModuleContext { owner, data: vec![], metadata: None, url: url.clone(), destination: destination.clone(), credentials_mode: credentials_mode.clone(), status: Ok(()), resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), })); let (action_sender, action_receiver) = ipc::channel().unwrap(); let listener = NetworkListener { context, task_source: global.networking_task_source(), canceller: Some(global.task_canceller(TaskSourceName::Networking)), }; ROUTER.add_route( action_receiver.to_opaque(), Box::new(move |message| { listener.notify_fetch(message.to().unwrap()); }), ); if let Some(doc) = document { doc.fetch_async(LoadType::Script(url), request, action_sender); } promise } #[allow(unsafe_code)] /// https://html.spec.whatwg.org/multipage/#fetch-an-inline-module-script-graph pub fn fetch_inline_module_script( owner: ModuleOwner, module_script_text: DOMString, url: ServoUrl, script_id: ScriptId, credentials_mode: CredentialsMode, ) { let global = owner.global(); let module_tree = ModuleTree::new(url.clone()); let promise = owner.gen_promise_with_final_handler(None, true); module_tree.set_promise(promise.clone()); let compiled_module = module_tree.compile_module_script(&global, module_script_text, url.clone()); match compiled_module { Ok(record) => { module_tree.set_record(record); let descendant_results = fetch_module_descendants_and_link( &owner, &module_tree, Destination::Script, credentials_mode, ); global.set_inline_module_map(script_id, module_tree); if descendant_results.is_none() { promise.resolve_native(&()); } }, Err(exception) => { module_tree.set_status(ModuleStatus::Ready); module_tree.set_error(Some(exception)); global.set_inline_module_map(script_id, module_tree); promise.resolve_native(&()); }, } } /// https://html.spec.whatwg.org/multipage/#fetch-the-descendants-of-and-link-a-module-script /// Step 1-3. #[allow(unsafe_code)] fn fetch_module_descendants_and_link( owner: &ModuleOwner, module_tree: &ModuleTree, destination: Destination, credentials_mode: CredentialsMode, ) -> Option> { let descendant_results = fetch_module_descendants(owner, module_tree, destination, credentials_mode); match descendant_results { Ok(descendants) => { if descendants.len() > 0 { unsafe { let global = owner.global(); let _realm = enter_realm(&*global); AlreadyInRealm::assert(&*global); let _ais = AutoIncumbentScript::new(&*global); let abv = RootedObjectVectorWrapper::new(*global.get_cx()); for descendant in descendants { assert!(abv.append(descendant.promise_obj().get())); } rooted!( in(*global.get_cx()) let raw_promise_all = GetWaitForAllPromise(*global.get_cx(), abv.handle()) ); let promise_all = Promise::new_with_js_promise(raw_promise_all.handle(), global.get_cx()); let promise = module_tree.get_promise().borrow(); let promise = promise.as_ref().unwrap().clone(); let resolve_promise = TrustedPromise::new(promise.clone()); let reject_promise = TrustedPromise::new(promise.clone()); let handler = PromiseNativeHandler::new( &global, Some(ModuleHandler::new(Box::new( task!(all_fetched_resolve: move || { let promise = resolve_promise.root(); promise.resolve_native(&()); }), ))), Some(ModuleHandler::new(Box::new( task!(all_failure_reject: move || { let promise = reject_promise.root(); promise.reject_native(&()); }), ))), ); promise_all.append_native_handler(&handler); return Some(promise_all); } } }, Err(err) => { module_tree.set_error(Some(err)); }, } None } #[allow(unsafe_code)] /// https://html.spec.whatwg.org/multipage/#fetch-the-descendants-of-a-module-script fn fetch_module_descendants( owner: &ModuleOwner, module_tree: &ModuleTree, destination: Destination, credentials_mode: CredentialsMode, ) -> Result>, ModuleError> { debug!("Start to load dependencies of {}", module_tree.url.clone()); let global = owner.global(); module_tree.set_status(ModuleStatus::FetchingDescendants); module_tree .resolve_requested_modules(&global) .map(|requested_urls| { module_tree.append_descendant_urls(requested_urls.clone()); let parent_urls = module_tree.get_parent_urls().borrow(); if parent_urls.intersection(&requested_urls).count() > 0 { return Vec::new(); } requested_urls .iter() .map(|requested_url| { // https://html.spec.whatwg.org/multipage/#internal-module-script-graph-fetching-procedure // Step 1. { let visited = module_tree.get_visited_urls().borrow(); assert!(visited.get(&requested_url).is_some()); } // Step 2. fetch_single_module_script( owner.clone(), requested_url.clone(), destination.clone(), Referrer::Client, ParserMetadata::NotParserInserted, "".to_owned(), credentials_mode.clone(), Some(module_tree.url.clone()), false, ) }) .collect() }) }