зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1794628 - Implement inverted-colors media feature r=geckoview-reviewers,morgan,emilio,m_kato,cmartin
Implemented the inverted-colors media feature from Media Queries Level 5 for all platforms. Spec: https://drafts.csswg.org/mediaqueries-5/#inverted Platform specific implementations: - Windows: Checks system color filter setting, and if it is inverted (note: Windows does not live update due to having to read a reg key) - Mac: Checks dedicated inverted accessibility system setting - Android: Checks dedicated inverted system setting - Linux: No GTK API exposes anything like it so always none Locked behind new pref `layout.css.inverted-colors.enabled`, always off by default for now. Also added new WPT tests (none previously). Other browsers: - WebKit: shipped since Safari 9.1 (Jan 2017) - Blink: no signal Test page: https://goose.icu/inverted-colors Differential Revision: https://phabricator.services.mozilla.com/D173201
This commit is contained in:
Родитель
7beef23771
Коммит
2d520652e0
|
@ -636,6 +636,7 @@ mozilla::StylePrefersContrast Gecko_MediaFeatures_PrefersContrast(
|
|||
const mozilla::dom::Document*);
|
||||
mozilla::StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme(
|
||||
const mozilla::dom::Document*, bool aUseContent);
|
||||
bool Gecko_MediaFeatures_InvertedColors(const mozilla::dom::Document*);
|
||||
mozilla::StyleScripting Gecko_MediaFeatures_Scripting(
|
||||
const mozilla::dom::Document*);
|
||||
|
||||
|
|
|
@ -325,6 +325,13 @@ StylePrefersContrast Gecko_MediaFeatures_PrefersContrast(
|
|||
return StylePrefersContrast::Custom;
|
||||
}
|
||||
|
||||
bool Gecko_MediaFeatures_InvertedColors(const Document* aDocument) {
|
||||
if (aDocument->ShouldResistFingerprinting()) {
|
||||
return false;
|
||||
}
|
||||
return LookAndFeel::GetInt(LookAndFeel::IntID::InvertedColors, 0) == 1;
|
||||
}
|
||||
|
||||
StyleScripting Gecko_MediaFeatures_Scripting(const Document* aDocument) {
|
||||
const auto* doc = aDocument;
|
||||
if (aDocument->IsStaticDocument()) {
|
||||
|
|
|
@ -887,6 +887,16 @@ slot {
|
|||
display: contents;
|
||||
}
|
||||
|
||||
/* Un-invert images and videos for users using inverted colors.
|
||||
* "User agents must add the following rule to their UA style sheet"
|
||||
* https://www.w3.org/TR/mediaqueries-5/#inverted
|
||||
*/
|
||||
@media (inverted-colors) {
|
||||
img:not(picture > img), picture, video {
|
||||
filter: invert(100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide noscript elements if scripting is enabled */
|
||||
@media (scripting) {
|
||||
noscript {
|
||||
|
|
|
@ -59,6 +59,10 @@ public class GeckoSystemStateListener implements InputManager.InputDeviceListene
|
|||
};
|
||||
contentResolver.registerContentObserver(animationSetting, false, mContentObserver);
|
||||
|
||||
final Uri invertSetting =
|
||||
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
|
||||
contentResolver.registerContentObserver(invertSetting, false, mContentObserver);
|
||||
|
||||
mIsNightMode =
|
||||
(sApplicationContext.getResources().getConfiguration().uiMode
|
||||
& Configuration.UI_MODE_NIGHT_MASK)
|
||||
|
@ -106,6 +110,26 @@ public class GeckoSystemStateListener implements InputManager.InputDeviceListene
|
|||
== 0.0f;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
@WrapForJNI(calledFrom = "gecko")
|
||||
/**
|
||||
* For inverted-colors queries feature.
|
||||
*
|
||||
* <p>Uses `Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED` which was introduced in API
|
||||
* version 21.
|
||||
*/
|
||||
private static boolean isInvertedColors() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ContentResolver contentResolver = sApplicationContext.getContentResolver();
|
||||
|
||||
return Settings.Secure.getInt(
|
||||
contentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0)
|
||||
== 1;
|
||||
}
|
||||
|
||||
/** For prefers-color-scheme media queries feature. */
|
||||
public boolean isNightMode() {
|
||||
return mIsNightMode;
|
||||
|
|
|
@ -8821,6 +8821,13 @@
|
|||
mirror: always
|
||||
rust: true
|
||||
|
||||
# Dictates whether or not the inverted-colors media query is enabled.
|
||||
- name: layout.css.inverted-colors.enabled
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
mirror: always
|
||||
rust: true
|
||||
|
||||
# Is support for forced-color-adjust properties enabled?
|
||||
- name: layout.css.forced-color-adjust.enabled
|
||||
type: RelaxedAtomicBool
|
||||
|
|
|
@ -297,6 +297,35 @@ fn eval_forced_colors(context: &Context, query_value: Option<ForcedColors>) -> b
|
|||
}
|
||||
}
|
||||
|
||||
/// Possible values for the inverted-colors media query.
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#inverted
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||
#[repr(u8)]
|
||||
enum InvertedColors {
|
||||
/// Colors are displayed normally.
|
||||
None,
|
||||
/// All pixels within the displayed area have been inverted.
|
||||
Inverted,
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#inverted
|
||||
fn eval_inverted_colors(
|
||||
context: &Context,
|
||||
query_value: Option<InvertedColors>,
|
||||
) -> bool {
|
||||
let inverted_colors =
|
||||
unsafe { bindings::Gecko_MediaFeatures_InvertedColors(context.device().document()) };
|
||||
let query_value = match query_value {
|
||||
Some(v) => v,
|
||||
None => return inverted_colors,
|
||||
};
|
||||
|
||||
match query_value {
|
||||
InvertedColors::None => !inverted_colors,
|
||||
InvertedColors::Inverted => inverted_colors,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||
#[repr(u8)]
|
||||
enum OverflowBlock {
|
||||
|
@ -669,7 +698,7 @@ macro_rules! bool_pref_feature {
|
|||
/// to support new types in these entries and (2) ensuring that either
|
||||
/// nsPresContext::MediaFeatureValuesChanged is called when the value that
|
||||
/// would be returned by the evaluator function could change.
|
||||
pub static MEDIA_FEATURES: [QueryFeatureDescription; 66] = [
|
||||
pub static MEDIA_FEATURES: [QueryFeatureDescription; 67] = [
|
||||
feature!(
|
||||
atom!("width"),
|
||||
AllowsRanges::Yes,
|
||||
|
@ -816,6 +845,12 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 66] = [
|
|||
keyword_evaluator!(eval_forced_colors, ForcedColors),
|
||||
FeatureFlags::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("inverted-colors"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_inverted_colors, InvertedColors),
|
||||
FeatureFlags::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("overflow-block"),
|
||||
AllowsRanges::No,
|
||||
|
|
|
@ -329,6 +329,13 @@ fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
|
|||
return !context.in_ua_or_chrome_sheet() &&
|
||||
!static_prefs::pref!("layout.css.prefers-reduced-transparency.enabled");
|
||||
}
|
||||
|
||||
// inverted-colors is always enabled in the ua and chrome. On
|
||||
// the web it is hidden behind a preferenc.
|
||||
if *feature == atom!("inverted-colors") {
|
||||
return !context.in_ua_or_chrome_sheet() &&
|
||||
!static_prefs::pref!("layout.css.inverted-colors.enabled");
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[inverted-colors.html]
|
||||
prefs: [layout.css.inverted-colors.enabled:true]
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="help" href="https://drafts.csswg.org/mediaqueries-5/#inverted" />
|
||||
<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_known("(inverted-colors)");
|
||||
query_should_be_known("(inverted-colors: none)");
|
||||
query_should_be_known("(inverted-colors: inverted)");
|
||||
|
||||
query_should_be_unknown("(inverted-colors: 0)");
|
||||
query_should_be_unknown("(inverted-colors: no-preference)");
|
||||
query_should_be_unknown("(inverted-colors: 10px)");
|
||||
query_should_be_unknown("(inverted-colors: none inverted)");
|
||||
query_should_be_unknown("(inverted-colors: none/inverted)");
|
||||
|
||||
test(() => {
|
||||
// https://drafts.csswg.org/mediaqueries-5/#boolean-context
|
||||
let booleanContext = window.matchMedia("(inverted-colors)");
|
||||
let none = window.matchMedia("(inverted-colors: none)");
|
||||
assert_equals(booleanContext.matches, !none.matches);
|
||||
}, "Check that none evaluates to false in the boolean context");
|
||||
|
||||
test(() => {
|
||||
let invalid = window.matchMedia("(inverted-colors: 10px)");
|
||||
assert_equals(invalid.matches, false);
|
||||
}, "Check that invalid evaluates to false");
|
||||
</script>
|
|
@ -1,4 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<noscript>
|
||||
Script is disabled
|
||||
</noscript>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="help" href="https://drafts.csswg.org/mediaqueries/#scripting">
|
||||
<link rel="match" href="scripting-print-noscript-ref.html">
|
||||
<style>
|
||||
@media (scripting) {
|
||||
#noscript {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="noscript">
|
||||
Script is disabled
|
||||
</div>
|
|
@ -1,4 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<div>
|
||||
Script is enabled
|
||||
</div>
|
|
@ -1,17 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="help" href="https://drafts.csswg.org/mediaqueries/#scripting">
|
||||
<link rel="match" href="scripting-print-script-ref.html">
|
||||
<style>
|
||||
#script {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (scripting) {
|
||||
#script {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="script">
|
||||
Script is enabled
|
||||
</div>
|
|
@ -1,26 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="help" href="https://drafts.csswg.org/mediaqueries-5/#scripting">
|
||||
<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_known("(scripting)");
|
||||
query_should_be_known("(scripting: enabled)");
|
||||
query_should_be_known("(scripting: initial-only)");
|
||||
query_should_be_known("(scripting: none)");
|
||||
|
||||
query_should_be_unknown("(scripting: 0)");
|
||||
query_should_be_unknown("(scripting: 10px)");
|
||||
query_should_be_unknown("(scripting: invalid)");
|
||||
|
||||
test(() => {
|
||||
let match_enabled = window.matchMedia("(scripting: enabled)");
|
||||
assert_true(match_enabled.matches);
|
||||
}, "Check that scripting currently matches 'enabled'");
|
||||
|
||||
test(() => {
|
||||
let booleanContext = window.matchMedia("(scripting)");
|
||||
assert_true(booleanContext.matches);
|
||||
}, "Check that scripting currently evaluates to true in the boolean context");
|
||||
</script>
|
|
@ -302,6 +302,14 @@ class LookAndFeel {
|
|||
*/
|
||||
PrefersReducedTransparency,
|
||||
|
||||
/**
|
||||
* Corresponding to inverted-colors.
|
||||
* https://drafts.csswg.org/mediaqueries-5/#inverted
|
||||
* 0: none
|
||||
* 1: inverted
|
||||
*/
|
||||
InvertedColors,
|
||||
|
||||
/**
|
||||
* Corresponding to PointerCapabilities in ServoTypes.h
|
||||
* 0: None
|
||||
|
|
|
@ -350,6 +350,10 @@ nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
|
|||
aResult = 0;
|
||||
break;
|
||||
|
||||
case IntID::InvertedColors:
|
||||
aResult = java::GeckoSystemStateListener::IsInvertedColors();
|
||||
break;
|
||||
|
||||
case IntID::PrimaryPointerCapabilities:
|
||||
aResult = java::GeckoAppShell::GetAllPointerCapabilities();
|
||||
|
||||
|
|
|
@ -488,6 +488,9 @@ nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
|
|||
case IntID::PrefersReducedTransparency:
|
||||
aResult = NSWorkspace.sharedWorkspace.accessibilityDisplayShouldReduceTransparency;
|
||||
break;
|
||||
case IntID::InvertedColors:
|
||||
aResult = NSWorkspace.sharedWorkspace.accessibilityDisplayShouldInvertColors;
|
||||
break;
|
||||
case IntID::UseAccessibilityTheme:
|
||||
aResult = NSWorkspace.sharedWorkspace.accessibilityDisplayShouldIncreaseContrast;
|
||||
break;
|
||||
|
|
|
@ -952,6 +952,10 @@ nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
|
|||
EnsureInit();
|
||||
aResult = mSystemTheme.mHighContrast;
|
||||
break;
|
||||
case IntID::InvertedColors:
|
||||
// No GTK API for checking if inverted colors is enabled
|
||||
aResult = 0;
|
||||
break;
|
||||
case IntID::TitlebarRadius: {
|
||||
EnsureInit();
|
||||
aResult = EffectiveTheme().mTitlebarRadius;
|
||||
|
|
|
@ -168,6 +168,9 @@ nsresult HeadlessLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
|
|||
case IntID::PrefersReducedTransparency:
|
||||
aResult = 0;
|
||||
break;
|
||||
case IntID::InvertedColors:
|
||||
aResult = 0;
|
||||
break;
|
||||
case IntID::PrimaryPointerCapabilities:
|
||||
aResult = 0;
|
||||
break;
|
||||
|
|
|
@ -182,6 +182,7 @@ static const char sIntPrefs[][45] = {
|
|||
"ui.systemUsesDarkTheme",
|
||||
"ui.prefersReducedMotion",
|
||||
"ui.prefersReducedTransparency",
|
||||
"ui.invertedColors",
|
||||
"ui.primaryPointerCapabilities",
|
||||
"ui.allPointerCapabilities",
|
||||
"ui.systemScrollbarSize",
|
||||
|
|
|
@ -70,6 +70,42 @@ static nsresult SystemWantsDarkTheme(int32_t& darkThemeEnabled) {
|
|||
return rv;
|
||||
}
|
||||
|
||||
static int32_t SystemColorFilter() {
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIWindowsRegKey> colorFilteringKey =
|
||||
do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
rv = colorFilteringKey->Open(
|
||||
nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
|
||||
u"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Accessibility\\ATConfig\\colorfiltering"_ns,
|
||||
nsIWindowsRegKey::ACCESS_QUERY_VALUE);
|
||||
if (NS_FAILED(rv)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The Active value is set to 1 when the "Turn on color filters" setting
|
||||
// in the Color filters section of Windows' Ease of Access settings is turned
|
||||
// on. If it is disabled (Active == 0 or does not exist), do not report having
|
||||
// a color filter.
|
||||
uint32_t active;
|
||||
rv = colorFilteringKey->ReadIntValue(u"Active"_ns, &active);
|
||||
if (NS_FAILED(rv) || active == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The FilterType value is set to whichever filter is enabled.
|
||||
uint32_t filterType;
|
||||
rv = colorFilteringKey->ReadIntValue(u"FilterType"_ns, &filterType);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
return filterType;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
nsLookAndFeel::nsLookAndFeel() {
|
||||
mozilla::Telemetry::Accumulate(mozilla::Telemetry::TOUCH_ENABLED_DEVICE,
|
||||
WinUtils::IsTouchDeviceSupportPresent());
|
||||
|
@ -614,6 +650,15 @@ nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
|
|||
aResult = !WindowsUIUtils::ComputeTransparencyEffects();
|
||||
break;
|
||||
}
|
||||
case IntID::InvertedColors: {
|
||||
int32_t colorFilter = SystemColorFilter();
|
||||
|
||||
// Color filter values
|
||||
// 1: Inverted
|
||||
// 2: Grayscale inverted
|
||||
aResult = colorFilter == 1 || colorFilter == 2 ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
case IntID::PrimaryPointerCapabilities: {
|
||||
aResult = static_cast<int32_t>(
|
||||
widget::WinUtils::GetPrimaryPointerCapabilities());
|
||||
|
|
|
@ -464,6 +464,7 @@ STATIC_ATOMS = [
|
|||
Atom("_for", "for"),
|
||||
Atom("forEach", "for-each"),
|
||||
Atom("forcedColors", "forced-colors"),
|
||||
Atom("invertedColors", "inverted-colors"),
|
||||
Atom("forceOwnRefreshDriver", "forceOwnRefreshDriver"),
|
||||
Atom("form", "form"),
|
||||
Atom("formaction", "formaction"),
|
||||
|
|
Загрузка…
Ссылка в новой задаче