Skip to content
Permalink
Browse files
Web Inspector: Add emulation toggles for prefers-reduced-motion and p…
…refers-contrast

https://bugs.webkit.org/show_bug.cgi?id=246882

Reviewed by Patrick Angle.

This was previously reviewed in #5650 then reverted by f306cbb

Adds controls in Web Inspector to override preferences
for reduced motion and increased contrast for the inspected page.

These correspond to CSS media features `prefers-reduced-motion: reduce | no-preference`
and `prefers-contrast: more | no-preference`.

The UI in Web Inspector starts off with preference values set to `System`
meaning there is no override applied. A user can pick `On` or `Off` to
force a value for the preference on the inspected page for the duration of
the Web Inspector session.

When a preference is overriden in Web Inspector, changes after the fact to
corresponding settings in System Settings > Accessibility > Display
have no effect on the inspected page until Web Inspector is closed or
the preference values are returned to `System`.

* LayoutTests/inspector/page/overrideUserPreference-expected.txt: Added.
* LayoutTests/inspector/page/overrideUserPreference.html: Added.
* Source/JavaScriptCore/inspector/protocol/Page.json:
* Source/WebCore/inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::accessibilitySettingsDidChangeImpl):
* Source/WebCore/inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::accessibilitySettingsDidChange):
* Source/WebCore/inspector/agents/InspectorPageAgent.cpp:
(WebCore::InspectorPageAgent::enable):
(WebCore::InspectorPageAgent::disable):
(WebCore::InspectorPageAgent::overrideUserPreference):
(WebCore::InspectorPageAgent::overridePrefersReducedMotion):
(WebCore::InspectorPageAgent::overridePrefersContrast):
(WebCore::InspectorPageAgent::accessibilitySettingsDidChange):
(WebCore::InspectorPageAgent::defaultUserPreferencesDidChange):
* Source/WebCore/inspector/agents/InspectorPageAgent.h:
* Source/WebCore/page/Page.cpp:
(WebCore::Page::accessibilitySettingsDidChange):
* Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js:
* Source/WebInspectorUI/UserInterface/Controllers/CSSManager.js:
(WI.CSSManager):
(WI.CSSManager.prototype.get overridenUserPreferences):
(WI.CSSManager.prototype.get defaultUserPreferences):
(WI.CSSManager.prototype.overrideUserPreference):
(WI.CSSManager.prototype.defaultUserPreferencesDidChange):
* Source/WebInspectorUI/UserInterface/Images/AppearanceOverride.svg: Added.
* Source/WebInspectorUI/UserInterface/Main.html:
* Source/WebInspectorUI/UserInterface/Protocol/PageObserver.js:
(WI.PageObserver.prototype.defaultUserPreferencesDidChange):
* Source/WebInspectorUI/UserInterface/Views/DOMTreeContentView.js:
(WI.DOMTreeContentView):
(WI.DOMTreeContentView.prototype.get navigationItems):
(WI.DOMTreeContentView.prototype._handleOverrideUserPreferencesButtonClicked):
(WI.DOMTreeContentView.prototype._overridenUserPreferencesDidChange):
(WI.DOMTreeContentView.prototype.didDismissPopover):
* Source/WebInspectorUI/UserInterface/Views/OverrideUserPreferencesPopover.css: Added.
(.popover .user-preferences-content):
(.popover .user-preferences-content > h1):
(.popover .user-preferences-content > label):
(.popover .user-preferences-content > select):
* Source/WebInspectorUI/UserInterface/Views/OverrideUserPreferencesPopover.js: Added.
(WI.OverrideUserPreferencesPopover):
(WI.OverrideUserPreferencesPopover.prototype.show):
(WI.OverrideUserPreferencesPopover.prototype.dismiss):
(WI.OverrideUserPreferencesPopover.prototype._presentOverTargetElement):
(WI.OverrideUserPreferencesPopover.prototype._createSelectElement):
(WI.OverrideUserPreferencesPopover.prototype._createContentElement):
(WI.OverrideUserPreferencesPopover.prototype._userPreferenceValueLabel):
(WI.OverrideUserPreferencesPopover.prototype._defaultUserPreferencesDidChange):

