Commit 0f53672f authored by Samuel Tang's avatar Samuel Tang Committed by Commit Bot

Desktop PWAs: Add protocol_handler parsing to manifest_parser

Manifest parser can now parse protocol_handlers

This feature allows the blink parser to parse protocol_handlers
defined in its app manifest. It is checked in behind the
flag ParseUrlProtocolHandler.

The current CL does not include semantic validation of the scheme.
We will include it in a subsequent CL to keep CLs small and scoped.

This feature is part of a Chrome experiment, see the feature page:
https://chromestatus.com/features/5151703944921088

The URLProtocolHandler explainer can be found here:
https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md

The main bug link is: https://bugs.chromium.org/p/chromium/issues/detail?id=1019239

The blinkdev proposal is here: https://groups.google.com/a/chromium.org/forum/?hl=en#!topic/blink-dev/x4Ev_l9Oj2U

Bug: 1019239
Change-Id: I85a99064485f12e7c224bdaf491ad413e451229c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2211200Reviewed-by: default avatarAlex Russell <slightlyoff@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarDaniel Murphy <dmurph@chromium.org>
Commit-Queue: Samuel Tang <samtan@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#774211}
parent 6ad8d12b
...@@ -49,7 +49,8 @@ bool Manifest::IsEmpty() const { ...@@ -49,7 +49,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.is_null() && scope.is_empty(); gcm_sender_id.is_null() && scope.is_empty() &&
protocol_handlers.empty();
} }
} // namespace blink } // namespace blink
...@@ -75,6 +75,9 @@ bool StructTraits<blink::mojom::ManifestDataView, ::blink::Manifest>::Read( ...@@ -75,6 +75,9 @@ bool StructTraits<blink::mojom::ManifestDataView, ::blink::Manifest>::Read(
if (!data.ReadFileHandlers(&out->file_handlers)) if (!data.ReadFileHandlers(&out->file_handlers))
return false; return false;
if (!data.ReadProtocolHandlers(&out->protocol_handlers))
return false;
if (!data.ReadRelatedApplications(&out->related_applications)) if (!data.ReadRelatedApplications(&out->related_applications))
return false; return false;
...@@ -241,4 +244,17 @@ bool StructTraits<blink::mojom::ManifestFileHandlerDataView, ...@@ -241,4 +244,17 @@ bool StructTraits<blink::mojom::ManifestFileHandlerDataView,
return true; return true;
} }
bool StructTraits<blink::mojom::ManifestProtocolHandlerDataView,
::blink::Manifest::ProtocolHandler>::
Read(blink::mojom::ManifestProtocolHandlerDataView data,
::blink::Manifest::ProtocolHandler* out) {
if (!data.ReadProtocol(&out->protocol))
return false;
if (!data.ReadUrl(&out->url))
return false;
return true;
}
} // namespace mojo } // namespace mojo
...@@ -126,6 +126,12 @@ struct BLINK_COMMON_EXPORT Manifest { ...@@ -126,6 +126,12 @@ struct BLINK_COMMON_EXPORT Manifest {
std::map<base::string16, std::vector<base::string16>> accept; std::map<base::string16, std::vector<base::string16>> accept;
}; };
// Structure representing a Protocol Handler.
struct BLINK_COMMON_EXPORT ProtocolHandler {
base::string16 protocol;
GURL url;
};
// Structure representing a related application. // Structure representing a related application.
struct BLINK_COMMON_EXPORT RelatedApplication { struct BLINK_COMMON_EXPORT RelatedApplication {
RelatedApplication(); RelatedApplication();
...@@ -188,6 +194,13 @@ struct BLINK_COMMON_EXPORT Manifest { ...@@ -188,6 +194,13 @@ struct BLINK_COMMON_EXPORT Manifest {
// https://github.com/WICG/file-handling/blob/master/explainer.md // https://github.com/WICG/file-handling/blob/master/explainer.md
std::vector<FileHandler> file_handlers; std::vector<FileHandler> file_handlers;
// Empty if parsing failed or the field was not present.
// TODO(crbug.com/1019239): This is going into the mainline manifest spec,
// remove the TODO once that PR goes in.
// The URLProtocolHandler explainer can be found here:
// https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md
std::vector<ProtocolHandler> protocol_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.
......
...@@ -112,6 +112,11 @@ struct BLINK_COMMON_EXPORT ...@@ -112,6 +112,11 @@ struct BLINK_COMMON_EXPORT
return manifest.file_handlers; return manifest.file_handlers;
} }
static const std::vector<::blink::Manifest::ProtocolHandler>&
protocol_handlers(const ::blink::Manifest& manifest) {
return manifest.protocol_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;
...@@ -299,6 +304,21 @@ struct BLINK_COMMON_EXPORT ...@@ -299,6 +304,21 @@ struct BLINK_COMMON_EXPORT
::blink::Manifest::FileHandler* out); ::blink::Manifest::FileHandler* out);
}; };
template <>
struct BLINK_COMMON_EXPORT
StructTraits<blink::mojom::ManifestProtocolHandlerDataView,
::blink::Manifest::ProtocolHandler> {
static base::StringPiece16 protocol(
const ::blink::Manifest::ProtocolHandler& protocol) {
return internal::TruncateString16(protocol.protocol);
}
static const GURL& url(const ::blink::Manifest::ProtocolHandler& protocol) {
return protocol.url;
}
static bool Read(blink::mojom::ManifestProtocolHandlerDataView data,
::blink::Manifest::ProtocolHandler* out);
};
template <> template <>
struct BLINK_COMMON_EXPORT struct BLINK_COMMON_EXPORT
EnumTraits<blink::mojom::ManifestImageResource_Purpose, EnumTraits<blink::mojom::ManifestImageResource_Purpose,
......
...@@ -38,6 +38,12 @@ struct Manifest { ...@@ -38,6 +38,12 @@ struct Manifest {
// As such, this field should not be exposed to the drive-by web. // As such, this field should not be exposed to the drive-by web.
array<ManifestFileHandler> file_handlers; array<ManifestFileHandler> file_handlers;
// TODO(crbug.com/1019239): This is going into the mainline manifest spec,
// remove the TODO once that PR goes in.
// The URLProtocolHandler explainer can be found here:
// https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md
array<ManifestProtocolHandler> protocol_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
...@@ -110,6 +116,12 @@ struct ManifestFileFilter { ...@@ -110,6 +116,12 @@ struct ManifestFileFilter {
array<mojo_base.mojom.String16> accept; array<mojo_base.mojom.String16> accept;
}; };
// Structure representing a URL protocol handler.
struct ManifestProtocolHandler {
mojo_base.mojom.String16 protocol;
url.mojom.Url url;
};
// 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
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "third_party/blink/renderer/core/css/parser/css_parser.h" #include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/modules/manifest/manifest_uma_util.h" #include "third_party/blink/renderer/modules/manifest/manifest_uma_util.h"
#include "third_party/blink/renderer/platform/json/json_parser.h" #include "third_party/blink/renderer/platform/json/json_parser.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h"
#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"
...@@ -82,6 +83,7 @@ void ManifestParser::Parse() { ...@@ -82,6 +83,7 @@ void ManifestParser::Parse() {
manifest_->share_target = std::move(*share_target); manifest_->share_target = std::move(*share_target);
manifest_->file_handlers = ParseFileHandlers(root_object.get()); manifest_->file_handlers = ParseFileHandlers(root_object.get());
manifest_->protocol_handlers = ParseProtocolHandlers(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 =
...@@ -850,6 +852,77 @@ bool ManifestParser::ParseFileHandlerAcceptExtension(const JSONValue* extension, ...@@ -850,6 +852,77 @@ bool ManifestParser::ParseFileHandlerAcceptExtension(const JSONValue* extension,
return true; return true;
} }
Vector<mojom::blink::ManifestProtocolHandlerPtr>
ManifestParser::ParseProtocolHandlers(const JSONObject* from) {
Vector<mojom::blink::ManifestProtocolHandlerPtr> protocols;
if (!RuntimeEnabledFeatures::ParseUrlProtocolHandlerEnabled() ||
!from->Get("protocol_handlers")) {
return protocols;
}
JSONArray* protocol_list = from->GetArray("protocol_handlers");
if (!protocol_list) {
AddErrorInfo("property 'protocol_handlers' ignored, type array expected.");
return protocols;
}
for (wtf_size_t i = 0; i < protocol_list->size(); ++i) {
const JSONObject* protocol_object = JSONObject::Cast(protocol_list->at(i));
if (!protocol_object) {
AddErrorInfo("protocol_handlers entry ignored, type object expected.");
continue;
}
base::Optional<mojom::blink::ManifestProtocolHandlerPtr> protocol =
ParseProtocolHandler(protocol_object);
if (!protocol)
continue;
protocols.push_back(std::move(protocol.value()));
}
return protocols;
}
base::Optional<mojom::blink::ManifestProtocolHandlerPtr>
ManifestParser::ParseProtocolHandler(const JSONObject* object) {
DCHECK(RuntimeEnabledFeatures::ParseUrlProtocolHandlerEnabled());
if (!object->Get("protocol")) {
AddErrorInfo(
"protocol_handlers entry ignored, required property 'protocol' is "
"missing.");
return base::nullopt;
}
auto protocol_handler = mojom::blink::ManifestProtocolHandler::New();
// TODO(crbug.com/1019239): The parsed protocol will need validation
// matching those accepted by navigator.registerProtocolHandler() in a follow
// up CL.
base::Optional<String> protocol = ParseString(object, "protocol", Trim);
if (!protocol.has_value()) {
AddErrorInfo(
"protocol_handlers entry ignored, required property 'protocol' is "
"invalid.");
return base::nullopt;
}
protocol_handler->protocol = protocol.value();
if (!object->Get("url")) {
AddErrorInfo(
"protocol_handlers entry ignored, required property 'url' is missing.");
return base::nullopt;
}
protocol_handler->url = ParseURL(object, "url", manifest_url_,
ParseURLOriginRestrictions::kSameOriginOnly);
if (!protocol_handler->url.IsValid()) {
AddErrorInfo(
"protocol_handlers entry ignored, required property 'url' is invalid.");
return base::nullopt;
}
return std::move(protocol_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);
......
...@@ -269,6 +269,25 @@ class MODULES_EXPORT ManifestParser { ...@@ -269,6 +269,25 @@ class MODULES_EXPORT ManifestParser {
bool ParseFileHandlerAcceptExtension(const JSONValue* extension, bool ParseFileHandlerAcceptExtension(const JSONValue* extension,
String* ouput); String* ouput);
// Parses the 'protocol_handlers' field of a Manifest, as defined in:
// https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md
// Returns the parsed list of ProtocolHandlers. The returned ProtocolHandlers
// are empty if the field didn't exist, parsing failed, or the input list was
// empty.
// This feature is experimental and would only be enabled behind the blink
// feature flag: RuntimeEnabledFeatures::ParseUrlProtocolHandlerEnabled()
Vector<mojom::blink::ManifestProtocolHandlerPtr> ParseProtocolHandlers(
const JSONObject* object);
// Parses a single ProtocolHandle field of a Manifest, as defined in:
// https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md
// Returns |base::nullopt| if the ProtocolHandler was invalid, or a
// ProtocolHandler if parsing succeeded.
// This feature is experimental and should only be used behind the blink
// feature flag: RuntimeEnabledFeatures::ParseUrlProtocolHandlerEnabled()
base::Optional<mojom::blink::ManifestProtocolHandlerPtr> ParseProtocolHandler(
const JSONObject* protocol_dictionary);
// Parses the 'platform' field of a related application, as defined in: // Parses the 'platform' field of a related application, as defined in:
// https://w3c.github.io/manifest/#dfn-steps-for-processing-the-platform-member-of-an-application // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-platform-member-of-an-application
// Returns the parsed string if any, a null string if the parsing failed. // Returns the parsed string if any, a null string if the parsing failed.
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/optional.h" #include "base/optional.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h"
namespace blink { namespace blink {
...@@ -1867,6 +1868,152 @@ TEST_F(ManifestParserTest, FileHandlerParseRules) { ...@@ -1867,6 +1868,152 @@ TEST_F(ManifestParserTest, FileHandlerParseRules) {
} }
} }
TEST_F(ManifestParserTest, ProtocolHandlerParseRules) {
// Does not contain protocol_handlers field.
{
auto& manifest = ParseManifest("{ }");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ(0u, manifest->protocol_handlers.size());
}
// protocol_handlers is not an array.
{
auto& manifest = ParseManifest("{ \"protocol_handlers\": { } }");
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'protocol_handlers' ignored, type array expected.",
errors()[0]);
EXPECT_EQ(0u, manifest->protocol_handlers.size());
}
// Contains protocol_handlers field but no protocol handlers.
{
auto& manifest = ParseManifest("{ \"protocol_handlers\": [ ] }");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ(0u, manifest->protocol_handlers.size());
}
// Entries must be objects
{
auto& manifest = ParseManifest(
"{"
" \"protocol_handlers\": ["
" \"hello world\""
" ]"
"}");
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ("protocol_handlers entry ignored, type object expected.",
errors()[0]);
EXPECT_EQ(0u, manifest->protocol_handlers.size());
}
// A valid protocol handler.
{
auto& manifest = ParseManifest(
"{"
" \"protocol_handlers\": ["
" {"
" \"protocol\": \"web+github\","
" \"url\": \"http://foo.com/?profile=%s\""
" }"
" ]"
"}");
auto& protocol_handlers = manifest->protocol_handlers;
ASSERT_EQ(0u, GetErrorCount());
ASSERT_EQ(1u, protocol_handlers.size());
ASSERT_EQ("web+github", protocol_handlers[0]->protocol);
ASSERT_EQ("http://foo.com/?profile=%s", protocol_handlers[0]->url);
}
// An invalid protocol handler with the URL not being from the same origin.
{
auto& manifest = ParseManifest(
"{"
" \"protocol_handlers\": ["
" {"
" \"protocol\": \"web+github\","
" \"url\": \"http://bar.com/?profile=%s\""
" }"
" ]"
"}");
auto& protocol_handlers = manifest->protocol_handlers;
ASSERT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'url' ignored, should be same origin as document.",
errors()[0]);
EXPECT_EQ(
"protocol_handlers entry ignored, required property 'url' is invalid.",
errors()[1]);
ASSERT_EQ(0u, protocol_handlers.size());
}
// An invalid protocol handler with no value for protocol.
{
auto& manifest = ParseManifest(
"{"
" \"protocol_handlers\": ["
" {"
" \"url\": \"http://foo.com/?profile=%s\""
" }"
" ]"
"}");
auto& protocol_handlers = manifest->protocol_handlers;
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"protocol_handlers entry ignored, required property 'protocol' is "
"missing.",
errors()[0]);
ASSERT_EQ(0u, protocol_handlers.size());
}
// An invalid protocol handler with no url.
{
auto& manifest = ParseManifest(
"{"
" \"protocol_handlers\": ["
" {"
" \"protocol\": \"web+github\""
" }"
" ]"
"}");
auto& protocol_handlers = manifest->protocol_handlers;
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"protocol_handlers entry ignored, required property 'url' is missing.",
errors()[0]);
ASSERT_EQ(0u, protocol_handlers.size());
}
// Multiple valid protocol handlers
{
auto& manifest = ParseManifest(
"{"
" \"protocol_handlers\": ["
" {"
" \"protocol\": \"web+github\","
" \"url\": \"http://foo.com/?profile=%s\""
" },"
" {"
" \"protocol\": \"web+test\","
" \"url\": \"http://foo.com/?test=%s\""
" }"
" ]"
"}");
auto& protocol_handlers = manifest->protocol_handlers;
ASSERT_EQ(0u, GetErrorCount());
ASSERT_EQ(2u, protocol_handlers.size());
ASSERT_EQ("web+github", protocol_handlers[0]->protocol);
ASSERT_EQ("http://foo.com/?profile=%s", protocol_handlers[0]->url);
ASSERT_EQ("web+test", protocol_handlers[1]->protocol);
ASSERT_EQ("http://foo.com/?test=%s", protocol_handlers[1]->url);
}
}
TEST_F(ManifestParserTest, ShareTargetParseRules) { TEST_F(ManifestParserTest, ShareTargetParseRules) {
// Contains share_target field but no keys. // Contains share_target field but no keys.
{ {
......
...@@ -52,6 +52,11 @@ TypeConverter<blink::Manifest, blink::mojom::blink::ManifestPtr>::Convert( ...@@ -52,6 +52,11 @@ TypeConverter<blink::Manifest, blink::mojom::blink::ManifestPtr>::Convert(
output.file_handlers.push_back(entry.To<blink::Manifest::FileHandler>()); output.file_handlers.push_back(entry.To<blink::Manifest::FileHandler>());
} }
for (auto& uri_protocol : input->protocol_handlers) {
output.protocol_handlers.push_back(
uri_protocol.To<blink::Manifest::ProtocolHandler>());
}
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>());
...@@ -225,6 +230,18 @@ TypeConverter<blink::Manifest::FileHandler, ...@@ -225,6 +230,18 @@ TypeConverter<blink::Manifest::FileHandler,
return output; return output;
} }
blink::Manifest::ProtocolHandler
TypeConverter<blink::Manifest::ProtocolHandler,
blink::mojom::blink::ManifestProtocolHandlerPtr>::
Convert(const blink::mojom::blink::ManifestProtocolHandlerPtr& input) {
blink::Manifest::ProtocolHandler output;
if (input.is_null())
return output;
output.protocol = blink::WebString(input->protocol).Utf16();
output.url = input->url;
return output;
}
blink::Manifest::RelatedApplication blink::Manifest::RelatedApplication
TypeConverter<blink::Manifest::RelatedApplication, TypeConverter<blink::Manifest::RelatedApplication,
blink::mojom::blink::ManifestRelatedApplicationPtr>:: blink::mojom::blink::ManifestRelatedApplicationPtr>::
......
...@@ -66,6 +66,13 @@ struct TypeConverter<blink::Manifest::FileHandler, ...@@ -66,6 +66,13 @@ struct TypeConverter<blink::Manifest::FileHandler,
const blink::mojom::blink::ManifestFileHandlerPtr& input); const blink::mojom::blink::ManifestFileHandlerPtr& input);
}; };
template <>
struct TypeConverter<blink::Manifest::ProtocolHandler,
blink::mojom::blink::ManifestProtocolHandlerPtr> {
static blink::Manifest::ProtocolHandler Convert(
const blink::mojom::blink::ManifestProtocolHandlerPtr& input);
};
template <> template <>
struct TypeConverter<blink::Manifest::RelatedApplication, struct TypeConverter<blink::Manifest::RelatedApplication,
blink::mojom::blink::ManifestRelatedApplicationPtr> { blink::mojom::blink::ManifestRelatedApplicationPtr> {
......
...@@ -1321,6 +1321,11 @@ ...@@ -1321,6 +1321,11 @@
name: "PaintUnderInvalidationChecking", name: "PaintUnderInvalidationChecking",
settable_from_internals: true, settable_from_internals: true,
}, },
{
// This flag enables the Manifest parser to handle URL Protocols.
name: "ParseUrlProtocolHandler",
status: "test",
},
{ {
name: "PassPaintVisualRectToCompositor", name: "PassPaintVisualRectToCompositor",
}, },
......
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