diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index 09ed27a30..c6708aed0 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -2012,6 +2012,46 @@ fn append_platform_cef_gpu_workarounds( } } +/// Whether a CEF command-line flag is `--time-ticks-at-unix-epoch` (in any +/// dash/casing form, with or without an inline `=` suffix). See +/// [`strip_time_ticks_at_unix_epoch`] for why we care. +fn is_time_ticks_at_unix_epoch_flag(flag: &str) -> bool { + let name = flag.trim_start_matches('-'); + // Chromium switches can carry their value inline (`--flag=value`); compare + // only the switch name so the inline form can't slip past the guard. + let name = name.split_once('=').map_or(name, |(n, _)| n); + name.eq_ignore_ascii_case("time-ticks-at-unix-epoch") +} + +/// Issue #3554: `--time-ticks-at-unix-epoch` carries the monotonic-clock +/// origin (in microseconds) that CEF child processes — renderer / GPU / +/// utility — use to map Chromium's `TimeTicks` onto wall-clock time. Chromium +/// derives this value itself when it spawns each child, and it must stay +/// consistent with the host clock. +/// +/// OpenHuman must never inject this switch into the CEF command line: a stale +/// or negative value (e.g. the `-1780937467390432` reported in #3554) pins the +/// renderer's internal clock ~56 years before the Unix epoch, which surfaces +/// as a wrong "Current Date & Time" in the app. The CEF command line is +/// assembled from several sources (the static list here plus any +/// `RuntimeInitAttribute::CommandLineArgs` contributed elsewhere), so strip the +/// flag from the final list as a guard and let Chromium compute the origin +/// locally for each process. +fn strip_time_ticks_at_unix_epoch(args: &mut Vec) { + args.retain(|(flag, value)| { + if is_time_ticks_at_unix_epoch_flag(flag) { + log::warn!( + "[cef-startup] dropping OpenHuman-supplied --time-ticks-at-unix-epoch{} so \ + Chromium computes the clock origin locally (issue #3554)", + value.map(|v| format!("={v}")).unwrap_or_default() + ); + false + } else { + true + } + }); +} + /// Linux only: replace Xlib's default error handler with a logging no-op. /// /// Why: on Wayland sessions (GNOME/KDE/Hyprland) running CEF via XWayland, @@ -2653,6 +2693,10 @@ pub fn run() { std::env::consts::ARCH, force_gpu_env.as_deref(), ); + // #3554: never forward a `--time-ticks-at-unix-epoch` switch to CEF — + // a corrupt/negative value drives the renderer's clock decades off and + // shows a wrong "Current Date & Time". Let Chromium compute it. + strip_time_ticks_at_unix_epoch(&mut args); tauri::Builder::::new().command_line_args::<&str, &str>(args) }; diff --git a/app/src-tauri/src/lib_tests.rs b/app/src-tauri/src/lib_tests.rs index 80447e95d..c781ec47b 100644 --- a/app/src-tauri/src/lib_tests.rs +++ b/app/src-tauri/src/lib_tests.rs @@ -404,6 +404,101 @@ fn platform_cef_gpu_workarounds_force_gpu_does_not_affect_intel_macos_path() { assert_eq!(args, vec![("--disable-gpu-compositing", None)]); } +// ------------------------------------------------------------------------- +// #3554 — never forward --time-ticks-at-unix-epoch to CEF (wrong system time) +// ------------------------------------------------------------------------- + +#[test] +fn time_ticks_flag_matches_any_dash_and_casing_form() { + for flag in [ + "--time-ticks-at-unix-epoch", + "-time-ticks-at-unix-epoch", + "time-ticks-at-unix-epoch", + "--Time-Ticks-At-Unix-Epoch", + ] { + assert!( + is_time_ticks_at_unix_epoch_flag(flag), + "{flag:?} should be recognised as the time-ticks switch" + ); + } +} + +#[test] +fn time_ticks_flag_does_not_match_unrelated_flags() { + for flag in [ + "--disable-gpu", + "--use-mock-keychain", + "--time-zone", + "--enable-features", + ] { + assert!( + !is_time_ticks_at_unix_epoch_flag(flag), + "{flag:?} must not be treated as the time-ticks switch" + ); + } +} + +#[test] +fn strip_time_ticks_removes_negative_value_and_keeps_the_rest() { + // The corrupt value reported in #3554, alongside flags we must preserve. + let mut args = vec![ + ("--use-mock-keychain", None), + ("--time-ticks-at-unix-epoch", Some("-1780937467390432")), + ("--disable-gpu", None), + ]; + strip_time_ticks_at_unix_epoch(&mut args); + + assert_eq!( + args, + vec![("--use-mock-keychain", None), ("--disable-gpu", None)], + "the corrupt time-ticks switch must be removed; everything else kept" + ); +} + +#[test] +fn strip_time_ticks_removes_inline_value_form() { + // The critical bypass case: when the value is carried inline as + // `--flag=value` (a single token) rather than as a separate value, the + // matcher must still recognise and strip it. + let mut args = vec![ + ("--use-mock-keychain", None), + ("--time-ticks-at-unix-epoch=-1780937467390432", None), + ("--disable-gpu", None), + ]; + strip_time_ticks_at_unix_epoch(&mut args); + + assert_eq!( + args, + vec![("--use-mock-keychain", None), ("--disable-gpu", None)], + "the inline time-ticks switch must be removed; everything else kept" + ); +} + +#[test] +fn strip_time_ticks_removes_the_flag_regardless_of_value() { + // Even a non-negative value is dropped: OpenHuman must let Chromium + // compute the clock origin rather than anchor it from the shell. + let mut args = vec![ + ("--time-ticks-at-unix-epoch", Some("1780937467390432")), + ("--time-ticks-at-unix-epoch", None), + ]; + strip_time_ticks_at_unix_epoch(&mut args); + + assert!( + args.is_empty(), + "all time-ticks variants must be stripped, got: {args:?}" + ); +} + +#[test] +fn strip_time_ticks_is_a_noop_without_the_flag() { + let mut args = vec![("--use-mock-keychain", None), ("--disable-gpu", None)]; + let expected = args.clone(); + strip_time_ticks_at_unix_epoch(&mut args); + + assert_eq!(args, expected, "unrelated args must be left untouched"); +} + /// On an Intel macOS build the ARCH constant must equal "x86_64". /// This is the architecture that triggers --disable-gpu-compositing. #[cfg(all(target_os = "macos", target_arch = "x86_64"))]