aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/script_module.rs
diff options
context:
space:
mode:
authorCYBAI <cyb.ai.815@gmail.com>2019-06-05 22:20:10 +0900
committerCYBAI <cyb.ai.815@gmail.com>2020-01-03 13:02:31 +0900
commitf2007751dd12016bc01fdbe2788322be2eae0d84 (patch)
tree11f6f623ef3ea22a10026ee682652b685b39508a /components/script/script_module.rs
parent86575bba1b50e772d347f5e0b4884cfb9c588971 (diff)
downloadservo-f2007751dd12016bc01fdbe2788322be2eae0d84.tar.gz
servo-f2007751dd12016bc01fdbe2788322be2eae0d84.zip
Scaffold module script
Diffstat (limited to 'components/script/script_module.rs')
-rw-r--r--components/script/script_module.rs1404
1 files changed, 1404 insertions, 0 deletions
diff --git a/components/script/script_module.rs b/components/script/script_module.rs
new file mode 100644
index 00000000000..9a1c297aac0
--- /dev/null
+++ b/components/script/script_module.rs
@@ -0,0 +1,1404 @@
+/* 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::compartments::{enter_realm, AlreadyInCompartment, InCompartment};
+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::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::glue::{AppendToAutoObjectVector, CreateAutoObjectVector};
+use js::jsapi::Handle as RawHandle;
+use js::jsapi::HandleObject;
+use js::jsapi::HandleValue as RawHandleValue;
+use js::jsapi::{AutoObjectVector, JSAutoRealm, JSObject, JSString};
+use js::jsapi::{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::{SetModuleDynamicImportHook, SetScriptPrivateReferenceHooks};
+use js::jsval::{JSVal, PrivateValue, UndefinedValue};
+use js::rust::jsapi_wrapped::{CompileModule, JS_GetArrayLength, JS_GetElement};
+use js::rust::jsapi_wrapped::{GetRequestedModuleSpecifier, JS_GetPendingException};
+use js::rust::wrappers::JS_SetPendingException;
+use js::rust::CompileOptionsWrapper;
+use js::rust::IntoHandle;
+use js::rust::{Handle, HandleValue};
+use net_traits::request::{Destination, ParserMetadata, 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::ptr;
+use std::rc::Rc;
+use std::sync::{Arc, Mutex};
+use url::ParseError as UrlParseError;
+
+pub fn get_source_text(source: &[u16]) -> SourceText<u16> {
+ 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<Heap<*mut JSObject>>);
+
+impl ModuleObject {
+ #[allow(unsafe_code)]
+ pub fn handle(&self) -> HandleObject {
+ unsafe { self.0.handle() }
+ }
+}
+
+#[derive(JSTraceable)]
+pub enum ModuleError {
+ Network(NetworkError),
+ RawException(RootedTraceableBox<Heap<JSVal>>),
+}
+
+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<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl ModuleError {
+ #[allow(unsafe_code)]
+ pub fn handle(&self) -> Handle<JSVal> {
+ 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<DOMString>,
+ record: DomRefCell<Option<ModuleObject>>,
+ status: DomRefCell<ModuleStatus>,
+ parent_urls: DomRefCell<HashSet<ServoUrl>>,
+ descendant_urls: DomRefCell<HashSet<ServoUrl>>,
+ visited_urls: DomRefCell<HashSet<ServoUrl>>,
+ error: DomRefCell<Option<ModuleError>>,
+ promise: DomRefCell<Option<Rc<Promise>>>,
+}
+
+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(HashSet::new()),
+ descendant_urls: DomRefCell::new(HashSet::new()),
+ visited_urls: DomRefCell::new(HashSet::new()),
+ error: DomRefCell::new(None),
+ promise: DomRefCell::new(None),
+ }
+ }
+
+ pub fn get_promise(&self) -> &DomRefCell<Option<Rc<Promise>>> {
+ &self.promise
+ }
+
+ pub fn set_promise(&self, promise: Rc<Promise>) {
+ *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<Option<ModuleObject>> {
+ &self.record
+ }
+
+ pub fn set_record(&self, record: ModuleObject) {
+ *self.record.borrow_mut() = Some(record);
+ }
+
+ pub fn get_error(&self) -> &DomRefCell<Option<ModuleError>> {
+ &self.error
+ }
+
+ pub fn set_error(&self, error: Option<ModuleError>) {
+ *self.error.borrow_mut() = error;
+ }
+
+ pub fn get_text(&self) -> &DomRefCell<DOMString> {
+ &self.text
+ }
+
+ pub fn set_text(&self, module_text: DOMString) {
+ *self.text.borrow_mut() = module_text;
+ }
+
+ pub fn get_parent_urls(&self) -> &DomRefCell<HashSet<ServoUrl>> {
+ &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: HashSet<ServoUrl>) {
+ self.parent_urls.borrow_mut().extend(parent_urls);
+ }
+
+ pub fn get_descendant_urls(&self) -> &DomRefCell<HashSet<ServoUrl>> {
+ &self.descendant_urls
+ }
+
+ pub fn append_descendant_urls(&self, descendant_urls: HashSet<ServoUrl>) {
+ 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<ServoUrl, Rc<ModuleTree>>,
+ discovered_urls: &mut HashSet<ServoUrl>,
+ ) -> 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<ServoUrl, Rc<ModuleTree>>) -> 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<HashSet<ServoUrl>> {
+ &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 _compartment = enter_realm(&*owner.global());
+ AlreadyInCompartment::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<ModuleObject, ModuleError> {
+ let module: Vec<u16> = 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 = CompileOptionsWrapper::new(*global.get_cx(), url_cstr.as_ptr(), 1);
+
+ rooted!(in(*global.get_cx()) let mut module_script = ptr::null_mut::<JSObject>());
+
+ let mut source = get_source_text(&module);
+
+ unsafe {
+ if !CompileModule(
+ *global.get_cx(),
+ compile_options.ptr,
+ &mut source,
+ &mut module_script.handle_mut(),
+ ) {
+ 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 {
+ JS_SetPendingException(*global.get_cx(), exception.handle());
+ report_pending_exception(*global.get_cx(), true);
+ }
+ }
+ }
+
+ /// https://html.spec.whatwg.org/multipage/#fetch-the-descendants-of-a-module-script
+ /// Step 5.
+ pub fn resolve_requested_modules(
+ &self,
+ global: &GlobalScope,
+ ) -> Result<HashSet<ServoUrl>, 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::<HashSet<ServoUrl>>()
+ });
+ }
+
+ 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<HashSet<ServoUrl>, ModuleError> {
+ let _ac = JSAutoRealm::new(*global.get_cx(), *global.reflector().get_jsobject());
+
+ let mut specifier_urls = HashSet::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<ServoUrl, UrlParseError> {
+ 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<ServoUrl>,
+ ) -> Option<ModuleError> {
+ // 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 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 child_parse_error.is_some() {
+ return child_parse_error;
+ }
+ }
+
+ // Step 9.
+ return None;
+ }
+}
+
+#[derive(JSTraceable, MallocSizeOf)]
+struct ModuleHandler {
+ #[ignore_malloc_size_of = "Measuring trait objects is hard"]
+ task: DomRefCell<Option<Box<dyn TaskBox>>>,
+}
+
+impl ModuleHandler {
+ pub fn new(task: Box<dyn TaskBox>) -> Box<dyn Callback> {
+ 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<HTMLScriptElement>),
+}
+
+impl ModuleOwner {
+ pub fn global(&self) -> DomRoot<GlobalScope> {
+ 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<ServoUrl>,
+ is_top_level: bool,
+ ) -> Rc<Promise> {
+ 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 compartment = enter_realm(&*self.global());
+ let comp = InCompartment::Entered(&compartment);
+ let _ais = AutoIncumbentScript::new(&*self.global());
+
+ let promise = Promise::new_in_current_compartment(&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<ServoUrl>, 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<ServoUrl> = 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::<Element>()
+ .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<u8>,
+ /// The response metadata received to date.
+ metadata: Option<Metadata>,
+ /// The initial URL requested.
+ url: ServoUrl,
+ /// Destination of current module context
+ destination: Destination,
+ /// 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<FetchMetadata, NetworkError>) {
+ 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<u8>) {
+ if self.status.is_ok() {
+ self.data.append(&mut chunk);
+ }
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#fetch-a-single-module-script>
+ /// Step 9-12
+ #[allow(unsafe_code)]
+ fn process_response_eof(&mut self, response: Result<ResourceFetchTiming, NetworkError>) {
+ let global = self.owner.global();
+
+ if let Some(window) = global.downcast::<Window>() {
+ 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 !SCRIPT_JS_MIMES.contains(&content_type.to_string().as_str()) {
+ return Err(NetworkError::Internal("Invalid MIME type".to_owned()));
+ }
+ }
+
+ // 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.
+ warn!("Failed to fetch {}", self.url.clone());
+ 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(),
+ );
+
+ // 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<GlobalScope> {
+ self.owner.global()
+ }
+}
+
+impl PreInvoke for ModuleContext {}
+
+#[allow(unsafe_code)]
+/// 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)]
+/// 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 global_scope = GlobalScope::from_context(cx);
+
+ // 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,
+) -> Rc<Promise> {
+ // Step 1.
+ fetch_single_module_script(
+ owner,
+ url,
+ destination,
+ Referrer::Client,
+ ParserMetadata::NotParserInserted,
+ 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,
+ parent_url: Option<ServoUrl>,
+ top_level_module_fetch: bool,
+) -> Rc<Promise> {
+ {
+ // Step 1.
+ let global = owner.global();
+ let module_map = global.get_module_map().borrow();
+
+ debug!("Start to fetch {}", url.clone());
+
+ 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.clone(),
+ 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) {
+ let module_error = module_tree.get_error().borrow();
+
+ if module_error.is_some() {
+ promise.resolve_native(&());
+ } else {
+ promise.resolve_native(&());
+ }
+ }
+ },
+ // Step 3.
+ ModuleStatus::FetchFailed | ModuleStatus::Ready | ModuleStatus::Finished => {
+ let module_error = module_tree.get_error().borrow();
+
+ if module_error.is_some() {
+ promise.resolve_native(&());
+ } else {
+ 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<DomRoot<Document>> = 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)
+ .mode(mode);
+
+ let context = Arc::new(Mutex::new(ModuleContext {
+ owner,
+ data: vec![],
+ metadata: None,
+ url: url.clone(),
+ destination: destination.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,
+) {
+ 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);
+
+ 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,
+) -> Option<Rc<Promise>> {
+ let descendant_results = fetch_module_descendants(owner, module_tree, destination);
+
+ match descendant_results {
+ Ok(descendants) => {
+ if descendants.len() > 0 {
+ unsafe {
+ let global = owner.global();
+
+ let _compartment = enter_realm(&*global);
+ AlreadyInCompartment::assert(&*global);
+ let _ais = AutoIncumbentScript::new(&*global);
+
+ let abv = CreateAutoObjectVector(*global.get_cx());
+
+ for descendant in descendants {
+ assert!(AppendToAutoObjectVector(
+ abv as *mut AutoObjectVector,
+ descendant.promise_obj().get()
+ ));
+ }
+
+ rooted!(in(*global.get_cx()) let raw_promise_all = GetWaitForAllPromise(*global.get_cx(), abv));
+
+ 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,
+) -> Result<Vec<Rc<Promise>>, 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,
+ Some(module_tree.url.clone()),
+ false,
+ )
+ })
+ .collect()
+ })
+}