fix(script): mute cross-origin classic script errors

This commit is contained in:
ldm0
2026-09-23 00:11:55 +08:00
parent e96bae5e7a
commit aee29b5ccf
21 changed files with 353 additions and 25 deletions
+6
View File
@@ -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,
},
}
}
+1
View File
@@ -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,
},
)
}
+214 -9
View File
@@ -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),
+1
View File
@@ -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;