mirror of
https://github.com/lexmount/moli.git
synced 2026-10-01 16:00:44 +00:00
fix(script): mute cross-origin classic script errors
This commit is contained in:
@@ -287,6 +287,11 @@ pub struct ScriptFetchMetadata {
|
||||
/// not equivalent to whether the eventual fetch runs on a parser-owned
|
||||
/// scheduler lane.
|
||||
pub parser_inserted: bool,
|
||||
/// Whether synchronous exceptions from this classic script must expose only
|
||||
/// the cross-origin-safe `Script error.` surface. This is false while the
|
||||
/// request is prepared and is populated from the response taint once an
|
||||
/// external classic script has loaded.
|
||||
pub muted_errors: bool,
|
||||
}
|
||||
|
||||
impl ScriptFetchMetadata {
|
||||
@@ -308,6 +313,7 @@ impl ScriptFetchMetadata {
|
||||
nonce: normalize_non_empty_attr(nonce),
|
||||
fetch_priority: FetchPriorityHint::from_attribute(fetch_priority),
|
||||
parser_inserted: false,
|
||||
muted_errors: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -578,6 +578,7 @@ mod tests {
|
||||
source_result: Ok(source.into()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,6 +587,7 @@ mod tests {
|
||||
source_result: Err(error.into()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -347,6 +347,7 @@ impl AsyncFallbackQueue {
|
||||
source_result: Err(error),
|
||||
source_bytes: None,
|
||||
network_result,
|
||||
muted_errors: false,
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -388,6 +389,7 @@ impl AsyncFallbackQueue {
|
||||
entry.script.clone(),
|
||||
source,
|
||||
completion.outcome.source_bytes,
|
||||
completion.outcome.muted_errors,
|
||||
);
|
||||
entry.load_failure = None;
|
||||
}
|
||||
@@ -396,6 +398,7 @@ impl AsyncFallbackQueue {
|
||||
source_result: Err(error),
|
||||
source_bytes: completion.outcome.source_bytes,
|
||||
network_result: completion.outcome.network_result,
|
||||
muted_errors: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -485,10 +488,11 @@ fn parse_time_task_from_load_outcome(
|
||||
source_result,
|
||||
source_bytes,
|
||||
network_result,
|
||||
muted_errors,
|
||||
} = outcome;
|
||||
match source_result {
|
||||
Ok(source) => ParseTimeDocumentScriptTask::classic_async_script(
|
||||
prepared_script_with_loaded_source(script, source, source_bytes),
|
||||
prepared_script_with_loaded_source(script, source, source_bytes, muted_errors),
|
||||
load_delay_binding,
|
||||
),
|
||||
Err(error) => ParseTimeDocumentScriptTask::async_script_failure(
|
||||
@@ -509,10 +513,11 @@ fn async_phase_page_task_from_load_outcome(
|
||||
source_result,
|
||||
source_bytes,
|
||||
network_result,
|
||||
muted_errors,
|
||||
} = outcome;
|
||||
match source_result {
|
||||
Ok(source) => PostParseDocumentScriptTask::async_script(
|
||||
prepared_script_with_loaded_source(script, source, source_bytes),
|
||||
prepared_script_with_loaded_source(script, source, source_bytes, muted_errors),
|
||||
load_delay_binding,
|
||||
),
|
||||
Err(error) => PostParseDocumentScriptTask::async_script_load_failure(
|
||||
@@ -530,8 +535,14 @@ fn fallback_entry_from_parse_time_entry(entry: ParseTimeAsyncEntry) -> AsyncFall
|
||||
source_result: Ok(source),
|
||||
source_bytes,
|
||||
network_result: _,
|
||||
muted_errors,
|
||||
}) => AsyncFallbackEntry {
|
||||
script: prepared_script_with_loaded_source(entry.original, source, source_bytes),
|
||||
script: prepared_script_with_loaded_source(
|
||||
entry.original,
|
||||
source,
|
||||
source_bytes,
|
||||
muted_errors,
|
||||
),
|
||||
load_delay_binding: entry.load_delay_binding,
|
||||
awaiting_completion: false,
|
||||
source_load: None,
|
||||
|
||||
@@ -43,6 +43,7 @@ mod tests {
|
||||
source_result: Ok("ready".to_owned()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -354,6 +354,7 @@ fn page_owned_document_script_work_from_source_load_outcome(
|
||||
source_result,
|
||||
source_bytes,
|
||||
network_result,
|
||||
muted_errors,
|
||||
} = outcome;
|
||||
match source_result {
|
||||
Ok(source) => PageOwnedDocumentScriptWork::Script {
|
||||
@@ -362,6 +363,7 @@ fn page_owned_document_script_work_from_source_load_outcome(
|
||||
script,
|
||||
source,
|
||||
source_bytes,
|
||||
muted_errors,
|
||||
)),
|
||||
runtime_script_claim,
|
||||
source_network_result: network_result,
|
||||
|
||||
@@ -230,6 +230,7 @@ mod tests {
|
||||
source_result: Ok("window.ready = true;".to_owned()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
},
|
||||
));
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ impl<Owner: Copy> ParserDeferredClassicSourceLoadRequest<Owner> {
|
||||
source_result: Err(message.into()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -107,11 +107,16 @@ impl ResolvedDeferPhaseScript {
|
||||
source_result,
|
||||
source_bytes,
|
||||
network_result,
|
||||
muted_errors,
|
||||
} = outcome;
|
||||
match source_result {
|
||||
Ok(source) => {
|
||||
self.script =
|
||||
prepared_script_with_loaded_source(self.script.clone(), source, source_bytes);
|
||||
self.script = prepared_script_with_loaded_source(
|
||||
self.script.clone(),
|
||||
source,
|
||||
source_bytes,
|
||||
muted_errors,
|
||||
);
|
||||
self.classic_source_state = ParserDeferredClassicSourceState::Ready(network_result);
|
||||
}
|
||||
Err(error) => {
|
||||
|
||||
@@ -2112,6 +2112,7 @@ impl DynamicScriptOwner {
|
||||
entry.script.clone(),
|
||||
source,
|
||||
completion.outcome.source_bytes.clone(),
|
||||
completion.outcome.muted_errors,
|
||||
);
|
||||
entry.ready_state = DynamicScriptReadyState::Ready {
|
||||
order,
|
||||
@@ -2290,6 +2291,7 @@ mod tests {
|
||||
source_result: Ok(source.into()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2301,6 +2303,7 @@ mod tests {
|
||||
source_result: Err(error.into()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,6 +844,7 @@ mod tests {
|
||||
source_result: Ok(source.into()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -860,6 +860,7 @@ mod tests {
|
||||
source_result: Ok(format!("window.defer{parser_position} = true")),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,10 +121,15 @@ impl ParserPreparedClassicScript {
|
||||
source_result,
|
||||
source_bytes,
|
||||
network_result,
|
||||
muted_errors,
|
||||
} = outcome;
|
||||
let source = source_result?;
|
||||
*self.script =
|
||||
prepared_script_with_loaded_source((*self.script).clone(), source, source_bytes);
|
||||
*self.script = prepared_script_with_loaded_source(
|
||||
(*self.script).clone(),
|
||||
source,
|
||||
source_bytes,
|
||||
muted_errors,
|
||||
);
|
||||
Ok(network_result)
|
||||
}
|
||||
|
||||
@@ -269,6 +274,7 @@ impl ParserClassicScriptSourceResult {
|
||||
source_result: result,
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@ use url::Url;
|
||||
|
||||
use crate::{
|
||||
network::{RendererResourceTaskRunner, ResourceRequestClient},
|
||||
types::{ScriptKind, ScriptMode, SharedNavigationResponseResult},
|
||||
types::{
|
||||
AsyncSubresourceFetchResponseFilter, ScriptKind, ScriptMode, SharedNavigationResponseResult,
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) use moli_parser::{
|
||||
@@ -189,6 +191,7 @@ impl SharedScriptSourceLoad {
|
||||
source_result: Ok(source.into()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
});
|
||||
load
|
||||
}
|
||||
@@ -200,6 +203,7 @@ impl SharedScriptSourceLoad {
|
||||
source_result: Err(error.into()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
});
|
||||
load
|
||||
}
|
||||
@@ -214,6 +218,7 @@ impl SharedScriptSourceLoad {
|
||||
source_result,
|
||||
source_bytes: None,
|
||||
network_result,
|
||||
muted_errors: false,
|
||||
});
|
||||
load
|
||||
}
|
||||
@@ -230,6 +235,7 @@ impl SharedScriptSourceLoad {
|
||||
source_result: task.await,
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
});
|
||||
});
|
||||
load
|
||||
@@ -260,10 +266,21 @@ impl Drop for SharedScriptSourceLoadCompleter {
|
||||
}
|
||||
|
||||
pub(crate) fn prepared_script_with_loaded_source(
|
||||
script: PreparedScript,
|
||||
mut script: PreparedScript,
|
||||
source: String,
|
||||
source_bytes: Option<Vec<u8>>,
|
||||
muted_errors: bool,
|
||||
) -> PreparedScript {
|
||||
debug_assert!(
|
||||
!muted_errors
|
||||
|| (script.kind == ScriptKind::Classic
|
||||
&& script.source_kind == crate::types::ScriptSourceKind::External),
|
||||
"only external classic scripts can carry muted errors"
|
||||
);
|
||||
script.fetch_metadata.muted_errors = muted_errors;
|
||||
if muted_errors {
|
||||
script.base_url = Url::parse("about:blank").expect("about:blank should be a valid URL");
|
||||
}
|
||||
if script.kind == ScriptKind::Module
|
||||
&& is_webassembly_module_script_url(&script.url)
|
||||
&& let Some(bytes) = source_bytes
|
||||
@@ -282,6 +299,7 @@ pub(crate) struct PreparedScriptSourceLoadOutcome {
|
||||
pub(crate) source_result: std::result::Result<String, String>,
|
||||
pub(crate) source_bytes: Option<Vec<u8>>,
|
||||
pub(crate) network_result: Option<SharedNavigationResponseResult>,
|
||||
pub(crate) muted_errors: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn script_preload_network_result(
|
||||
@@ -303,12 +321,14 @@ pub(crate) async fn load_prepared_script_source_outcome_with_document_character_
|
||||
source_result: Ok(source.clone()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
}
|
||||
}
|
||||
ScriptSource::LoadedBinary { source, bytes } => PreparedScriptSourceLoadOutcome {
|
||||
source_result: Ok(source.clone()),
|
||||
source_bytes: Some(bytes.clone()),
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
},
|
||||
ScriptSource::External => {
|
||||
if let Some(outcome) = local_or_unsupported_external_script_source_load_outcome(
|
||||
@@ -337,6 +357,7 @@ pub(crate) async fn load_prepared_script_source_outcome_with_document_character_
|
||||
source_result: Err(error.clone()),
|
||||
source_bytes: None,
|
||||
network_result: Some(Arc::new(Err(error))),
|
||||
muted_errors: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -401,13 +422,23 @@ pub(crate) async fn load_service_worker_aware_external_script_source_outcome(
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Some(response)) => external_script_source_load_outcome_from_response_inner(
|
||||
script,
|
||||
*response.response,
|
||||
document_character_set,
|
||||
response.response_filter,
|
||||
None,
|
||||
),
|
||||
Ok(Some(response)) => {
|
||||
let provenance = if response.from_network_fallback {
|
||||
ClassicScriptResponseProvenance::Network
|
||||
} else {
|
||||
ClassicScriptResponseProvenance::ServiceWorker {
|
||||
filter: response.response_filter,
|
||||
}
|
||||
};
|
||||
external_script_source_load_outcome_from_response_inner(
|
||||
script,
|
||||
*response.response,
|
||||
document_character_set,
|
||||
response.response_filter,
|
||||
None,
|
||||
provenance,
|
||||
)
|
||||
}
|
||||
Ok(None) => {
|
||||
load_prepared_script_source_outcome_with_document_character_set(
|
||||
script,
|
||||
@@ -424,6 +455,7 @@ pub(crate) async fn load_service_worker_aware_external_script_source_outcome(
|
||||
source_result: Err(message.clone()),
|
||||
source_bytes: None,
|
||||
network_result: Some(Arc::new(Err(message))),
|
||||
muted_errors: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,6 +472,7 @@ fn local_or_unsupported_external_script_source_load_outcome(
|
||||
.map_err(|error| error.to_string()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
}),
|
||||
"blob" => Some(match crate::network_host::local_url_response(&script.url) {
|
||||
Some(response) => {
|
||||
@@ -484,6 +517,7 @@ fn failed_external_script_source_load_outcome(message: String) -> PreparedScript
|
||||
source_result: Err(message.clone()),
|
||||
source_bytes: None,
|
||||
network_result: Some(Arc::new(Err(message))),
|
||||
muted_errors: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,6 +554,52 @@ pub(crate) fn spawn_service_worker_aware_external_script_source_load(
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ClassicScriptResponseProvenance {
|
||||
Network,
|
||||
ServiceWorker {
|
||||
filter: Option<AsyncSubresourceFetchResponseFilter>,
|
||||
},
|
||||
}
|
||||
|
||||
fn classic_script_errors_are_muted(
|
||||
script: &PreparedScript,
|
||||
response: &crate::protocol_types::NavigationResponse,
|
||||
provenance: ClassicScriptResponseProvenance,
|
||||
) -> bool {
|
||||
if script.kind != ScriptKind::Classic
|
||||
|| script.source_kind != crate::types::ScriptSourceKind::External
|
||||
|| external_script_request_mode(script.kind, &script.fetch_metadata) != RequestMode::NoCors
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
match provenance {
|
||||
ClassicScriptResponseProvenance::Network => {
|
||||
!classic_script_response_url_chain_is_same_origin(script, response)
|
||||
}
|
||||
ClassicScriptResponseProvenance::ServiceWorker { filter } => matches!(
|
||||
filter,
|
||||
Some(
|
||||
AsyncSubresourceFetchResponseFilter::Opaque
|
||||
| AsyncSubresourceFetchResponseFilter::OpaqueRedirect
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn classic_script_response_url_chain_is_same_origin(
|
||||
script: &PreparedScript,
|
||||
response: &crate::protocol_types::NavigationResponse,
|
||||
) -> bool {
|
||||
moli_url::same_origin(&script.initiator_url, &script.url)
|
||||
&& response.redirect_chain.iter().all(|redirect| {
|
||||
moli_url::same_origin(&script.initiator_url, &redirect.from_url)
|
||||
&& moli_url::same_origin(&script.initiator_url, &redirect.to_url)
|
||||
})
|
||||
&& moli_url::same_origin(&script.initiator_url, &response.final_url)
|
||||
}
|
||||
|
||||
pub(crate) fn external_script_source_load_outcome_from_response(
|
||||
script: &PreparedScript,
|
||||
request_origin: &moli_url::WebOrigin,
|
||||
@@ -546,6 +626,7 @@ pub(crate) fn external_script_source_load_outcome_from_response(
|
||||
document_character_set,
|
||||
response_filter,
|
||||
cors_error,
|
||||
ClassicScriptResponseProvenance::Network,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -572,7 +653,9 @@ fn external_script_source_load_outcome_from_response_inner(
|
||||
document_character_set: Option<&str>,
|
||||
response_filter: Option<crate::types::AsyncSubresourceFetchResponseFilter>,
|
||||
cors_error: Option<String>,
|
||||
provenance: ClassicScriptResponseProvenance,
|
||||
) -> PreparedScriptSourceLoadOutcome {
|
||||
let muted_errors = classic_script_errors_are_muted(script, &response, provenance);
|
||||
let response_bytes = response.body_bytes().to_vec();
|
||||
let opaque_status_zero = response_filter
|
||||
== Some(crate::types::AsyncSubresourceFetchResponseFilter::Opaque)
|
||||
@@ -609,6 +692,7 @@ fn external_script_source_load_outcome_from_response_inner(
|
||||
source_result,
|
||||
source_bytes: Some(response_bytes),
|
||||
network_result: Some(Arc::new(Ok(response))),
|
||||
muted_errors,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -766,6 +850,7 @@ mod tests {
|
||||
source_result: Ok("window.ready = true;".to_owned()),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
}
|
||||
},
|
||||
RendererResourceTaskRunner::from_current_tokio()
|
||||
@@ -863,6 +948,24 @@ mod tests {
|
||||
crate::protocol_types::NavigationResponse::from_head_and_body(head, body, body_bytes)
|
||||
}
|
||||
|
||||
fn script_redirect(from_url: &Url, to_url: &Url) -> crate::types::NavigationRedirect {
|
||||
crate::types::NavigationRedirect {
|
||||
source: moli_fetch::RedirectSource::Network,
|
||||
from_url: from_url.clone(),
|
||||
to_url: to_url.clone(),
|
||||
status: 302,
|
||||
headers: Vec::new(),
|
||||
network_extra_info_available: true,
|
||||
request_extra_info: None,
|
||||
response_extra_info: None,
|
||||
redirect_has_extra_info: true,
|
||||
request_cookie_report: None,
|
||||
cookie_set_reports: Vec::new(),
|
||||
from_cache: false,
|
||||
negotiated_http_version: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepared_external_script(url: &str, kind: ScriptKind) -> PreparedScript {
|
||||
let url = url::Url::parse(url).expect("test script url");
|
||||
prepared_external_script_with_initiator(url.clone(), kind, url)
|
||||
@@ -1187,6 +1290,104 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classic_script_network_response_taint_controls_muted_errors() {
|
||||
let document_url = Url::parse("https://document.test/page.html").unwrap();
|
||||
let same_origin_url = Url::parse("https://document.test/script.js").unwrap();
|
||||
let cross_origin_url = Url::parse("https://cdn.test/script.js").unwrap();
|
||||
|
||||
let same_origin_script = prepared_external_script_with_initiator(
|
||||
same_origin_url.clone(),
|
||||
ScriptKind::Classic,
|
||||
document_url.clone(),
|
||||
);
|
||||
let same_origin_response = script_response(&same_origin_url, Vec::new());
|
||||
assert!(!classic_script_errors_are_muted(
|
||||
&same_origin_script,
|
||||
&same_origin_response,
|
||||
ClassicScriptResponseProvenance::Network,
|
||||
));
|
||||
|
||||
let mut cross_origin_script = prepared_external_script_with_initiator(
|
||||
cross_origin_url.clone(),
|
||||
ScriptKind::Classic,
|
||||
document_url.clone(),
|
||||
);
|
||||
let cross_origin_response = script_response(&cross_origin_url, Vec::new());
|
||||
assert!(classic_script_errors_are_muted(
|
||||
&cross_origin_script,
|
||||
&cross_origin_response,
|
||||
ClassicScriptResponseProvenance::Network,
|
||||
));
|
||||
|
||||
cross_origin_script.fetch_metadata.cross_origin = Some("anonymous".to_owned());
|
||||
assert!(!classic_script_errors_are_muted(
|
||||
&cross_origin_script,
|
||||
&cross_origin_response,
|
||||
ClassicScriptResponseProvenance::Network,
|
||||
));
|
||||
|
||||
let mut through_cross_origin_response = script_response(&same_origin_url, Vec::new());
|
||||
through_cross_origin_response.redirected = true;
|
||||
through_cross_origin_response.redirect_chain = vec![
|
||||
script_redirect(&same_origin_url, &cross_origin_url),
|
||||
script_redirect(&cross_origin_url, &same_origin_url),
|
||||
];
|
||||
assert!(classic_script_errors_are_muted(
|
||||
&same_origin_script,
|
||||
&through_cross_origin_response,
|
||||
ClassicScriptResponseProvenance::Network,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classic_script_service_worker_response_filter_controls_muted_errors() {
|
||||
let document_url = Url::parse("https://document.test/page.html").unwrap();
|
||||
let script_url = Url::parse("https://cdn.test/script.js").unwrap();
|
||||
let script = prepared_external_script_with_initiator(
|
||||
script_url.clone(),
|
||||
ScriptKind::Classic,
|
||||
document_url,
|
||||
);
|
||||
let response = script_response(&script_url, Vec::new());
|
||||
|
||||
assert!(classic_script_errors_are_muted(
|
||||
&script,
|
||||
&response,
|
||||
ClassicScriptResponseProvenance::ServiceWorker {
|
||||
filter: Some(AsyncSubresourceFetchResponseFilter::Opaque),
|
||||
},
|
||||
));
|
||||
assert!(!classic_script_errors_are_muted(
|
||||
&script,
|
||||
&response,
|
||||
ClassicScriptResponseProvenance::ServiceWorker { filter: None },
|
||||
));
|
||||
assert!(classic_script_errors_are_muted(
|
||||
&script,
|
||||
&response,
|
||||
ClassicScriptResponseProvenance::Network,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loaded_muted_classic_script_sanitizes_its_base_url() {
|
||||
let document_url = Url::parse("https://document.test/page.html").unwrap();
|
||||
let script_url = Url::parse("https://cdn.test/script.js").unwrap();
|
||||
let script =
|
||||
prepared_external_script_with_initiator(script_url, ScriptKind::Classic, document_url);
|
||||
|
||||
let loaded = prepared_script_with_loaded_source(
|
||||
script,
|
||||
"throw new Error('secret')".to_owned(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
|
||||
assert!(loaded.fetch_metadata.muted_errors);
|
||||
assert_eq!(loaded.base_url.as_str(), "about:blank");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classic_script_modes_map_to_chromium_resource_priorities() {
|
||||
let normal = prepared_external_classic_with_mode(ScriptMode::Normal);
|
||||
@@ -1376,9 +1577,13 @@ mod tests {
|
||||
None,
|
||||
Some(crate::types::AsyncSubresourceFetchResponseFilter::Opaque),
|
||||
None,
|
||||
ClassicScriptResponseProvenance::ServiceWorker {
|
||||
filter: Some(AsyncSubresourceFetchResponseFilter::Opaque),
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(outcome.source_result.expect("opaque script source"), source);
|
||||
assert!(outcome.muted_errors);
|
||||
assert_eq!(
|
||||
outcome.source_bytes.expect("opaque script source bytes"),
|
||||
source.as_bytes()
|
||||
|
||||
@@ -3125,6 +3125,7 @@ document.body.appendChild(replacementFrame);
|
||||
),
|
||||
source_bytes: None,
|
||||
network_result: None,
|
||||
muted_errors: false,
|
||||
},
|
||||
),
|
||||
MainParserDeferredClassicSourceNetworkAttribution::new(
|
||||
|
||||
@@ -227,6 +227,7 @@ async fn page_resource_completion_rejects_stale_document_before_application() {
|
||||
source_result: Ok("globalThis.__staleDeferRan = true".to_owned()),
|
||||
source_bytes: None,
|
||||
network_result: network_error.map(|error| Arc::new(Err(error.to_owned()))),
|
||||
muted_errors: false,
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
@@ -613,6 +613,7 @@ impl BufferedDocumentPreloadState {
|
||||
script.clone(),
|
||||
source,
|
||||
outcome.source_bytes,
|
||||
outcome.muted_errors,
|
||||
);
|
||||
ParserBlockingPreloadDisposition::Ready(AppliedPreloadedScriptSource {
|
||||
network_result: script_preload_network_result(outcome.network_result),
|
||||
@@ -690,6 +691,7 @@ impl BufferedDocumentPreloadState {
|
||||
script.clone(),
|
||||
source,
|
||||
outcome.source_bytes,
|
||||
outcome.muted_errors,
|
||||
);
|
||||
Some(AppliedPreloadedScriptSource {
|
||||
network_result: script_preload_network_result(outcome.network_result),
|
||||
|
||||
@@ -8492,6 +8492,7 @@ impl ScriptVm {
|
||||
Err(eval_exec::RawScriptExecutionError::Exception { report, .. }) => {
|
||||
self.report_classic_script_exception_and_finish_evaluation_best_effort(
|
||||
&report,
|
||||
fetch_metadata.muted_errors,
|
||||
);
|
||||
Ok(LoadedScriptExecutionOutcome::Completed(
|
||||
PreparedScriptBodyActivity::Entered,
|
||||
|
||||
@@ -17,9 +17,12 @@ impl ScriptVm {
|
||||
pub(super) fn report_classic_script_exception_and_finish_evaluation_best_effort(
|
||||
&mut self,
|
||||
report: &V8ExceptionReport,
|
||||
muted_errors: bool,
|
||||
) {
|
||||
log_uncaught_script_exception(report);
|
||||
if let Err(error) = self.dispatch_classic_script_exception_and_finish_evaluation(report) {
|
||||
if let Err(error) =
|
||||
self.dispatch_classic_script_exception_and_finish_evaluation(report, muted_errors)
|
||||
{
|
||||
self.record_runtime_warning(format_args!(
|
||||
"classic script exception reporting failed: {error}"
|
||||
));
|
||||
@@ -29,6 +32,7 @@ impl ScriptVm {
|
||||
fn dispatch_classic_script_exception_and_finish_evaluation(
|
||||
&mut self,
|
||||
report: &V8ExceptionReport,
|
||||
muted_errors: bool,
|
||||
) -> Result<()> {
|
||||
let context_ptr: *const v8::Global<v8::Context> = &self.page_default_context;
|
||||
let context_host = self._context_host.clone();
|
||||
@@ -38,19 +42,33 @@ impl ScriptVm {
|
||||
let scope = &mut scope.init();
|
||||
let context = unsafe { v8::Local::new(scope, &*context_ptr) };
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let error_value = report
|
||||
.exception
|
||||
.as_ref()
|
||||
.map(|exception| v8::Local::new(scope, exception));
|
||||
let error_value = if muted_errors {
|
||||
None
|
||||
} else {
|
||||
report
|
||||
.exception
|
||||
.as_ref()
|
||||
.map(|exception| v8::Local::new(scope, exception))
|
||||
};
|
||||
let (summary, source, line, column) = if muted_errors {
|
||||
("Script error.", "", 0, 0)
|
||||
} else {
|
||||
(
|
||||
report.summary.as_str(),
|
||||
report.source.as_deref().unwrap_or(""),
|
||||
report.line.unwrap_or(0) as u32,
|
||||
report.column.unwrap_or(0) as u32,
|
||||
)
|
||||
};
|
||||
// SAFETY: as_ptr() — V8 callbacks are re-entrant; borrow_mut() panics. See util.rs.
|
||||
let host_ptr: *mut JsContextHost = (*context_host).as_ptr();
|
||||
let dispatch_result = dispatch_window_error_event_with_details(
|
||||
scope,
|
||||
host_ptr,
|
||||
&report.summary,
|
||||
report.source.as_deref().unwrap_or(""),
|
||||
report.line.unwrap_or(0) as u32,
|
||||
report.column.unwrap_or(0) as u32,
|
||||
summary,
|
||||
source,
|
||||
line,
|
||||
column,
|
||||
error_value,
|
||||
)
|
||||
.map_err(anyhow::Error::msg);
|
||||
|
||||
@@ -168,6 +168,63 @@ async fn classic_script_exception_reports_window_error_then_completes() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn muted_classic_script_exceptions_expose_only_cross_origin_safe_details() {
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
|
||||
let mut vm = new_storage_test_vm_with_loader("https://example.com/", &loader);
|
||||
vm.eval(
|
||||
r#"
|
||||
globalThis.__mutedClassicScriptErrors = [];
|
||||
window.onerror = (message, source, line, column, error) => {
|
||||
globalThis.__mutedClassicScriptErrors.push({
|
||||
message,
|
||||
source,
|
||||
line,
|
||||
column,
|
||||
errorIsNull: error === null,
|
||||
});
|
||||
return true;
|
||||
};
|
||||
"installed";
|
||||
"#,
|
||||
)
|
||||
.expect("window error observer should install");
|
||||
|
||||
for (position, source) in [
|
||||
(8, "throw new Error('runtime secret');"),
|
||||
(9, "function syntaxError( {"),
|
||||
] {
|
||||
let mut script = ready_dynamic_runtime_script(position);
|
||||
script.url = Url::parse(&format!("https://cross-origin.test/script-{position}.js"))
|
||||
.expect("cross-origin script URL");
|
||||
script.base_url = script.url.clone();
|
||||
let script = crate::planning::prepared_script_with_loaded_source(
|
||||
script,
|
||||
source.to_owned(),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
|
||||
let outcome = vm
|
||||
.execute_loaded_prepared_script_source(&script, source, None)
|
||||
.await
|
||||
.expect("a muted exception should still complete classic script evaluation");
|
||||
assert!(matches!(
|
||||
outcome,
|
||||
crate::script_vm::LoadedScriptExecutionOutcome::Completed(
|
||||
crate::script_vm::PreparedScriptBodyActivity::Entered
|
||||
)
|
||||
));
|
||||
assert_eq!(script.base_url.as_str(), "about:blank");
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
vm.eval("JSON.stringify(globalThis.__mutedClassicScriptErrors)")
|
||||
.expect("muted classic script errors should remain observable"),
|
||||
r#"[{"message":"Script error.","source":"","line":0,"column":0,"errorIsNull":true},{"message":"Script error.","source":"","line":0,"column":0,"errorIsNull":true}]"#,
|
||||
);
|
||||
}
|
||||
|
||||
fn is_document_script_execution_work(
|
||||
work: &PostParsePageOwnedWork,
|
||||
lane: crate::document_script_scheduler::DocumentScriptExecutionLane,
|
||||
|
||||
@@ -709,6 +709,7 @@ pub(crate) enum ServiceWorkerDirectFetchResult {
|
||||
pub(crate) struct ServiceWorkerDirectFetchResponse {
|
||||
pub(crate) response: Box<crate::protocol_types::NavigationResponse>,
|
||||
pub(crate) response_filter: Option<crate::types::AsyncSubresourceFetchResponseFilter>,
|
||||
pub(crate) from_network_fallback: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -330,6 +330,7 @@ impl ServiceWorkerRuntimeService {
|
||||
ServiceWorkerDirectFetchResult::Response(ServiceWorkerDirectFetchResponse {
|
||||
response: Box::new(navigation_response),
|
||||
response_filter,
|
||||
from_network_fallback: true,
|
||||
})
|
||||
}
|
||||
Err(error) => ServiceWorkerDirectFetchResult::Failure(error.to_string()),
|
||||
@@ -618,6 +619,7 @@ impl ServiceWorkerRuntimeService {
|
||||
ServiceWorkerDirectFetchResponse {
|
||||
response: Box::new(navigation_response),
|
||||
response_filter,
|
||||
from_network_fallback: false,
|
||||
},
|
||||
));
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user