Skip to content

Commit

Permalink
Dark mode manifest parsing.
Browse files Browse the repository at this point in the history
Parses the user_preferences field from the manifest and sets
dark_mode_theme_color and dark_mode_background_color in
WebApplicationInfo.

This is based off the proposal in this comment:
w3c/manifest#975 (comment)
There is no explainer yet.

This is the same format as the translations member.

Intent to prototype:
https://groups.google.com/a/chromium.org/g/blink-dev/c/Y6zNtG0f-6A

Bug: 1271804
Change-Id: I9764f300159d35041323d4a1602e06ba7f80afd8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3292905
Commit-Queue: Louise Brett <[email protected]>
Reviewed-by: Kentaro Hara <[email protected]>
Reviewed-by: Dominick Ng <[email protected]>
Cr-Commit-Position: refs/heads/main@{#945184}
NOKEYCHECK=True
GitOrigin-RevId: b076313fcbacd0be2f2b663f7d16083a19555e07
  • Loading branch information
loubrett authored and copybara-github committed Nov 25, 2021
1 parent 8663402 commit 1572deb
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 0 deletions.
5 changes: 5 additions & 0 deletions blink/common/features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,11 @@ const base::Feature kLogUnexpectedIPCPostedToBackForwardCachedDocuments{
"LogUnexpectedIPCPostedToBackForwardCachedDocuments",
base::FEATURE_ENABLED_BY_DEFAULT};

// Enables web apps to provide theme color and background color overrides for
// dark mode.
const base::Feature kWebAppEnableDarkMode{"WebAppEnableDarkMode",
base::FEATURE_DISABLED_BY_DEFAULT};

// Enables the "handle_links" manifest field for web apps.
// Explainer:
// https://github.com/WICG/pwa-url-handler/blob/main/handle_links/explainer.md
Expand Down
2 changes: 2 additions & 0 deletions blink/public/common/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@ BLINK_COMMON_EXPORT extern const base::Feature kPreferCompositingToLCDText;
BLINK_COMMON_EXPORT extern const base::Feature
kLogUnexpectedIPCPostedToBackForwardCachedDocuments;

BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableDarkMode;

BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableHandleLinks;

BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableIsolatedStorage;
Expand Down
20 changes: 20 additions & 0 deletions blink/public/mojom/manifest/manifest.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ struct Manifest {
// https://github.com/WICG/manifest-incubations/blob/gh-pages/translations-explainer.md
// A mapping of locales to their translations.
map<mojo_base.mojom.String16, ManifestTranslationItem> translations;

// TODO(crbug.com/1271804): This field is non-standard and part of a Chrome
// experiment. See:
// https://github.com/w3c/manifest/issues/975#issuecomment-960222756
ManifestUserPreferences? user_preferences;
};

// Structure representing a Shortcut Item per the Manifest specification, see:
Expand Down Expand Up @@ -290,6 +295,21 @@ struct ManifestTranslationItem {
mojo_base.mojom.String16? description;
};

// Structure representing overrides for certain user preferences.
struct ManifestUserPreferences {
ManifestUserPreferenceOverrides? color_scheme_dark;
};

// Structure representing manifest fields that can be overridden given certain
// user preferences (eg. dark mode). All fields are optional.
struct ManifestUserPreferenceOverrides {
bool has_theme_color;
uint32 theme_color;

bool has_background_color;
uint32 background_color;
};

// Debug information for a parsed manifest.
struct ManifestDebugInfo {
array<ManifestError> errors;
Expand Down
58 changes: 58 additions & 0 deletions blink/renderer/modules/manifest/manifest_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ bool ManifestParser::Parse() {
manifest_->translations = ParseTranslations(root_object.get());
}

if (RuntimeEnabledFeatures::WebAppDarkModeEnabled(feature_context_)) {
manifest_->user_preferences = ParseUserPreferences(root_object.get());
}

ManifestUmaUtil::ParseSucceeded(manifest_);

return has_comments;
Expand Down Expand Up @@ -1547,6 +1551,60 @@ ManifestParser::ParseTranslations(const JSONObject* object) {
return result;
}

mojom::blink::ManifestUserPreferenceOverridesPtr
ManifestParser::ParsePreferenceOverrides(const JSONObject* object,
const String& preference) {
auto user_preference_overrides =
mojom::blink::ManifestUserPreferenceOverrides::New();

if (!object->Get(preference))
return nullptr;

JSONObject* overrides = object->GetJSONObject(preference);
if (!overrides) {
AddErrorInfo("preference '" + preference + "' ignored, object expected.");
return nullptr;
}

absl::optional<RGBA32> theme_color = ParseThemeColor(overrides);
user_preference_overrides->has_theme_color = theme_color.has_value();
if (user_preference_overrides->has_theme_color)
user_preference_overrides->theme_color = *theme_color;

absl::optional<RGBA32> background_color = ParseBackgroundColor(overrides);
user_preference_overrides->has_background_color =
background_color.has_value();
if (user_preference_overrides->has_background_color)
user_preference_overrides->background_color = *background_color;

// All of the fields that can be overridden by user_preferences are
// optional. If no overrides are supplied, skip the preference.
if (!user_preference_overrides->has_theme_color &&
!user_preference_overrides->has_background_color) {
return nullptr;
}
return user_preference_overrides;
}

mojom::blink::ManifestUserPreferencesPtr ManifestParser::ParseUserPreferences(
const JSONObject* object) {
auto result = mojom::blink::ManifestUserPreferences::New();

if (!object->Get("user_preferences"))
return nullptr;

JSONObject* user_preferences_map = object->GetJSONObject("user_preferences");
if (!user_preferences_map) {
AddErrorInfo("property 'user_preferences' ignored, object expected.");
return nullptr;
}

result->color_scheme_dark =
ParsePreferenceOverrides(user_preferences_map, "color_scheme_dark");

return result;
}

void ManifestParser::AddErrorInfo(const String& error_msg,
bool critical,
int error_line,
Expand Down
12 changes: 12 additions & 0 deletions blink/renderer/modules/manifest/manifest_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,18 @@ class MODULES_EXPORT ManifestParser {
HashMap<String, mojom::blink::ManifestTranslationItemPtr> ParseTranslations(
const JSONObject* object);

// Parses individual preferences from the 'user_preferences' field.
// Returns nullptr if not present or parsing failed.
mojom::blink::ManifestUserPreferenceOverridesPtr ParsePreferenceOverrides(
const JSONObject* object,
const String& preference);

// Parse the 'user_preferences' field of the manifest as defined in:
// https://github.com/w3c/manifest/issues/975#issuecomment-960222756
// Returns nullptr if parsing fails.
mojom::blink::ManifestUserPreferencesPtr ParseUserPreferences(
const JSONObject* object);

void AddErrorInfo(const String& error_msg,
bool critical = false,
int error_line = 0,
Expand Down
97 changes: 97 additions & 0 deletions blink/renderer/modules/manifest/manifest_parser_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5117,4 +5117,101 @@ TEST_F(ManifestParserTest, TranslationsStringsParseRules) {
}
}

TEST_F(ManifestParserTest, UserPreferencesParseRules) {
{
ScopedWebAppDarkModeForTest feature(false);

// Feature not enabled, should not be parsed.
auto& manifest = ParseManifest(
R"({ "user_preferences":
{"color_scheme_dark": {"theme_color": "#FF0000"}} })");
EXPECT_TRUE(manifest->user_preferences.is_null());
EXPECT_EQ(0u, GetErrorCount());
}
{
ScopedWebAppDarkModeForTest feature(true);

// Manifest does not contain a 'user_preferences' field.
{
auto& manifest = ParseManifest(R"({ })");
EXPECT_TRUE(manifest->user_preferences.is_null());
EXPECT_EQ(0u, GetErrorCount());
}

// user_preferences object is empty.
{
auto& manifest = ParseManifest(R"({ "user_preferences": {} })");
EXPECT_FALSE(manifest->user_preferences.is_null());
EXPECT_TRUE(manifest->user_preferences->color_scheme_dark.is_null());
EXPECT_EQ(0u, GetErrorCount());
}

// Empty preference is ignored.
{
auto& manifest =
ParseManifest(R"({ "user_preferences": {"color_scheme_dark": {}} })");
EXPECT_FALSE(manifest->user_preferences.is_null());
EXPECT_TRUE(manifest->user_preferences->color_scheme_dark.is_null());
EXPECT_EQ(0u, GetErrorCount());
}

// Valid theme_color and background_color should be parsed
{
auto& manifest = ParseManifest(
R"({ "user_preferences": {"color_scheme_dark":
{"theme_color": "#FF0000", "background_color": "#FFF"}} })");
EXPECT_FALSE(manifest->user_preferences.is_null());
EXPECT_FALSE(manifest->user_preferences->color_scheme_dark.is_null());
EXPECT_EQ(manifest->user_preferences->color_scheme_dark->theme_color,
0xFFFF0000u);
EXPECT_EQ(manifest->user_preferences->color_scheme_dark->background_color,
0xFFFFFFFFu);
EXPECT_EQ(0u, GetErrorCount());
}

// Don't parse if the property isn't an object.
{
auto& manifest = ParseManifest(R"({ "user_preferences": [] })");
EXPECT_TRUE(manifest->user_preferences.is_null());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'user_preferences' ignored, object expected.",
errors()[0]);
}

// Ignore preference if it isn't an object.
{
auto& manifest =
ParseManifest(R"({ "user_preferences": {"color_scheme_dark": []} })");
EXPECT_FALSE(manifest->user_preferences.is_null());
EXPECT_TRUE(manifest->user_preferences->color_scheme_dark.is_null());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("preference 'color_scheme_dark' ignored, object expected.",
errors()[0]);
}

// Preferences overriding a single value should be parsed.
{
auto& manifest = ParseManifest(
R"({ "user_preferences":
{"color_scheme_dark": {"theme_color": "#FF0000"}} })");
EXPECT_FALSE(manifest->user_preferences.is_null());
EXPECT_FALSE(manifest->user_preferences->color_scheme_dark.is_null());
EXPECT_EQ(manifest->user_preferences->color_scheme_dark->theme_color,
0xFFFF0000u);
EXPECT_FALSE(
manifest->user_preferences->color_scheme_dark->has_background_color);
EXPECT_EQ(0u, GetErrorCount());
}

// Unknown preference string should be ignored.
{
auto& manifest = ParseManifest(
R"({ "user_preferences": {"something": {"theme_color": "#FF0000"}} })");
EXPECT_FALSE(manifest->user_preferences.is_null());
EXPECT_TRUE(manifest->user_preferences->color_scheme_dark.is_null());
EXPECT_EQ(0u, GetErrorCount());
}
}
}

} // namespace blink
4 changes: 4 additions & 0 deletions blink/renderer/platform/runtime_enabled_features.json5
Original file line number Diff line number Diff line change
Expand Up @@ -2406,6 +2406,10 @@
name: "WebAnimationsSVG",
status: "experimental",
},
{
name: "WebAppDarkMode",
status: "experimental",
},
{
name: "WebAppHandleLinks",
status: "experimental",
Expand Down

0 comments on commit 1572deb

Please sign in to comment.