Commit e3017a7b authored by Mike Jackson's avatar Mike Jackson Committed by Commit Bot

dpwas: Support display_override in webapp manifest

This change adds support for the display_override property to the
webapp manifest. This change proposes that we reuse the existing
DisplayMode enum. It adds the field to manifest.mojom, as well
as parsing of the field.

This change proposes that we ignore unknown entries, and that
the overrides are stored in order.

Explainer: https://github.com/WICG/display-override/blob/master/explainer.m
Design document: https://docs.google.com/document/d/1hEmbGVHMN38q1YTaaGccQ-Y5CHr7xIURYPRWXTuvZLo/edit?usp=sharing
I2P: https://groups.google.com/a/chromium.org/d/topic/blink-dev/WvIeZT8uSzw/discussion

Bug: 1092667
Change-Id: I772abbadf00f32cad2fbd62280ebcee7b4616702
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2274170
Commit-Queue: Mike Jackson <mjackson@microsoft.com>
Reviewed-by: default avatarAlan Cutter <alancutter@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarMatt Giuca <mgiuca@chromium.org>
Reviewed-by: default avatarAlexey Baskakov <loyso@chromium.org>
Reviewed-by: default avatarDaniel Murphy <dmurph@chromium.org>
Cr-Commit-Position: refs/heads/master@{#788625}
parent 88b1d193
...@@ -152,6 +152,9 @@ void UpdateWebAppInfoFromManifest(const blink::Manifest& manifest, ...@@ -152,6 +152,9 @@ void UpdateWebAppInfoFromManifest(const blink::Manifest& manifest,
if (manifest.display != DisplayMode::kUndefined) if (manifest.display != DisplayMode::kUndefined)
web_app_info->display_mode = manifest.display; web_app_info->display_mode = manifest.display;
if (!manifest.display_override.empty())
web_app_info->display_override = manifest.display_override;
// Create the WebApplicationInfo icons list *outside* of |web_app_info|, so // Create the WebApplicationInfo icons list *outside* of |web_app_info|, so
// that we can decide later whether or not to replace the existing icons array // that we can decide later whether or not to replace the existing icons array
// (conditionally on whether there were any that didn't have purpose ANY). // (conditionally on whether there were any that didn't have purpose ANY).
......
...@@ -112,6 +112,7 @@ TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest) { ...@@ -112,6 +112,7 @@ TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest) {
EXPECT_EQ(AppUrl(), web_app_info.app_url); EXPECT_EQ(AppUrl(), web_app_info.app_url);
EXPECT_EQ(AppUrl().GetWithoutFilename(), web_app_info.scope); EXPECT_EQ(AppUrl().GetWithoutFilename(), web_app_info.scope);
EXPECT_EQ(DisplayMode::kBrowser, web_app_info.display_mode); EXPECT_EQ(DisplayMode::kBrowser, web_app_info.display_mode);
EXPECT_TRUE(web_app_info.display_override.empty());
// The icon info from |web_app_info| should be left as is, since the manifest // The icon info from |web_app_info| should be left as is, since the manifest
// doesn't have any icon information. // doesn't have any icon information.
...@@ -133,10 +134,15 @@ TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest) { ...@@ -133,10 +134,15 @@ TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest) {
// Add an icon without purpose ANY (expect to be ignored). // Add an icon without purpose ANY (expect to be ignored).
icon.purpose = {blink::Manifest::ImageResource::Purpose::MONOCHROME}; icon.purpose = {blink::Manifest::ImageResource::Purpose::MONOCHROME};
manifest.icons.push_back(icon); manifest.icons.push_back(icon);
manifest.display_override.push_back(DisplayMode::kMinimalUi);
manifest.display_override.push_back(DisplayMode::kStandalone);
UpdateWebAppInfoFromManifest(manifest, &web_app_info); UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_EQ(base::UTF8ToUTF16(kAppTitle), web_app_info.title); EXPECT_EQ(base::UTF8ToUTF16(kAppTitle), web_app_info.title);
EXPECT_EQ(DisplayMode::kMinimalUi, web_app_info.display_mode); EXPECT_EQ(DisplayMode::kMinimalUi, web_app_info.display_mode);
ASSERT_EQ(2u, web_app_info.display_override.size());
EXPECT_EQ(DisplayMode::kMinimalUi, web_app_info.display_override[0]);
EXPECT_EQ(DisplayMode::kStandalone, web_app_info.display_override[1]);
EXPECT_EQ(2u, web_app_info.icon_infos.size()); EXPECT_EQ(2u, web_app_info.icon_infos.size());
EXPECT_EQ(AppIcon2(), web_app_info.icon_infos[0].url); EXPECT_EQ(AppIcon2(), web_app_info.icon_infos[0].url);
......
...@@ -124,6 +124,9 @@ struct WebApplicationInfo { ...@@ -124,6 +124,9 @@ struct WebApplicationInfo {
// https://w3c.github.io/manifest/#display-modes // https://w3c.github.io/manifest/#display-modes
blink::mojom::DisplayMode display_mode = blink::mojom::DisplayMode::kBrowser; blink::mojom::DisplayMode display_mode = blink::mojom::DisplayMode::kBrowser;
// App preference to control display fallback ordering
std::vector<blink::mojom::DisplayMode> display_override;
// User preference as to whether the app should be opened in a window. // User preference as to whether the app should be opened in a window.
// If false, the app will be opened in a tab. // If false, the app will be opened in a tab.
// If true, the app will be opened in a window, with minimal-ui buttons // If true, the app will be opened in a window, with minimal-ui buttons
......
...@@ -42,6 +42,7 @@ Manifest::~Manifest() = default; ...@@ -42,6 +42,7 @@ Manifest::~Manifest() = default;
bool Manifest::IsEmpty() const { bool Manifest::IsEmpty() const {
return name.is_null() && short_name.is_null() && start_url.is_empty() && return name.is_null() && short_name.is_null() && start_url.is_empty() &&
display == blink::mojom::DisplayMode::kUndefined && display == blink::mojom::DisplayMode::kUndefined &&
display_override.empty() &&
orientation == device::mojom::ScreenOrientationLockType::DEFAULT && orientation == device::mojom::ScreenOrientationLockType::DEFAULT &&
icons.empty() && shortcuts.empty() && !share_target.has_value() && icons.empty() && shortcuts.empty() && !share_target.has_value() &&
related_applications.empty() && file_handlers.empty() && related_applications.empty() && file_handlers.empty() &&
......
...@@ -91,6 +91,9 @@ bool StructTraits<blink::mojom::ManifestDataView, ::blink::Manifest>::Read( ...@@ -91,6 +91,9 @@ bool StructTraits<blink::mojom::ManifestDataView, ::blink::Manifest>::Read(
if (!data.ReadDisplay(&out->display)) if (!data.ReadDisplay(&out->display))
return false; return false;
if (!data.ReadDisplayOverride(&out->display_override))
return false;
if (!data.ReadOrientation(&out->orientation)) if (!data.ReadOrientation(&out->orientation))
return false; return false;
......
...@@ -173,6 +173,10 @@ struct BLINK_COMMON_EXPORT Manifest { ...@@ -173,6 +173,10 @@ struct BLINK_COMMON_EXPORT Manifest {
// present. // present.
blink::mojom::DisplayMode display = blink::mojom::DisplayMode::kUndefined; blink::mojom::DisplayMode display = blink::mojom::DisplayMode::kUndefined;
// Empty if the parsing failed, the field was not present, or all the
// values inside the JSON array were invalid.
std::vector<blink::mojom::DisplayMode> display_override;
// Set to device::mojom::ScreenOrientationLockType::DEFAULT if the parsing // Set to device::mojom::ScreenOrientationLockType::DEFAULT if the parsing
// failed or the field was not present. // failed or the field was not present.
device::mojom::ScreenOrientationLockType orientation = device::mojom::ScreenOrientationLockType orientation =
......
...@@ -71,6 +71,11 @@ struct BLINK_COMMON_EXPORT ...@@ -71,6 +71,11 @@ struct BLINK_COMMON_EXPORT
return manifest.display; return manifest.display;
} }
static const std::vector<blink::mojom::DisplayMode> display_override(
const ::blink::Manifest& manifest) {
return manifest.display_override;
}
static device::mojom::ScreenOrientationLockType orientation( static device::mojom::ScreenOrientationLockType orientation(
const ::blink::Manifest& manifest) { const ::blink::Manifest& manifest) {
return manifest.orientation; return manifest.orientation;
......
...@@ -24,6 +24,8 @@ struct Manifest { ...@@ -24,6 +24,8 @@ struct Manifest {
DisplayMode display; DisplayMode display;
array<DisplayMode> display_override;
device.mojom.ScreenOrientationLockType orientation; device.mojom.ScreenOrientationLockType orientation;
array<ManifestImageResource> icons; array<ManifestImageResource> icons;
......
...@@ -82,6 +82,7 @@ void ManifestParser::Parse() { ...@@ -82,6 +82,7 @@ void ManifestParser::Parse() {
manifest_->start_url = ParseStartURL(root_object.get()); manifest_->start_url = ParseStartURL(root_object.get());
manifest_->scope = ParseScope(root_object.get(), manifest_->start_url); manifest_->scope = ParseScope(root_object.get(), manifest_->start_url);
manifest_->display = ParseDisplay(root_object.get()); manifest_->display = ParseDisplay(root_object.get());
manifest_->display_override = ParseDisplayOverride(root_object.get());
manifest_->orientation = ParseOrientation(root_object.get()); manifest_->orientation = ParseOrientation(root_object.get());
manifest_->icons = ParseIcons(root_object.get()); manifest_->icons = ParseIcons(root_object.get());
...@@ -306,6 +307,39 @@ blink::mojom::DisplayMode ManifestParser::ParseDisplay( ...@@ -306,6 +307,39 @@ blink::mojom::DisplayMode ManifestParser::ParseDisplay(
return display_enum; return display_enum;
} }
Vector<mojom::blink::DisplayMode> ManifestParser::ParseDisplayOverride(
const JSONObject* object) {
Vector<mojom::blink::DisplayMode> display_override;
if (!RuntimeEnabledFeatures::DisplayOverrideEnabled())
return display_override;
JSONValue* json_value = object->Get("display_override");
if (!json_value)
return display_override;
JSONArray* display_override_list = object->GetArray("display_override");
if (!display_override_list) {
AddErrorInfo("property 'display_override' ignored, type array expected.");
return display_override;
}
for (wtf_size_t i = 0; i < display_override_list->size(); ++i) {
String display_enum_string;
// AsString will return an empty string if a type error occurs,
// which will cause DisplayModeFromString to return kUndefined,
// resulting in this entry being ignored.
display_override_list->at(i)->AsString(&display_enum_string);
display_enum_string = display_enum_string.StripWhiteSpace();
mojom::blink::DisplayMode display_enum =
DisplayModeFromString(display_enum_string.Utf8());
if (display_enum != mojom::blink::DisplayMode::kUndefined)
display_override.push_back(display_enum);
}
return display_override;
}
device::mojom::blink::ScreenOrientationLockType device::mojom::blink::ScreenOrientationLockType
ManifestParser::ParseOrientation(const JSONObject* object) { ManifestParser::ParseOrientation(const JSONObject* object) {
base::Optional<String> orientation = ParseString(object, "orientation", Trim); base::Optional<String> orientation = ParseString(object, "orientation", Trim);
......
...@@ -131,6 +131,13 @@ class MODULES_EXPORT ManifestParser { ...@@ -131,6 +131,13 @@ class MODULES_EXPORT ManifestParser {
// parsing failed. // parsing failed.
blink::mojom::DisplayMode ParseDisplay(const JSONObject* object); blink::mojom::DisplayMode ParseDisplay(const JSONObject* object);
// Parses the 'display_override' field of the manifest.
// https://github.com/WICG/display-override/blob/master/explainer.md
// Returns a vector of the parsed DisplayMode if any, an empty vector if
// the field was not present or empty.
Vector<mojom::blink::DisplayMode> ParseDisplayOverride(
const JSONObject* object);
// Parses the 'orientation' field of the manifest, as defined in: // Parses the 'orientation' field of the manifest, as defined in:
// https://w3c.github.io/manifest/#dfn-steps-for-processing-the-orientation-member // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-orientation-member
// Returns the parsed device::mojom::blink::ScreenOrientationLockType if any, // Returns the parsed device::mojom::blink::ScreenOrientationLockType if any,
......
...@@ -507,7 +507,7 @@ TEST_F(ManifestParserTest, DisplayParseRules) { ...@@ -507,7 +507,7 @@ TEST_F(ManifestParserTest, DisplayParseRules) {
EXPECT_EQ(0u, GetErrorCount()); EXPECT_EQ(0u, GetErrorCount());
} }
// Accept 'fullscreen'. // Accept 'standalone'.
{ {
auto& manifest = ParseManifest("{ \"display\": \"standalone\" }"); auto& manifest = ParseManifest("{ \"display\": \"standalone\" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kStandalone); EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kStandalone);
...@@ -536,6 +536,133 @@ TEST_F(ManifestParserTest, DisplayParseRules) { ...@@ -536,6 +536,133 @@ TEST_F(ManifestParserTest, DisplayParseRules) {
} }
} }
TEST_F(ManifestParserTest, DisplayOverrideParseRules) {
// Smoke test: if no display_override, no value.
{
auto& manifest = ParseManifest("{ \"display_override\": [] }");
EXPECT_TRUE(manifest->display_override.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if not array, value will be ignored
{
auto& manifest = ParseManifest("{ \"display_override\": 23 }");
EXPECT_TRUE(manifest->display_override.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'display_override' ignored, type array expected.",
errors()[0]);
}
// Smoke test: if array value is not a string, it will be ignored
{
auto& manifest = ParseManifest("{ \"display_override\": [ 23 ] }");
EXPECT_TRUE(manifest->display_override.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if array value is not not recognized, it will be ignored
{
auto& manifest = ParseManifest("{ \"display_override\": [ \"test\" ] }");
EXPECT_TRUE(manifest->display_override.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Case insensitive
{
auto& manifest = ParseManifest("{ \"display_override\": [ \"BROWSER\" ] }");
EXPECT_FALSE(manifest->display_override.IsEmpty());
EXPECT_EQ(manifest->display_override[0],
blink::mojom::DisplayMode::kBrowser);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespace
{
auto& manifest =
ParseManifest("{ \"display_override\": [ \" browser \" ] }");
EXPECT_FALSE(manifest->display_override.IsEmpty());
EXPECT_EQ(manifest->display_override[0],
blink::mojom::DisplayMode::kBrowser);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'browser'
{
auto& manifest = ParseManifest("{ \"display_override\": [ \"browser\" ] }");
EXPECT_FALSE(manifest->display_override.IsEmpty());
EXPECT_EQ(manifest->display_override[0],
blink::mojom::DisplayMode::kBrowser);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'browser', 'minimal-ui'
{
auto& manifest = ParseManifest(
"{ \"display_override\": [ \"browser\", \"minimal-ui\" ] }");
EXPECT_FALSE(manifest->display_override.IsEmpty());
EXPECT_EQ(manifest->display_override[0],
blink::mojom::DisplayMode::kBrowser);
EXPECT_EQ(manifest->display_override[1],
blink::mojom::DisplayMode::kMinimalUi);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// if array value is not not recognized, it will be ignored
// Accept 'browser', 'minimal-ui'
{
auto& manifest = ParseManifest(
"{ \"display_override\": [ 3, \"browser\", \"invalid-display\", "
"\"minimal-ui\" ] }");
EXPECT_FALSE(manifest->display_override.IsEmpty());
EXPECT_EQ(manifest->display_override[0],
blink::mojom::DisplayMode::kBrowser);
EXPECT_EQ(manifest->display_override[1],
blink::mojom::DisplayMode::kMinimalUi);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// validate both display and display-override fields are parsed
// if array value is not not recognized, it will be ignored
// Accept 'browser', 'minimal-ui', 'standalone'
{
auto& manifest = ParseManifest(
"{ \"display\": \"standalone\", \"display_override\": [ \"browser\", "
"\"minimal-ui\", \"standalone\" ] }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kStandalone);
EXPECT_EQ(0u, GetErrorCount());
EXPECT_FALSE(manifest->display_override.IsEmpty());
EXPECT_EQ(manifest->display_override[0],
blink::mojom::DisplayMode::kBrowser);
EXPECT_EQ(manifest->display_override[1],
blink::mojom::DisplayMode::kMinimalUi);
EXPECT_EQ(manifest->display_override[2],
blink::mojom::DisplayMode::kStandalone);
EXPECT_FALSE(IsManifestEmpty(manifest));
}
// validate duplicate entries.
// Accept 'browser', 'minimal-ui', 'browser'
{
auto& manifest = ParseManifest(
"{ \"display_override\": [ \"browser\", \"minimal-ui\", "
"\"browser\" ] }");
EXPECT_FALSE(manifest->display_override.IsEmpty());
EXPECT_EQ(manifest->display_override[0],
blink::mojom::DisplayMode::kBrowser);
EXPECT_EQ(manifest->display_override[1],
blink::mojom::DisplayMode::kMinimalUi);
EXPECT_EQ(manifest->display_override[2],
blink::mojom::DisplayMode::kBrowser);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, OrientationParseRules) { TEST_F(ManifestParserTest, OrientationParseRules) {
// Smoke test. // Smoke test.
{ {
......
...@@ -647,6 +647,10 @@ ...@@ -647,6 +647,10 @@
name: "DisplayCutoutAPI", name: "DisplayCutoutAPI",
settable_from_internals: true, settable_from_internals: true,
}, },
{
name: "DisplayOverride",
status: "test",
},
{ {
name: "DocumentCookie", name: "DocumentCookie",
}, },
......
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