0

DPWA: parse manifest.id to web_app.manifest_id

Allow specifying "id" in web app manifest and parse it as a literal
string to WebApp.manifest_id. After that, existing code takes care
of generating WebApp.app_id from hash of
{start_url_origin}/{manifest_id}. When id is not specified in the
manifest, falls back to use start_url for app id.

Bug: 1215724
Change-Id: Ia8c201e1f23e26d43e6e2e3c1c9cd8416ea3d4ae
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2935478
Commit-Queue: Phillis Tang <phillis@chromium.org>
Reviewed-by: Dominick Ng <dominickn@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Reviewed-by: Daniel Murphy <dmurph@chromium.org>
Cr-Commit-Position: refs/heads/master@{#894369}
This commit is contained in:
Phillis Tang
2021-06-21 20:15:40 +00:00
committed by Chromium LUCI CQ
parent 35343463ad
commit c6b38394f1
35 changed files with 360 additions and 11 deletions

@ -3510,6 +3510,10 @@ const FeatureEntry kFeatureEntries[] = {
flag_descriptions::kDesktopPWAsLinkCapturingName,
flag_descriptions::kDesktopPWAsLinkCapturingDescription, kOsDesktop,
FEATURE_VALUE_TYPE(blink::features::kWebAppEnableLinkCapturing)},
{"enable-desktop-pwas-manifest-id",
flag_descriptions::kDesktopPWAsManifestIdName,
flag_descriptions::kDesktopPWAsManifestIdDescription, kOsDesktop,
FEATURE_VALUE_TYPE(blink::features::kWebAppEnableManifestId)},
{"enable-desktop-pwas-run-on-os-login",
flag_descriptions::kDesktopPWAsRunOnOsLoginName,
flag_descriptions::kDesktopPWAsRunOnOsLoginDescription,

@ -1670,6 +1670,11 @@
"owners": [ "alancutter@chromium.org", "desktop-pwas-team@google.com" ],
"expiry_milestone": 96
},
{
"name": "enable-desktop-pwas-manifest-id",
"owners": [ "phillis@chromium.org", "desktop-pwas-team@google.com" ],
"expiry_milestone": 98
},
{
"name": "enable-desktop-pwas-notification-icon-and-title",
"owners": [ "loyso@chromium.org", "desktop-pwas-team@google.com" ],

@ -774,6 +774,12 @@ const char kDesktopPWAsLinkCapturingDescription[] =
"https://github.com/WICG/sw-launch/blob/master/"
"declarative_link_capturing.md";
const char kDesktopPWAsManifestIdName[] = "Desktop PWA manifest id";
const char kDesktopPWAsManifestIdDescription[] =
"Enable web app manifests to declare id. Prototype "
"implementation of: "
"https://github.com/philloooo/pwa-unique-id/blob/main/explainer.md";
const char kDesktopPWAsTabStripName[] = "Desktop PWA tab strips";
const char kDesktopPWAsTabStripDescription[] =
"Experimental UI for exploring what PWA windows would look like with a tab "

@ -465,6 +465,9 @@ extern const char kDesktopPWAsNotificationIconAndTitleDescription[];
extern const char kDesktopPWAsLinkCapturingName[];
extern const char kDesktopPWAsLinkCapturingDescription[];
extern const char kDesktopPWAsManifestIdName[];
extern const char kDesktopPWAsManifestIdDescription[];
extern const char kDesktopPWAsTabStripName[];
extern const char kDesktopPWAsTabStripDescription[];

@ -69,6 +69,7 @@
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/clipboard_buffer.h"
@ -1359,4 +1360,42 @@ IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_NoDestroyProfile, Shutdown) {
EXPECT_EQ(web_contents, nullptr);
}
class WebAppBrowserTest_ManifestId : public WebAppBrowserTest {
public:
WebAppBrowserTest_ManifestId() = default;
private:
base::test::ScopedFeatureList scoped_feature_list_{
blink::features::kWebAppEnableManifestId};
};
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_ManifestId, NoManifestId) {
NavigateToURLAndWait(browser(), GetInstallableAppURL());
const AppId app_id = InstallPwaForCurrentUrl();
auto* provider = WebAppProviderBase::GetProviderBase(profile());
auto* app = provider->registrar().AsWebAppRegistrar()->GetAppById(app_id);
EXPECT_EQ(web_app::GenerateAppIdFromURL(
provider->registrar().GetAppStartUrl(app_id)),
app_id);
EXPECT_EQ(app->start_url().spec().substr(
app->start_url().GetOrigin().spec().size()),
app->manifest_id());
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_ManifestId, ManifestIdSpecified) {
NavigateAndAwaitInstallabilityCheck(
browser(),
https_server()->GetURL(
"/banners/manifest_test_page.html?manifest=manifest_with_id.json"));
const AppId app_id = InstallPwaForCurrentUrl();
auto* provider = WebAppProviderBase::GetProviderBase(profile());
auto* app = provider->registrar().AsWebAppRegistrar()->GetAppById(app_id);
EXPECT_EQ(web_app::GenerateAppId(app->manifest_id(), app->start_url()),
app_id);
EXPECT_NE(web_app::GenerateAppIdFromURL(app->start_url()), app_id);
}
} // namespace web_app

@ -109,6 +109,8 @@ class AppRegistrar {
virtual absl::optional<SkColor> GetAppBackgroundColor(
const AppId& app_id) const = 0;
virtual const GURL& GetAppStartUrl(const AppId& app_id) const = 0;
virtual absl::optional<std::string> GetAppManifestId(
const AppId& app_id) const = 0;
virtual const std::string* GetAppLaunchQueryParams(
const AppId& app_id) const = 0;
virtual const apps::ShareTarget* GetAppShareTarget(

@ -54,18 +54,24 @@ AppId GenerateAppIdFromURL(const GURL& url) {
return crx_file::id_util::GenerateId(GenerateAppHashFromURL(url));
}
AppId GenerateAppId(const absl::optional<std::string>& manifest_id,
const GURL& start_url) {
std::string GenerateAppIdUnhashed(
const absl::optional<std::string>& manifest_id,
const GURL& start_url) {
// When manifest_id is specified, the app id is generated from
// <start_url_origin>/<manifest_id>.
// Note: start_url.GetOrigin().spec() returns the origin ending with slash.
if (manifest_id.has_value()) {
GURL app_id(start_url.GetOrigin().spec() + manifest_id.value());
DCHECK(app_id.is_valid());
return crx_file::id_util::GenerateId(
crypto::SHA256HashString(app_id.spec()));
return app_id.spec();
}
return GenerateAppIdFromURL(start_url);
return start_url.spec();
}
AppId GenerateAppId(const absl::optional<std::string>& manifest_id,
const GURL& start_url) {
return crx_file::id_util::GenerateId(
crypto::SHA256HashString(GenerateAppIdUnhashed(manifest_id, start_url)));
}
// Generate the public key for the fake extension that we synthesize to contain

@ -42,6 +42,9 @@ AppId GetAppIdFromApplicationName(const std::string& app_name);
//
// App ID and App Key match Extension ID and Extension Key for migration.
AppId GenerateAppIdFromURL(const GURL& url);
std::string GenerateAppIdUnhashed(
const absl::optional<std::string>& manifest_id,
const GURL& start_url);
AppId GenerateAppId(const absl::optional<std::string>& manifest_id,
const GURL& start_url);

@ -213,6 +213,11 @@ void UpdateWebAppInfoFromManifest(const blink::Manifest& manifest,
else if (manifest.short_name)
web_app_info->title = *manifest.short_name;
if (manifest.id.has_value()) {
web_app_info->manifest_id =
absl::optional<std::string>(base::UTF16ToUTF8(manifest.id.value()));
}
// Set the url based on the manifest value, if any.
if (manifest.start_url.is_valid())
web_app_info->start_url = manifest.start_url;

@ -3003,4 +3003,105 @@ IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithWebAppNoteTaking,
EXPECT_TRUE(web_app->note_taking_new_note_url().is_empty());
}
class ManifestUpdateManagerBrowserTest_ManifestId
: public ManifestUpdateManagerBrowserTest {
public:
ManifestUpdateManagerBrowserTest_ManifestId() {
scoped_feature_list_.InitAndEnableFeature(
blink::features::kWebAppEnableManifestId);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_ManifestId,
AllowStartUrlUpdate) {
constexpr char kManifestTemplate[] = R"(
{
"name": "Test app name",
"start_url": "$1",
"scope": "/",
"display": "minimal-ui",
"id": "test",
"icons": $2
}
)";
OverrideManifest(kManifestTemplate, {"/startA", kInstallableIconList});
AppId app_id = InstallWebApp();
EXPECT_EQ(GetProvider().registrar().GetAppStartUrl(app_id).path(), "/startA");
OverrideManifest(kManifestTemplate, {"/startB", kInstallableIconList});
EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
ManifestUpdateResult::kAppUpdated);
EXPECT_EQ(GetProvider().registrar().GetAppStartUrl(app_id).path(), "/startB");
histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
ManifestUpdateResult::kAppUpdated, 1);
}
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_ManifestId,
CheckIgnoresIdChange) {
constexpr char kManifestTemplate[] = R"(
{
"name": "Test app name",
"id": "$1",
"start_url": "start",
"scope": "/",
"display": "standalone",
"icons": $2
}
)";
OverrideManifest(kManifestTemplate, {"test", kInstallableIconList});
AppId app_id = InstallWebApp();
OverrideManifest(kManifestTemplate, {"testb", kInstallableIconList});
EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
ManifestUpdateResult::kAppIdMismatch);
histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
ManifestUpdateResult::kAppIdMismatch, 1);
}
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_ManifestId,
ChecksSettingIdMatchDefault) {
constexpr char kManifestTemplate[] = R"(
{
"name": "Test app name",
"start_url": "/start",
"scope": "/",
"display": "standalone",
"icons": $1
}
)";
OverrideManifest(kManifestTemplate, {kInstallableIconList});
AppId app_id = InstallWebApp();
// manifest_id should default to start_url when it's not provided in manifest.
EXPECT_EQ(GetProvider()
.registrar()
.AsWebAppRegistrar()
->GetAppById(app_id)
->manifest_id()
.value(),
"start");
constexpr char kManifestTemplate2[] = R"(
{
"name": "Test app name",
"id": "$1",
"start_url": "/start",
"scope": "/",
"display": "standalone",
"icons": $2
}
)";
// Setting manifest id to match default value won't trigger update as the
// parsed manifest is the same.
OverrideManifest(kManifestTemplate2, {"start", kInstallableIconList});
EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
ManifestUpdateResult::kAppUpToDate);
histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
ManifestUpdateResult::kAppUpToDate, 1);
}
} // namespace web_app

