Commit 1f50465e authored by Alison Maher's avatar Alison Maher Committed by Commit Bot

Implement forced-colors media query

This change adds support for the forced-colors media query and adds
forced colors to settings. The logic for updating forced colors from
its default state based on the OS has not yet been implemented.

Spec:
https://drafts.csswg.org/mediaqueries-5/#forced-colors

Bug: 970285
Change-Id: I634b4abeec22f2d166648948f290fff8f4229478
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1643650Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarKevin Babbitt <kbabbitt@microsoft.com>
Commit-Queue: Alison Maher <almaher@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#667332}
parent 6e5a6179
......@@ -43,6 +43,7 @@ source_set("headers") {
"cache_storage/cache_storage_utils.h",
"client_hints/client_hints.h",
"common_export.h",
"css/forced_colors.h",
"css/preferred_color_scheme.h",
"device_memory/approximated_device_memory.h",
"dom_storage/session_storage_namespace_id.h",
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_CSS_FORCED_COLORS_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_CSS_FORCED_COLORS_H_
namespace blink {
// Use for passing forced colors from the OS to the renderer.
enum class ForcedColors {
kNone,
kActive,
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_CSS_FORCED_COLORS_H_
......@@ -33,6 +33,7 @@
#include <unicode/uscript.h>
#include "third_party/blink/public/common/css/forced_colors.h"
#include "third_party/blink/public/common/css/preferred_color_scheme.h"
#include "third_party/blink/public/platform/pointer_properties.h"
#include "third_party/blink/public/platform/web_common.h"
......@@ -289,6 +290,7 @@ class WebSettings {
virtual void SetLazyImageLoadingDistanceThresholdPx4G(int) = 0;
virtual void SetForceDarkModeEnabled(bool) = 0;
virtual void SetPreferredColorScheme(PreferredColorScheme) = 0;
virtual void SetForcedColors(ForcedColors) = 0;
protected:
~WebSettings() = default;
......
......@@ -910,6 +910,7 @@
"no-preference"
"light"
"dark"
"active"
# at-rules
"@charset"
......
......@@ -1188,5 +1188,9 @@
// (prefers-reduced-motion:) media feature
"reduce",
// (forced-colors:) media feature
// none
"active",
],
}
......@@ -10,6 +10,7 @@
"color",
"color-index",
"color-gamut",
"forced-colors",
"grid",
"monochrome",
"height",
......
......@@ -29,6 +29,7 @@
#include "third_party/blink/renderer/core/css/media_query_evaluator.h"
#include "third_party/blink/public/common/css/forced_colors.h"
#include "third_party/blink/public/common/css/preferred_color_scheme.h"
#include "third_party/blink/public/common/manifest/web_display_mode.h"
#include "third_party/blink/public/platform/pointer_properties.h"
......@@ -847,6 +848,24 @@ static bool PrefersColorSchemeMediaFeatureEval(
value.id == CSSValueID::kLight);
}
static bool ForcedColorsMediaFeatureEval(const MediaQueryExpValue& value,
MediaFeaturePrefix,
const MediaValues& media_values) {
ForcedColors forced_colors = media_values.GetForcedColors();
if (!value.IsValid())
return forced_colors != ForcedColors::kNone;
if (!value.is_id)
return false;
// Check the forced colors against value.id.
return (forced_colors == ForcedColors::kNone &&
value.id == CSSValueID::kNone) ||
(forced_colors != ForcedColors::kNone &&
value.id == CSSValueID::kActive);
}
void MediaQueryEvaluator::Init() {
// Create the table.
g_function_map = new FunctionMap;
......
......@@ -15,6 +15,7 @@
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/media_type_names.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
......@@ -173,6 +174,18 @@ MediaQueryEvaluatorTestCase g_non_ua_sheet_immersive_test_cases[] = {
{nullptr, 0} // Do not remove the terminator line.
};
MediaQueryEvaluatorTestCase g_forcedcolors_active_cases[] = {
{"(forced-colors: active)", 1},
{"(forced-colors: none)", 0},
{nullptr, 0} // Do not remove the terminator line.
};
MediaQueryEvaluatorTestCase g_forcedcolors_none_cases[] = {
{"(forced-colors: active)", 0},
{"(forced-colors: none)", 1},
{nullptr, 0} // Do not remove the terminator line.
};
void TestMQEvaluator(MediaQueryEvaluatorTestCase* test_cases,
const MediaQueryEvaluator& media_query_evaluator,
CSSParserMode mode) {
......@@ -326,4 +339,24 @@ TEST(MediaQueryEvaluatorTest, DynamicImmersive) {
TestMQEvaluator(g_immersive_test_cases, media_query_evaluator, kUASheetMode);
}
TEST(MediaQueryEvaluatorTest, CachedForcedColors) {
ScopedForcedColorsForTest scoped_feature(true);
MediaValuesCached::MediaValuesCachedData data;
data.forced_colors = ForcedColors::kNone;
MediaValues* media_values = MakeGarbageCollected<MediaValuesCached>(data);
// Forced colors - none.
MediaQueryEvaluator media_query_evaluator(*media_values);
TestMQEvaluator(g_forcedcolors_none_cases, media_query_evaluator);
// Forced colors - active.
{
data.forced_colors = ForcedColors::kActive;
MediaValues* media_values = MakeGarbageCollected<MediaValuesCached>(data);
MediaQueryEvaluator media_query_evaluator(*media_values);
TestMQEvaluator(g_forcedcolors_active_cases, media_query_evaluator);
}
}
} // namespace blink
......@@ -85,6 +85,12 @@ static inline bool FeatureWithValidIdent(const String& media_feature,
if (media_feature == kPrefersReducedMotionMediaFeature)
return ident == CSSValueID::kNoPreference || ident == CSSValueID::kReduce;
if (RuntimeEnabledFeatures::ForcedColorsEnabled()) {
if (media_feature == kForcedColorsMediaFeature) {
return ident == CSSValueID::kNone || ident == CSSValueID::kActive;
}
}
return false;
}
......@@ -201,7 +207,8 @@ static inline bool FeatureWithoutValue(const String& media_feature) {
media_feature == kColorGamutMediaFeature ||
media_feature == kImmersiveMediaFeature ||
media_feature == kPrefersColorSchemeMediaFeature ||
media_feature == kPrefersReducedMotionMediaFeature;
media_feature == kPrefersReducedMotionMediaFeature ||
media_feature == kForcedColorsMediaFeature;
}
bool MediaQueryExp::IsViewportDependent() const {
......
......@@ -190,6 +190,12 @@ bool MediaValues::CalculatePrefersReducedMotion(LocalFrame* frame) {
return frame->GetSettings()->GetPrefersReducedMotion();
}
ForcedColors MediaValues::CalculateForcedColors(LocalFrame* frame) {
DCHECK(frame);
DCHECK(frame->GetSettings());
return frame->GetSettings()->GetForcedColors();
}
bool MediaValues::ComputeLengthImpl(double value,
CSSPrimitiveValue::UnitType type,
unsigned default_font_size,
......
......@@ -19,6 +19,7 @@ class CSSPrimitiveValue;
class LocalFrame;
enum class ColorSpaceGamut;
enum class PreferredColorScheme;
enum class ForcedColors;
class CORE_EXPORT MediaValues : public GarbageCollectedFinalized<MediaValues> {
public:
......@@ -79,6 +80,7 @@ class CORE_EXPORT MediaValues : public GarbageCollectedFinalized<MediaValues> {
virtual ColorSpaceGamut ColorGamut() const = 0;
virtual PreferredColorScheme GetPreferredColorScheme() const = 0;
virtual bool PrefersReducedMotion() const = 0;
virtual ForcedColors GetForcedColors() const = 0;
protected:
static double CalculateViewportWidth(LocalFrame*);
......@@ -102,6 +104,7 @@ class CORE_EXPORT MediaValues : public GarbageCollectedFinalized<MediaValues> {
static ColorSpaceGamut CalculateColorGamut(LocalFrame*);
static PreferredColorScheme CalculatePreferredColorScheme(LocalFrame*);
static bool CalculatePrefersReducedMotion(LocalFrame*);
static ForcedColors CalculateForcedColors(LocalFrame*);
};
} // namespace blink
......
......@@ -4,6 +4,7 @@
#include "third_party/blink/renderer/core/css/media_values_cached.h"
#include "third_party/blink/public/common/css/forced_colors.h"
#include "third_party/blink/public/common/css/preferred_color_scheme.h"
#include "third_party/blink/renderer/core/css/css_primitive_value.h"
#include "third_party/blink/renderer/core/dom/document.h"
......@@ -33,7 +34,8 @@ MediaValuesCached::MediaValuesCachedData::MediaValuesCachedData()
display_shape(kDisplayShapeRect),
color_gamut(ColorSpaceGamut::kUnknown),
preferred_color_scheme(PreferredColorScheme::kNoPreference),
prefers_reduced_motion(false) {}
prefers_reduced_motion(false),
forced_colors(ForcedColors::kNone) {}
MediaValuesCached::MediaValuesCachedData::MediaValuesCachedData(
Document& document)
......@@ -74,6 +76,7 @@ MediaValuesCached::MediaValuesCachedData::MediaValuesCachedData(
color_gamut = MediaValues::CalculateColorGamut(frame);
preferred_color_scheme = MediaValues::CalculatePreferredColorScheme(frame);
prefers_reduced_motion = MediaValues::CalculatePrefersReducedMotion(frame);
forced_colors = MediaValues::CalculateForcedColors(frame);
}
}
......@@ -196,4 +199,8 @@ bool MediaValuesCached::PrefersReducedMotion() const {
return data_.prefers_reduced_motion;
}
ForcedColors MediaValuesCached::GetForcedColors() const {
return data_.forced_colors;
}
} // namespace blink
......@@ -38,6 +38,7 @@ class CORE_EXPORT MediaValuesCached final : public MediaValues {
ColorSpaceGamut color_gamut;
PreferredColorScheme preferred_color_scheme;
bool prefers_reduced_motion;
ForcedColors forced_colors;
MediaValuesCachedData();
explicit MediaValuesCachedData(Document&);
......@@ -65,6 +66,7 @@ class CORE_EXPORT MediaValuesCached final : public MediaValues {
data.color_gamut = color_gamut;
data.preferred_color_scheme = preferred_color_scheme;
data.prefers_reduced_motion = prefers_reduced_motion;
data.forced_colors = forced_colors;
return data;
}
};
......@@ -103,6 +105,7 @@ class CORE_EXPORT MediaValuesCached final : public MediaValues {
ColorSpaceGamut ColorGamut() const override;
PreferredColorScheme GetPreferredColorScheme() const override;
bool PrefersReducedMotion() const override;
ForcedColors GetForcedColors() const override;
void OverrideViewportDimensions(double width, double height) override;
......
......@@ -4,6 +4,7 @@
#include "third_party/blink/renderer/core/css/media_values_dynamic.h"
#include "third_party/blink/public/common/css/forced_colors.h"
#include "third_party/blink/public/common/css/preferred_color_scheme.h"
#include "third_party/blink/renderer/core/css/css_primitive_value.h"
#include "third_party/blink/renderer/core/css/css_resolution_units.h"
......@@ -150,6 +151,10 @@ bool MediaValuesDynamic::PrefersReducedMotion() const {
return CalculatePrefersReducedMotion(frame_);
}
ForcedColors MediaValuesDynamic::GetForcedColors() const {
return CalculateForcedColors(frame_);
}
Document* MediaValuesDynamic::GetDocument() const {
return frame_->GetDocument();
}
......
......@@ -50,6 +50,7 @@ class CORE_EXPORT MediaValuesDynamic : public MediaValues {
ColorSpaceGamut ColorGamut() const override;
PreferredColorScheme GetPreferredColorScheme() const override;
bool PrefersReducedMotion() const override;
ForcedColors GetForcedColors() const override;
Document* GetDocument() const override;
bool HasValues() const override;
void OverrideViewportDimensions(double width, double height) override;
......
......@@ -7,6 +7,7 @@
#include <memory>
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/css/forced_colors.h"
#include "third_party/blink/public/common/css/preferred_color_scheme.h"
#include "third_party/blink/public/platform/web_float_rect.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
......@@ -1558,6 +1559,32 @@ TEST_F(StyleEngineTest, MediaQueriesChangePrefersReducedMotion) {
GetCSSPropertyColor()));
}
TEST_F(StyleEngineTest, MediaQueriesChangeForcedColors) {
ScopedForcedColorsForTest scoped_feature(true);
GetDocument().body()->SetInnerHTMLFromString(R"HTML(
<style>
@media (forced-colors: none) {
body { color: red }
}
@media (forced-colors: active) {
body { color: green }
}
</style>
<body></body>
)HTML");
UpdateAllLifecyclePhases();
EXPECT_EQ(MakeRGB(255, 0, 0),
GetDocument().body()->GetComputedStyle()->VisitedDependentColor(
GetCSSPropertyColor()));
GetDocument().GetSettings()->SetForcedColors(ForcedColors::kActive);
UpdateAllLifecyclePhases();
EXPECT_EQ(MakeRGB(0, 128, 0),
GetDocument().body()->GetComputedStyle()->VisitedDependentColor(
GetCSSPropertyColor()));
}
TEST_F(StyleEngineTest, ShadowRootStyleRecalcCrash) {
GetDocument().body()->SetInnerHTMLFromString("<div id=host></div>");
auto* host = To<HTMLElement>(GetDocument().getElementById("host"));
......
......@@ -755,6 +755,10 @@ void WebSettingsImpl::SetPreferredColorScheme(
settings_->SetPreferredColorScheme(color_scheme);
}
void WebSettingsImpl::SetForcedColors(ForcedColors forced_colors) {
settings_->SetForcedColors(forced_colors);
}
STATIC_ASSERT_ENUM(WebSettings::ImageAnimationPolicy::kAllowed,
kImageAnimationPolicyAllowed);
STATIC_ASSERT_ENUM(WebSettings::ImageAnimationPolicy::kAnimateOnce,
......
......@@ -216,6 +216,7 @@ class CORE_EXPORT WebSettingsImpl final : public WebSettings {
void SetForceDarkModeEnabled(bool) override;
void SetPreferredColorScheme(PreferredColorScheme) override;
void SetForcedColors(ForcedColors) override;
bool RenderVSyncNotificationEnabled() const {
return render_v_sync_notification_enabled_;
......
......@@ -31,6 +31,7 @@
#include <memory>
#include "base/macros.h"
#include "third_party/blink/public/common/css/forced_colors.h"
#include "third_party/blink/public/common/css/preferred_color_scheme.h"
#include "third_party/blink/public/common/manifest/web_display_mode.h"
#include "third_party/blink/public/platform/pointer_properties.h"
......
......@@ -1030,5 +1030,13 @@
name: "DontSendKeyEventsToJavascript",
initial: false,
},
// Forced colors from the OS/application passed to the renderer for
// evaluating the forced-colors media query.
{
name: "forcedColors",
initial: "ForcedColors::kNone",
invalidate: "MediaQuery",
type: "ForcedColors",
},
],
}
......@@ -628,6 +628,9 @@
name: "ForbidSyncXHRInPageDismissal",
status: "test",
},
{
name: "ForcedColors",
},
{
name: "ForceOverlayFullscreenVideo",
},
......
This is a testharness.js-based test.
PASS Should be parseable in a CSS stylesheet: '(forced-colors)'
FAIL Should be parseable in a CSS stylesheet: '(forced-colors: none)' assert_true: expected true got false
FAIL Should be parseable in a CSS stylesheet: '(forced-colors: active)' assert_true: expected true got false
PASS Should not be parseable in a CSS stylesheet: '(forced-colors: 0)'
PASS Should not be parseable in a CSS stylesheet: '(forced-colors: no-preference)'
PASS Should not be parseable in a CSS stylesheet: '(forced-colors: 10px)'
PASS Should not be parseable in a CSS stylesheet: '(forced-colors: active 0)'
PASS Should not be parseable in a CSS stylesheet: '(forced-colors: none active)'
PASS Should not be parseable in a CSS stylesheet: '(forced-colors: active/none)'
PASS Should be parseable in JS: '(forced-colors)'
FAIL Should be parseable in JS: '(forced-colors: none)' assert_true: expected true got false
FAIL Should be parseable in JS: '(forced-colors: active)' assert_true: expected true got false
PASS Should not be parseable in JS: '(forced-colors: 0)'
PASS Should not be parseable in JS: '(forced-colors: no-preference)'
PASS Should not be parseable in JS: '(forced-colors: 10px)'
PASS Should not be parseable in JS: '(forced-colors: active 0)'
PASS Should not be parseable in JS: '(forced-colors: none active)'
PASS Should not be parseable in JS: '(forced-colors: active/none)'
FAIL Check that none evaluates to false in the boolean context assert_equals: expected true but got false
Harness: the test ran to completion.
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/mediaqueries-5/#forced-colors" />
<script type="text/javascript" src="/resources/testharness.js"></script>
<script type="text/javascript" src="/resources/testharnessreport.js"></script>
<script type="text/javascript" src="resources/matchmedia-utils.js"></script>
<script>
query_should_be_css_parseable("(forced-colors)");
query_should_be_css_parseable("(forced-colors: none)");
query_should_be_css_parseable("(forced-colors: active)");
query_should_not_be_css_parseable("(forced-colors: 0)");
query_should_not_be_css_parseable("(forced-colors: no-preference)");
query_should_not_be_css_parseable("(forced-colors: 10px)");
query_should_not_be_css_parseable("(forced-colors: active 0)");
query_should_not_be_css_parseable("(forced-colors: none active)");
query_should_not_be_css_parseable("(forced-colors: active/none)");
query_should_be_js_parseable("(forced-colors)");
query_should_be_js_parseable("(forced-colors: none)");
query_should_be_js_parseable("(forced-colors: active)");
query_should_not_be_js_parseable("(forced-colors: 0)");
query_should_not_be_js_parseable("(forced-colors: no-preference)");
query_should_not_be_js_parseable("(forced-colors: 10px)");
query_should_not_be_js_parseable("(forced-colors: active 0)");
query_should_not_be_js_parseable("(forced-colors: none active)");
query_should_not_be_js_parseable("(forced-colors: active/none)");
test(() => {
let booleanContext = window.matchMedia("(forced-colors)");
let none = window.matchMedia("(forced-colors: none)");
assert_equals(booleanContext.matches, !none.matches);
}, "Check that none evaluates to false in the boolean context");
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment