Commit bd145026 authored by Mathias Bynens's avatar Mathias Bynens Committed by Commit Bot

[DevTools] Support CSS media feature emulation in CDP

This patch makes the pre-existing `Emulation.setEmulatedMedia` API in
the Chrome DevTools Protocol support emulating CSS media features, in
addition to CSS media types (which it already supported).

The new functionality can be used to emulate `prefers-color-scheme`
(light/dark mode), `prefers-reduced-motion`, and any other media
features Chrome implements from now on.

Design doc: https://goo.gle/devtools-dark-mode

Bug: chromium:1004246
Change-Id: I62ade2fa978a78e90e00b47915101950ca735c62
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1821608Reviewed-by: default avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: default avatarYang Guo <yangguo@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Commit-Queue: Mathias Bynens <mathias@chromium.org>
Cr-Commit-Position: refs/heads/master@{#701084}
parent b5463988
...@@ -1858,7 +1858,8 @@ bool StyleEngine::SupportsDarkColorScheme() { ...@@ -1858,7 +1858,8 @@ bool StyleEngine::SupportsDarkColorScheme() {
void StyleEngine::UpdateColorScheme() { void StyleEngine::UpdateColorScheme() {
auto* settings = GetDocument().GetSettings(); auto* settings = GetDocument().GetSettings();
DCHECK(settings); if (!settings)
return;
ForcedColors old_forced_colors = forced_colors_; ForcedColors old_forced_colors = forced_colors_;
forced_colors_ = settings->GetForcedColors(); forced_colors_ = settings->GetForcedColors();
......
...@@ -2690,6 +2690,11 @@ domain Emulation ...@@ -2690,6 +2690,11 @@ domain Emulation
# Orientation angle. # Orientation angle.
integer angle integer angle
type MediaFeature extends object
properties
string name
string value
# advance: If the scheduler runs out of immediate work, the virtual time base may fast forward to # advance: If the scheduler runs out of immediate work, the virtual time base may fast forward to
# allow the next delayed task (if any) to run; pause: The virtual time base may not advance; # allow the next delayed task (if any) to run; pause: The virtual time base may not advance;
# pauseIfNetworkFetchesPending: The virtual time base may not advance if there are any pending # pauseIfNetworkFetchesPending: The virtual time base may not advance if there are any pending
...@@ -2786,11 +2791,13 @@ domain Emulation ...@@ -2786,11 +2791,13 @@ domain Emulation
mobile mobile
desktop desktop
# Emulates the given media for CSS media queries. # Emulates the given media type or media feature for CSS media queries.
command setEmulatedMedia command setEmulatedMedia
parameters parameters
# Media type to emulate. Empty string disables the override. # Media type to emulate. Empty string disables the override.
string media optional string media
# Media features to emulate.
optional array of MediaFeature features
# Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position # Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position
# unavailable. # unavailable.
......
...@@ -38,6 +38,7 @@ InspectorEmulationAgent::InspectorEmulationAgent( ...@@ -38,6 +38,7 @@ InspectorEmulationAgent::InspectorEmulationAgent(
touch_event_emulation_enabled_(&agent_state_, /*default_value=*/false), touch_event_emulation_enabled_(&agent_state_, /*default_value=*/false),
max_touch_points_(&agent_state_, /*default_value=*/1), max_touch_points_(&agent_state_, /*default_value=*/1),
emulated_media_(&agent_state_, /*default_value=*/WTF::String()), emulated_media_(&agent_state_, /*default_value=*/WTF::String()),
emulated_media_features_(&agent_state_, /*default_value=*/WTF::String()),
navigator_platform_override_(&agent_state_, navigator_platform_override_(&agent_state_,
/*default_value=*/WTF::String()), /*default_value=*/WTF::String()),
user_agent_override_(&agent_state_, /*default_value=*/WTF::String()), user_agent_override_(&agent_state_, /*default_value=*/WTF::String()),
...@@ -89,7 +90,16 @@ void InspectorEmulationAgent::Restore() { ...@@ -89,7 +90,16 @@ void InspectorEmulationAgent::Restore() {
GetWebViewImpl()->GetDevToolsEmulator()->SetDocumentCookieDisabled(true); GetWebViewImpl()->GetDevToolsEmulator()->SetDocumentCookieDisabled(true);
setTouchEmulationEnabled(touch_event_emulation_enabled_.Get(), setTouchEmulationEnabled(touch_event_emulation_enabled_.Get(),
max_touch_points_.Get()); max_touch_points_.Get());
setEmulatedMedia(emulated_media_.Get()); auto features =
std::make_unique<protocol::Array<protocol::Emulation::MediaFeature>>();
for (auto const& name : emulated_media_features_.Keys()) {
auto const& value = emulated_media_features_.Get(name);
features->push_back(std::move(protocol::Emulation::MediaFeature::create()
.setName(name)
.setValue(value)
.build()));
}
setEmulatedMedia(emulated_media_.Get(), std::move(features));
auto rgba = ParseRGBA(default_background_color_override_rgba_.Get()); auto rgba = ParseRGBA(default_background_color_override_rgba_.Get());
if (rgba) if (rgba)
setDefaultBackgroundColorOverride(std::move(rgba)); setDefaultBackgroundColorOverride(std::move(rgba));
...@@ -145,7 +155,12 @@ Response InspectorEmulationAgent::disable() { ...@@ -145,7 +155,12 @@ Response InspectorEmulationAgent::disable() {
setScrollbarsHidden(false); setScrollbarsHidden(false);
setDocumentCookieDisabled(false); setDocumentCookieDisabled(false);
setTouchEmulationEnabled(false, Maybe<int>()); setTouchEmulationEnabled(false, Maybe<int>());
setEmulatedMedia(String()); // Clear emulated media features. Note that the current approach
// doesn't work well in cases where two clients have the same set of
// features overridden to the same value by two different clients
// (e.g. if we allowed two different front-ends with the same
// settings to attach to the same page). TODO: support this use case.
setEmulatedMedia(String(), {});
setCPUThrottlingRate(1); setCPUThrottlingRate(1);
setFocusEmulationEnabled(false); setFocusEmulationEnabled(false);
setDefaultBackgroundColorOverride(Maybe<protocol::DOM::RGBA>()); setDefaultBackgroundColorOverride(Maybe<protocol::DOM::RGBA>());
...@@ -220,12 +235,35 @@ Response InspectorEmulationAgent::setTouchEmulationEnabled( ...@@ -220,12 +235,35 @@ Response InspectorEmulationAgent::setTouchEmulationEnabled(
return response; return response;
} }
Response InspectorEmulationAgent::setEmulatedMedia(const String& media) { Response InspectorEmulationAgent::setEmulatedMedia(
Maybe<String> media,
Maybe<protocol::Array<protocol::Emulation::MediaFeature>> features) {
Response response = AssertPage(); Response response = AssertPage();
if (!response.isSuccess()) if (!response.isSuccess())
return response; return response;
emulated_media_.Set(media); if (media.isJust()) {
GetWebViewImpl()->GetPage()->GetSettings().SetMediaTypeOverride(media); auto mediaValue = media.takeJust();
emulated_media_.Set(mediaValue);
GetWebViewImpl()->GetPage()->GetSettings().SetMediaTypeOverride(mediaValue);
} else {
emulated_media_.Set("");
GetWebViewImpl()->GetPage()->GetSettings().SetMediaTypeOverride("");
}
for (const WTF::String& feature : emulated_media_features_.Keys()) {
GetWebViewImpl()->GetPage()->SetMediaFeatureOverride(AtomicString(feature),
"");
}
emulated_media_features_.Clear();
if (features.isJust()) {
auto featuresValue = features.takeJust();
for (auto const& mediaFeature : *featuresValue.get()) {
auto const& name = mediaFeature->getName();
auto const& value = mediaFeature->getValue();
emulated_media_features_.Set(name, value);
GetWebViewImpl()->GetPage()->SetMediaFeatureOverride(AtomicString(name),
value);
}
}
return response; return response;
} }
......
...@@ -44,7 +44,10 @@ class CORE_EXPORT InspectorEmulationAgent final ...@@ -44,7 +44,10 @@ class CORE_EXPORT InspectorEmulationAgent final
protocol::Response setTouchEmulationEnabled( protocol::Response setTouchEmulationEnabled(
bool enabled, bool enabled,
protocol::Maybe<int> max_touch_points) override; protocol::Maybe<int> max_touch_points) override;
protocol::Response setEmulatedMedia(const String&) override; protocol::Response setEmulatedMedia(
protocol::Maybe<String> media,
protocol::Maybe<protocol::Array<protocol::Emulation::MediaFeature>>
features) override;
protocol::Response setCPUThrottlingRate(double) override; protocol::Response setCPUThrottlingRate(double) override;
protocol::Response setFocusEmulationEnabled(bool) override; protocol::Response setFocusEmulationEnabled(bool) override;
protocol::Response setVirtualTimePolicy( protocol::Response setVirtualTimePolicy(
...@@ -122,6 +125,7 @@ class CORE_EXPORT InspectorEmulationAgent final ...@@ -122,6 +125,7 @@ class CORE_EXPORT InspectorEmulationAgent final
InspectorAgentState::Boolean touch_event_emulation_enabled_; InspectorAgentState::Boolean touch_event_emulation_enabled_;
InspectorAgentState::Integer max_touch_points_; InspectorAgentState::Integer max_touch_points_;
InspectorAgentState::String emulated_media_; InspectorAgentState::String emulated_media_;
InspectorAgentState::StringMap emulated_media_features_;
InspectorAgentState::String navigator_platform_override_; InspectorAgentState::String navigator_platform_override_;
InspectorAgentState::String user_agent_override_; InspectorAgentState::String user_agent_override_;
InspectorAgentState::String accept_language_override_; InspectorAgentState::String accept_language_override_;
......
Tests that CSS media features can be overridden.
matchMedia("(prefers-color-scheme: __invalid__)").matches: false
matchMedia("(prefers-color-scheme: __invalid__)").matches applied: 2px x 2px
matchMedia("(prefers-color-scheme: no-preference)").matches: true
matchMedia("(prefers-color-scheme: no-preference)").matches applied: 2px x 2px
matchMedia("(prefers-color-scheme: light)").matches: true
matchMedia("(prefers-color-scheme: light)").matches applied: 3px x 2px
matchMedia("(prefers-color-scheme: dark)").matches: true
matchMedia("(prefers-color-scheme: dark)").matches applied: 4px x 2px
matchMedia("(prefers-color-scheme: __invalid__)").matches: false
matchMedia("(prefers-color-scheme: __invalid__)").matches applied: 2px x 2px
matchMedia("(prefers-reduced-motion: __invalid__)").matches: false
matchMedia("(prefers-reduced-motion: __invalid__)").matches applied: 2px x 2px
matchMedia("(prefers-reduced-motion: no-preference)").matches: true
matchMedia("(prefers-reduced-motion: no-preference)").matches applied: 2px x 2px
matchMedia("(prefers-reduced-motion: reduce)").matches: true
matchMedia("(prefers-reduced-motion: reduce)").matches applied: 2px x 3px
matchMedia("(prefers-reduced-motion: __invalid__)").matches: false
matchMedia("(prefers-reduced-motion: __invalid__)").matches applied: 2px x 2px
matchMedia("(prefers-color-scheme: dark) and (prefers-reduced-motion: reduce)").matches: true
matchMedia("(prefers-color-scheme: dark) and (prefers-reduced-motion: reduce)").matches applied: 999px x 999px
matchMedia("(prefers-color-scheme: __invalid__)").matches: false
matchMedia("(prefers-color-scheme: __invalid__)").matches applied: 2px x 2px
(async function(testRunner) {
const {page, session, dp} = await testRunner.startBlank(
'Tests that CSS media features can be overridden.');
await session.navigate('../resources/css-media-features.html');
async function setEmulatedMediaFeature(feature, value) {
await dp.Emulation.setEmulatedMedia({
features: [
{
name: feature,
value: value,
},
],
});
const mediaQuery = `(${feature}: ${value})`;
const code = `matchMedia(${JSON.stringify(mediaQuery)}).matches`;
const result = await session.evaluate(code);
testRunner.log(`${code}: ${result}`);
const width = await session.evaluate('getComputedStyle(p).width');
const height = await session.evaluate('getComputedStyle(p).height');
testRunner.log(`${code} applied: ${width} x ${height}`);
}
async function setEmulatedMediaFeatures({ features, mediaQuery }) {
await dp.Emulation.setEmulatedMedia({
features,
});
const code = `matchMedia(${JSON.stringify(mediaQuery)}).matches`;
const result = await session.evaluate(code);
testRunner.log(`${code}: ${result}`);
const width = await session.evaluate('getComputedStyle(p).width');
const height = await session.evaluate('getComputedStyle(p).height');
testRunner.log(`${code} applied: ${width} x ${height}`);
}
// Test `prefers-color-scheme`.
// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
await setEmulatedMediaFeature('prefers-color-scheme', '__invalid__');
await setEmulatedMediaFeature('prefers-color-scheme', 'no-preference');
await setEmulatedMediaFeature('prefers-color-scheme', 'light');
await setEmulatedMediaFeature('prefers-color-scheme', 'dark');
await setEmulatedMediaFeature('prefers-color-scheme', '__invalid__');
// Test `prefers-reduced-motion`.
// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
await setEmulatedMediaFeature('prefers-reduced-motion', '__invalid__');
await setEmulatedMediaFeature('prefers-reduced-motion', 'no-preference');
await setEmulatedMediaFeature('prefers-reduced-motion', 'reduce');
await setEmulatedMediaFeature('prefers-reduced-motion', '__invalid__');
// Test combinations.
await setEmulatedMediaFeatures({
features: [
{ name: 'prefers-color-scheme', value: 'dark' },
{ name: 'prefers-reduced-motion', value: 'reduce' },
],
mediaQuery: '(prefers-color-scheme: dark) and (prefers-reduced-motion: reduce)',
});
await setEmulatedMediaFeatures({
features: [
{ name: 'prefers-color-scheme', value: '__invalid__' },
],
mediaQuery: '(prefers-color-scheme: __invalid__)',
});
testRunner.completeTest();
});
<!DOCTYPE html>
<style>
p { width: 1px; height: 1px; }
@media (prefers-color-scheme: no-preference) { p { width: 2px; } }
@media (prefers-color-scheme: light) { p { width: 3px; } }
@media (prefers-color-scheme: dark) { p { width: 4px; } }
@media (prefers-reduced-motion: no-preference) { p { height: 2px; } }
@media (prefers-reduced-motion: reduce) { p { height: 3px; } }
@media (prefers-color-scheme: dark) and (prefers-reduced-motion: reduce) { p { width: 999px; height: 999px; } }
</style>
<p id="p">
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