mirror of
https://github.com/servo/servo.git
synced 2025-06-28 11:03:39 +01:00
script: complete resolve_module_specifier
(#37552)
Implement whole spec of `resolve_module_specifier`. Servo can now support script element with import map type! Testing: `tests/wpt/tests/import-map` Fixes: #37316 #36394 --------- Signed-off-by: Wu Yu Wei <yuweiwu@pm.me>
This commit is contained in:
parent
d7269c0f3b
commit
927573de97
35 changed files with 376 additions and 281 deletions
|
@ -4,7 +4,7 @@
|
|||
|
||||
use std::cell::{Cell, OnceCell, Ref};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::ops::Index;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
@ -137,7 +137,7 @@ use crate::microtask::{Microtask, MicrotaskQueue, UserMicrotask};
|
|||
use crate::network_listener::{NetworkListener, PreInvoke};
|
||||
use crate::realms::{InRealm, enter_realm};
|
||||
use crate::script_module::{
|
||||
DynamicModuleList, ImportMap, ModuleScript, ModuleTree, ScriptFetchOptions,
|
||||
DynamicModuleList, ImportMap, ModuleScript, ModuleTree, ResolvedModule, ScriptFetchOptions,
|
||||
};
|
||||
use crate::script_runtime::{CanGc, JSContext as SafeJSContext, ThreadSafeJSContext};
|
||||
use crate::script_thread::{ScriptThread, with_script_thread};
|
||||
|
@ -385,6 +385,9 @@ pub(crate) struct GlobalScope {
|
|||
///
|
||||
/// <https://html.spec.whatwg.org/multipage/#import-maps>
|
||||
import_map: DomRefCell<ImportMap>,
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#resolved-module-set>
|
||||
resolved_module_set: DomRefCell<HashSet<ResolvedModule>>,
|
||||
}
|
||||
|
||||
/// A wrapper for glue-code between the ipc router and the event-loop.
|
||||
|
@ -789,6 +792,7 @@ impl GlobalScope {
|
|||
count_queuing_strategy_size_function: OnceCell::new(),
|
||||
notification_permission_request_callback_map: Default::default(),
|
||||
import_map: Default::default(),
|
||||
resolved_module_set: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3487,8 +3491,40 @@ impl GlobalScope {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn import_map(&self) -> &DomRefCell<ImportMap> {
|
||||
&self.import_map
|
||||
pub(crate) fn import_map(&self) -> Ref<'_, ImportMap> {
|
||||
self.import_map.borrow()
|
||||
}
|
||||
|
||||
pub(crate) fn import_map_mut(&self) -> RefMut<'_, ImportMap> {
|
||||
self.import_map.borrow_mut()
|
||||
}
|
||||
|
||||
pub(crate) fn resolved_module_set(&self) -> Ref<'_, HashSet<ResolvedModule>> {
|
||||
self.resolved_module_set.borrow()
|
||||
}
|
||||
|
||||
pub(crate) fn resolved_module_set_mut(&self) -> RefMut<'_, HashSet<ResolvedModule>> {
|
||||
self.resolved_module_set.borrow_mut()
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#add-module-to-resolved-module-set>
|
||||
pub(crate) fn add_module_to_resolved_module_set(
|
||||
&self,
|
||||
base_url: &str,
|
||||
specifier: &str,
|
||||
specifier_url: Option<ServoUrl>,
|
||||
) {
|
||||
// Step 1. Let global be settingsObject's global object.
|
||||
// Step 2. If global does not implement Window, then return.
|
||||
if self.is::<Window>() {
|
||||
// Step 3. Let record be a new specifier resolution record, with serialized base URL
|
||||
// set to serializedBaseURL, specifier set to normalizedSpecifier, and specifier as
|
||||
// a URL set to asURL.
|
||||
let record =
|
||||
ResolvedModule::new(base_url.to_owned(), specifier.to_owned(), specifier_url);
|
||||
// Step 4. Append record to global's resolved module set.
|
||||
self.resolved_module_set.borrow_mut().insert(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -856,7 +856,9 @@ impl HTMLScriptElement {
|
|||
credentials_mode: module_credentials_mode,
|
||||
};
|
||||
|
||||
// TODO: Step 30. Environment settings object.
|
||||
// Step 30. Let settings object be el's node document's relevant settings object.
|
||||
// This is done by passing ModuleOwner in step 31.11 and step 32.2.
|
||||
// What we actually need is global's import map eventually.
|
||||
|
||||
let base_url = doc.base_url();
|
||||
if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) {
|
||||
|
|
|
@ -22,13 +22,13 @@ use js::jsapi::{
|
|||
GetModuleResolveHook, GetRequestedModuleSpecifier, GetRequestedModulesCount,
|
||||
Handle as RawHandle, HandleObject, HandleValue as RawHandleValue, Heap,
|
||||
JS_ClearPendingException, JS_DefineProperty4, JS_IsExceptionPending, JS_NewStringCopyN,
|
||||
JSAutoRealm, JSContext, JSObject, JSPROP_ENUMERATE, JSRuntime, JSString, ModuleErrorBehaviour,
|
||||
JSAutoRealm, JSContext, JSObject, JSPROP_ENUMERATE, JSRuntime, ModuleErrorBehaviour,
|
||||
ModuleEvaluate, ModuleLink, MutableHandleValue, SetModuleDynamicImportHook,
|
||||
SetModuleMetadataHook, SetModulePrivate, SetModuleResolveHook, SetScriptPrivateReferenceHooks,
|
||||
ThrowOnModuleEvaluationFailure, Value,
|
||||
};
|
||||
use js::jsval::{JSVal, PrivateValue, UndefinedValue};
|
||||
use js::rust::wrappers::{JS_GetPendingException, JS_SetPendingException};
|
||||
use js::rust::wrappers::{JS_GetModulePrivate, JS_GetPendingException, JS_SetPendingException};
|
||||
use js::rust::{
|
||||
CompileOptionsWrapper, Handle, HandleObject as RustHandleObject, HandleValue, IntoHandle,
|
||||
MutableHandleObject as RustMutableHandleObject, transform_str_to_source_text,
|
||||
|
@ -512,7 +512,6 @@ impl ModuleTree {
|
|||
self.resolve_requested_module_specifiers(
|
||||
global,
|
||||
module_script.handle().into_handle(),
|
||||
url,
|
||||
can_gc,
|
||||
)
|
||||
.map(|_| ())
|
||||
|
@ -616,7 +615,6 @@ impl ModuleTree {
|
|||
&self,
|
||||
global: &GlobalScope,
|
||||
module_object: HandleObject,
|
||||
base_url: &ServoUrl,
|
||||
can_gc: CanGc,
|
||||
) -> Result<IndexSet<ServoUrl>, RethrowError> {
|
||||
let cx = GlobalScope::get_cx();
|
||||
|
@ -628,17 +626,19 @@ impl ModuleTree {
|
|||
let length = GetRequestedModulesCount(*cx, module_object);
|
||||
|
||||
for index in 0..length {
|
||||
rooted!(in(*cx) let specifier = GetRequestedModuleSpecifier(
|
||||
*cx, module_object, index
|
||||
));
|
||||
|
||||
let url = ModuleTree::resolve_module_specifier(
|
||||
let specifier = jsstring_to_str(
|
||||
*cx,
|
||||
base_url,
|
||||
specifier.handle().into_handle(),
|
||||
ptr::NonNull::new(GetRequestedModuleSpecifier(*cx, module_object, index))
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
if url.is_none() {
|
||||
rooted!(in(*cx) let mut private = UndefinedValue());
|
||||
JS_GetModulePrivate(module_object.get(), private.handle_mut());
|
||||
let private = private.handle().into_handle();
|
||||
let script = module_script_from_reference_private(&private);
|
||||
let url = ModuleTree::resolve_module_specifier(global, script, specifier, can_gc);
|
||||
|
||||
if url.is_err() {
|
||||
let specifier_error =
|
||||
gen_type_error(global, "Wrong module specifier".to_owned(), can_gc);
|
||||
|
||||
|
@ -652,24 +652,111 @@ impl ModuleTree {
|
|||
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>,
|
||||
) -> Option<ServoUrl> {
|
||||
let specifier_str = unsafe { jsstring_to_str(cx, ptr::NonNull::new(*specifier).unwrap()) };
|
||||
global: &GlobalScope,
|
||||
script: Option<&ModuleScript>,
|
||||
specifier: DOMString,
|
||||
can_gc: CanGc,
|
||||
) -> Fallible<ServoUrl> {
|
||||
// Step 1~3 to get settingsObject and baseURL
|
||||
let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.global()));
|
||||
// Step 1. Let settingsObject and baseURL be null.
|
||||
let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
|
||||
// Step 2. If referringScript is not null, then:
|
||||
// Set settingsObject to referringScript's settings object.
|
||||
// Set baseURL to referringScript's base URL.
|
||||
Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
|
||||
// Step 3. Otherwise:
|
||||
// Set settingsObject to the current settings object.
|
||||
// Set baseURL to settingsObject's API base URL.
|
||||
// FIXME(#37553): Is this the correct current settings object?
|
||||
None => (global, &global.api_base_url()),
|
||||
};
|
||||
|
||||
// TODO: We return the url here to keep the origianl behavior. should fix when we implement the full spec.
|
||||
// Step 4. Let importMap be an empty import map.
|
||||
// Step 5. If settingsObject's global object implements Window, then set importMap to settingsObject's
|
||||
// global object's import map.
|
||||
let import_map = if global.is::<Window>() {
|
||||
Some(global.import_map())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Step 6. Let serializedBaseURL be baseURL, serialized.
|
||||
let serialized_base_url = base_url.as_str();
|
||||
// Step 7. Let asURL be the result of resolving a URL-like module specifier given specifier and baseURL.
|
||||
Self::resolve_url_like_module_specifier(&specifier_str, url)
|
||||
let as_url = Self::resolve_url_like_module_specifier(&specifier, base_url);
|
||||
// Step 8. Let normalizedSpecifier be the serialization of asURL, if asURL is non-null;
|
||||
// otherwise, specifier.
|
||||
let normalized_specifier = match &as_url {
|
||||
Some(url) => url.as_str(),
|
||||
None => &specifier,
|
||||
};
|
||||
|
||||
// Step 9. Let result be a URL-or-null, initially null.
|
||||
let mut result = None;
|
||||
if let Some(map) = import_map {
|
||||
// Step 10. For each scopePrefix → scopeImports of importMap's scopes:
|
||||
for (prefix, imports) in &map.scopes {
|
||||
// Step 10.1 If scopePrefix is serializedBaseURL, or if scopePrefix ends with U+002F (/)
|
||||
// and scopePrefix is a code unit prefix of serializedBaseURL, then:
|
||||
let prefix = prefix.as_str();
|
||||
if prefix == serialized_base_url ||
|
||||
(serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
|
||||
{
|
||||
// Step 10.1.1 Let scopeImportsMatch be the result of resolving an imports match
|
||||
// given normalizedSpecifier, asURL, and scopeImports.
|
||||
// Step 10.1.2 If scopeImportsMatch is not null, then set result to scopeImportsMatch,
|
||||
// and break.
|
||||
result = resolve_imports_match(
|
||||
normalized_specifier,
|
||||
as_url.as_ref(),
|
||||
imports,
|
||||
can_gc,
|
||||
)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 11. If result is null, set result to the result of resolving an imports match given
|
||||
// normalizedSpecifier, asURL, and importMap's imports.
|
||||
if result.is_none() {
|
||||
result = resolve_imports_match(
|
||||
normalized_specifier,
|
||||
as_url.as_ref(),
|
||||
&map.imports,
|
||||
can_gc,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 12. If result is null, set it to asURL.
|
||||
if result.is_none() {
|
||||
result = as_url.clone();
|
||||
}
|
||||
|
||||
// Step 13. If result is not null, then:
|
||||
match result {
|
||||
Some(result) => {
|
||||
// Step 13.1 Add module to resolved module set given settingsObject, serializedBaseURL,
|
||||
// normalizedSpecifier, and asURL.
|
||||
global.add_module_to_resolved_module_set(
|
||||
serialized_base_url,
|
||||
normalized_specifier,
|
||||
as_url.clone(),
|
||||
);
|
||||
// Step 13.2 Return result.
|
||||
Ok(result)
|
||||
},
|
||||
// Step 14. Throw a TypeError indicating that specifier was a bare specifier,
|
||||
// but was not remapped to anything by importMap.
|
||||
None => Err(Error::Type(
|
||||
"Specifier was a bare specifier, but was not remapped to anything by importMap."
|
||||
.to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#resolving-a-url-like-module-specifier>
|
||||
|
@ -773,12 +860,9 @@ impl ModuleTree {
|
|||
return;
|
||||
},
|
||||
// Step 5.
|
||||
Some(raw_record) => self.resolve_requested_module_specifiers(
|
||||
&global,
|
||||
raw_record.handle(),
|
||||
&self.url,
|
||||
can_gc,
|
||||
),
|
||||
Some(raw_record) => {
|
||||
self.resolve_requested_module_specifiers(&global, raw_record.handle(), can_gc)
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1333,6 +1417,7 @@ unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
|
|||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
/// <https://tc39.es/ecma262/#sec-hostimportmoduledynamically>
|
||||
/// <https://html.spec.whatwg.org/multipage/#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability)>
|
||||
pub(crate) unsafe extern "C" fn host_import_module_dynamically(
|
||||
cx: *mut JSContext,
|
||||
|
@ -1344,20 +1429,6 @@ pub(crate) unsafe extern "C" fn host_import_module_dynamically(
|
|||
let cx = SafeJSContext::from_ptr(cx);
|
||||
let in_realm_proof = AlreadyInRealm::assert_for_cx(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 mut options = ScriptFetchOptions::default_classic_script(&global_scope);
|
||||
|
||||
// Step 4.
|
||||
let module_data = module_script_from_reference_private(&reference_private);
|
||||
if let Some(data) = module_data {
|
||||
base_url = data.base_url.clone();
|
||||
options = data.options.descendant_fetch_options();
|
||||
}
|
||||
|
||||
let promise = Promise::new_with_js_promise(Handle::from_raw(promise), cx);
|
||||
|
||||
//Step 5 & 6.
|
||||
|
@ -1365,8 +1436,6 @@ pub(crate) unsafe extern "C" fn host_import_module_dynamically(
|
|||
&global_scope,
|
||||
specifier,
|
||||
reference_private,
|
||||
base_url,
|
||||
options,
|
||||
promise,
|
||||
CanGc::note(),
|
||||
) {
|
||||
|
@ -1434,18 +1503,26 @@ fn fetch_an_import_module_script_graph(
|
|||
global: &GlobalScope,
|
||||
module_request: RawHandle<*mut JSObject>,
|
||||
reference_private: RawHandleValue,
|
||||
base_url: ServoUrl,
|
||||
options: ScriptFetchOptions,
|
||||
promise: Rc<Promise>,
|
||||
can_gc: CanGc,
|
||||
) -> Result<(), RethrowError> {
|
||||
// Step 1.
|
||||
let cx = GlobalScope::get_cx();
|
||||
rooted!(in(*cx) let specifier = unsafe { GetModuleRequestSpecifier(*cx, module_request) });
|
||||
let url = ModuleTree::resolve_module_specifier(*cx, &base_url, specifier.handle().into());
|
||||
let specifier = unsafe {
|
||||
jsstring_to_str(
|
||||
*cx,
|
||||
ptr::NonNull::new(GetModuleRequestSpecifier(*cx, module_request)).unwrap(),
|
||||
)
|
||||
};
|
||||
let mut options = ScriptFetchOptions::default_classic_script(global);
|
||||
let module_data = unsafe { module_script_from_reference_private(&reference_private) };
|
||||
if let Some(data) = module_data {
|
||||
options = data.options.descendant_fetch_options();
|
||||
}
|
||||
let url = ModuleTree::resolve_module_specifier(global, module_data, specifier, can_gc);
|
||||
|
||||
// Step 2.
|
||||
if url.is_none() {
|
||||
if url.is_err() {
|
||||
let specifier_error = gen_type_error(global, "Wrong module specifier".to_owned(), can_gc);
|
||||
return Err(specifier_error);
|
||||
}
|
||||
|
@ -1494,8 +1571,8 @@ fn fetch_an_import_module_script_graph(
|
|||
}
|
||||
|
||||
#[allow(unsafe_code, non_snake_case)]
|
||||
/// <https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule>
|
||||
/// <https://html.spec.whatwg.org/multipage/#hostresolveimportedmodule(referencingscriptormodule%2C-specifier)>
|
||||
/// <https://tc39.es/ecma262/#sec-HostLoadImportedModule>
|
||||
/// <https://html.spec.whatwg.org/multipage/#hostloadimportedmodule>
|
||||
unsafe extern "C" fn HostResolveImportedModule(
|
||||
cx: *mut JSContext,
|
||||
reference_private: RawHandleValue,
|
||||
|
@ -1504,25 +1581,17 @@ unsafe extern "C" fn HostResolveImportedModule(
|
|||
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 = module_script_from_reference_private(&reference_private);
|
||||
if let Some(data) = module_data {
|
||||
base_url = data.base_url.clone();
|
||||
}
|
||||
|
||||
// Step 5.
|
||||
rooted!(in(*GlobalScope::get_cx()) let specifier = GetModuleRequestSpecifier(cx, specifier));
|
||||
let url = ModuleTree::resolve_module_specifier(
|
||||
*GlobalScope::get_cx(),
|
||||
&base_url,
|
||||
specifier.handle().into(),
|
||||
let module_data = module_script_from_reference_private(&reference_private);
|
||||
let specifier = jsstring_to_str(
|
||||
cx,
|
||||
ptr::NonNull::new(GetModuleRequestSpecifier(cx, specifier)).unwrap(),
|
||||
);
|
||||
let url =
|
||||
ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier, CanGc::note());
|
||||
|
||||
// Step 6.
|
||||
assert!(url.is_some());
|
||||
assert!(url.is_ok());
|
||||
|
||||
let parsed_url = url.unwrap();
|
||||
|
||||
|
@ -1870,6 +1939,32 @@ pub(crate) fn fetch_inline_module_script(
|
|||
pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
|
||||
pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record>
|
||||
#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
|
||||
pub(crate) struct ResolvedModule {
|
||||
/// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record-serialized-base-url>
|
||||
base_url: String,
|
||||
/// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record-specifier>
|
||||
specifier: String,
|
||||
/// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record-as-url>
|
||||
#[no_trace]
|
||||
specifier_url: Option<ServoUrl>,
|
||||
}
|
||||
|
||||
impl ResolvedModule {
|
||||
pub(crate) fn new(
|
||||
base_url: String,
|
||||
specifier: String,
|
||||
specifier_url: Option<ServoUrl>,
|
||||
) -> Self {
|
||||
Self {
|
||||
base_url,
|
||||
specifier,
|
||||
specifier_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#import-map-processing-model>
|
||||
#[derive(Default, JSTraceable, MallocSizeOf)]
|
||||
pub(crate) struct ImportMap {
|
||||
|
@ -1910,15 +2005,52 @@ fn merge_existing_and_new_import_maps(
|
|||
let new_import_map_scopes = new_import_map.scopes;
|
||||
|
||||
// Step 2. Let oldImportMap be global's import map.
|
||||
let mut old_import_map = global.import_map().borrow_mut();
|
||||
let mut old_import_map = global.import_map_mut();
|
||||
|
||||
// Step 3. Let newImportMapImports be a deep copy of newImportMap's imports.
|
||||
let new_import_map_imports = new_import_map.imports;
|
||||
let mut new_import_map_imports = new_import_map.imports;
|
||||
|
||||
let resolved_module_set = global.resolved_module_set();
|
||||
// Step 4. For each scopePrefix → scopeImports of newImportMapScopes:
|
||||
for (scope_prefix, scope_imports) in new_import_map_scopes {
|
||||
// TODO: implement after we complete `resolve_module_specifier`
|
||||
for (scope_prefix, mut scope_imports) in new_import_map_scopes {
|
||||
// Step 4.1. For each record of global's resolved module set:
|
||||
for record in resolved_module_set.iter() {
|
||||
// If scopePrefix is record's serialized base URL, or if scopePrefix ends with
|
||||
// U+002F (/) and scopePrefix is a code unit prefix of record's serialized base URL, then:
|
||||
let prefix = scope_prefix.as_str();
|
||||
if prefix == record.base_url ||
|
||||
(record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
|
||||
{
|
||||
// For each specifierKey → resolutionResult of scopeImports:
|
||||
scope_imports.retain(|key, val| {
|
||||
// If specifierKey is record's specifier, or if all of the following conditions are true:
|
||||
// specifierKey ends with U+002F (/);
|
||||
// specifierKey is a code unit prefix of record's specifier;
|
||||
// either record's specifier as a URL is null or is special,
|
||||
if *key == record.specifier ||
|
||||
(key.ends_with('\u{002f}') &&
|
||||
record.specifier.starts_with(key) &&
|
||||
(record.specifier_url.is_none() ||
|
||||
record
|
||||
.specifier_url
|
||||
.as_ref()
|
||||
.map(|u| u.is_special_scheme())
|
||||
.unwrap_or_default()))
|
||||
{
|
||||
// The user agent may report a warning to the console indicating the ignored rule.
|
||||
// They may choose to avoid reporting if the rule is identical to an existing one.
|
||||
Console::internal_warn(
|
||||
global,
|
||||
DOMString::from(format!("Ignored rule: {key} -> {val:?}.")),
|
||||
);
|
||||
// Remove scopeImports[specifierKey].
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4.2 If scopePrefix exists in oldImportMap's scopes
|
||||
if old_import_map.scopes.contains_key(&scope_prefix) {
|
||||
|
@ -1959,8 +2091,25 @@ fn merge_existing_and_new_import_maps(
|
|||
.insert(url.clone(), integrity.clone());
|
||||
}
|
||||
|
||||
// TODO: implement after we complete `resolve_module_specifier`
|
||||
// Step 6. For each record of global's resolved module set:
|
||||
for record in resolved_module_set.iter() {
|
||||
// For each specifier → url of newImportMapImports:
|
||||
new_import_map_imports.retain(|specifier, val| {
|
||||
// If specifier starts with record's specifier, then:
|
||||
if specifier.starts_with(&record.specifier) {
|
||||
// The user agent may report a warning to the console indicating the ignored rule.
|
||||
// They may choose to avoid reporting if the rule is identical to an existing one.
|
||||
Console::internal_warn(
|
||||
global,
|
||||
DOMString::from(format!("Ignored rule: {specifier} -> {val:?}.")),
|
||||
);
|
||||
// Remove newImportMapImports[specifier].
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Step 7. Set oldImportMap's imports to the result of merge module specifier maps,
|
||||
// given newImportMapImports and oldImportMap's imports.
|
||||
|
@ -2318,3 +2467,87 @@ fn normalize_specifier_key(
|
|||
// step 4. Return specifierKey.
|
||||
Some(specifier_key.to_string())
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#resolving-an-imports-match>
|
||||
///
|
||||
/// When the error is thrown, it will terminate the entire resolve a module specifier algorithm
|
||||
/// without any further fallbacks.
|
||||
pub(crate) fn resolve_imports_match(
|
||||
normalized_specifier: &str,
|
||||
as_url: Option<&ServoUrl>,
|
||||
specifier_map: &ModuleSpecifierMap,
|
||||
_can_gc: CanGc,
|
||||
) -> Fallible<Option<ServoUrl>> {
|
||||
// Step 1. For each specifierKey → resolutionResult of specifierMap:
|
||||
for (specifier_key, resolution_result) in specifier_map {
|
||||
// Step 1.1 If specifierKey is normalizedSpecifier, then:
|
||||
if specifier_key == normalized_specifier {
|
||||
if let Some(resolution_result) = resolution_result {
|
||||
// Step 1.1.2 Assert: resolutionResult is a URL.
|
||||
// This is checked by Url type already.
|
||||
// Step 1.1.3 Return resolutionResult.
|
||||
return Ok(Some(resolution_result.clone()));
|
||||
} else {
|
||||
// Step 1.1.1 If resolutionResult is null, then throw a TypeError.
|
||||
return Err(Error::Type(
|
||||
"Resolution of specifierKey was blocked by a null entry.".to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1.2 If all of the following are true:
|
||||
// - specifierKey ends with U+002F (/)
|
||||
// - specifierKey is a code unit prefix of normalizedSpecifier
|
||||
// - either asURL is null, or asURL is special, then:
|
||||
if specifier_key.ends_with('\u{002f}') &&
|
||||
normalized_specifier.starts_with(specifier_key) &&
|
||||
(as_url.is_none() || as_url.map(|u| u.is_special_scheme()).unwrap_or_default())
|
||||
{
|
||||
// Step 1.2.1 If resolutionResult is null, then throw a TypeError.
|
||||
// Step 1.2.2 Assert: resolutionResult is a URL.
|
||||
let Some(resolution_result) = resolution_result else {
|
||||
return Err(Error::Type(
|
||||
"Resolution of specifierKey was blocked by a null entry.".to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
// Step 1.2.3 Let afterPrefix be the portion of normalizedSpecifier after the initial specifierKey prefix.
|
||||
let after_prefix = normalized_specifier
|
||||
.strip_prefix(specifier_key)
|
||||
.expect("specifier_key should be the prefix of normalized_specifier");
|
||||
|
||||
// Step 1.2.4 Assert: resolutionResult, serialized, ends with U+002F (/), as enforced during parsing.
|
||||
debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
|
||||
|
||||
// Step 1.2.5 Let url be the result of URL parsing afterPrefix with resolutionResult.
|
||||
let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
|
||||
|
||||
// Step 1.2.6 If url is failure, then throw a TypeError
|
||||
// Step 1.2.7 Assert: url is a URL.
|
||||
let Ok(url) = url else {
|
||||
return Err(Error::Type(
|
||||
"Resolution of normalizedSpecifier was blocked since
|
||||
the afterPrefix portion could not be URL-parsed relative to
|
||||
the resolutionResult mapped to by the specifierKey prefix."
|
||||
.to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
// Step 1.2.8 If the serialization of resolutionResult is not
|
||||
// a code unit prefix of the serialization of url, then throw a TypeError
|
||||
if !url.as_str().starts_with(resolution_result.as_str()) {
|
||||
return Err(Error::Type(
|
||||
"Resolution of normalizedSpecifier was blocked due to
|
||||
it backtracking above its prefix specifierKey."
|
||||
.to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
// Step 1.2.9 Return url.
|
||||
return Ok(Some(url));
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2. Return null.
|
||||
Ok(None)
|
||||
}
|
||||
|
|
|
@ -105,6 +105,17 @@ impl ServoUrl {
|
|||
scheme == "about" || scheme == "blob" || scheme == "data"
|
||||
}
|
||||
|
||||
/// <https://url.spec.whatwg.org/#special-scheme>
|
||||
pub fn is_special_scheme(&self) -> bool {
|
||||
let scheme = self.scheme();
|
||||
scheme == "ftp" ||
|
||||
scheme == "file" ||
|
||||
scheme == "http" ||
|
||||
scheme == "https" ||
|
||||
scheme == "ws" ||
|
||||
scheme == "wss"
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[dynamic-import.html]
|
||||
[After a dynamic import(), import maps work fine]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[modulepreload-link-header.html]
|
||||
[With modulepreload link header, import maps work fine]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[modulepreload.html]
|
||||
[After <link rel=modulepreload> import maps should work fine]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[script-tag-inline.html]
|
||||
[After inline <script type="module"> import maps work fine]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[script-tag.html]
|
||||
[After <script type="module"> import maps work fine]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[worker-request.html]
|
||||
[After module worker creation import maps are still effective]
|
||||
expected: FAIL
|
|
@ -1,27 +0,0 @@
|
|||
[bare-specifiers.sub.html]
|
||||
[bare/bare: static import]
|
||||
expected: FAIL
|
||||
|
||||
[bare/bare: dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[bare/bare: dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
||||
|
||||
[bare/cross-origin-bare: static import]
|
||||
expected: FAIL
|
||||
|
||||
[bare/cross-origin-bare: dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[bare/cross-origin-bare: dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
||||
|
||||
[bare/to-data: static import]
|
||||
expected: FAIL
|
||||
|
||||
[bare/to-data: dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[bare/to-data: dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[applied-to-target-dynamic.sub.html]
|
||||
[The URL after mapping violates CSP (but not the URL before mapping)]
|
||||
expected: FAIL
|
||||
|
||||
[The URL before mapping violates CSP (but not the URL after mapping)]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[applied-to-target.sub.html]
|
||||
[The URL after mapping violates CSP (but not the URL before mapping)]
|
||||
expected: FAIL
|
||||
|
||||
[The URL before mapping violates CSP (but not the URL after mapping)]
|
||||
expected: FAIL
|
3
tests/wpt/meta/import-maps/csp/hash.html.ini
vendored
3
tests/wpt/meta/import-maps/csp/hash.html.ini
vendored
|
@ -1,3 +0,0 @@
|
|||
[hash.html]
|
||||
[Importmap should be accepted due to hash]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[nonce.html]
|
||||
[Importmap should be accepted according to its correct nonce]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[unsafe-inline.html]
|
||||
[Importmap should be accepted due to unsafe-inline]
|
||||
expected: FAIL
|
|
@ -1,36 +0,0 @@
|
|||
[data-url-specifiers.sub.html]
|
||||
[data:text/javascript,log.push('data:foo'): static import]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:foo'): dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:foo'): dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:cross-origin-foo'): static import]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:cross-origin-foo'): dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:cross-origin-foo'): dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:to-data'): static import]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:to-data'): dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:to-data'): dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:to-bare'): static import]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:to-bare'): dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[data:text/javascript,log.push('data:to-bare'): dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
|
@ -2,14 +2,11 @@
|
|||
[script was not loaded, as its resolved URL failed its integrity check]
|
||||
expected: FAIL
|
||||
|
||||
[script was loaded, as its resolved URL had no integrity check, despite its specifier having one]
|
||||
expected: FAIL
|
||||
|
||||
[Script with no import definition was not loaded, as it failed its integrity check]
|
||||
expected: FAIL
|
||||
|
||||
[Bare specifier used for integrity loaded, as its definition should have used the URL]
|
||||
expected: FAIL
|
||||
|
||||
[Script imported inside an event handler was not loaded as its integrity check failed]
|
||||
expected: FAIL
|
||||
|
||||
[Bare specifier script was not loaded, as it failed its integrity check]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
[http-url-like-specifiers.sub.html]
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=foo: static import]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=foo: dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=foo: dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=cross-origin-foo: static import]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=cross-origin-foo: dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=cross-origin-foo: dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=to-data: static import]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=to-data: dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=to-data: dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=to-bare: static import]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=to-bare: dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[http://web-platform.test:8000/import-maps/resources/log.js?pipe=sub&name=to-bare: dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
|
@ -1,18 +0,0 @@
|
|||
[import-maps-base-url.sub.html]
|
||||
[bare/bare: static import]
|
||||
expected: FAIL
|
||||
|
||||
[bare/bare: dynamic import (from module)]
|
||||
expected: FAIL
|
||||
|
||||
[bare/bare: dynamic import (from text/javascript)]
|
||||
expected: FAIL
|
||||
|
||||
[bare/bare: static import with inject <base>]
|
||||
expected: FAIL
|
||||
|
||||
[bare/bare: dynamic import (from module) with inject <base>]
|
||||
expected: FAIL
|
||||
|
||||
[bare/bare: dynamic import (from text/javascript) with inject <base>]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[module-map-key.html]
|
||||
[Module map's key is the URL after import map resolution]
|
||||
expected: FAIL
|
2
tests/wpt/meta/import-maps/multiple-import-maps/already-resolved-dropped.html.ini
vendored
Normal file
2
tests/wpt/meta/import-maps/multiple-import-maps/already-resolved-dropped.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[already-resolved-dropped.html]
|
||||
expected: CRASH
|
|
@ -1,3 +0,0 @@
|
|||
[basic.html]
|
||||
[Second import map should be used for resolution]
|
||||
expected: FAIL
|
|
@ -1,9 +0,0 @@
|
|||
[conflict-first-persists.html]
|
||||
[First defined rule persists in case of conflict]
|
||||
expected: FAIL
|
||||
|
||||
[First defined rule persists in case of conflict - prefixed bare specifiers]
|
||||
expected: FAIL
|
||||
|
||||
[First defined rule persists in case of conflict - non-prefix bare specifier]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[resolution-consistency-in-module-tree-inline.html]
|
||||
[Module tree that started to download before a new import map should still take it into account]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[resolution-consistency-in-module-tree.html]
|
||||
[Module tree that started to download before a new import map should still take it into account]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[url-resolution-conflict.html]
|
||||
[Second import map should not override same resolved URL]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[with-errors.html]
|
||||
[Second import map should be used for resolution even after an import map with errors]
|
||||
expected: FAIL
|
|
@ -1,3 +1,4 @@
|
|||
[dynamic.html]
|
||||
expected: CRASH
|
||||
[Resolution after import map should not be redefined]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,2 +1,9 @@
|
|||
[external-script-bare-descendent.html]
|
||||
expected: ERROR
|
||||
[Resolution after import map should not be redefined]
|
||||
expected: FAIL
|
||||
|
||||
[Resolution after import map should not be redefined for bare prefixes or exact matches]
|
||||
expected: FAIL
|
||||
|
||||
[Resolution after import map should be redefined for non-prefixes]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[integrity.html]
|
||||
[Static script loaded as its integrity check passed]
|
||||
expected: FAIL
|
|
@ -1,3 +1,4 @@
|
|||
[prefix.html]
|
||||
expected: CRASH
|
||||
[Prefix resolution after import map should not be redefined]
|
||||
expected: FAIL
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
[Static script did not load as it failed its integrity check]
|
||||
expected: FAIL
|
||||
|
||||
[Static script loaded as its integrity check passed]
|
||||
expected: FAIL
|
||||
|
||||
[Static script did not load as it failed its integrity check, even without an import defined]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[script-enforcement-006.html]
|
||||
[Untrusted HTMLScriptElement of importmap type uses the source text returned by the default policy.]
|
||||
expected: FAIL
|
|
@ -1,6 +1,3 @@
|
|||
[script-enforcement-008.https.html]
|
||||
[script-src CSP directive is properly set.]
|
||||
expected: FAIL
|
||||
|
||||
[Untrusted HTMLScriptElement of importmap type uses the source text returned by the default policy for inline CSP check.]
|
||||
expected: FAIL
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue