Commit 6e3c1c2a authored by Lu Huang's avatar Lu Huang Committed by Commit Bot

Parse 'url_handlers' web app manifest member.

These changes enable the 'url_handlers' member in the web app manifest
to be parsed. This is the first of a number of CLs that will allow
progressive web apps to be launched to handle or "capture" https URL
activations. Please refer to the crbug for links to the design doc and
overview "uber-CL".

design doc: https://docs.google.com/document/d/19dGklalQTRtRrG3PKrVbDRmYPLHGLnsGEwUaed7sAFQ/edit?usp=sharing

design overview cl: https://chromium-review.googlesource.com/c/chromium/src/+/2393252

Chrome status: https://www.chromestatus.com/feature/5739732661174272

Bug: 1072058
Change-Id: Ic8251bf74e0a17cd147d4d46389e66f7576c1763
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2405696Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarMarijn Kruisselbrink <mek@chromium.org>
Reviewed-by: default avatarMandy Chen <mandy.chen@microsoft.com>
Commit-Queue: Lu Huang <luhua@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#822529}
parent 561c27fa
...@@ -47,7 +47,8 @@ bool Manifest::IsEmpty() const { ...@@ -47,7 +47,8 @@ bool Manifest::IsEmpty() const {
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() &&
!prefer_related_applications && !theme_color && !background_color && !prefer_related_applications && !theme_color && !background_color &&
!gcm_sender_id && scope.is_empty() && protocol_handlers.empty(); !gcm_sender_id && scope.is_empty() && protocol_handlers.empty() &&
url_handlers.empty();
} }
} // namespace blink } // namespace blink
...@@ -77,6 +77,9 @@ bool StructTraits<blink::mojom::ManifestDataView, ::blink::Manifest>::Read( ...@@ -77,6 +77,9 @@ bool StructTraits<blink::mojom::ManifestDataView, ::blink::Manifest>::Read(
if (!data.ReadProtocolHandlers(&out->protocol_handlers)) if (!data.ReadProtocolHandlers(&out->protocol_handlers))
return false; return false;
if (!data.ReadUrlHandlers(&out->url_handlers))
return false;
if (!data.ReadRelatedApplications(&out->related_applications)) if (!data.ReadRelatedApplications(&out->related_applications))
return false; return false;
...@@ -193,6 +196,16 @@ bool StructTraits<blink::mojom::ManifestFileFilterDataView, ...@@ -193,6 +196,16 @@ bool StructTraits<blink::mojom::ManifestFileFilterDataView,
return true; return true;
} }
bool StructTraits<blink::mojom::ManifestUrlHandlerDataView,
::blink::Manifest::UrlHandler>::
Read(blink::mojom::ManifestUrlHandlerDataView data,
::blink::Manifest::UrlHandler* out) {
if (!data.ReadOrigin(&out->origin))
return false;
return true;
}
bool StructTraits<blink::mojom::ManifestShareTargetParamsDataView, bool StructTraits<blink::mojom::ManifestShareTargetParamsDataView,
::blink::Manifest::ShareTargetParams>:: ::blink::Manifest::ShareTargetParams>::
Read(blink::mojom::ManifestShareTargetParamsDataView data, Read(blink::mojom::ManifestShareTargetParamsDataView data,
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/size.h"
#include "url/gurl.h" #include "url/gurl.h"
#include "url/origin.h"
namespace blink { namespace blink {
...@@ -125,6 +126,10 @@ struct BLINK_COMMON_EXPORT Manifest { ...@@ -125,6 +126,10 @@ struct BLINK_COMMON_EXPORT Manifest {
GURL url; GURL url;
}; };
struct BLINK_COMMON_EXPORT UrlHandler {
url::Origin origin;
};
// Structure representing a related application. // Structure representing a related application.
struct BLINK_COMMON_EXPORT RelatedApplication { struct BLINK_COMMON_EXPORT RelatedApplication {
RelatedApplication(); RelatedApplication();
...@@ -199,6 +204,13 @@ struct BLINK_COMMON_EXPORT Manifest { ...@@ -199,6 +204,13 @@ struct BLINK_COMMON_EXPORT Manifest {
// https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md
std::vector<ProtocolHandler> protocol_handlers; std::vector<ProtocolHandler> protocol_handlers;
// TODO(crbug.com/1072058): This field is non-standard and part of an
// experiment. See:
// https://github.com/WICG/pwa-url-handler/blob/master/explainer.md
// Empty if the parsing failed, the field was not present, empty or all the
// entries inside the array were invalid.
std::vector<UrlHandler> url_handlers;
// Empty if the parsing failed, the field was not present, empty or all the // Empty if the parsing failed, the field was not present, empty or all the
// applications inside the array were invalid. The order of the array // applications inside the array were invalid. The order of the array
// indicates the priority of the application to use. // indicates the priority of the application to use.
......
...@@ -123,6 +123,11 @@ struct BLINK_COMMON_EXPORT ...@@ -123,6 +123,11 @@ struct BLINK_COMMON_EXPORT
return manifest.protocol_handlers; return manifest.protocol_handlers;
} }
static const std::vector<::blink::Manifest::UrlHandler>& url_handlers(
const ::blink::Manifest& manifest) {
return manifest.url_handlers;
}
static const std::vector<::blink::Manifest::RelatedApplication>& static const std::vector<::blink::Manifest::RelatedApplication>&
related_applications(const ::blink::Manifest& manifest) { related_applications(const ::blink::Manifest& manifest) {
return manifest.related_applications; return manifest.related_applications;
...@@ -239,6 +244,19 @@ struct BLINK_COMMON_EXPORT ...@@ -239,6 +244,19 @@ struct BLINK_COMMON_EXPORT
::blink::Manifest::FileFilter* out); ::blink::Manifest::FileFilter* out);
}; };
template <>
struct BLINK_COMMON_EXPORT
StructTraits<blink::mojom::ManifestUrlHandlerDataView,
::blink::Manifest::UrlHandler> {
static const url::Origin& origin(
const ::blink::Manifest::UrlHandler& url_handler) {
return url_handler.origin;
}
static bool Read(blink::mojom::ManifestUrlHandlerDataView data,
::blink::Manifest::UrlHandler* out);
};
template <> template <>
struct BLINK_COMMON_EXPORT struct BLINK_COMMON_EXPORT
StructTraits<blink::mojom::ManifestShareTargetParamsDataView, StructTraits<blink::mojom::ManifestShareTargetParamsDataView,
......
...@@ -9,6 +9,7 @@ import "mojo/public/mojom/base/string16.mojom"; ...@@ -9,6 +9,7 @@ import "mojo/public/mojom/base/string16.mojom";
import "third_party/blink/public/mojom/manifest/display_mode.mojom"; import "third_party/blink/public/mojom/manifest/display_mode.mojom";
import "ui/gfx/geometry/mojom/geometry.mojom"; import "ui/gfx/geometry/mojom/geometry.mojom";
import "url/mojom/url.mojom"; import "url/mojom/url.mojom";
import "url/mojom/origin.mojom";
// The Manifest structure is an internal representation of the Manifest file // The Manifest structure is an internal representation of the Manifest file
...@@ -46,6 +47,8 @@ struct Manifest { ...@@ -46,6 +47,8 @@ struct Manifest {
// https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md
array<ManifestProtocolHandler> protocol_handlers; array<ManifestProtocolHandler> protocol_handlers;
array<ManifestUrlHandler> url_handlers;
array<ManifestRelatedApplication> related_applications; array<ManifestRelatedApplication> related_applications;
// A boolean that is used as a hint for the user agent to say that related // A boolean that is used as a hint for the user agent to say that related
...@@ -124,6 +127,10 @@ struct ManifestProtocolHandler { ...@@ -124,6 +127,10 @@ struct ManifestProtocolHandler {
url.mojom.Url url; url.mojom.Url url;
}; };
struct ManifestUrlHandler {
url.mojom.Origin origin;
};
// Structure representing a related application. // Structure representing a related application.
struct ManifestRelatedApplication { struct ManifestRelatedApplication {
// The platform on which the application can be found. This can be any // The platform on which the application can be found. This can be any
......
...@@ -2,4 +2,5 @@ include_rules = [ ...@@ -2,4 +2,5 @@ include_rules = [
"+base/strings", "+base/strings",
"+net/base/mime_util.h", "+net/base/mime_util.h",
"+third_party/blink/renderer/core/frame/web_local_frame_impl.h", "+third_party/blink/renderer/core/frame/web_local_frame_impl.h",
"+url/url_constants.h",
] ]
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
#include "third_party/blink/renderer/modules/manifest/manifest_parser.h" #include "third_party/blink/renderer/modules/manifest/manifest_parser.h"
#include "base/feature_list.h"
#include "net/base/mime_util.h" #include "net/base/mime_util.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/manifest/manifest_util.h" #include "third_party/blink/public/common/manifest/manifest_util.h"
#include "third_party/blink/public/common/mime_util/mime_util.h" #include "third_party/blink/public/common/mime_util/mime_util.h"
#include "third_party/blink/public/platform/web_icon_sizes_parser.h" #include "third_party/blink/public/platform/web_icon_sizes_parser.h"
...@@ -19,6 +21,7 @@ ...@@ -19,6 +21,7 @@
#include "third_party/blink/renderer/platform/weborigin/security_origin.h" #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "url/url_constants.h"
namespace blink { namespace blink {
...@@ -92,7 +95,7 @@ void ManifestParser::Parse() { ...@@ -92,7 +95,7 @@ void ManifestParser::Parse() {
manifest_->file_handlers = ParseFileHandlers(root_object.get()); manifest_->file_handlers = ParseFileHandlers(root_object.get());
manifest_->protocol_handlers = ParseProtocolHandlers(root_object.get()); manifest_->protocol_handlers = ParseProtocolHandlers(root_object.get());
manifest_->url_handlers = ParseUrlHandlers(root_object.get());
manifest_->related_applications = ParseRelatedApplications(root_object.get()); manifest_->related_applications = ParseRelatedApplications(root_object.get());
manifest_->prefer_related_applications = manifest_->prefer_related_applications =
ParsePreferRelatedApplications(root_object.get()); ParsePreferRelatedApplications(root_object.get());
...@@ -1006,6 +1009,80 @@ ManifestParser::ParseProtocolHandler(const JSONObject* object) { ...@@ -1006,6 +1009,80 @@ ManifestParser::ParseProtocolHandler(const JSONObject* object) {
return std::move(protocol_handler); return std::move(protocol_handler);
} }
Vector<mojom::blink::ManifestUrlHandlerPtr> ManifestParser::ParseUrlHandlers(
const JSONObject* from) {
Vector<mojom::blink::ManifestUrlHandlerPtr> url_handlers;
if (!base::FeatureList::IsEnabled(
blink::features::kWebAppEnableUrlHandlers) ||
!from->Get("url_handlers")) {
return url_handlers;
}
JSONArray* handlers_list = from->GetArray("url_handlers");
if (!handlers_list) {
AddErrorInfo("property 'url_handlers' ignored, type array expected.");
return url_handlers;
}
for (wtf_size_t i = 0; i < handlers_list->size(); ++i) {
const JSONObject* handler_object = JSONObject::Cast(handlers_list->at(i));
if (!handler_object) {
AddErrorInfo("url_handlers entry ignored, type object expected.");
continue;
}
base::Optional<mojom::blink::ManifestUrlHandlerPtr> url_handler =
ParseUrlHandler(handler_object);
if (!url_handler) {
continue;
}
url_handlers.push_back(std::move(url_handler.value()));
}
return url_handlers;
}
base::Optional<mojom::blink::ManifestUrlHandlerPtr>
ManifestParser::ParseUrlHandler(const JSONObject* object) {
DCHECK(
base::FeatureList::IsEnabled(blink::features::kWebAppEnableUrlHandlers));
if (!object->Get("origin")) {
AddErrorInfo(
"url_handlers entry ignored, required property 'origin' is missing.");
return base::nullopt;
}
const base::Optional<String> origin_string =
ParseString(object, "origin", Trim);
if (!origin_string.has_value()) {
AddErrorInfo(
"url_handlers entry ignored, required property 'origin' is invalid.");
return base::nullopt;
}
// TODO(crbug.com/1072058): pre-process for sub-domain wildcard
// prefix before parsing as origin. Add a boolean value to indicate the
// presence of a sub-domain wildcard prefix so the browser process does not
// have to parse it.
// TODO(crbug.com/1072058): pre-process for input without scheme.
// (eg. example.com instead of https://example.com) because we can always
// assume the use of https for URL handling. Remove this TODO if we decide
// to require fully specified https scheme in this origin input.
auto origin = SecurityOrigin::CreateFromString(*origin_string);
if (!origin || origin->IsOpaque()) {
AddErrorInfo(
"url_handlers entry ignored, required property 'origin' is invalid.");
return base::nullopt;
}
if (origin->Protocol() != url::kHttpsScheme) {
AddErrorInfo(
"url_handlers entry ignored, required property 'origin' must use the "
"https scheme.");
return base::nullopt;
}
auto url_handler = mojom::blink::ManifestUrlHandler::New();
url_handler->origin = origin;
return std::move(url_handler);
}
String ManifestParser::ParseRelatedApplicationPlatform( String ManifestParser::ParseRelatedApplicationPlatform(
const JSONObject* application) { const JSONObject* application) {
base::Optional<String> platform = ParseString(application, "platform", Trim); base::Optional<String> platform = ParseString(application, "platform", Trim);
......
...@@ -253,6 +253,25 @@ class MODULES_EXPORT ManifestParser { ...@@ -253,6 +253,25 @@ class MODULES_EXPORT ManifestParser {
base::Optional<mojom::blink::ManifestShareTargetPtr> ParseShareTarget( base::Optional<mojom::blink::ManifestShareTargetPtr> ParseShareTarget(
const JSONObject* object); const JSONObject* object);
// Parses the 'url_handlers' field of a Manifest, as defined in:
// https://github.com/WICG/pwa-url-handler/blob/master/explainer.md
// Returns the parsed list of UrlHandlers. The returned UrlHandlers are empty
// if the field didn't exist, parsing failed, the input list was empty, or if
// the blink feature flag is disabled.
// This feature is experimental and is only enabled by the blink feature flag:
// blink::features::kWebAppEnableUrlHandlers.
Vector<mojom::blink::ManifestUrlHandlerPtr> ParseUrlHandlers(
const JSONObject* object);
// Parses a single URL handler entry in 'url_handlers', as defined in:
// https://github.com/WICG/pwa-url-handler/blob/master/explainer.md
// Returns |base::nullopt| if the UrlHandler was invalid, or a UrlHandler if
// parsing succeeded.
// This feature is experimental and is only enabled by the blink feature flag:
// blink::features::kWebAppEnableUrlHandlers.
base::Optional<mojom::blink::ManifestUrlHandlerPtr> ParseUrlHandler(
const JSONObject* object);
// Parses the 'file_handlers' field of a Manifest, as defined in: // Parses the 'file_handlers' field of a Manifest, as defined in:
// https://github.com/WICG/file-handling/blob/master/explainer.md // https://github.com/WICG/file-handling/blob/master/explainer.md
// Returns the parsed list of FileHandlers. The returned FileHandlers are // Returns the parsed list of FileHandlers. The returned FileHandlers are
......
...@@ -10,7 +10,10 @@ ...@@ -10,7 +10,10 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h"
...@@ -2302,6 +2305,151 @@ TEST_F(ManifestParserTest, ProtocolHandlerParseRules) { ...@@ -2302,6 +2305,151 @@ TEST_F(ManifestParserTest, ProtocolHandlerParseRules) {
} }
} }
TEST_F(ManifestParserTest, UrlHandlerParseRules) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(blink::features::kWebAppEnableUrlHandlers);
// Manifest does not contain a 'url_handlers' field.
{
auto& manifest = ParseManifest("{ }");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ(0u, manifest->url_handlers.size());
}
// 'url_handlers' is not an array.
{
auto& manifest = ParseManifest("{ \"url_handlers\": { } }");
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'url_handlers' ignored, type array expected.",
errors()[0]);
EXPECT_EQ(0u, manifest->url_handlers.size());
}
// Contains 'url_handlers' field but no URL handler entries.
{
auto& manifest = ParseManifest("{ \"url_handlers\": [ ] }");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ(0u, manifest->url_handlers.size());
}
// 'url_handlers' array entries must be objects.
{
auto& manifest = ParseManifest(
"{"
" \"url_handlers\": ["
" \"foo.com\""
" ]"
"}");
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ("url_handlers entry ignored, type object expected.", errors()[0]);
EXPECT_EQ(0u, manifest->url_handlers.size());
}
// A valid url handler.
{
auto& manifest = ParseManifest(
"{"
" \"url_handlers\": ["
" {"
" \"origin\": \"https://foo.com\""
" }"
" ]"
"}");
auto& url_handlers = manifest->url_handlers;
ASSERT_EQ(0u, GetErrorCount());
ASSERT_EQ(1u, url_handlers.size());
ASSERT_TRUE(blink::SecurityOrigin::CreateFromString("https://foo.com")
->IsSameOriginWith(url_handlers[0]->origin.get()));
}
// Scheme must be https.
{
auto& manifest = ParseManifest(
"{"
" \"url_handlers\": ["
" {"
" \"origin\": \"http://foo.com\""
" }"
" ]"
"}");
auto& url_handlers = manifest->url_handlers;
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"url_handlers entry ignored, required property 'origin' must use the "
"https scheme.",
errors()[0]);
ASSERT_EQ(0u, url_handlers.size());
}
// Origin must be valid.
{
auto& manifest = ParseManifest(
"{"
" \"url_handlers\": ["
" {"
" \"origin\": \"https:///////\""
" }"
" ]"
"}");
auto& url_handlers = manifest->url_handlers;
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"url_handlers entry ignored, required property 'origin' is invalid.",
errors()[0]);
ASSERT_EQ(0u, url_handlers.size());
}
// Parse multiple valid handlers.
{
auto& manifest = ParseManifest(
"{"
" \"url_handlers\": ["
" {"
" \"origin\": \"https://foo.com\""
" },"
" {"
" \"origin\": \"https://bar.com\""
" }"
" ]"
"}");
auto& url_handlers = manifest->url_handlers;
ASSERT_EQ(0u, GetErrorCount());
ASSERT_EQ(2u, url_handlers.size());
ASSERT_TRUE(blink::SecurityOrigin::CreateFromString("https://foo.com")
->IsSameOriginWith(url_handlers[0]->origin.get()));
ASSERT_TRUE(blink::SecurityOrigin::CreateFromString("https://bar.com")
->IsSameOriginWith(url_handlers[1]->origin.get()));
}
// Parse both valid and invalid handlers.
{
auto& manifest = ParseManifest(
"{"
" \"url_handlers\": ["
" {"
" \"origin\": \"https://foo.com\""
" },"
" {"
" \"origin\": \"about:\""
" }"
" ]"
"}");
auto& url_handlers = manifest->url_handlers;
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"url_handlers entry ignored, required property 'origin' is invalid.",
errors()[0]);
ASSERT_EQ(1u, url_handlers.size());
ASSERT_TRUE(blink::SecurityOrigin::CreateFromString("https://foo.com")
->IsSameOriginWith(url_handlers[0]->origin.get()));
}
}
TEST_F(ManifestParserTest, ShareTargetParseRules) { TEST_F(ManifestParserTest, ShareTargetParseRules) {
// Contains share_target field but no keys. // Contains share_target field but no keys.
{ {
......
...@@ -54,6 +54,11 @@ TypeConverter<blink::Manifest, blink::mojom::blink::ManifestPtr>::Convert( ...@@ -54,6 +54,11 @@ TypeConverter<blink::Manifest, blink::mojom::blink::ManifestPtr>::Convert(
uri_protocol.To<blink::Manifest::ProtocolHandler>()); uri_protocol.To<blink::Manifest::ProtocolHandler>());
} }
for (auto& url_handler : input->url_handlers) {
output.url_handlers.push_back(
url_handler.To<blink::Manifest::UrlHandler>());
}
for (auto& related_application : input->related_applications) { for (auto& related_application : input->related_applications) {
output.related_applications.push_back( output.related_applications.push_back(
related_application.To<blink::Manifest::RelatedApplication>()); related_application.To<blink::Manifest::RelatedApplication>());
...@@ -235,6 +240,20 @@ TypeConverter<blink::Manifest::ProtocolHandler, ...@@ -235,6 +240,20 @@ TypeConverter<blink::Manifest::ProtocolHandler,
return output; return output;
} }
blink::Manifest::UrlHandler
TypeConverter<blink::Manifest::UrlHandler,
blink::mojom::blink::ManifestUrlHandlerPtr>::
Convert(const blink::mojom::blink::ManifestUrlHandlerPtr& input) {
blink::Manifest::UrlHandler output;
if (input.is_null())
return output;
if (!output.origin.opaque())
output.origin = input->origin->ToUrlOrigin();
return output;
}
blink::Manifest::RelatedApplication blink::Manifest::RelatedApplication
TypeConverter<blink::Manifest::RelatedApplication, TypeConverter<blink::Manifest::RelatedApplication,
blink::mojom::blink::ManifestRelatedApplicationPtr>:: blink::mojom::blink::ManifestRelatedApplicationPtr>::
......
...@@ -73,6 +73,13 @@ struct TypeConverter<blink::Manifest::ProtocolHandler, ...@@ -73,6 +73,13 @@ struct TypeConverter<blink::Manifest::ProtocolHandler,
const blink::mojom::blink::ManifestProtocolHandlerPtr& input); const blink::mojom::blink::ManifestProtocolHandlerPtr& input);
}; };
template <>
struct TypeConverter<blink::Manifest::UrlHandler,
blink::mojom::blink::ManifestUrlHandlerPtr> {
static blink::Manifest::UrlHandler Convert(
const blink::mojom::blink::ManifestUrlHandlerPtr& input);
};
template <> template <>
struct TypeConverter<blink::Manifest::RelatedApplication, struct TypeConverter<blink::Manifest::RelatedApplication,
blink::mojom::blink::ManifestRelatedApplicationPtr> { blink::mojom::blink::ManifestRelatedApplicationPtr> {
......
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