在 Firefox 中添加 prefers-contrast

在本文中,我们将介绍 prefers-contrast 媒体查询 在 Firefox 中的设计和实现过程。我们首先定义高对比度模式,然后介绍 prefers-contrast 的重要性。最后,我们将介绍 Firefox 中的媒体查询实现。到最后,您将更深入地了解媒体查询在 Firefox 中的工作原理,以及 prefers-contrast 查询为何如此重要和令人兴奋。

当我们谈论页面的对比度时,我们是在评估网页作者的色彩选择对可读性的影响。对于视力较低的用户来说,对比度低或不足的网页可能难以使用。文本与其背景之间的缺乏区分会导致它们“融合在一起”。

prefers-contrast 是什么

虽然 WCAG(网页内容无障碍指南)为对比度设置了标准,但并非所有网站都遵循这些标准。为了保持网页无障碍,许多浏览器和操作系统提供高对比度设置来更改网页和内容的外观。当启用这些设置时,我们说网站访问者启用了高对比度模式。

高对比度模式会增加屏幕的对比度,以便视力较低的用户更容易浏览。高对比度模式会根据所使用的操作系统进行各种调整。它可以减少屏幕的视觉复杂性,强制文本和背景之间具有高对比度的颜色,对屏幕应用滤镜等等。在所有应用程序和网站上自动执行此操作并使其有效非常困难。

例如,高对比度模式应该如何处理图像?在强光或弱光下拍摄的照片可能缺乏对比度,其主题可能难以区分。对于在图像上设置的文本,该怎么办?如果图像不是单色,则某些部分可能具有高对比度,而其他部分可能没有。目前,Firefox 通过在文本上绘制 背板 来处理图像上的文本。所有这些都很好,但仍然不是理想的。理想情况下,网页可以检测到何时启用了高对比度模式,然后使其更容易访问。要做到这一点,我们需要了解不同的操作系统如何实现高对比度模式。

操作系统级别的高对比度设置

大多数操作系统都提供高对比度设置。在 macOS 上,用户可以在“系统偏好设置”→“辅助功能”→“显示”中指定他们是否更喜欢高对比度。为了满足这种偏好,macOS 会对屏幕应用高对比度滤镜。但是,它不会执行任何操作来通知应用程序已启用高对比度或调整屏幕布局。这使得在 macOS 上运行的应用程序难以针对高对比度模式用户进行调整。此外,这意味着用户完全依赖操作系统进行正确的修改。

Windows 采用了一种截然不同的方法。启用高对比度模式后,Windows 会将此信息公开给应用程序。它不是对屏幕应用滤镜,而是强制应用程序使用某些高对比度(或用户定义的)颜色。与 macOS 不同,Windows 还会在启用高对比度设置时通知应用程序。这样,应用程序可以自行调整,使其更易于高对比度模式友好。

同样,Firefox 允许用户自定义高对比度颜色或对网页内容应用不同的颜色。此选项可以通过 Firefox 的所有操作系统的“首选项”设置中的“语言和外观”下的颜色选项来启用。当我们谈论由用户设置的颜色(而不是由页面或应用程序设置)时,我们将它们描述为强制的颜色。

Firefox 中的强制颜色

a screenshot of Firefox Forced Colors Menu on a dark background

正如我们所见,不同的操作系统以不同的方式处理高对比度设置。这会影响 prefers-contrast 在这些平台上的工作方式。在 Windows 上,因为 Firefox 会在使用高对比度主题时收到通知,所以 prefers-contrast 可以检测到来自 Windows 的高对比度和来自 Firefox 本身的强制颜色。在 macOS 上,因为 Firefox 不会在使用高对比度主题时收到通知,所以 prefers-contrast 只能检测到何时在浏览器中强制使用颜色。

想要看看使用强制颜色是什么样子吗?以下是在启用默认 Windows 高对比度主题的 Firefox 上的 Google 主页

google homepage with windows high contrast mode enabled

请注意,Firefox 如何将背景颜色 (强制) 覆盖为黑色,并将轮廓覆盖为黄色。

这种强制颜色方法有一些需要改进的地方。在上面的 Google 主页上,您会注意到配置文件图像不再显示在登录按钮旁边。以下是亚马逊主页,也在 Firefox 上,启用了相同的 Windows 高对比度主题

screenshot of high-contrast Amazon homepage with dark background

“骑电动车”和“当前客户最爱”下的图像消失了,“父亲节特惠”部分的文本没有提高对比度。

prefers-contrast 的作用