@ -257,7 +257,8 @@ void ManifestUpdateTask::OnDidGetInstallableData(
// additionally the new app ID would get added to the sync profile. This has
// the potential to flood the user sync profile with an infinite number of
// apps should the site be serving a random start_url on every navigation.
if (app_id_ != GenerateAppIdFromURL(web_application_info_->start_url)) {
if (app_id_ != GenerateAppId(web_application_info_->manifest_id,
web_application_info_->start_url)) {
DestroySelf(ManifestUpdateResult::kAppIdMismatch);
return;
}
@ -275,6 +276,20 @@ bool ManifestUpdateTask::IsUpdateNeededForManifest() const {
const WebApp* app = registrar_.AsWebAppRegistrar()->GetAppById(app_id_);
DCHECK(app);
// Allows updating start_url and manifest_id when kWebAppEnableManifestId is
// enabled. Both fields are allowed to change as long as the app_id generated
// from them doesn't change.
if (base::FeatureList::IsEnabled(blink::features::kWebAppEnableManifestId)) {
if (web_application_info_->manifest_id !=
registrar_.GetAppManifestId(app_id_)) {
return true;
}
if (web_application_info_->start_url !=
registrar_.GetAppStartUrl(app_id_)) {
return true;
}
}
if (web_application_info_->theme_color !=
registrar_.GetAppThemeColor(app_id_))
return true;

@ -128,6 +128,12 @@ const GURL& TestAppRegistrar::GetAppStartUrl(const AppId& app_id) const {
return iterator->second.launch_url;
}
absl::optional<std::string> TestAppRegistrar::GetAppManifestId(
const AppId& app_id) const {
NOTIMPLEMENTED();
return absl::nullopt;
}
const std::string* TestAppRegistrar::GetAppLaunchQueryParams(
const AppId& app_id) const {
return nullptr;

@ -65,6 +65,8 @@ class TestAppRegistrar : public AppRegistrar {
absl::optional<SkColor> GetAppBackgroundColor(
const AppId& app_id) const override;
const GURL& GetAppStartUrl(const AppId& app_id) const override;
absl::optional<std::string> GetAppManifestId(
const AppId& app_id) const override;
const std::string* GetAppLaunchQueryParams(
const AppId& app_id) const override;
const apps::ShareTarget* GetAppShareTarget(

@ -14,6 +14,7 @@
#include "base/strings/string_util.h"
#include "chrome/browser/web_applications/components/web_app_chromeos_data.h"
#include "chrome/browser/web_applications/components/web_app_constants.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/browser/web_applications/components/web_app_utils.h"
#include "components/sync/base/time.h"
#include "third_party/blink/public/common/manifest/manifest_util.h"
@ -368,6 +369,9 @@ std::ostream& operator<<(std::ostream& out,
std::ostream& operator<<(std::ostream& out, const WebApp& app) {
out << "app_id: " << app.app_id_ << std::endl;
out << "unhashed app_id: "
<< GenerateAppIdUnhashed(app.manifest_id_, app.start_url_) << std::endl;
out << "manifest_url: " << app.manifest_url_ << std::endl;
out << "manifest_id: " << app.manifest_id_ << std::endl;

@ -134,14 +134,20 @@ void WebAppInstallFinalizer::FinalizeInstall(
// A web app might be sync installed with id received from WebAppSpecifics
// that's different from start_url hash, in this case we look up the app by
// start_url and respect the app_id from the existing WebApp.
if (!existing_web_app)
if (!base::FeatureList::IsEnabled(blink::features::kWebAppEnableManifestId) &&
!existing_web_app) {
existing_web_app =
GetWebAppRegistrar().GetAppByStartUrl(web_app_info.start_url);
}
std::unique_ptr<WebApp> web_app;
if (existing_web_app) {
app_id = existing_web_app->app_id();
// Prepare copy-on-write:
DCHECK_EQ(web_app_info.start_url, existing_web_app->start_url());
// Allows changing manifest_id and start_url when manifest_id is enabled.
if (!base::FeatureList::IsEnabled(
blink::features::kWebAppEnableManifestId)) {
DCHECK_EQ(web_app_info.start_url, existing_web_app->start_url());
}
web_app = std::make_unique<WebApp>(*existing_web_app);
// The UI may initiate a full install to overwrite the existing
@ -315,7 +321,7 @@ void WebAppInstallFinalizer::FinalizeUpdate(
const WebApp* existing_web_app = GetWebAppRegistrar().GetAppById(app_id);
if (!existing_web_app || existing_web_app->is_in_sync_install() ||
web_app_info.start_url != existing_web_app->start_url()) {
app_id != existing_web_app->app_id()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), AppId(),
InstallResultCode::kWebAppDisabled));

@ -24,6 +24,7 @@
#include "components/services/app_service/public/cpp/protocol_handler_info.h"
#include "components/services/app_service/public/cpp/share_target.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/manifest/manifest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
@ -116,6 +117,10 @@ void SetWebAppManifestFields(const WebApplicationInfo& web_app_info,
DCHECK(!web_app_info.title.empty());
web_app.SetName(base::UTF16ToUTF8(web_app_info.title));
if (base::FeatureList::IsEnabled(blink::features::kWebAppEnableManifestId)) {
web_app.SetStartUrl(web_app_info.start_url);
web_app.SetManifestId(web_app_info.manifest_id);
}
web_app.SetDisplayMode(web_app_info.display_mode);
web_app.SetDisplayModeOverride(web_app_info.display_override);

@ -139,6 +139,12 @@ const GURL& WebAppRegistrar::GetAppStartUrl(const AppId& app_id) const {
return web_app ? web_app->start_url() : GURL::EmptyGURL();
}
absl::optional<std::string> WebAppRegistrar::GetAppManifestId(
const AppId& app_id) const {
auto* web_app = GetAppById(app_id);
return web_app ? web_app->manifest_id() : absl::nullopt;
}
const std::string* WebAppRegistrar::GetAppLaunchQueryParams(
const AppId& app_id) const {
auto* web_app = GetAppById(app_id);

@ -63,6 +63,8 @@ class WebAppRegistrar : public AppRegistrar, public ProfileManagerObserver {
absl::optional<SkColor> GetAppBackgroundColor(
const AppId& app_id) const override;
const GURL& GetAppStartUrl(const AppId& app_id) const override;
absl::optional<std::string> GetAppManifestId(
const AppId& app_id) const override;
const std::string* GetAppLaunchQueryParams(
const AppId& app_id) const override;
const apps::ShareTarget* GetAppShareTarget(

@ -157,6 +157,7 @@ TEST(WebAppTest, EmptyAppToDebugString) {
WebAppToPlatformAgnosticString(std::make_unique<WebApp>("empty_app"));
EXPECT_EQ(debug_string,
R"(app_id: empty_app
unhashed app_id:
manifest_url:
manifest_id: nullopt
name:
@ -217,6 +218,7 @@ TEST(WebAppTest, SampleAppToDebugString) {
test::CreateRandomWebApp(GURL("https://example.com/"), /*seed=*/1234));
EXPECT_EQ(debug_string,
R"(app_id: eajjdjobhihlgobdfaehiiheinneagde
unhashed app_id: https://example.com/scope1234/start1234
manifest_url: https://example.com/manifest1234.json
manifest_id: nullopt
name: Name1234

@ -0,0 +1,39 @@
{
"name": "Manifest test app with id specified",
"id": "testid",
"icons": [
{
"src": "launcher-icon-1x.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "launcher-icon-1-5x.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "launcher-icon-2x.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any monochrome"
},
{
"src": "launcher-icon-3x.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "launcher-icon-4x.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "image-512px.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "start",
"display": "fullscreen"
}

@ -264,6 +264,7 @@ void SetRuntimeFeaturesFromChromiumFeatures() {
kSetOnlyIfOverridden},
{wf::EnableParseUrlProtocolHandler,
blink::features::kWebAppEnableProtocolHandlers},
{wf::EnableWebAppManifestId, blink::features::kWebAppEnableManifestId},
{wf::EnablePaymentApp, features::kServiceWorkerPaymentApps},
{wf::EnablePaymentHandlerMinimalUI, features::kWebPaymentsMinimalUI},
{wf::EnablePaymentRequest, features::kWebPayments},

@ -844,6 +844,12 @@ const base::Feature kWebAppEnableIsolatedStorage{
const base::Feature kWebAppEnableLinkCapturing{
"WebAppEnableLinkCapturing", base::FEATURE_DISABLED_BY_DEFAULT};
// Enables Unique ID feature in web apps. Controls parsing of "id" field in web
// app manifests. See explainer for more information:
// https://github.com/philloooo/pwa-unique-id
const base::Feature kWebAppEnableManifestId{"WebAppEnableManifestId",
base::FEATURE_DISABLED_BY_DEFAULT};
// Controls URL handling feature in web apps. Controls parsing of "url_handlers"
// field in web app manifests. See explainer for more information:
// https://github.com/WICG/pwa-url-handler/blob/master/explainer.md

@ -40,7 +40,7 @@ Manifest::Manifest(const Manifest& other) = default;
Manifest::~Manifest() = default;
bool Manifest::IsEmpty() const {
return !name && !short_name && start_url.is_empty() &&
return !name && !short_name && !id && start_url.is_empty() &&
display == blink::mojom::DisplayMode::kUndefined &&
display_override.empty() &&
orientation == device::mojom::ScreenOrientationLockType::DEFAULT &&

@ -86,6 +86,10 @@ bool StructTraits<blink::mojom::ManifestDataView, ::blink::Manifest>::Read(
return false;
out->description = std::move(string.string);
if (!data.ReadId(&string))
return false;
out->id = std::move(string.string);
if (!data.ReadGcmSenderId(&string))
return false;
out->gcm_sender_id = std::move(string.string);

@ -339,6 +339,8 @@ BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableIsolatedStorage;
BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableLinkCapturing;
BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableManifestId;
BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableUrlHandlers;
BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableProtocolHandlers;

@ -164,6 +164,10 @@ struct BLINK_COMMON_EXPORT Manifest {
// Null if the parsing failed or the field was not present.
absl::optional<std::u16string> description;
// Null if the start_url parsing failed or missing, otherwise defaults to
// start_url with origin stripped when id field is not present.
absl::optional<std::u16string> id;
// Empty if the parsing failed or the field was not present.
GURL start_url;

@ -60,6 +60,11 @@ struct BLINK_COMMON_EXPORT
return internal::TruncateOptionalString16(manifest.description);
}
static absl::optional<base::StringPiece16> id(
const ::blink::Manifest& manifest) {
return internal::TruncateOptionalString16(manifest.id);
}
static absl::optional<base::StringPiece16> gcm_sender_id(
const ::blink::Manifest& manifest) {
return internal::TruncateOptionalString16(manifest.gcm_sender_id);

@ -24,6 +24,8 @@ struct Manifest {
mojo_base.mojom.String16? description;
mojo_base.mojom.String16? id;
url.mojom.Url start_url;
DisplayMode display;

@ -167,6 +167,7 @@ class WebRuntimeFeatures {
BLINK_PLATFORM_EXPORT static void EnableUserActivationSameOriginVisibility(
bool);
BLINK_PLATFORM_EXPORT static void EnableV8IdleTasks(bool);
BLINK_PLATFORM_EXPORT static void EnableWebAppManifestId(bool);
BLINK_PLATFORM_EXPORT static void EnableWebAuth(bool);
BLINK_PLATFORM_EXPORT static void EnableWebBluetooth(bool);
BLINK_PLATFORM_EXPORT static void

@ -124,6 +124,7 @@ void ManifestParser::Parse() {
manifest_->short_name = ParseShortName(root_object.get());
manifest_->description = ParseDescription(root_object.get());
manifest_->start_url = ParseStartURL(root_object.get());
manifest_->id = ParseId(root_object.get(), manifest_->start_url);
manifest_->scope = ParseScope(root_object.get(), manifest_->start_url);
manifest_->display = ParseDisplay(root_object.get());
manifest_->display_override = ParseDisplayOverride(root_object.get());
@ -331,6 +332,20 @@ String ManifestParser::ParseDescription(const JSONObject* object) {
return description.has_value() ? *description : String();
}
String ManifestParser::ParseId(const JSONObject* object,
const KURL& start_url) {
if (!start_url.IsValid() ||
!base::FeatureList::IsEnabled(blink::features::kWebAppEnableManifestId)) {
return String();
}
absl::optional<String> id = ParseString(object, "id", NoTrim);
// Default to start_url with origin stripped.
return id.has_value()
? *id
: start_url.GetString().Substring(start_url.PathStart() + 1);
}
KURL ManifestParser::ParseStartURL(const JSONObject* object) {
return ParseURL(object, "start_url", manifest_url_,
ParseURLRestrictions::kSameOriginOnly);

@ -120,6 +120,9 @@ class MODULES_EXPORT ManifestParser {
// Returns the parsed string if any, a null string if the parsing failed.
String ParseDescription(const JSONObject* object);
// Parses the 'id' field of the manifest.
String ParseId(const JSONObject* object, const KURL& start_url);
// Parses the 'scope' field of the manifest, as defined in:
// https://w3c.github.io/manifest/#scope-member. Returns the parsed KURL if
// any, or start URL (falling back to document URL) without filename, path,

@ -116,7 +116,7 @@ TEST_F(ManifestParserTest, ValidNoContentParses) {
TEST_F(ManifestParserTest, MultipleErrorsReporting) {
auto& manifest = ParseManifest(
"{ \"name\": 42, \"short_name\": 4,"
"{ \"name\": 42, \"short_name\": 4, \"id\": 12,"
"\"orientation\": {}, \"display\": \"foo\","
"\"start_url\": null, \"icons\": {}, \"theme_color\": 42,"
"\"background_color\": 42, \"shortcuts\": {} }");
@ -259,6 +259,37 @@ TEST_F(ManifestParserTest, ShortNameParseRules) {
}
}
TEST_F(ManifestParserTest, IdParseRules) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(blink::features::kWebAppEnableManifestId);
// Empty manifest.
{
auto& manifest = ParseManifest("{ }");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ(String(), manifest->id);
}
// Does not contain id field.
{
auto& manifest = ParseManifest("{\"start_url\": \"/start?query=a\" }");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ("start?query=a", manifest->id);
}
// Empty string.
{
auto& manifest =
ParseManifest("{ \"start_url\": \"/start?query=a\", \"id\": \"\" }");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ("", manifest->id);
}
// Smoke test.
{
auto& manifest =
ParseManifest("{ \"start_url\": \"/start?query=a\", \"id\": \"foo\" }");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ("foo", manifest->id);
}
}
TEST_F(ManifestParserTest, StartURLParseRules) {
// Smoke test.
{
@ -279,6 +310,7 @@ TEST_F(ManifestParserTest, StartURLParseRules) {
{
auto& manifest = ParseManifest("{ \"start_url\": {} }");
ASSERT_TRUE(manifest->start_url.IsEmpty());
ASSERT_EQ(String(), manifest->id);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'start_url' ignored, type string expected.",
errors()[0]);

@ -674,6 +674,10 @@ void WebRuntimeFeatures::EnableParseUrlProtocolHandler(bool enable) {
RuntimeEnabledFeatures::SetParseUrlProtocolHandlerEnabled(enable);
}
void WebRuntimeFeatures::EnableWebAppManifestId(bool enable) {
RuntimeEnabledFeatures::SetWebAppManifestIdEnabled(enable);
}
void WebRuntimeFeatures::EnableWebID(bool enable) {
RuntimeEnabledFeatures::SetWebIDEnabled(enable);
}

@ -2214,6 +2214,13 @@
origin_trial_feature_name: "WebAppLinkCapturing",
origin_trial_os: ["chromeos"],
},
{
// This flag enables the Manifest parser to handle id field.
// Also enabled when blink::features::kWebAppEnableManifestId is
// overridden on the command line (or via chrome://flags).
name: "WebAppManifestId",
status: "experimental",
},
{
name:"WebAppsLockScreen",
status:"experimental",

@ -46159,6 +46159,7 @@ from previous Chrome versions.
<int value="-1269084216" label="ash-md"/>
<int value="-1268836676" label="disable-out-of-process-pdf"/>
<int value="-1267958145" label="disable-pdf-material-ui"/>
<int value="-1265627803" label="WebAppEnableManifestId:enabled"/>
<int value="-1262730949" label="EnableDspHotword:enabled"/>
<int value="-1262303946" label="SubresourceRedirectPreviews:disabled"/>
<int value="-1262152606" label="disable-lock-screen-apps"/>
@ -48003,6 +48004,7 @@ from previous Chrome versions.
label="enable-experimental-accessibility-language-detection"/>
<int value="324522065" label="app-menu-icon"/>
<int value="324631366" label="enable-drive-search-in-app-launcher"/>
<int value="325489620" label="WebAppEnableManifestId:disabled"/>
<int value="325907076" label="SanitizerAPI:enabled"/>
<int value="327045548" label="SafeSearchUrlReporting:enabled"/>
<int value="328722396" label="NTPCondensedLayout:disabled"/>