Canonical link: https://commits.webkit.org/257383@main
  • Loading branch information
rcaliman-apple committed Dec 5, 2022
1 parent 0fdf996 commit 808a9c220b48e53a2a49a93b2560d33eb481d1bc
Show file tree
Hide file tree
Showing 16 changed files with 611 additions and 1 deletion.
@@ -0,0 +1,30 @@
Tests for the Page.overrideUserPreference command.


== Running test suite: Page.overrideUserPreference
-- Running test case: Page.overrideUserPreference.PrefersReducedMotion
PASS: (prefers-reduced-motion) media query does not match.
PASS: --test-prefers-reduced-motion: no-preference
Overriding PrefersReducedMotion value to Reduce
PASS: (prefers-reduced-motion) media query matches.
PASS: --test-prefers-reduced-motion: reduce
Overriding PrefersReducedMotion value to NoPreference
PASS: (prefers-reduced-motion) media query does not match.
PASS: --test-prefers-reduced-motion: no-preference
Removing PrefersReducedMotion override
PASS: (prefers-reduced-motion) media query does not match.
PASS: --test-prefers-reduced-motion: no-preference

-- Running test case: Page.overrideUserPreference.PrefersContrast
PASS: (prefers-contrast) media query does not match.
PASS: --test-prefers-contrast: no-preference
Overriding PrefersContrast value to More
PASS: (prefers-contrast) media query matches.
PASS: --test-prefers-contrast: more
Overriding PrefersContrast value to NoPreference
PASS: (prefers-contrast) media query does not match.
PASS: --test-prefers-contrast: no-preference
Removing PrefersContrast override
PASS: (prefers-contrast) media query does not match.
PASS: --test-prefers-contrast: no-preference

@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html>
<head>
<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
<script>

function test()
{
async function testOverridePreference({cssPropertyName, mediaQuery, testCases})
{
for (let {preferenceName, preferenceValue, expectedMatchMedia, expectedValue} of testCases) {
if (preferenceName && preferenceValue) {
InspectorTest.log(`Overriding ${preferenceName} value to ${preferenceValue}`);
await PageAgent.overrideUserPreference(preferenceName, preferenceValue);
} else if (preferenceName && !preferenceValue) {
InspectorTest.log(`Removing ${preferenceName} override`);
await PageAgent.overrideUserPreference(preferenceName);
}

let matches = await InspectorTest.evaluateInPage(`window.matchMedia("${mediaQuery}").matches`);
let value = await InspectorTest.evaluateInPage(`window.getComputedStyle(document.body).getPropertyValue("${cssPropertyName}")`);

if (expectedMatchMedia)
InspectorTest.expectTrue(matches, `${mediaQuery} media query matches.`);
else
InspectorTest.expectFalse(matches, `${mediaQuery} media query does not match.`);

InspectorTest.expectEqual(value, expectedValue, `${cssPropertyName}: ${expectedValue}`);
}
}

let suite = InspectorTest.createAsyncSuite("Page.overrideUserPreference");

suite.addTestCase({
name: "Page.overrideUserPreference.PrefersReducedMotion",
description: "",
async test() {
let cssPropertyName = "--test-prefers-reduced-motion";
let mediaQuery = "(prefers-reduced-motion)";
let testCases = [
{
expectedValue: "no-preference",
expectedMatchMedia: false,
},
{
preferenceName: InspectorBackend.Enum.Page.UserPreferenceName.PrefersReducedMotion,
preferenceValue: InspectorBackend.Enum.Page.UserPreferenceValue.Reduce,
expectedValue: "reduce",
expectedMatchMedia: true,
},
{
preferenceName: InspectorBackend.Enum.Page.UserPreferenceName.PrefersReducedMotion,
preferenceValue: InspectorBackend.Enum.Page.UserPreferenceValue.NoPreference,
expectedValue: "no-preference",
expectedMatchMedia: false,
},
{
preferenceName: InspectorBackend.Enum.Page.UserPreferenceName.PrefersReducedMotion,
preferenceValue: null,
expectedValue: "no-preference",
expectedMatchMedia: false,
},
];

await testOverridePreference({cssPropertyName, mediaQuery, testCases});
},
});

suite.addTestCase({
name: "Page.overrideUserPreference.PrefersContrast",
description: "",
async test() {
let cssPropertyName = "--test-prefers-contrast";
let mediaQuery = "(prefers-contrast)";
let testCases = [
{
expectedValue: "no-preference",
expectedMatchMedia: false,
},
{
preferenceName: InspectorBackend.Enum.Page.UserPreferenceName.PrefersContrast,
preferenceValue: InspectorBackend.Enum.Page.UserPreferenceValue.More,
expectedValue: "more",
expectedMatchMedia: true,
},
{
preferenceName: InspectorBackend.Enum.Page.UserPreferenceName.PrefersContrast,
preferenceValue: InspectorBackend.Enum.Page.UserPreferenceValue.NoPreference,
expectedValue: "no-preference",
expectedMatchMedia: false,
},
{
preferenceName: InspectorBackend.Enum.Page.UserPreferenceName.PrefersContrast,
preferenceValue: null,
expectedValue: "no-preference",
expectedMatchMedia: false,
},
];

await testOverridePreference({cssPropertyName, mediaQuery, testCases});
},
});

suite.runTestCasesAndFinish();
}

</script>
</head>
<body onload="runTest()">
<p>Tests for the Page.overrideUserPreference command.</p>

<style>
body {
--test-prefers-reduced-motion: no-preference;
--test-prefers-contrast: no-preference;
}

@media (prefers-reduced-motion) {
body {
--test-prefers-reduced-motion: reduce;
}
}

@media (prefers-contrast) {
body {
--test-prefers-contrast: more;
}
}
</style>
</body>
</html>
@@ -24,6 +24,27 @@
"WebSecurityEnabled"
]
},
{
"id": "UserPreference",
"type": "object",
"description": "A user preference that can be overriden by Web Inspector, like an accessibility preference.",
"properties": [
{ "name": "name", "$ref": "UserPreferenceName", "description": "Preference name." },
{ "name": "value", "$ref": "UserPreferenceValue", "description": "Preference value." }
]
},
{
"id": "UserPreferenceName",
"type": "string",
"enum": ["PrefersReducedMotion", "PrefersContrast"],
"description": "User preference name."
},
{
"id": "UserPreferenceValue",
"type": "string",
"enum": ["NoPreference", "Reduce", "More"],
"description": "User preference value."
},
{
"id": "ResourceType",
"type": "string",
@@ -156,6 +177,15 @@
{ "name": "value", "type": "boolean", "optional": true, "description": "Value to override the setting with. If this value is not provided, the override is removed. Overrides are removed when Web Inspector closes/disconnects." }
]
},
{
"name": "overrideUserPreference",
"description": "Allows the frontend to override the user's preferences on the inspected page.",
"targetTypes": ["page"],
"parameters": [
{ "name": "name", "$ref": "UserPreferenceName" },
{ "name": "value", "$ref": "UserPreferenceValue", "optional": true, "description": "Value to override the user preference with. If this value is not provided, the override is removed. Overrides are removed when Web Inspector closes/disconnects." }
]
},
{
"name": "getCookies",
"description": "Returns all browser cookies. Depending on the backend support, will return detailed cookie information in the <code>cookies</code> field.",
@@ -385,6 +415,14 @@
"parameters": [
{ "name": "appearance", "$ref": "Appearance", "description": "Name of the appearance that is active (not considering any forced appearance.)" }
]
},
{
"name": "defaultUserPreferencesDidChange",
"description": "Fired when the default value of a user preference changes at the system level.",
"targetTypes": ["page"],
"parameters": [
{ "name": "preferences", "type": "array", "items": { "$ref": "UserPreference" }, "description": "List of user preferences that can be overriden and their new system (default) values." }
]
}
]
}
@@ -844,6 +844,12 @@ void InspectorInstrumentation::frameClearedScheduledNavigationImpl(Instrumenting
inspectorPageAgent->frameClearedScheduledNavigation(frame);
}

void InspectorInstrumentation::accessibilitySettingsDidChangeImpl(InstrumentingAgents& instrumentingAgents)
{
if (auto* inspectorPageAgent = instrumentingAgents.enabledPageAgent())
inspectorPageAgent->accessibilitySettingsDidChange();
}

#if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT)
void InspectorInstrumentation::defaultAppearanceDidChangeImpl(InstrumentingAgents& instrumentingAgents, bool useDarkAppearance)
{
@@ -235,6 +235,7 @@ class InspectorInstrumentation {
static void frameStoppedLoading(Frame&);
static void frameScheduledNavigation(Frame&, Seconds delay);
static void frameClearedScheduledNavigation(Frame&);
static void accessibilitySettingsDidChange(Page&);
#if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT)
static void defaultAppearanceDidChange(Page&, bool useDarkAppearance);
#endif
@@ -445,6 +446,7 @@ class InspectorInstrumentation {
static void frameStoppedLoadingImpl(InstrumentingAgents&, Frame&);
static void frameScheduledNavigationImpl(InstrumentingAgents&, Frame&, Seconds delay);
static void frameClearedScheduledNavigationImpl(InstrumentingAgents&, Frame&);
static void accessibilitySettingsDidChangeImpl(InstrumentingAgents&);
#if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT)
static void defaultAppearanceDidChangeImpl(InstrumentingAgents&, bool useDarkAppearance);
#endif
@@ -1285,6 +1287,12 @@ inline void InspectorInstrumentation::frameClearedScheduledNavigation(Frame& fra
frameClearedScheduledNavigationImpl(*agents, frame);
}

inline void InspectorInstrumentation::accessibilitySettingsDidChange(Page& page)
{
FAST_RETURN_IF_NO_FRONTENDS(void());
accessibilitySettingsDidChangeImpl(instrumentingAgents(page));
}

#if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT)
inline void InspectorInstrumentation::defaultAppearanceDidChange(Page& page, bool useDarkAppearance)
{
@@ -40,6 +40,7 @@
#include "DocumentInlines.h"
#include "DocumentLoader.h"
#include "ElementInlines.h"
#include "ForcedAccessibilityValue.h"
#include "Frame.h"
#include "FrameLoadRequest.h"
#include "FrameLoader.h"
@@ -63,6 +64,7 @@
#include "SecurityOrigin.h"
#include "Settings.h"
#include "StyleScope.h"
#include "Theme.h"
#include <pal/text/TextEncoding.h>
#include "UserGestureIndicator.h"
#include <JavaScriptCore/ContentSearchUtilities.h>
@@ -359,6 +361,7 @@ Protocol::ErrorStringOr<void> InspectorPageAgent::enable()
#if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT)
defaultAppearanceDidChange(m_inspectedPage.defaultUseDarkAppearance());
#endif
defaultUserPreferencesDidChange();

return { };
}
@@ -389,6 +392,8 @@ Protocol::ErrorStringOr<void> InspectorPageAgent::disable()
inspectedPageSettings.setShowRepaintCounterInspectorOverride(std::nullopt);
inspectedPageSettings.setWebRTCEncryptionEnabledInspectorOverride(std::nullopt);
inspectedPageSettings.setWebSecurityEnabledInspectorOverride(std::nullopt);
inspectedPageSettings.setForcedPrefersReducedMotionAccessibilityValue(ForcedAccessibilityValue::System);
inspectedPageSettings.setForcedPrefersContrastAccessibilityValue(ForcedAccessibilityValue::System);

m_client->setDeveloperPreferenceOverride(InspectorClient::DeveloperPreference::PrivateClickMeasurementDebugModeEnabled, std::nullopt);
m_client->setDeveloperPreferenceOverride(InspectorClient::DeveloperPreference::ITPDebugModeEnabled, std::nullopt);
@@ -498,6 +503,48 @@ Protocol::ErrorStringOr<void> InspectorPageAgent::overrideSetting(Protocol::Page
return { };
}

Protocol::ErrorStringOr<void> InspectorPageAgent::overrideUserPreference(Protocol::Page::UserPreferenceName preference, std::optional<Protocol::Page::UserPreferenceValue>&& value)
{
switch (preference) {
case Protocol::Page::UserPreferenceName::PrefersReducedMotion:
overridePrefersReducedMotion(WTFMove(value));
return { };

case Protocol::Page::UserPreferenceName::PrefersContrast:
overridePrefersContrast(WTFMove(value));
return { };
}

ASSERT_NOT_REACHED();
return { };
}

void InspectorPageAgent::overridePrefersReducedMotion(std::optional<Protocol::Page::UserPreferenceValue>&& value)
{
ForcedAccessibilityValue forcedValue = ForcedAccessibilityValue::System;

if (value == Protocol::Page::UserPreferenceValue::Reduce)
forcedValue = ForcedAccessibilityValue::On;
else if (value == Protocol::Page::UserPreferenceValue::NoPreference)
forcedValue = ForcedAccessibilityValue::Off;

m_inspectedPage.settings().setForcedPrefersReducedMotionAccessibilityValue(forcedValue);
m_inspectedPage.accessibilitySettingsDidChange();
}

void InspectorPageAgent::overridePrefersContrast(std::optional<Protocol::Page::UserPreferenceValue>&& value)
{
ForcedAccessibilityValue forcedValue = ForcedAccessibilityValue::System;

if (value == Protocol::Page::UserPreferenceValue::More)
forcedValue = ForcedAccessibilityValue::On;
else if (value == Protocol::Page::UserPreferenceValue::NoPreference)
forcedValue = ForcedAccessibilityValue::Off;

m_inspectedPage.settings().setForcedPrefersContrastAccessibilityValue(forcedValue);
m_inspectedPage.accessibilitySettingsDidChange();
}

static Protocol::Page::CookieSameSitePolicy cookieSameSitePolicyJSON(Cookie::SameSitePolicy policy)
{
switch (policy) {
@@ -911,6 +958,44 @@ void InspectorPageAgent::frameClearedScheduledNavigation(Frame& frame)
m_frontendDispatcher->frameClearedScheduledNavigation(frameId(&frame));
}

void InspectorPageAgent::accessibilitySettingsDidChange()
{
defaultUserPreferencesDidChange();
}

void InspectorPageAgent::defaultUserPreferencesDidChange()
{
auto defaultUserPreferences = JSON::ArrayOf<Protocol::Page::UserPreference>::create();

#if USE(NEW_THEME)
bool prefersReducedMotion = Theme::singleton().userPrefersReducedMotion();
#else
bool prefersReducedMotion = false;
#endif

auto prefersReducedMotionUserPreference = Protocol::Page::UserPreference::create()
.setName(Protocol::Page::UserPreferenceName::PrefersReducedMotion)
.setValue(prefersReducedMotion ? Protocol::Page::UserPreferenceValue::Reduce : Protocol::Page::UserPreferenceValue::NoPreference)
.release();

defaultUserPreferences->addItem(prefersReducedMotionUserPreference);

#if USE(NEW_THEME)
bool prefersContrast = Theme::singleton().userPrefersContrast();
#else
bool prefersContrast = false;
#endif

auto prefersContrastUserPreference = Protocol::Page::UserPreference::create()
.setName(Protocol::Page::UserPreferenceName::PrefersContrast)
.setValue(prefersContrast ? Protocol::Page::UserPreferenceValue::More : Protocol::Page::UserPreferenceValue::NoPreference)
.release();

defaultUserPreferences->addItem(prefersContrastUserPreference);

m_frontendDispatcher->defaultUserPreferencesDidChange(WTFMove(defaultUserPreferences));
}

#if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT)
void InspectorPageAgent::defaultAppearanceDidChange(bool useDarkAppearance)
{

0 comments on commit 808a9c2

Please sign in to comment.