我们不能责怪 Google 和亚马逊在这些高对比度主页的外观中出现图像丢失和其他问题。如果没有 prefers-contrast 媒体查询,就没有标准化的方法来检测访问者的对比度偏好。即使 Google 和亚马逊想更改他们的网页以使其更易于访问不同的对比度偏好,他们也无法做到。他们没有办法知道用户何时启用了高对比度模式,即使浏览器可以告诉他们。

这就是 prefers-contrast 如此重要的原因。prefers-contrast 媒体查询允许网站作者确定访问者的对比度偏好并相应地更新网站。使用 prefers-contrast,网站作者可以区分低对比度和高对比度,并检测到何时强制使用颜色,例如

@media (prefers-contrast: forced) {
    /* some awesome, accessible, high contrast css */
}

这很棒,因为见多识广的网站设计师比自动高对比度设置更擅长使他们的网页更易于访问。

prefers-contrast 的工作原理

本节介绍了 prefers-contrast 在 Firefox 中的实际实现方式。这是一个深入了解浏览器内部机制的有趣过程,但如果您只对 perfers-contrast 的作用和目的感兴趣,那么您可以跳过本节,直接阅读结论部分。

解析

我们将从解析开始我们的媒体查询实现之旅。解析处理将 CSS 和 HTML 转换为浏览器可以理解的内部表示。Firefox 使用名为 Servo 的浏览器引擎来处理此过程。幸运的是,Servo 使事情变得非常简单。要连接我们媒体查询的解析,我们将前往 Servo 代码库中的 media_features.rs,我们将添加一个枚举来表示我们的媒体查询。

/// Possible values for prefers-contrast media query.
/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast
#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq, Parse, ToCss)]
#[repr(u8)]
#[allow(missing_docs)]
enum PrefersContrast {
    High,
    Low,
    NoPreference,
    Forced,
}

因为我们使用了 #[derive(Parse)],所以 Stylo 将使用我们的枚举名称及其选项来生成解析代码。就是这么简单。:-)

评估媒体查询

现在我们已经连接了解析逻辑,我们将添加一些逻辑来评估我们的媒体查询。如果 prefers-contrast 只公开了低、无偏好和高,那么这将就像创建一个返回上面枚举实例的函数一样简单。

也就是说,添加强制选项会给我们的媒体查询带来一些有趣的问题。不可能同时偏好低对比度和高对比度。但是,网站访问者偏好高对比度并拥有强制颜色很常见。正如我们之前讨论过的,如果访问者在 Windows 上启用高对比度模式,也会强制网页使用颜色。因为枚举一次只能处于一种状态(即,prefers-contrast 枚举不能同时是高对比度和固定状态),所以我们需要对单个函数设计进行一些修改。

为了正确地表示 prefers-contrast,我们将把逻辑分成两半。前半部分将确定是否强制使用颜色,后半部分将确定网站访问者的对比度偏好。我们可以用布尔值来表示强制颜色是否存在,但我们需要一个新的枚举来表示对比度偏好。让我们将其添加到 media_features.rs

/// Represents the parts of prefers-contrast that explicitly deal with
/// contrast. Used in combination with information about rather or not
/// forced colors are active this allows for evaluation of the
/// prefers-contrast media query.
#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq)]
#[repr(u8)]
pub enum ContrastPref {
    /// High contrast is preferred. Corresponds to an accessibility theme
    /// being enabled or firefox forcing high contrast colors.
    High,
    /// Low contrast is prefered. Corresponds to the
    /// browser.display.prefers_low_contrast pref being true.
    Low,
    /// The default value if neither high nor low contrast is enabled.
    NoPreference,
}

太棒了!我们完成了解析和枚举,它们可以代表 prefers-contrast 媒体查询和网站访问者的对比度偏好的所有可能状态。

在 C++ 和 Rust 中添加函数

现在,我们将添加一些逻辑来使 prefers-contrast 正常工作。我们将分两步进行。首先,我们将添加一个 C++ 函数来确定对比度偏好,然后我们将添加一个 Rust 函数来调用它并评估媒体查询。

我们的 C++ 函数将驻留在 Gecko 中,即 Firefox 的布局引擎。有关高对比度设置的信息也收集在 Gecko 中。这对我们来说非常方便。我们希望我们的 C++ 函数返回我们之前提到的 ContrastPref 枚举。让我们从为其生成从 Rust 到 C++ 的绑定开始。

ServoBindings.toml 开始,我们将添加一个从 Stylo 类型到 Gecko 类型的映射

cbindgen-types = [
    # ...
    { gecko = "StyleContrastPref", servo = "gecko::media_features::ContrastPref" },
    # ...
]

然后,我们将向 Servo 的 cbindgen.toml 添加类似的内容

include = [
    # ...
    "ContrastPref",
    # ...
]

就这样!cbindgen 将生成绑定,以便我们拥有一个枚举来使用和从 C++ 代码中返回。

我们编写了一个 C++ 函数,它相当简单。我们将转到 nsMediaFeatures.cpp 并添加它。如果浏览器正在 抵制指纹识别,我们将返回无偏好。否则,我们将根据是否启用了高对比度模式 (UseAccessibilityTheme) 返回高或无偏好。

StyleContrastPref Gecko_MediaFeatures_PrefersContrast(const Document* aDocument, const bool aForcedColors) {
    if (nsContentUtils::ShouldResistFingerprinting(aDocument)) {
        return StyleContrastPref::NoPreference;
    }
    // Neither Linux, Windows, nor Mac has a way to indicate that low
    // contrast is preferred so the presence of an accessibility theme
    // implies that high contrast is preferred.
    //
    // Note that MacOS does not expose whether or not high contrast is
    // enabled so for MacOS users this will always evaluate to
    // false. For more information and discussion see:
    // https://github.com/w3c/csswg-drafts/issues/3856#issuecomment-642313572
    // https://github.com/w3c/csswg-drafts/issues/2943
    if (!!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
        return StyleContrastPref::High;
    }
    return StyleContrastPref::NoPreference;
}

旁注: 此实现没有检测低对比度偏好的方法。正如我们之前讨论的那样,Windows、macOS 和 Linux 都没有标准的方法来表明低对比度是首选。因此,对于我们的初始实现,我们选择保持简单,并使它无法切换。这并不是说这里没有改进的空间。用户可以使用各种非标准方法来表明他们喜欢低对比度,例如在 Windows、Linux 或 Firefox 中强制使用低对比度颜色。

在 Firefox 中确定对比度偏好

最后,我们将函数定义添加到 GeckoBindings.h 中,以便我们的 Rust 代码可以调用它。

mozilla::StyleContrastPref Gecko_MediaFeatures_PrefersContrast(
    const mozilla::dom::Document*, const bool aForcedColors);

现在解析、逻辑和 C++ 绑定都已设置完毕,我们已准备好添加用于评估媒体查询的 Rust 函数。回到 media_features.rs,我们将添加一个函数来完成此操作。

我们的函数接受一个包含有关评估媒体查询位置信息的设备。它包含一个可选的查询值,表示正在评估的媒体查询的值。查询值是可选的,因为有时媒体查询可以在没有查询的情况下进行评估。在这种情况下,我们评估通常会与查询进行比较的对比度偏好的真值。这称为在“布尔上下文”中评估媒体查询。如果对比度偏好不是“无偏好”,我们将继续应用媒体查询内的 CSS。

对比度偏好示例

这些信息很多,以下是一些示例:

@media (prefers-contrast: high) { } /* query_value: Some(high) */
@media (prefers-contrast: low) { } /* query_value: Some(low) */
@media (prefers-contrast) { } /* query_value: None | "eval in boolean context" */

在布尔上下文中(上面的第三个示例),我们首先确定实际的对比度偏好。然后,如果它不是无偏好,则媒体查询将评估为真并应用内部 CSS。另一方面,如果它是无偏好,则媒体查询将评估为假,我们不会应用 CSS。

考虑到这一点,让我们为我们的媒体查询构建逻辑!

fn eval_prefers_contrast(device: &Device, query_value: Option) -> bool {
    let forced_colors = !device.use_document_colors();
    let contrast_pref =
        unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(device.document(), forced_colors) };
    if let Some(query_value) = query_value {
        match query_value {
            PrefersContrast::Forced => forced_colors,
            PrefersContrast::High => contrast_pref == ContrastPref::High,
            PrefersContrast::Low => contrast_pref == ContrastPref::Low,
            PrefersContrast::NoPreference => contrast_pref == ContrastPref::NoPreference,
        }
    } else {
        // Only prefers-contrast: no-preference evaluates to false.
        forced_colors || (contrast_pref != ContrastPref::NoPreference)
    }
}

最后一步是将我们的媒体查询注册到 Firefox。仍然在 media_features.rs 中,我们将通知 Stylo 我们已完成。然后我们可以将我们的函数和枚举添加到媒体特征列表中

pub static MEDIA_FEATURES: [MediaFeatureDescription; 54] = [
    // ...
    feature!(
        atom!("prefers-contrast"),
        AllowsRanges::No,
        keyword_evaluator!(eval_prefers_contrast, PrefersContrast),
        // Note: by default this is only enabled in browser chrome and
        // ua. It can be enabled on the web via the
        // layout.css.prefers-contrast.enabled preference. See
        // disabled_by_pref in media_feature_expression.rs for how that
        // is done.
        ParsingRequirements::empty(),
    ),
    // ...
];

结论

就这样,我们完成了!经过仔细的步骤,我们已经介绍了 Firefox 中 prefers-contrast 的几乎完整实现。触发更新和测试没有涵盖,但它们是相对较小的细节。如果您想查看 prefers-contrast 的所有代码和测试,请查看 Phabricator 补丁 here

prefers-contrast 是一个功能强大且重要的媒体查询,它使网页作者更容易创建无障碍网页。使用 prefers-contrast,网站可以根据高对比度和强制对比度偏好进行调整,而以前这些都是无法做到的。要获得 prefers-contrast,请获取 Firefox Nightly 的副本,并在 about:config 中将 layout.css.prefers-contrast.enabled 设置为 true。现在,开始构建一个更无障碍的网络吧!🎉

Mozilla 致力于使互联网成为一个全球性的公共资源,对所有人开放且可访问。prefers-contrast 媒体查询以及我们的无障碍团队的其他工作确保我们履行对视力障碍用户和其他残疾用户的承诺。如果您有兴趣了解更多关于 Mozilla 的无障碍工作信息,您可以查看 无障碍博客无障碍维基页面

关于 Zeke Medley

Zeke 是布局团队的实习生。他曾在加州大学伯克利分校学习电气工程和计算机科学,并且喜欢在空闲时间玩模拟城市。

更多 Zeke Medley 的文章…


5 条评论

  1. James Craig

    Apple 尚未实施,因为还有几个主要问题尚未解决。

    其中包括
    https://github.com/w3c/csswg-drafts/issues/2943
    https://github.com/w3c/csswg-drafts/issues/3856#issuecomment-642313572

    2020 年 7 月 7 日 上午 9:33

    1. Zeke Medley

      遗憾的是,macOS 的支持目前非常有限,因为目前没有太多关于对比度偏好的信息公开给应用程序。

      我同意继续解决这些问题会很好。实际上,它们都与文章中有关 `Gecko_MediaFeatures_PrefersContrast` 函数的注释中的链接相关。

      2020 年 7 月 7 日 下午 12:07

  2. Chris Heilmann

    现在发布这个似乎很奇怪,因为 prefers-contrast: forced 根本不是规范的一部分。 https://drafts.csswg.org/mediaqueries-5/#prefers-contrast.

    正如 Craig 指出的那样,现在正对此进行很多必要的讨论。

    事实上,Windows 带有一个完整的强对比度模式,它将触发强制颜色媒体查询,并且被很多需要整个操作系统处于强对比度模式的人使用。

    “这很好,因为了解情况的网站设计师在使他们的网页无障碍方面比自动强对比度设置要好得多。”忘记了那些需要在访问你的网站之前就让操作系统为他们工作的人。这让我想起了 2000 年左右人们向他们的网站添加 Flash 扩展以朗读网站内容的时代,因为“看不见的人需要听到你的网站”。

    我很高兴看到这个领域正在进行很多工作,而且我现在在 Chromium 开发工具中发布了一个强对比度模拟器,但这篇文章似乎为时尚早,并且没有完全遵循标准流程。

    2020 年 7 月 8 日 上午 9:00

    1. Zeke Medley

      嗨 Chris,

      forced 在一个月前被添加到规范[1] 中,但看起来它还没有被写出来。目前正在进行一些重要的讨论,这可能是造成这种情况的原因。

      我们意识到规范可能很脆弱,这就是为什么目前 prefers-contrast 仅在 `layout.css.prefers-contrast.enabled` 偏好后面启用。目的是在规范最终确定之前让 prefers-contrast 处于偏好状态。同时,我们希望我们的原型会更新以反映规范中的更改。在我们的意图原型电子邮件[2] 中有一些更详细的信息。

      我理解这可能会让人觉得有点早。目的是在偏好状态下将其发布,以便我们可以在内部使用它以更好地支持 Firefox 中的强对比度,并让作者有机会尝试使用它。希望下面链接的意图原型电子邮件可以帮助澄清一些问题。

      我很高兴看到你的强对比度工作在 Chrome 中发布。看到这个领域正在进行如此多的工作真是太好了。

      [1] https://github.com/w3c/csswg-drafts/issues/3856#issuecomment-642139541
      [2] https://groups.google.com/g/mozilla.dev.platform/c/bTEmQxffGvA

      2020 年 7 月 15 日 上午 9:45

  3. Nikhil

    大家好,
    我也是一名盲人学生,
    我想代表自己感谢 Firefox,他们一直在为盲人创建如此无障碍的网页,
    我也感谢维护 Firefox 无障碍功能的开发者。

    2020 年 7 月 14 日 上午 3:42

本文评论已关闭。