mirror of
https://github.com/block/buzz.git
synced 2026-11-10 03:11:40 +00:00
feat(desktop): apply WebKit rendering workarounds at startup on Linux (#3271)
On some Linux GPU/driver/compositor combinations, WebKitGTK's dmabuf renderer aborts the web process during startup, so Buzz comes up with no window at all and the user has no way to fix it. Setting `WEBKIT_DISABLE_DMABUF_RENDERER=1` avoids the abort by falling back to the shared-memory buffer path. WebKit reads each of its rendering variables exactly once per process, so the choice has to be made before anything initializes — there is no runtime toggle and no second chance later in the same process. This decides up front from two cheap preflight signals rather than reacting to a crash: - **NVIDIA GPU** — any DRM device under `/sys/class/drm` reporting PCI vendor `0x10de`, the driver family behind most upstream reports. - **AppImage** — the `APPIMAGE` environment variable. linuxdeploy's AppRun hook pins `GDK_BACKEND=x11`, and the dmabuf renderer buys nothing on that XWayland path. Either signal disables the dmabuf renderer. Neither signal leaves the environment untouched. ## Escape hatches `--safe-rendering` forces the safest configuration for one launch — `WEBKIT_DISABLE_DMABUF_RENDERER` plus `WEBKIT_DISABLE_COMPOSITING_MODE` — for a machine neither signal recognises. Any user assignment of a variable this module may set stands the heuristic down **wholesale**. Presence is the test, not truthiness, so `VAR=0` and `VAR=` both count: a user asking for the dmabuf renderer *on* gets it, even on a machine the heuristic would have opted out. `--safe-rendering` against such an assignment is refused with a diagnostic naming both the assignment and the key to unset, and exits non-zero — the flag and the environment are two incompatible answers to one question, and neither is guessed. ## Placement `webkit_rendering::apply()` runs at the top of `fn main()`, before `buzz_lib::run()`. That is the only point where the process is still single threaded with no GTK object alive, which is what makes `std::env::set_var` sound; the module doc and the call site both say so. The whole module is `#[cfg(target_os = "linux")]` — macOS and Windows compile none of it. The decision is a pure function of argv, an injected environment lookup, and an injected DRM root, so all of it is unit-testable without mutating the process environment. Closes #2338. Upstream: [tauri#9394](https://github.com/tauri-apps/tauri/issues/9394). Same approach and same variable as [clash-verge-rev](https://github.com/clash-verge-rev/clash-verge-rev/blob/main/src-tauri/src/utils/linux/workarounds.rs) `workarounds.rs` and [screenpipe](https://github.com/screenpipe/screenpipe/blob/main/apps/screenpipe-app-tauri/src-tauri/src/linux_webkit_env.rs) `linux_webkit_env.rs`. Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
@@ -29,6 +29,8 @@ mod secret_store;
|
||||
mod shutdown;
|
||||
mod templates;
|
||||
mod util;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod webkit_rendering;
|
||||
use app_state::{build_app_state, resolve_persisted_identity, AppState};
|
||||
use builderlab::*;
|
||||
use commands::*;
|
||||
|
||||
@@ -2,5 +2,15 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
// Before anything else: WebKitGTK reads its rendering environment once at
|
||||
// process start, and this is the only point where the process is still
|
||||
// single threaded and no GTK object exists yet, which is what makes
|
||||
// `std::env::set_var` sound.
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Err(diagnostic) = buzz_lib::webkit_rendering::apply() {
|
||||
eprintln!("buzz-desktop: {diagnostic}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
buzz_lib::run()
|
||||
}
|
||||
|
||||
208
desktop/src-tauri/src/webkit_rendering.rs
Normal file
208
desktop/src-tauri/src/webkit_rendering.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
//! WebKit rendering workarounds for Linux, applied before WebKit initializes.
|
||||
//!
|
||||
//! WebKitGTK's dmabuf renderer aborts the web process during startup on some
|
||||
//! GPU/driver/compositor combinations, so Buzz comes up with no window at all
|
||||
//! and the user has no way to fix it (#2338, upstream tauri#9394). Setting
|
||||
//! `WEBKIT_DISABLE_DMABUF_RENDERER=1` avoids the abort by falling back to the
|
||||
//! shared-memory buffer path.
|
||||
//!
|
||||
//! WebKit reads each of these variables exactly once per process, so the choice
|
||||
//! has to be made before anything initializes — there is no runtime toggle and
|
||||
//! no second chance later in the same process. This module therefore decides
|
||||
//! from cheap preflight signals instead of reacting to a crash:
|
||||
//!
|
||||
//! * an NVIDIA GPU, the driver family behind most upstream reports; and
|
||||
//! * AppImage packaging, where linuxdeploy's AppRun hook pins `GDK_BACKEND=x11`
|
||||
//! and the dmabuf renderer buys nothing on that XWayland path (#2338).
|
||||
//!
|
||||
//! `--safe-rendering` is the manual escape hatch for a machine neither signal
|
||||
//! recognises; it also disables accelerated compositing, for that launch only.
|
||||
//!
|
||||
//! This is the shape the Tauri ecosystem converged on: clash-verge-rev's
|
||||
//! `utils/linux/workarounds.rs` and screenpipe's `linux_webkit_env.rs` both set
|
||||
//! the same variable from the same signals at the same point in startup.
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::Path;
|
||||
|
||||
/// Force the safest rendering configuration for this launch.
|
||||
const SAFE_RENDERING: &str = "--safe-rendering";
|
||||
|
||||
/// PCI vendor ID reported by NVIDIA devices under `/sys/class/drm`.
|
||||
const NVIDIA_PCI_VENDOR: &str = "0x10de";
|
||||
|
||||
/// Where DRM devices advertise their PCI vendor.
|
||||
const DRM_ROOT: &str = "/sys/class/drm";
|
||||
|
||||
/// Drops the zero-copy dmabuf buffer path. The workaround for #2338.
|
||||
const DISABLE_DMABUF: &str = "WEBKIT_DISABLE_DMABUF_RENDERER";
|
||||
/// Drops accelerated compositing as well. `--safe-rendering` only.
|
||||
const DISABLE_COMPOSITING: &str = "WEBKIT_DISABLE_COMPOSITING_MODE";
|
||||
|
||||
/// What the heuristic applies: the #2338 workaround alone, matching the
|
||||
/// ecosystem precedents. `DISABLE_COMPOSITING` is deliberately not here — no
|
||||
/// report has isolated it as necessary, and it costs more rendering than this.
|
||||
const HEURISTIC: [&str; 1] = [DISABLE_DMABUF];
|
||||
|
||||
/// What `--safe-rendering` applies, which is also every variable this module may
|
||||
/// set and therefore every variable a user assignment takes away from it. Being
|
||||
/// the same list is the invariant: nothing outside it is ever written, so a user
|
||||
/// value for any other WebKit variable is not a conflict.
|
||||
const OWNED: [&str; 2] = [DISABLE_DMABUF, DISABLE_COMPOSITING];
|
||||
|
||||
/// Reads one environment variable. Injected so the decision is testable without
|
||||
/// mutating the process environment. `OsString` rather than `String` because
|
||||
/// presence is the test — a non-UTF-8 assignment is still the user's.
|
||||
type EnvLookup<'a> = &'a dyn Fn(&str) -> Option<OsString>;
|
||||
|
||||
/// What this launch should do about its rendering environment.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Plan {
|
||||
/// Set each of these to `1`, then report `why`.
|
||||
Apply {
|
||||
vars: &'static [&'static str],
|
||||
why: String,
|
||||
},
|
||||
/// Change nothing, and report `why`.
|
||||
Leave { why: String },
|
||||
/// The request cannot be delivered. Report it and exit non-zero rather than
|
||||
/// starting an app that silently ignores what the user asked for.
|
||||
Fatal { diagnostic: String },
|
||||
}
|
||||
|
||||
/// Applies the workaround for this launch.
|
||||
///
|
||||
/// Must be called from `main()` before `crate::run()`: WebKit memoizes these
|
||||
/// variables at process start, and `std::env::set_var` is only sound while the
|
||||
/// process is still single threaded, which it is nowhere else in Buzz.
|
||||
///
|
||||
/// `Err` carries a user-facing diagnostic; the caller reports it and exits.
|
||||
pub fn apply() -> Result<(), String> {
|
||||
match plan(
|
||||
std::env::args_os(),
|
||||
&|key| std::env::var_os(key),
|
||||
Path::new(DRM_ROOT),
|
||||
) {
|
||||
Plan::Apply { vars, why } => {
|
||||
for var in vars {
|
||||
// Safe here and only here — see the doc comment above.
|
||||
std::env::set_var(var, "1");
|
||||
}
|
||||
let applied: Vec<String> = vars.iter().map(|var| format!("{var}=1")).collect();
|
||||
eprintln!("buzz-desktop: {} — {why}", applied.join(" "));
|
||||
Ok(())
|
||||
}
|
||||
Plan::Leave { why } => {
|
||||
eprintln!("buzz-desktop: WebKit rendering left as-is — {why}");
|
||||
Ok(())
|
||||
}
|
||||
Plan::Fatal { diagnostic } => Err(diagnostic),
|
||||
}
|
||||
}
|
||||
|
||||
/// The whole decision, as a pure function of argv, the environment, and the DRM
|
||||
/// device tree.
|
||||
fn plan(
|
||||
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
|
||||
env: EnvLookup<'_>,
|
||||
drm_root: &Path,
|
||||
) -> Plan {
|
||||
let safe_rendering = args
|
||||
.into_iter()
|
||||
.any(|arg| arg.as_ref() == OsStr::new(SAFE_RENDERING));
|
||||
let user_set = user_set(env);
|
||||
|
||||
if !user_set.is_empty() {
|
||||
// A user who has assigned one of these has taken over the decision, so
|
||||
// the heuristic stands down wholesale — writing the *other* variable
|
||||
// behind their back would be exactly the surprise they opted out of.
|
||||
return match safe_rendering {
|
||||
// Two incompatible answers to one question, and no basis for
|
||||
// picking: honouring the flag would overwrite configuration the
|
||||
// user typed, honouring the environment would silently ignore a
|
||||
// rescue flag from a user whose app does not start.
|
||||
true => Plan::Fatal {
|
||||
diagnostic: conflict(&user_set),
|
||||
},
|
||||
false => Plan::Leave {
|
||||
why: format!("{} set in the environment", describe(&user_set)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if safe_rendering {
|
||||
return Plan::Apply {
|
||||
vars: &OWNED,
|
||||
why: format!("{SAFE_RENDERING} requested, this launch only"),
|
||||
};
|
||||
}
|
||||
|
||||
let signals = [
|
||||
(nvidia_gpu(drm_root), "NVIDIA GPU"),
|
||||
(env("APPIMAGE").is_some(), "AppImage"),
|
||||
];
|
||||
let hits: Vec<&str> = signals
|
||||
.iter()
|
||||
.filter_map(|(hit, label)| hit.then_some(*label))
|
||||
.collect();
|
||||
|
||||
match hits.is_empty() {
|
||||
true => Plan::Leave {
|
||||
why: "no NVIDIA GPU and not an AppImage".to_string(),
|
||||
},
|
||||
false => Plan::Apply {
|
||||
vars: &HEURISTIC,
|
||||
why: hits.join(", "),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Owned variables the environment already carries, keyed by name.
|
||||
///
|
||||
/// Presence is the test, not truthiness: `VAR=0` and `VAR=` are both genuine
|
||||
/// user assignments, and both take the decision away from this module.
|
||||
fn user_set(env: EnvLookup<'_>) -> Vec<(&'static str, OsString)> {
|
||||
OWNED
|
||||
.iter()
|
||||
.filter_map(|key| env(key).map(|value| (*key, value)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// User assignments rendered as `KEY=value`, for a log line or a diagnostic.
|
||||
fn describe(user_set: &[(&str, OsString)]) -> String {
|
||||
let shown: Vec<String> = user_set
|
||||
.iter()
|
||||
.map(|(key, value)| format!("{key}={}", value.to_string_lossy()))
|
||||
.collect();
|
||||
shown.join(", ")
|
||||
}
|
||||
|
||||
/// Whether any DRM device reports NVIDIA's PCI vendor ID. An unreadable device
|
||||
/// tree is not a hit — the workaround has a real cost, so it needs evidence.
|
||||
fn nvidia_gpu(drm_root: &Path) -> bool {
|
||||
let Ok(entries) = std::fs::read_dir(drm_root) else {
|
||||
return false;
|
||||
};
|
||||
entries.flatten().any(|entry| {
|
||||
std::fs::read_to_string(entry.path().join("device/vendor"))
|
||||
.is_ok_and(|vendor| vendor.trim().eq_ignore_ascii_case(NVIDIA_PCI_VENDOR))
|
||||
})
|
||||
}
|
||||
|
||||
/// The diagnostic for `--safe-rendering` against a user-set owned variable.
|
||||
///
|
||||
/// The message both shows what is set and names the keys to unset — the two
|
||||
/// things a user whose app will not start needs in order to act on it.
|
||||
fn conflict(user_set: &[(&str, OsString)]) -> String {
|
||||
let keys: Vec<&str> = user_set.iter().map(|(key, _)| *key).collect();
|
||||
format!(
|
||||
"{SAFE_RENDERING} cannot be applied: {} already set in the environment. \
|
||||
Either unset {} and run {SAFE_RENDERING} again, or keep that \
|
||||
environment and drop the flag.",
|
||||
describe(user_set),
|
||||
keys.join(", "),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
250
desktop/src-tauri/src/webkit_rendering/tests.rs
Normal file
250
desktop/src-tauri/src/webkit_rendering/tests.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
//! Behaviour of the preflight decision.
|
||||
//!
|
||||
//! Every case goes through `plan`, which takes argv, the environment, and the
|
||||
//! DRM root as arguments — so nothing here mutates the process environment and
|
||||
//! the tests are order-independent.
|
||||
|
||||
use super::*;
|
||||
|
||||
const NO_ARGS: [&str; 0] = [];
|
||||
|
||||
/// A `/sys/class/drm` stand-in. `vendors` are written as `card<n>/device/vendor`
|
||||
/// with the trailing newline the kernel emits.
|
||||
fn drm(vendors: &[&str]) -> tempfile::TempDir {
|
||||
let root = tempfile::tempdir().expect("tempdir");
|
||||
for (index, vendor) in vendors.iter().enumerate() {
|
||||
let device = root.path().join(format!("card{index}")).join("device");
|
||||
std::fs::create_dir_all(&device).expect("device dir");
|
||||
std::fs::write(device.join("vendor"), format!("{vendor}\n")).expect("vendor");
|
||||
}
|
||||
root
|
||||
}
|
||||
|
||||
fn env_from(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<OsString> {
|
||||
let owned: Vec<(String, OsString)> = pairs
|
||||
.iter()
|
||||
.map(|(key, value)| (key.to_string(), OsString::from(value)))
|
||||
.collect();
|
||||
move |key| {
|
||||
owned
|
||||
.iter()
|
||||
.find(|(candidate, _)| candidate == key)
|
||||
.map(|(_, value)| value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// The variables a plan would set, or `None` for a plan that sets nothing.
|
||||
fn applied(plan: &Plan) -> Option<&[&str]> {
|
||||
match plan {
|
||||
Plan::Apply { vars, .. } => Some(vars),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Detection ───────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_nvidia_gpu_disables_the_dmabuf_renderer() {
|
||||
let drm = drm(&["0x10de"]);
|
||||
let plan = plan(NO_ARGS, &env_from(&[]), drm.path());
|
||||
|
||||
assert_eq!(
|
||||
applied(&plan),
|
||||
Some(&["WEBKIT_DISABLE_DMABUF_RENDERER"][..])
|
||||
);
|
||||
let Plan::Apply { why, .. } = &plan else {
|
||||
unreachable!()
|
||||
};
|
||||
assert!(why.contains("NVIDIA"), "{why}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_an_nvidia_gpu_alongside_another_vendor_still_counts() {
|
||||
// Hybrid graphics: the integrated GPU enumerates first, and WebKit may
|
||||
// still land on the discrete one.
|
||||
let drm = drm(&["0x8086", "0x10de"]);
|
||||
|
||||
assert_eq!(
|
||||
applied(&plan(NO_ARGS, &env_from(&[]), drm.path())),
|
||||
Some(&["WEBKIT_DISABLE_DMABUF_RENDERER"][..])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_the_vendor_id_match_ignores_case() {
|
||||
let drm = drm(&["0x10DE"]);
|
||||
|
||||
assert_eq!(
|
||||
applied(&plan(NO_ARGS, &env_from(&[]), drm.path())),
|
||||
Some(&["WEBKIT_DISABLE_DMABUF_RENDERER"][..])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_an_appimage_launch_disables_the_dmabuf_renderer() {
|
||||
// No NVIDIA GPU: the AppImage signal has to carry this on its own, which is
|
||||
// #2338's reporter (Intel Mesa under the AppRun's pinned XWayland backend).
|
||||
let drm = drm(&["0x8086"]);
|
||||
let env = env_from(&[("APPIMAGE", "/home/u/Buzz.AppImage")]);
|
||||
let plan = plan(NO_ARGS, &env, drm.path());
|
||||
|
||||
assert_eq!(
|
||||
applied(&plan),
|
||||
Some(&["WEBKIT_DISABLE_DMABUF_RENDERER"][..])
|
||||
);
|
||||
let Plan::Apply { why, .. } = &plan else {
|
||||
unreachable!()
|
||||
};
|
||||
assert!(why.contains("AppImage"), "{why}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a_plain_non_nvidia_launch_changes_nothing() {
|
||||
let drm = drm(&["0x8086", "0x1002"]);
|
||||
|
||||
assert!(matches!(
|
||||
plan(NO_ARGS, &env_from(&[]), drm.path()),
|
||||
Plan::Leave { .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_an_unreadable_drm_tree_is_not_treated_as_a_hit() {
|
||||
// Containers and hardened kernels can hide `/sys/class/drm` entirely. The
|
||||
// workaround costs real rendering performance, so absent evidence is not
|
||||
// evidence — this must not become an unconditional export.
|
||||
let missing = std::path::Path::new("/nonexistent/class/drm");
|
||||
|
||||
assert!(matches!(
|
||||
plan(NO_ARGS, &env_from(&[]), missing),
|
||||
Plan::Leave { .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a_device_without_a_vendor_file_is_skipped_not_fatal() {
|
||||
// `/sys/class/drm` also contains connector entries (`card0-HDMI-A-1`) and
|
||||
// `renderD*` nodes, which have no `device/vendor` under them.
|
||||
let root = tempfile::tempdir().expect("tempdir");
|
||||
std::fs::create_dir_all(root.path().join("card0-HDMI-A-1")).expect("connector");
|
||||
let device = root.path().join("card1").join("device");
|
||||
std::fs::create_dir_all(&device).expect("device dir");
|
||||
std::fs::write(device.join("vendor"), "0x10de\n").expect("vendor");
|
||||
|
||||
assert_eq!(
|
||||
applied(&plan(NO_ARGS, &env_from(&[]), root.path())),
|
||||
Some(&["WEBKIT_DISABLE_DMABUF_RENDERER"][..])
|
||||
);
|
||||
}
|
||||
|
||||
// ── User environment ────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_a_user_set_variable_disables_the_heuristic_wholesale() {
|
||||
// `0` is the value a truthiness check would drop: the user is asking for the
|
||||
// dmabuf renderer *on*, on a machine the heuristic would have opted out.
|
||||
let drm = drm(&["0x10de"]);
|
||||
let env = env_from(&[(DISABLE_DMABUF, "0")]);
|
||||
let plan = plan(NO_ARGS, &env, drm.path());
|
||||
|
||||
let Plan::Leave { why } = &plan else {
|
||||
panic!("a user assignment must not be overwritten: {plan:?}");
|
||||
};
|
||||
assert!(why.contains("WEBKIT_DISABLE_DMABUF_RENDERER=0"), "{why}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_an_empty_assignment_is_still_a_user_assignment() {
|
||||
let drm = drm(&["0x10de"]);
|
||||
let env = env_from(&[(DISABLE_DMABUF, "")]);
|
||||
|
||||
assert!(matches!(
|
||||
plan(NO_ARGS, &env, drm.path()),
|
||||
Plan::Leave { .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a_user_set_compositing_variable_also_stands_the_heuristic_down() {
|
||||
// The heuristic never sets this one, but it is still ours to set under
|
||||
// `--safe-rendering`, so a user value takes the whole decision away rather
|
||||
// than leaving us free to write the sibling variable.
|
||||
let drm = drm(&["0x10de"]);
|
||||
let env = env_from(&[(DISABLE_COMPOSITING, "1")]);
|
||||
|
||||
assert!(matches!(
|
||||
plan(NO_ARGS, &env, drm.path()),
|
||||
Plan::Leave { .. }
|
||||
));
|
||||
}
|
||||
|
||||
// ── --safe-rendering ────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_safe_rendering_applies_the_safest_set_without_any_hardware_signal() {
|
||||
// The escape hatch exists for the machine neither signal recognises, so it
|
||||
// must not depend on either one.
|
||||
let drm = drm(&["0x8086"]);
|
||||
let args = ["buzz://channel/1", SAFE_RENDERING];
|
||||
let plan = plan(args, &env_from(&[]), drm.path());
|
||||
|
||||
assert_eq!(
|
||||
applied(&plan),
|
||||
Some(
|
||||
&[
|
||||
"WEBKIT_DISABLE_DMABUF_RENDERER",
|
||||
"WEBKIT_DISABLE_COMPOSITING_MODE"
|
||||
][..]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_an_unrelated_flag_is_not_mistaken_for_safe_rendering() {
|
||||
let drm = drm(&["0x8086"]);
|
||||
|
||||
assert!(matches!(
|
||||
plan(["--safe-renderingX"], &env_from(&[]), drm.path()),
|
||||
Plan::Leave { .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_safe_rendering_against_a_user_set_variable_is_fatal_not_guessed() {
|
||||
let drm = drm(&["0x8086"]);
|
||||
let env = env_from(&[(DISABLE_DMABUF, "0")]);
|
||||
let plan = plan([SAFE_RENDERING], &env, drm.path());
|
||||
|
||||
let Plan::Fatal { diagnostic } = &plan else {
|
||||
panic!("the flag and the environment disagree; neither may be guessed: {plan:?}");
|
||||
};
|
||||
// The message has to name what is set and what to unset, or the user whose
|
||||
// app will not start cannot act on it.
|
||||
assert!(diagnostic.contains(SAFE_RENDERING), "{diagnostic}");
|
||||
assert!(
|
||||
diagnostic.contains("WEBKIT_DISABLE_DMABUF_RENDERER=0"),
|
||||
"{diagnostic}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a_non_utf8_user_assignment_is_reported_not_ignored() {
|
||||
// Presence is the test, so this still stands the heuristic down; the
|
||||
// diagnostic must name the key rather than dropping the whole entry.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
let drm = drm(&["0x10de"]);
|
||||
let invalid = OsString::from_vec(vec![0xff, 0xfe]);
|
||||
let env = |key: &str| match key == DISABLE_DMABUF {
|
||||
true => Some(invalid.clone()),
|
||||
false => None,
|
||||
};
|
||||
|
||||
let Plan::Fatal { diagnostic } = plan([SAFE_RENDERING], &env, drm.path()) else {
|
||||
panic!("a non-UTF-8 assignment is still a user assignment");
|
||||
};
|
||||
assert!(diagnostic.contains(DISABLE_DMABUF), "{diagnostic}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user