blob: 2f23170eac2d0b5f460e943a7cba4ec0c5ba9eb5 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/manifest/manifest_parser.h"
#include <stdint.h>
#include <memory>
#include "base/macros.h"
#include "base/optional.h"
#include "base/test/scoped_feature_list.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/weborigin/kurl.h"
namespace blink {
bool IsManifestEmpty(const mojom::blink::ManifestPtr& manifest) {
return manifest == mojom::blink::Manifest::New();
}
class ManifestParserTest : public testing::Test {
protected:
ManifestParserTest() {}
~ManifestParserTest() override {}
mojom::blink::ManifestPtr& ParseManifestWithURLs(const String& data,
const KURL& manifest_url,
const KURL& document_url) {
ManifestParser parser(data, manifest_url, document_url);
parser.Parse();
Vector<mojom::blink::ManifestErrorPtr> errors;
parser.TakeErrors(&errors);
errors_.clear();
for (auto& error : errors)
errors_.push_back(std::move(error->message));
manifest_ = parser.manifest().Clone();
return manifest_;
}
mojom::blink::ManifestPtr& ParseManifest(const String& data) {
return ParseManifestWithURLs(data, default_manifest_url,
default_document_url);
}
const Vector<String>& errors() const { return errors_; }
unsigned int GetErrorCount() const { return errors_.size(); }
const KURL& DefaultDocumentUrl() const { return default_document_url; }
const KURL& DefaultManifestUrl() const { return default_manifest_url; }
private:
mojom::blink::ManifestPtr manifest_;
Vector<String> errors_;
const KURL default_document_url = KURL("http://foo.com/index.html");
const KURL default_manifest_url = KURL("http://foo.com/manifest.json");
DISALLOW_COPY_AND_ASSIGN(ManifestParserTest);
};
TEST_F(ManifestParserTest, CrashTest) {
// Passing temporary variables should not crash.
const String json = "{\"start_url\": \"/\"}";
KURL url("http://example.com");
ManifestParser parser(json, url, url);
parser.Parse();
Vector<mojom::blink::ManifestErrorPtr> errors;
const auto& manifest = parser.manifest();
parser.TakeErrors(&errors);
// .Parse() should have been call without crashing and succeeded.
EXPECT_EQ(0u, errors.size());
EXPECT_FALSE(IsManifestEmpty(manifest));
}
TEST_F(ManifestParserTest, EmptyStringNull) {
auto& manifest = ParseManifest("");
// This Manifest is not a valid JSON object, it's a parsing error.
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("Line: 1, column: 1, Syntax error.", errors()[0]);
// A parsing error is equivalent to an empty manifest.
ASSERT_TRUE(IsManifestEmpty(manifest));
}
TEST_F(ManifestParserTest, ValidNoContentParses) {
auto& manifest = ParseManifest("{}");
// Empty Manifest is not a parsing error.
EXPECT_EQ(0u, GetErrorCount());
// Check that the fields are null or set to their default values.
ASSERT_FALSE(IsManifestEmpty(manifest));
ASSERT_TRUE(manifest->name.IsNull());
ASSERT_TRUE(manifest->short_name.IsNull());
ASSERT_TRUE(manifest->start_url.IsEmpty());
ASSERT_EQ(manifest->display, blink::mojom::DisplayMode::kUndefined);
ASSERT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::DEFAULT);
ASSERT_FALSE(manifest->has_theme_color);
ASSERT_FALSE(manifest->has_background_color);
ASSERT_TRUE(manifest->gcm_sender_id.IsNull());
ASSERT_EQ(DefaultDocumentUrl().BaseAsString(), manifest->scope.GetString());
ASSERT_TRUE(manifest->shortcuts.IsEmpty());
}
TEST_F(ManifestParserTest, MultipleErrorsReporting) {
auto& manifest = ParseManifest(
"{ \"name\": 42, \"short_name\": 4,"
"\"orientation\": {}, \"display\": \"foo\","
"\"start_url\": null, \"icons\": {}, \"theme_color\": 42,"
"\"background_color\": 42, \"shortcuts\": {} }");
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(9u, GetErrorCount());
EXPECT_EQ("property 'name' ignored, type string expected.", errors()[0]);
EXPECT_EQ("property 'short_name' ignored, type string expected.",
errors()[1]);
EXPECT_EQ("property 'start_url' ignored, type string expected.", errors()[2]);
EXPECT_EQ("unknown 'display' value ignored.", errors()[3]);
EXPECT_EQ("property 'orientation' ignored, type string expected.",
errors()[4]);
EXPECT_EQ("property 'icons' ignored, type array expected.", errors()[5]);
EXPECT_EQ("property 'theme_color' ignored, type string expected.",
errors()[6]);
EXPECT_EQ("property 'background_color' ignored, type string expected.",
errors()[7]);
EXPECT_EQ("property 'shortcuts' ignored, type array expected.", errors()[8]);
}
TEST_F(ManifestParserTest, NameParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest("{ \"name\": \"foo\" }");
ASSERT_EQ(manifest->name, "foo");
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest("{ \"name\": \" foo \" }");
ASSERT_EQ(manifest->name, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if name isn't a string.
{
auto& manifest = ParseManifest("{ \"name\": {} }");
ASSERT_TRUE(manifest->name.IsNull());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'name' ignored, type string expected.", errors()[0]);
}
// Don't parse if name isn't a string.
{
auto& manifest = ParseManifest("{ \"name\": 42 }");
ASSERT_TRUE(manifest->name.IsNull());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'name' ignored, type string expected.", errors()[0]);
}
// Test stripping out of \t \r and \n.
{
auto& manifest = ParseManifest("{ \"name\": \"abc\\t\\r\\ndef\" }");
ASSERT_EQ(manifest->name, "abcdef");
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, DescriptionParseRules) {
// Smoke test.
{
auto& manifest =
ParseManifest(R"({ "description": "foo is the new black" })");
ASSERT_EQ(manifest->description, "foo is the new black");
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest(R"({ "description": " foo " })");
ASSERT_EQ(manifest->description, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if description isn't a string.
{
auto& manifest = ParseManifest(R"({ "description": {} })");
ASSERT_TRUE(manifest->description.IsNull());
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'description' ignored, type string expected.",
errors()[0]);
}
// Don't parse if description isn't a string.
{
auto& manifest = ParseManifest(R"({ "description": 42 })");
ASSERT_TRUE(manifest->description.IsNull());
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'description' ignored, type string expected.",
errors()[0]);
}
}
TEST_F(ManifestParserTest, ShortNameParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest("{ \"short_name\": \"foo\" }");
ASSERT_EQ(manifest->short_name, "foo");
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest("{ \"short_name\": \" foo \" }");
ASSERT_EQ(manifest->short_name, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if name isn't a string.
{
auto& manifest = ParseManifest("{ \"short_name\": {} }");
ASSERT_TRUE(manifest->short_name.IsNull());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'short_name' ignored, type string expected.",
errors()[0]);
}
// Don't parse if name isn't a string.
{
auto& manifest = ParseManifest("{ \"short_name\": 42 }");
ASSERT_TRUE(manifest->short_name.IsNull());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'short_name' ignored, type string expected.",
errors()[0]);
}
// Test stripping out of \t \r and \n.
{
auto& manifest = ParseManifest("{ \"short_name\": \"abc\\t\\r\\ndef\" }");
ASSERT_EQ(manifest->short_name, "abcdef");
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, StartURLParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest("{ \"start_url\": \"land.html\" }");
ASSERT_EQ(manifest->start_url, KURL(DefaultDocumentUrl(), "land.html"));
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Whitespaces.
{
auto& manifest = ParseManifest("{ \"start_url\": \" land.html \" }");
ASSERT_EQ(manifest->start_url, KURL(DefaultDocumentUrl(), "land.html"));
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if property isn't a string.
{
auto& manifest = ParseManifest("{ \"start_url\": {} }");
ASSERT_TRUE(manifest->start_url.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'start_url' ignored, type string expected.",
errors()[0]);
}
// Don't parse if property isn't a string.
{
auto& manifest = ParseManifest("{ \"start_url\": 42 }");
ASSERT_TRUE(manifest->start_url.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'start_url' ignored, type string expected.",
errors()[0]);
}
// Don't parse if property isn't a valid URL.
{
auto& manifest =
ParseManifest("{ \"start_url\": \"http://www.google.ca:a\" }");
ASSERT_TRUE(manifest->start_url.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'start_url' ignored, URL is invalid.", errors()[0]);
}
// Absolute start_url, same origin with document.
{
auto& manifest =
ParseManifestWithURLs("{ \"start_url\": \"http://foo.com/land.html\" }",
KURL("http://foo.com/manifest.json"),
KURL("http://foo.com/index.html"));
ASSERT_EQ(manifest->start_url.GetString(), "http://foo.com/land.html");
EXPECT_EQ(0u, GetErrorCount());
}
// Absolute start_url, cross origin with document.
{
auto& manifest =
ParseManifestWithURLs("{ \"start_url\": \"http://bar.com/land.html\" }",
KURL("http://foo.com/manifest.json"),
KURL("http://foo.com/index.html"));
ASSERT_TRUE(manifest->start_url.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'start_url' ignored, should "
"be same origin as document.",
errors()[0]);
}
// Resolving has to happen based on the manifest_url.
{
auto& manifest =
ParseManifestWithURLs("{ \"start_url\": \"land.html\" }",
KURL("http://foo.com/landing/manifest.json"),
KURL("http://foo.com/index.html"));
ASSERT_EQ(manifest->start_url.GetString(),
"http://foo.com/landing/land.html");
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, ScopeParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest(
"{ \"scope\": \"land\", \"start_url\": \"land/landing.html\" }");
ASSERT_EQ(manifest->scope, KURL(DefaultDocumentUrl(), "land"));
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Whitespaces.
{
auto& manifest = ParseManifest(
"{ \"scope\": \" land \", \"start_url\": \"land/landing.html\" }");
ASSERT_EQ(manifest->scope, KURL(DefaultDocumentUrl(), "land"));
EXPECT_EQ(0u, GetErrorCount());
}
// Return the default value if the property isn't a string.
{
auto& manifest = ParseManifest("{ \"scope\": {} }");
ASSERT_EQ(manifest->scope.GetString(), DefaultDocumentUrl().BaseAsString());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'scope' ignored, type string expected.", errors()[0]);
}
// Return the default value if property isn't a string.
{
auto& manifest = ParseManifest(
"{ \"scope\": 42, "
"\"start_url\": \"http://foo.com/land/landing.html\" }");
ASSERT_EQ(manifest->scope, KURL(DefaultDocumentUrl(), "land/"));
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'scope' ignored, type string expected.", errors()[0]);
}
// Absolute scope, start URL is in scope.
{
auto& manifest = ParseManifestWithURLs(
"{ \"scope\": \"http://foo.com/land\", "
"\"start_url\": \"http://foo.com/land/landing.html\" }",
KURL("http://foo.com/manifest.json"),
KURL("http://foo.com/index.html"));
ASSERT_EQ(manifest->scope.GetString(), "http://foo.com/land");
EXPECT_EQ(0u, GetErrorCount());
}
// Absolute scope, start URL is not in scope.
{
auto& manifest = ParseManifestWithURLs(
"{ \"scope\": \"http://foo.com/land\", "
"\"start_url\": \"http://foo.com/index.html\" }",
KURL("http://foo.com/manifest.json"),
KURL("http://foo.com/index.html"));
ASSERT_EQ(manifest->scope.GetString(), DefaultDocumentUrl().BaseAsString());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'scope' ignored. Start url should be within scope "
"of scope URL.",
errors()[0]);
}
// Absolute scope, start URL has different origin than scope URL.
{
auto& manifest = ParseManifestWithURLs(
"{ \"scope\": \"http://foo.com/land\", "
"\"start_url\": \"http://bar.com/land/landing.html\" }",
KURL("http://foo.com/manifest.json"),
KURL("http://foo.com/index.html"));
ASSERT_EQ(manifest->scope.GetString(), DefaultDocumentUrl().BaseAsString());
ASSERT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"property 'start_url' ignored, should be same origin as document.",
errors()[0]);
EXPECT_EQ(
"property 'scope' ignored. Start url should be within scope "
"of scope URL.",
errors()[1]);
}
// scope and start URL have diferent origin than document URL.
{
KURL document_url("http://bar.com/index.html");
auto& manifest = ParseManifestWithURLs(
"{ \"scope\": \"http://foo.com/land\", "
"\"start_url\": \"http://foo.com/land/landing.html\" }",
KURL("http://foo.com/manifest.json"), document_url);
ASSERT_EQ(manifest->scope.GetString(), document_url.BaseAsString());
ASSERT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"property 'start_url' ignored, should be same origin as document.",
errors()[0]);
EXPECT_EQ(
"property 'scope' ignored. Start url should be within scope "
"of scope URL.",
errors()[1]);
}
// No start URL. Document URL is in a subdirectory of scope.
{
auto& manifest =
ParseManifestWithURLs("{ \"scope\": \"http://foo.com/land\" }",
KURL("http://foo.com/manifest.json"),
KURL("http://foo.com/land/site/index.html"));
ASSERT_EQ(manifest->scope.GetString(), "http://foo.com/land");
ASSERT_EQ(0u, GetErrorCount());
}
// No start URL. Document is out of scope.
{
KURL document_url("http://foo.com/index.html");
auto& manifest =
ParseManifestWithURLs("{ \"scope\": \"http://foo.com/land\" }",
KURL("http://foo.com/manifest.json"),
KURL("http://foo.com/index.html"));
ASSERT_EQ(manifest->scope.GetString(), document_url.BaseAsString());
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'scope' ignored. Start url should be within scope "
"of scope URL.",
errors()[0]);
}
// Resolving has to happen based on the manifest_url.
{
auto& manifest = ParseManifestWithURLs(
"{ \"scope\": \"treasure\" }", KURL("http://foo.com/map/manifest.json"),
KURL("http://foo.com/map/treasure/island/index.html"));
ASSERT_EQ(manifest->scope.GetString(), "http://foo.com/map/treasure");
EXPECT_EQ(0u, GetErrorCount());
}
// Scope is parent directory.
{
auto& manifest = ParseManifestWithURLs(
"{ \"scope\": \"..\" }", KURL("http://foo.com/map/manifest.json"),
KURL("http://foo.com/index.html"));
ASSERT_EQ(manifest->scope.GetString(), "http://foo.com/");
EXPECT_EQ(0u, GetErrorCount());
}
// Scope tries to go up past domain.
{
auto& manifest = ParseManifestWithURLs(
"{ \"scope\": \"../..\" }", KURL("http://foo.com/map/manifest.json"),
KURL("http://foo.com/index.html"));
ASSERT_EQ(manifest->scope.GetString(), "http://foo.com/");
EXPECT_EQ(0u, GetErrorCount());
}
// Scope defaults to start_url with the filename, query, and fragment removed.
{
auto& manifest = ParseManifest("{ \"start_url\": \"land/landing.html\" }");
ASSERT_EQ(manifest->scope, KURL(DefaultDocumentUrl(), "land/"));
EXPECT_EQ(0u, GetErrorCount());
}
{
auto& manifest =
ParseManifest("{ \"start_url\": \"land/land/landing.html\" }");
ASSERT_EQ(manifest->scope, KURL(DefaultDocumentUrl(), "land/land/"));
EXPECT_EQ(0u, GetErrorCount());
}
// Scope defaults to document_url if start_url is not present.
{
auto& manifest = ParseManifest("{}");
ASSERT_EQ(manifest->scope, KURL(DefaultDocumentUrl(), "."));
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, DisplayParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest("{ \"display\": \"browser\" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kBrowser);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest("{ \"display\": \" browser \" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kBrowser);
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if name isn't a string.
{
auto& manifest = ParseManifest("{ \"display\": {} }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kUndefined);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'display' ignored,"
" type string expected.",
errors()[0]);
}
// Don't parse if name isn't a string.
{
auto& manifest = ParseManifest("{ \"display\": 42 }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kUndefined);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'display' ignored,"
" type string expected.",
errors()[0]);
}
// Parse fails if string isn't known.
{
auto& manifest = ParseManifest("{ \"display\": \"browser_something\" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kUndefined);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("unknown 'display' value ignored.", errors()[0]);
}
// Accept 'fullscreen'.
{
auto& manifest = ParseManifest("{ \"display\": \"fullscreen\" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kFullscreen);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'standalone'.
{
auto& manifest = ParseManifest("{ \"display\": \"standalone\" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kStandalone);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'minimal-ui'.
{
auto& manifest = ParseManifest("{ \"display\": \"minimal-ui\" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kMinimalUi);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'browser'.
{
auto& manifest = ParseManifest("{ \"display\": \"browser\" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kBrowser);
EXPECT_EQ(0u, GetErrorCount());
}
// Case insensitive.
{
auto& manifest = ParseManifest("{ \"display\": \"BROWSER\" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kBrowser);
EXPECT_EQ(0u, GetErrorCount());
}
// Parsing fails for 'window-controls-overlay' when WCO flag is disabled.
{
auto& manifest =
ParseManifest("{ \"display\": \"window-controls-overlay\" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kUndefined);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("inapplicable 'display' value ignored.", errors()[0]);
}
// Parsing fails for 'window-controls-overlay' when WCO flag is enabled.
{
ScopedWebAppWindowControlsOverlayForTest window_controls_overlay(true);
auto& manifest =
ParseManifest("{ \"display\": \"window-controls-overlay\" }");
EXPECT_EQ(manifest->display, blink::mojom::DisplayMode::kUndefined);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("inapplicable 'display' value ignored.", errors()[0]);
}
}
TEST_F(ManifestParserTest, DisplayOverrideParseRules) {
ScopedWebAppManifestDisplayOverrideForTest display_override(true);
// 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());
}
// Reject 'window-controls-overlay' when WCO flag is disabled.
{
auto& manifest = ParseManifest(
"{ \"display_override\": [ \"window-controls-overlay\" ] }");
EXPECT_TRUE(manifest->display_override.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'window-controls-overlay' when WCO flag is enabled.
{
ScopedWebAppWindowControlsOverlayForTest window_controls_overlay(true);
auto& manifest = ParseManifest(
"{ \"display_override\": [ \"window-controls-overlay\" ] }");
EXPECT_FALSE(manifest->display_override.IsEmpty());
EXPECT_EQ(manifest->display_override[0],
blink::mojom::DisplayMode::kWindowControlsOverlay);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, OrientationParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest("{ \"orientation\": \"natural\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::NATURAL);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest("{ \"orientation\": \"natural\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::NATURAL);
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if name isn't a string.
{
auto& manifest = ParseManifest("{ \"orientation\": {} }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::DEFAULT);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'orientation' ignored, type string expected.",
errors()[0]);
}
// Don't parse if name isn't a string.
{
auto& manifest = ParseManifest("{ \"orientation\": 42 }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::DEFAULT);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'orientation' ignored, type string expected.",
errors()[0]);
}
// Parse fails if string isn't known.
{
auto& manifest = ParseManifest("{ \"orientation\": \"naturalish\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::DEFAULT);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("unknown 'orientation' value ignored.", errors()[0]);
}
// Accept 'any'.
{
auto& manifest = ParseManifest("{ \"orientation\": \"any\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::ANY);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'natural'.
{
auto& manifest = ParseManifest("{ \"orientation\": \"natural\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::NATURAL);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'landscape'.
{
auto& manifest = ParseManifest("{ \"orientation\": \"landscape\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::LANDSCAPE);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'landscape-primary'.
{
auto& manifest =
ParseManifest("{ \"orientation\": \"landscape-primary\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::LANDSCAPE_PRIMARY);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'landscape-secondary'.
{
auto& manifest =
ParseManifest("{ \"orientation\": \"landscape-secondary\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::LANDSCAPE_SECONDARY);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'portrait'.
{
auto& manifest = ParseManifest("{ \"orientation\": \"portrait\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::PORTRAIT);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'portrait-primary'.
{
auto& manifest = ParseManifest("{ \"orientation\": \"portrait-primary\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::PORTRAIT_PRIMARY);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept 'portrait-secondary'.
{
auto& manifest =
ParseManifest("{ \"orientation\": \"portrait-secondary\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::PORTRAIT_SECONDARY);
EXPECT_EQ(0u, GetErrorCount());
}
// Case insensitive.
{
auto& manifest = ParseManifest("{ \"orientation\": \"LANDSCAPE\" }");
EXPECT_EQ(manifest->orientation,
device::mojom::ScreenOrientationLockType::LANDSCAPE);
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, IconsParseRules) {
// Smoke test: if no icon, no value.
{
auto& manifest = ParseManifest("{ \"icons\": [] }");
EXPECT_TRUE(manifest->icons.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if empty icon, no value.
{
auto& manifest = ParseManifest("{ \"icons\": [ {} ] }");
EXPECT_TRUE(manifest->icons.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: icon with invalid src, no value.
{
auto& manifest = ParseManifest("{ \"icons\": [ { \"icons\": [] } ] }");
EXPECT_TRUE(manifest->icons.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if icon with empty src, it will be present in the list.
{
auto& manifest = ParseManifest("{ \"icons\": [ { \"src\": \"\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
EXPECT_EQ(icons.size(), 1u);
EXPECT_EQ(icons[0]->src.GetString(), "http://foo.com/manifest.json");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if one icons with valid src, it will be present in the list.
{
auto& manifest = ParseManifest("{ \"icons\": [{ \"src\": \"foo.jpg\" }] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
EXPECT_EQ(icons.size(), 1u);
EXPECT_EQ(icons[0]->src.GetString(), "http://foo.com/foo.jpg");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, ScreenshotsParseRules) {
// Smoke test: if no screenshot, no value.
{
auto& manifest = ParseManifest(R"({ "screenshots": [] })");
EXPECT_TRUE(manifest->screenshots.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if empty screenshot, no value.
{
auto& manifest = ParseManifest(R"({ "screenshots": [ {} ] })");
EXPECT_TRUE(manifest->screenshots.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: screenshot with invalid src, no value.
{
auto& manifest =
ParseManifest(R"({ "screenshots": [ { "screenshots": [] } ] })");
EXPECT_TRUE(manifest->screenshots.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if screenshot with empty src, it will be present in the list.
{
auto& manifest = ParseManifest(R"({ "screenshots": [ { "src": "" } ] })");
EXPECT_FALSE(manifest->screenshots.IsEmpty());
auto& screenshots = manifest->screenshots;
EXPECT_EQ(screenshots.size(), 1u);
EXPECT_EQ(screenshots[0]->src.GetString(), "http://foo.com/manifest.json");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if one icons has valid src, it will be present in the list.
{
auto& manifest =
ParseManifest(R"({ "screenshots": [{ "src": "foo.jpg" }] })");
EXPECT_FALSE(manifest->screenshots.IsEmpty());
auto& screenshots = manifest->screenshots;
EXPECT_EQ(screenshots.size(), 1u);
EXPECT_EQ(screenshots[0]->src.GetString(), "http://foo.com/foo.jpg");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, IconSrcParseRules) {
// Smoke test.
{
auto& manifest =
ParseManifest("{ \"icons\": [ {\"src\": \"foo.png\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->src, KURL(DefaultDocumentUrl(), "foo.png"));
EXPECT_EQ(0u, GetErrorCount());
}
// Whitespaces.
{
auto& manifest =
ParseManifest("{ \"icons\": [ {\"src\": \" foo.png \" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->src, KURL(DefaultDocumentUrl(), "foo.png"));
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if property isn't a string.
{
auto& manifest = ParseManifest("{ \"icons\": [ {\"src\": {} } ] }");
EXPECT_TRUE(manifest->icons.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'src' ignored, type string expected.", errors()[0]);
}
// Don't parse if property isn't a string.
{
auto& manifest = ParseManifest("{ \"icons\": [ {\"src\": 42 } ] }");
EXPECT_TRUE(manifest->icons.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'src' ignored, type string expected.", errors()[0]);
}
// Resolving has to happen based on the document_url.
{
auto& manifest = ParseManifestWithURLs(
"{ \"icons\": [ {\"src\": \"icons/foo.png\" } ] }",
KURL("http://foo.com/landing/index.html"), DefaultManifestUrl());
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->src.GetString(),
"http://foo.com/landing/icons/foo.png");
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, IconTypeParseRules) {
// Smoke test.
{
auto& manifest =
ParseManifest("{ \"icons\": [ {\"src\": \"\", \"type\": \"foo\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->type, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
" \"type\": \" foo \" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->type, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if property isn't a string.
{
auto& manifest =
ParseManifest("{ \"icons\": [ {\"src\": \"\", \"type\": {} } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_TRUE(manifest->icons[0]->type.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'type' ignored, type string expected.", errors()[0]);
}
// Don't parse if property isn't a string.
{
auto& manifest =
ParseManifest("{ \"icons\": [ {\"src\": \"\", \"type\": 42 } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_TRUE(manifest->icons[0]->type.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'type' ignored, type string expected.", errors()[0]);
}
}
TEST_F(ManifestParserTest, IconSizesParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": \"42x42\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->sizes.size(), 1u);
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": \" 42x42 \" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->sizes.size(), 1u);
EXPECT_EQ(0u, GetErrorCount());
}
// Ignore sizes if property isn't a string.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": {} } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->sizes.size(), 0u);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'sizes' ignored, type string expected.", errors()[0]);
}
// Ignore sizes if property isn't a string.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": 42 } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->sizes.size(), 0u);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'sizes' ignored, type string expected.", errors()[0]);
}
// Smoke test: value correctly parsed.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": \"42x42 48x48\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
EXPECT_EQ(icons[0]->sizes[0], gfx::Size(42, 42));
EXPECT_EQ(icons[0]->sizes[1], gfx::Size(48, 48));
EXPECT_EQ(0u, GetErrorCount());
}
// <WIDTH>'x'<HEIGHT> and <WIDTH>'X'<HEIGHT> are equivalent.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": \"42X42 48X48\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
EXPECT_EQ(icons[0]->sizes[0], gfx::Size(42, 42));
EXPECT_EQ(icons[0]->sizes[1], gfx::Size(48, 48));
EXPECT_EQ(0u, GetErrorCount());
}
// Twice the same value is parsed twice.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": \"42X42 42x42\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
EXPECT_EQ(icons[0]->sizes[0], gfx::Size(42, 42));
EXPECT_EQ(icons[0]->sizes[1], gfx::Size(42, 42));
EXPECT_EQ(0u, GetErrorCount());
}
// Width or height can't start with 0.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": \"004X007 042x00\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->sizes.size(), 0u);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("found icon with no valid size.", errors()[0]);
}
// Width and height MUST contain digits.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": \"e4X1.0 55ax1e10\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->sizes.size(), 0u);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("found icon with no valid size.", errors()[0]);
}
// 'any' is correctly parsed and transformed to gfx::Size(0,0).
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": \"any AnY ANY aNy\" } ] }");
gfx::Size any = gfx::Size(0, 0);
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
EXPECT_EQ(icons[0]->sizes.size(), 4u);
EXPECT_EQ(icons[0]->sizes[0], any);
EXPECT_EQ(icons[0]->sizes[1], any);
EXPECT_EQ(icons[0]->sizes[2], any);
EXPECT_EQ(icons[0]->sizes[3], any);
EXPECT_EQ(0u, GetErrorCount());
}
// Some invalid width/height combinations.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"sizes\": \"x 40xx 1x2x3 x42 42xx42\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->sizes.size(), 0u);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("found icon with no valid size.", errors()[0]);
}
}
TEST_F(ManifestParserTest, IconPurposeParseRules) {
const String kPurposeParseStringError =
"property 'purpose' ignored, type string expected.";
const String kPurposeInvalidValueError =
"found icon with no valid purpose; ignoring it.";
const String kSomeInvalidPurposeError =
"found icon with one or more invalid purposes; those purposes are "
"ignored.";
// Smoke test.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"purpose\": \"any\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->purpose.size(), 1u);
EXPECT_EQ(0u, GetErrorCount());
}
// Trim leading and trailing whitespaces.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"purpose\": \" any \" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
EXPECT_EQ(manifest->icons[0]->purpose.size(), 1u);
EXPECT_EQ(0u, GetErrorCount());
}
// 'any' is added when property isn't present.
{
auto& manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
EXPECT_EQ(icons[0]->purpose.size(), 1u);
EXPECT_EQ(icons[0]->purpose[0],
mojom::blink::ManifestImageResource::Purpose::ANY);
EXPECT_EQ(0u, GetErrorCount());
}
// 'any' is added with error message when property isn't a string (is a
// number).
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"purpose\": 42 } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
EXPECT_EQ(icons[0]->purpose.size(), 1u);
EXPECT_EQ(icons[0]->purpose[0],
mojom::blink::ManifestImageResource::Purpose::ANY);
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(kPurposeParseStringError, errors()[0]);
}
// 'any' is added with error message when property isn't a string (is a
// dictionary).
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"purpose\": {} } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
EXPECT_EQ(icons[0]->purpose.size(), 1u);
EXPECT_EQ(icons[0]->purpose[0],
mojom::blink::ManifestImageResource::Purpose::ANY);
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(kPurposeParseStringError, errors()[0]);
}
// Smoke test: values correctly parsed.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"purpose\": \"Any Monochrome Maskable\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
ASSERT_EQ(icons[0]->purpose.size(), 3u);
EXPECT_EQ(icons[0]->purpose[0],
mojom::blink::ManifestImageResource::Purpose::ANY);
EXPECT_EQ(icons[0]->purpose[1],
mojom::blink::ManifestImageResource::Purpose::MONOCHROME);
EXPECT_EQ(icons[0]->purpose[2],
mojom::blink::ManifestImageResource::Purpose::MASKABLE);
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces between values.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"purpose\": \" Any Monochrome \" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
ASSERT_EQ(icons[0]->purpose.size(), 2u);
EXPECT_EQ(icons[0]->purpose[0],
mojom::blink::ManifestImageResource::Purpose::ANY);
EXPECT_EQ(icons[0]->purpose[1],
mojom::blink::ManifestImageResource::Purpose::MONOCHROME);
EXPECT_EQ(0u, GetErrorCount());
}
// Twice the same value is parsed twice.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"purpose\": \"monochrome monochrome\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
ASSERT_EQ(icons[0]->purpose.size(), 2u);
EXPECT_EQ(icons[0]->purpose[0],
mojom::blink::ManifestImageResource::Purpose::MONOCHROME);
EXPECT_EQ(icons[0]->purpose[1],
mojom::blink::ManifestImageResource::Purpose::MONOCHROME);
EXPECT_EQ(0u, GetErrorCount());
}
// Invalid icon purpose is ignored.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"purpose\": \"monochrome fizzbuzz\" } ] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
ASSERT_EQ(icons[0]->purpose.size(), 1u);
EXPECT_EQ(icons[0]->purpose[0],
mojom::blink::ManifestImageResource::Purpose::MONOCHROME);
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(kSomeInvalidPurposeError, errors()[0]);
}
// If developer-supplied purpose is invalid, entire icon is removed.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\","
"\"purpose\": \"fizzbuzz\" } ] }");
ASSERT_TRUE(manifest->icons.IsEmpty());
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(kPurposeInvalidValueError, errors()[0]);
}
// Two icons, one with an invalid purpose and the other normal.
{
auto& manifest = ParseManifest(
"{ \"icons\": [ {\"src\": \"\", \"purpose\": \"fizzbuzz\" }, "
" {\"src\": \"\" }] }");
EXPECT_FALSE(manifest->icons.IsEmpty());
auto& icons = manifest->icons;
ASSERT_EQ(1u, icons.size());
ASSERT_EQ(icons[0]->purpose.size(), 1u);
EXPECT_EQ(icons[0]->purpose[0],
mojom::blink::ManifestImageResource::Purpose::ANY);
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(kPurposeInvalidValueError, errors()[0]);
}
}
TEST_F(ManifestParserTest, ShortcutsParseRules) {
// Smoke test: if no shortcut, no value.
{
auto& manifest = ParseManifest("{ \"shortcuts\": [] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if empty shortcut, no value.
{
auto& manifest = ParseManifest("{ \"shortcuts\": [ {} ] }");
EXPECT_TRUE(manifest->icons.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'url' of 'shortcut' not present.", errors()[0]);
}
// Smoke test: shortcut with invalid name and url, it will not be present in
// the list.
{
auto& manifest =
ParseManifest("{ \"shortcuts\": [ { \"shortcuts\": [] } ] }");
EXPECT_TRUE(manifest->icons.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'url' of 'shortcut' not present.", errors()[0]);
}
// Smoke test: shortcut with no name, it will not be present in the list.
{
auto& manifest = ParseManifest("{ \"shortcuts\": [ { \"url\": \"\" } ] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'name' of 'shortcut' not present.", errors()[0]);
}
// Smoke test: shortcut with no url, it will not be present in the list.
{
auto& manifest = ParseManifest("{ \"shortcuts\": [ { \"name\": \"\" } ] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'url' of 'shortcut' not present.", errors()[0]);
}
// Smoke test: shortcut with empty name, and empty src, will not be present in
// the list.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ { \"name\": \"\", \"url\": \"\" } ] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'name' of 'shortcut' is an empty string.", errors()[0]);
}
// Smoke test: shortcut with valid (non-empty) name and src, will be present
// in the list.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [{ \"name\": \"New Post\", \"url\": \"compose\" }] "
"}");
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
auto& shortcuts = manifest->shortcuts;
EXPECT_EQ(shortcuts.size(), 1u);
EXPECT_EQ(shortcuts[0]->name, "New Post");
EXPECT_EQ(shortcuts[0]->url.GetString(), "http://foo.com/compose");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, ShortcutNameParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"foo\", \"url\": \"NameParseTest\" } ] "
"}");
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(manifest->shortcuts[0]->name, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \" foo \", \"url\": \"NameParseTest\" "
"} ] }");
ASSERT_EQ(manifest->shortcuts[0]->name, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if shortcut->name isn't present.
{
auto& manifest =
ParseManifest("{ \"shortcuts\": [ {\"url\": \"NameParseTest\" } ] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'name' of 'shortcut' not present.", errors()[0]);
}
// Don't parse if shortcut->name isn't a string.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": {}, \"url\": \"NameParseTest\" } ] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'name' of 'shortcut' ignored, type string expected.",
errors()[0]);
}
// Don't parse if shortcut->name isn't a string.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": 42, \"url\": \"NameParseTest\" } ] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'name' of 'shortcut' ignored, type string expected.",
errors()[0]);
}
// Don't parse if shortcut->name is an empty string.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"\", \"url\": \"NameParseTest\" } ] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'name' of 'shortcut' is an empty string.", errors()[0]);
}
}
TEST_F(ManifestParserTest, ShortcutShortNameParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"ShortNameParseTest\", \"short_name\": "
"\"foo\", \"url\": \"ShortNameParseTest\" } ] }");
ASSERT_EQ(manifest->shortcuts[0]->short_name, "foo");
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Shortcut member is parsed when no short_name is present
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"ShortNameParseTest\", \"url\": "
"\"ShortNameParseTest\" } ] }");
ASSERT_TRUE(manifest->shortcuts[0]->short_name.IsNull());
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"ShortNameParseTest\", \"short_name\": "
"\" foo \", \"url\": \"ShortNameParseTest\" } ] }");
ASSERT_EQ(manifest->shortcuts[0]->short_name, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse short_name if it isn't a string.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"ShortNameParseTest\", \"short_name\": "
"{}, \"url\": \"ShortNameParseTest\" } ] }");
ASSERT_TRUE(manifest->shortcuts[0]->short_name.IsNull());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'short_name' of 'shortcut' ignored, type string expected.",
errors()[0]);
}
// Don't parse short_name if it isn't a string.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"ShortNameParseTest\", \"short_name\": "
"42, \"url\": \"ShortNameParseTest\" } ] }");
ASSERT_TRUE(manifest->shortcuts[0]->short_name.IsNull());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'short_name' of 'shortcut' ignored, type string expected.",
errors()[0]);
}
}
TEST_F(ManifestParserTest, ShortcutDescriptionParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"DescriptionParseTest\", "
"\"description\": "
"\"foo\", \"url\": \"DescriptionParseTest\" } ] }");
ASSERT_EQ(manifest->shortcuts[0]->description, "foo");
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Shortcut member is parsed when no description is present
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"DescriptionParseTest\", \"url\": "
"\"DescriptionParseTest\" } ] }");
ASSERT_TRUE(manifest->shortcuts[0]->description.IsNull());
ASSERT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"DescriptionParseTest\", "
"\"description\": "
"\" foo \", \"url\": \"DescriptionParseTest\" } ] }");
ASSERT_EQ(manifest->shortcuts[0]->description, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse description if it isn't a string.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"DescriptionParseTest\", "
"\"description\": "
"{}, \"url\": \"DescriptionParseTest\" } ] }");
ASSERT_TRUE(manifest->shortcuts[0]->description.IsNull());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'description' of 'shortcut' ignored, type string expected.",
errors()[0]);
}
// Don't parse description if it isn't a string.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"DescriptionParseTest\", "
"\"description\": "
"42, \"url\": \"DescriptionParseTest\" } ] }");
ASSERT_TRUE(manifest->shortcuts[0]->description.IsNull());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'description' of 'shortcut' ignored, type string expected.",
errors()[0]);
}
}
TEST_F(ManifestParserTest, ShortcutUrlParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"UrlParseTest\", \"url\": \"foo\" } ] "
"}");
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(manifest->shortcuts[0]->url, KURL(DefaultDocumentUrl(), "foo"));
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test. Don't parse (with an error) when url is not present.
{
auto& manifest = ParseManifest("{ \"shortcuts\": [ { \"name\": \"\" } ] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'url' of 'shortcut' not present.", errors()[0]);
}
// Whitespaces.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"UrlParseTest\", \"url\": \" foo "
"\" } ] }");
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(manifest->shortcuts[0]->url, KURL(DefaultDocumentUrl(), "foo"));
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if url isn't a string.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"UrlParseTest\", \"url\": {} } ] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'url' ignored, type string expected.", errors()[0]);
EXPECT_EQ("property 'url' of 'shortcut' not present.", errors()[1]);
}
// Don't parse if url isn't a string.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"UrlParseTest\", \"url\": 42 } ] }");
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'url' ignored, type string expected.", errors()[0]);
EXPECT_EQ("property 'url' of 'shortcut' not present.", errors()[1]);
}
// Resolving has to happen based on the manifest_url.
{
auto& manifest = ParseManifestWithURLs(
"{ \"shortcuts\": [ {\"name\": \"UrlParseTest\", \"url\": \"foo\" } ] "
"}",
KURL("http://foo.com/landing/manifest.json"), DefaultDocumentUrl());
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(manifest->shortcuts[0]->url.GetString(),
"http://foo.com/landing/foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Shortcut url should have same origin as the document url.
{
auto& manifest = ParseManifestWithURLs(
"{ \"shortcuts\": [ {\"name\": \"UrlParseTest\", \"url\": "
"\"http://bar.com/landing\" } ] "
"}",
KURL("http://foo.com/landing/manifest.json"), DefaultDocumentUrl());
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'url' ignored, should be within scope of the manifest.",
errors()[0]);
EXPECT_EQ("property 'url' of 'shortcut' not present.", errors()[1]);
}
// Shortcut url should be within the manifest scope.
// The scope will be http://foo.com/landing.
// The shortcut_url will be http://foo.com/shortcut which is in not in scope.
{
auto& manifest = ParseManifestWithURLs(
"{ \"scope\": \"http://foo.com/landing\", \"shortcuts\": [ {\"name\": "
"\"UrlParseTest\", \"url\": \"shortcut\" } ] }",
KURL("http://foo.com/manifest.json"),
KURL("http://foo.com/landing/index.html"));
EXPECT_TRUE(manifest->shortcuts.IsEmpty());
ASSERT_EQ(manifest->scope.GetString(), "http://foo.com/landing");
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'url' ignored, should be within scope of the manifest.",
errors()[0]);
EXPECT_EQ("property 'url' of 'shortcut' not present.", errors()[1]);
}
// Shortcut url should be within the manifest scope.
// The scope will be http://foo.com/land.
// The shortcut_url will be http://foo.com/land/shortcut which is in scope.
{
auto& manifest = ParseManifestWithURLs(
"{ \"scope\": \"http://foo.com/land\", \"start_url\": "
"\"http://foo.com/land/landing.html\", \"shortcuts\": [ {\"name\": "
"\"UrlParseTest\", \"url\": \"shortcut\" } ] }",
KURL("http://foo.com/land/manifest.json"),
KURL("http://foo.com/index.html"));
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
ASSERT_EQ(manifest->scope.GetString(), "http://foo.com/land");
EXPECT_EQ(manifest->shortcuts[0]->url.GetString(),
"http://foo.com/land/shortcut");
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, ShortcutIconsParseRules) {
// Smoke test: if no icons, shortcut->icons has no value.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"IconParseTest\", \"url\": \"foo\", "
"\"icons\": [] } ] }");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
EXPECT_TRUE(manifest->shortcuts[0]->icons.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if empty icon, shortcut->icons has no value.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"IconParseTest\", \"url\": \"foo\", "
"\"icons\": [{}] } ] }");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
EXPECT_TRUE(manifest->shortcuts[0]->icons.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: icon with invalid src, shortcut->icons has no value.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"IconParseTest\", \"url\": \"foo\", "
"\"icons\": [{ \"icons\": [] }] } ] }");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
EXPECT_TRUE(manifest->shortcuts[0]->icons.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if icon with empty src, it will be present in shortcut->icons.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"IconParseTest\", \"url\": \"foo\", "
"\"icons\": [ { \"src\": \"\" } ] } ] }");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
EXPECT_FALSE(manifest->shortcuts[0]->icons.IsEmpty());
auto& icons = manifest->shortcuts[0]->icons;
EXPECT_EQ(icons.size(), 1u);
EXPECT_EQ(icons[0]->src.GetString(), "http://foo.com/manifest.json");
EXPECT_EQ(0u, GetErrorCount());
}
// Smoke test: if one icon with valid src, it will be present in
// shortcut->icons.
{
auto& manifest = ParseManifest(
"{ \"shortcuts\": [ {\"name\": \"IconParseTest\", \"url\": \"foo\", "
"\"icons\": [ { \"src\": \"foo.jpg\" } ] } ] }");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_FALSE(manifest->shortcuts.IsEmpty());
EXPECT_FALSE(manifest->shortcuts[0]->icons.IsEmpty());
auto& icons = manifest->shortcuts[0]->icons;
EXPECT_EQ(icons.size(), 1u);
EXPECT_EQ(icons[0]->src.GetString(), "http://foo.com/foo.jpg");
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, FileHandlerParseRules) {
// Does not contain file_handlers field.
{
auto& manifest = ParseManifest("{ }");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ(0u, manifest->file_handlers.size());
}
// file_handlers is not an array.
{
auto& manifest = ParseManifest("{ \"file_handlers\": { } }");
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'file_handlers' ignored, type array expected.",
errors()[0]);
EXPECT_EQ(0u, manifest->file_handlers.size());
}
// Contains file_handlers field but no file handlers.
{
auto& manifest = ParseManifest("{ \"file_handlers\": [ ] }");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ(0u, manifest->file_handlers.size());
}
// Entries must be objects
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" \"hello world\""
" ]"
"}");
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ("FileHandler ignored, type object expected.", errors()[0]);
EXPECT_EQ(0u, manifest->file_handlers.size());
}
// Entry without an action is invalid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"accept\": {"
" \"image/png\": ["
" \".png\""
" ]"
" }"
" }"
" ]"
"}");
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ("FileHandler ignored. Property 'action' is invalid.",
errors()[0]);
EXPECT_EQ(0u, manifest->file_handlers.size());
}
// Entry with an action on a different origin is invalid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"action\": \"https://example.com/files\","
" \"accept\": {"
" \"image/png\": ["
" \".png\""
" ]"
" }"
" }"
" ]"
"}");
ASSERT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"property 'action' ignored, should be within scope of the manifest.",
errors()[0]);
EXPECT_EQ("FileHandler ignored. Property 'action' is invalid.",
errors()[1]);
EXPECT_EQ(0u, manifest->file_handlers.size());
}
// Entry with an action outside of the manifest scope is invalid.
{
auto& manifest = ParseManifest(
"{"
" \"start_url\": \"/app/\","
" \"scope\": \"/app/\","
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"action\": \"/files\","
" \"accept\": {"
" \"image/png\": ["
" \".png\""
" ]"
" }"
" }"
" ]"
"}");
ASSERT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"property 'action' ignored, should be within scope of the manifest.",
errors()[0]);
EXPECT_EQ("FileHandler ignored. Property 'action' is invalid.",
errors()[1]);
EXPECT_EQ(0u, manifest->file_handlers.size());
}
// Entry without a name is valid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"action\": \"/files\","
" \"accept\": {"
" \"image/png\": ["
" \".png\""
" ]"
" }"
" }"
" ]"
"}");
ASSERT_EQ(0u, GetErrorCount());
EXPECT_EQ(1u, manifest->file_handlers.size());
}
// Entry without an accept is invalid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"action\": \"/files\""
" }"
" ]"
"}");
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ("FileHandler ignored. Property 'accept' is invalid.",
errors()[0]);
EXPECT_EQ(0u, manifest->file_handlers.size());
}
// Entry where accept is not an object is invalid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"action\": \"/files\","
" \"accept\": \"image/png\""
" }"
" ]"
"}");
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ("FileHandler ignored. Property 'accept' is invalid.",
errors()[0]);
EXPECT_EQ(0u, manifest->file_handlers.size());
}
// Entry where accept extensions are not an array or string is invalid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"action\": \"/files\","
" \"accept\": {"
" \"image/png\": {}"
" }"
" }"
" ]"
"}");
ASSERT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"property 'accept' type ignored. File extensions must be type array or "
"type string.",
errors()[0]);
EXPECT_EQ("FileHandler ignored. Property 'accept' is invalid.",
errors()[1]);
EXPECT_EQ(0u, manifest->file_handlers.size());
}
// Entry where accept extensions are not an array or string is invalid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"action\": \"/files\","
" \"accept\": {"
" \"image/png\": ["
" {}"
" ]"
" }"
" }"
" ]"
"}");
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'accept' file extension ignored, type string expected.",
errors()[0]);
EXPECT_EQ(1u, manifest->file_handlers.size());
}
// Entry with an empty list of extensions is valid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"action\": \"/files\","
" \"accept\": {"
" \"image/png\": []"
" }"
" }"
" ]"
"}");
auto& file_handlers = manifest->file_handlers;
ASSERT_EQ(0u, GetErrorCount());
ASSERT_EQ(1u, file_handlers.size());
EXPECT_EQ("name", file_handlers[0]->name);
EXPECT_EQ(KURL("http://foo.com/files"), file_handlers[0]->action);
ASSERT_TRUE(file_handlers[0]->accept.Contains("image/png"));
EXPECT_EQ(0u, file_handlers[0]->accept.find("image/png")->value.size());
}
// Extensions that do not start with a '.' are invalid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"action\": \"/files\","
" \"accept\": {"
" \"image/png\": ["
" \"png\""
" ]"
" }"
" }"
" ]"
"}");
auto& file_handlers = manifest->file_handlers;
ASSERT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'accept' file extension ignored, must start with a '.'.",
errors()[0]);
ASSERT_EQ(1u, file_handlers.size());
EXPECT_EQ("name", file_handlers[0]->name);
EXPECT_EQ(KURL("http://foo.com/files"), file_handlers[0]->action);
ASSERT_TRUE(file_handlers[0]->accept.Contains("image/png"));
EXPECT_EQ(0u, file_handlers[0]->accept.find("image/png")->value.size());
}
// Extensions specified as a single string is valid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"action\": \"/files\","
" \"accept\": {"
" \"image/png\": \".png\""
" }"
" }"
" ]"
"}");
auto& file_handlers = manifest->file_handlers;
ASSERT_EQ(0u, GetErrorCount());
ASSERT_EQ(1u, file_handlers.size());
EXPECT_EQ("name", file_handlers[0]->name);
EXPECT_EQ(KURL("http://foo.com/files"), file_handlers[0]->action);
ASSERT_TRUE(file_handlers[0]->accept.Contains("image/png"));
ASSERT_EQ(1u, file_handlers[0]->accept.find("image/png")->value.size());
EXPECT_EQ(".png", file_handlers[0]->accept.find("image/png")->value[0]);
}
// An array of extensions is valid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"name\","
" \"action\": \"/files\","
" \"accept\": {"
" \"image/jpg\": ["
" \".jpg\","
" \".jpeg\""
" ]"
" }"
" }"
" ]"
"}");
auto& file_handlers = manifest->file_handlers;
ASSERT_EQ(0u, GetErrorCount());
ASSERT_EQ(1u, file_handlers.size());
EXPECT_EQ("name", file_handlers[0]->name);
EXPECT_EQ(KURL("http://foo.com/files"), file_handlers[0]->action);
ASSERT_TRUE(file_handlers[0]->accept.Contains("image/jpg"));
ASSERT_EQ(2u, file_handlers[0]->accept.find("image/jpg")->value.size());
EXPECT_EQ(".jpg", file_handlers[0]->accept.find("image/jpg")->value[0]);
EXPECT_EQ(".jpeg", file_handlers[0]->accept.find("image/jpg")->value[1]);
}
// Multiple mime types are valid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"Image\","
" \"action\": \"/files\","
" \"accept\": {"
" \"image/png\": \".png\","
" \"image/jpg\": ["
" \".jpg\","
" \".jpeg\""
" ]"
" }"
" }"
" ]"
"}");
auto& file_handlers = manifest->file_handlers;
ASSERT_EQ(0u, GetErrorCount());
ASSERT_EQ(1u, file_handlers.size());
EXPECT_EQ("Image", file_handlers[0]->name);
EXPECT_EQ(KURL("http://foo.com/files"), file_handlers[0]->action);
ASSERT_TRUE(file_handlers[0]->accept.Contains("image/jpg"));
ASSERT_EQ(2u, file_handlers[0]->accept.find("image/jpg")->value.size());
EXPECT_EQ(".jpg", file_handlers[0]->accept.find("image/jpg")->value[0]);
EXPECT_EQ(".jpeg", file_handlers[0]->accept.find("image/jpg")->value[1]);
ASSERT_TRUE(file_handlers[0]->accept.Contains("image/png"));
ASSERT_EQ(1u, file_handlers[0]->accept.find("image/png")->value.size());
EXPECT_EQ(".png", file_handlers[0]->accept.find("image/png")->value[0]);
}
// file_handlers with multiple entries is valid.
{
auto& manifest = ParseManifest(
"{"
" \"file_handlers\": ["
" {"
" \"name\": \"Graph\","
" \"action\": \"/graph\","
" \"accept\": {"
" \"text/svg+xml\": ["
" \".svg\","
" \".graph\""
" ]"
" }"
" },"
" {"
" \"name\": \"Raw\","
" \"action\": \"/raw\","
" \"accept\": {"
" \"text/csv\": \".csv\""
" }"
" }"
" ]"
"}");
auto& file_handlers = manifest->file_handlers;
ASSERT_EQ(0u, GetErrorCount());
ASSERT_EQ(2u, file_handlers.size());
EXPECT_EQ("Graph", file_handlers[0]->name);
EXPECT_EQ(KURL("http://foo.com/graph"), file_handlers[0]->action);
ASSERT_TRUE(file_handlers[0]->accept.Contains("text/svg+xml"));
ASSERT_EQ(2u, file_handlers[0]->accept.find("text/svg+xml")->value.size());
EXPECT_EQ(".svg", file_handlers[0]->accept.find("text/svg+xml")->value[0]);
EXPECT_EQ(".graph",
file_handlers[0]->accept.find("text/svg+xml")->value[1]);
EXPECT_EQ("Raw", file_handlers[1]->name);
EXPECT_EQ(KURL("http://foo.com/raw"), file_handlers[1]->action);
ASSERT_TRUE(file_handlers[1]->accept.Contains("text/csv"));
ASSERT_EQ(1u, file_handlers[1]->accept.find("text/csv")->value.size());
EXPECT_EQ(".csv", file_handlers[1]->accept.find("text/csv")->value[0]);
}
}
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 within scope of the manifest.",
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 the URL not being within manifest scope.
{
auto& manifest = ParseManifest(
"{"
" \"start_url\": \"/app/\","
" \"scope\": \"/app/\","
" \"protocol_handlers\": ["
" {"
" \"protocol\": \"web+github\","
" \"url\": \"/?profile=%s\""
" }"
" ]"
"}");
auto& protocol_handlers = manifest->protocol_handlers;
ASSERT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'url' ignored, should be within scope of the manifest.",
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());
}
// An invalid protocol handler with a url that doesn't contain the %s token.
{
auto& manifest = ParseManifest(
"{"
" \"protocol_handlers\": ["
" {"
" \"protocol\": \"web+github\","
" \"url\": \"http://foo.com/?profile=\""
" }"
" ]"
"}");
auto& protocol_handlers = manifest->protocol_handlers;
ASSERT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"The url provided ('http://foo.com/?profile=') does not contain '%s'.",
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 a non-allowed protocol.
{
auto& manifest = ParseManifest(
"{"
" \"protocol_handlers\": ["
" {"
" \"protocol\": \"github\","
" \"url\": \"http://foo.com/?profile=\""
" }"
" ]"
"}");
auto& protocol_handlers = manifest->protocol_handlers;
ASSERT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"The scheme 'github' doesn't belong to the scheme allowlist. Please "
"prefix non-allowlisted schemes with the string 'web+'.",
errors()[0]);
EXPECT_EQ(
"protocol_handlers entry ignored, required property 'protocol' is "
"invalid.",
errors()[1]);
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\""
" },"
" {"
" \"protocol\": \"web+relative\","
" \"url\": \"relativeURL=%s\""
" }"
" ]"
"}");
auto& protocol_handlers = manifest->protocol_handlers;
ASSERT_EQ(0u, GetErrorCount());
ASSERT_EQ(3u, 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);
ASSERT_EQ("web+relative", protocol_handlers[2]->protocol);
ASSERT_EQ("http://foo.com/relativeURL=%s", protocol_handlers[2]->url);
}
}
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) {
// Contains share_target field but no keys.
{
auto& manifest = ParseManifest("{ \"share_target\": {} }");
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'share_target' ignored. Property 'action' is invalid.",
errors()[0]);
}
// Contains share_target field but no params key.
{
auto& manifest =
ParseManifest("{ \"share_target\": { \"action\": \"\" } }");
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(3u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
EXPECT_EQ(
"property 'share_target' ignored. Property 'params' type "
"dictionary expected.",
errors()[2]);
}
// Contains share_target field but no action key.
{
auto& manifest = ParseManifest("{ \"share_target\": { \"params\": {} } }");
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'share_target' ignored. Property 'action' is invalid.",
errors()[0]);
}
// Key in share_target that isn't valid.
{
auto& manifest = ParseManifest(
"{ \"share_target\": {\"incorrect_key\": \"some_value\" } }");
ASSERT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'share_target' ignored. Property 'action' is invalid.",
errors()[0]);
}
}
TEST_F(ManifestParserTest, ShareTargetUrlTemplateParseRules) {
KURL manifest_url = KURL("https://foo.com/manifest.json");
KURL document_url = KURL("https://foo.com/index.html");
// Contains share_target, but action is empty.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"\", \"params\": {} } }",
manifest_url, document_url);
ASSERT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->action, manifest_url);
EXPECT_TRUE(manifest->share_target->params->text.IsNull());
EXPECT_TRUE(manifest->share_target->params->title.IsNull());
EXPECT_TRUE(manifest->share_target->params->url.IsNull());
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
}
// Parse but throw an error if url_template property isn't a string.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"\", \"params\": {} } }",
manifest_url, document_url);
EXPECT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->action, manifest_url);
EXPECT_TRUE(manifest->share_target->params->text.IsNull());
EXPECT_TRUE(manifest->share_target->params->title.IsNull());
EXPECT_TRUE(manifest->share_target->params->url.IsNull());
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
}
// Don't parse if action property isn't a string.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": {}, \"params\": {} } }",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'action' ignored, type string expected.", errors()[0]);
EXPECT_EQ("property 'share_target' ignored. Property 'action' is invalid.",
errors()[1]);
}
// Don't parse if action property isn't a string.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": 42, \"params\": {} } }",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'action' ignored, type string expected.", errors()[0]);
EXPECT_EQ("property 'share_target' ignored. Property 'action' is invalid.",
errors()[1]);
}
// Don't parse if params property isn't a dict.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"\", \"params\": \"\" } }",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(3u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
EXPECT_EQ(
"property 'share_target' ignored. Property 'params' type "
"dictionary expected.",
errors()[2]);
}
// Don't parse if params property isn't a dict.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"\", \"params\": 42 } }",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(3u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
EXPECT_EQ(
"property 'share_target' ignored. Property 'params' type "
"dictionary expected.",
errors()[2]);
}
// Ignore params keys with invalid types.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"\", \"params\": { \"text\": 42 }"
" } }",
manifest_url, document_url);
ASSERT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->action, manifest_url);
EXPECT_TRUE(manifest->share_target->params->text.IsNull());
EXPECT_TRUE(manifest->share_target->params->title.IsNull());
EXPECT_TRUE(manifest->share_target->params->url.IsNull());
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(3u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
EXPECT_EQ("property 'text' ignored, type string expected.", errors()[2]);
}
// Ignore params keys with invalid types.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"\", "
"\"params\": { \"title\": 42 } } }",
manifest_url, document_url);
ASSERT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->action, manifest_url);
EXPECT_TRUE(manifest->share_target->params->text.IsNull());
EXPECT_TRUE(manifest->share_target->params->title.IsNull());
EXPECT_TRUE(manifest->share_target->params->url.IsNull());
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(3u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
EXPECT_EQ("property 'title' ignored, type string expected.", errors()[2]);
}
// Don't parse if params property has keys with invalid types.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"\", \"params\": { \"url\": {}, "
"\"text\": \"hi\" } } }",
manifest_url, document_url);
ASSERT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->action, manifest_url);
EXPECT_EQ(manifest->share_target->params->text, "hi");
EXPECT_TRUE(manifest->share_target->params->title.IsNull());
EXPECT_TRUE(manifest->share_target->params->url.IsNull());
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(3u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
EXPECT_EQ("property 'url' ignored, type string expected.", errors()[2]);
}
// Don't parse if action property isn't a valid URL.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com:a\", \"params\": "
"{} } }",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'action' ignored, URL is invalid.", errors()[0]);
EXPECT_EQ("property 'share_target' ignored. Property 'action' is invalid.",
errors()[1]);
}
// Fail parsing if action is at a different origin than the Web
// manifest.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo2.com/\", "
"\"params\": {} } }",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"property 'action' ignored, should be within scope of the manifest.",
errors()[0]);
EXPECT_EQ(
"property 'share_target' ignored. Property 'action' is "
"invalid.",
errors()[1]);
}
// Fail parsing if action is not within scope of the manifest.
{
auto& manifest = ParseManifestWithURLs(
"{ \"start_url\": \"/app/\","
" \"scope\": \"/app/\","
" \"share_target\": { \"action\": \"/\", "
"\"params\": {} } }",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"property 'action' ignored, should be within scope of the manifest.",
errors()[0]);
EXPECT_EQ(
"property 'share_target' ignored. Property 'action' is "
"invalid.",
errors()[1]);
}
// Smoke test: Contains share_target and action, and action is valid.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": {\"action\": \"share/\", \"params\": {} } }",
manifest_url, document_url);
ASSERT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->action.GetString(),
"https://foo.com/share/");
EXPECT_TRUE(manifest->share_target->params->text.IsNull());
EXPECT_TRUE(manifest->share_target->params->title.IsNull());
EXPECT_TRUE(manifest->share_target->params->url.IsNull());
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
}
// Smoke test: Contains share_target and action, and action is valid, params
// is populated.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": {\"action\": \"share/\", \"params\": { \"text\": "
"\"foo\", \"title\": \"bar\", \"url\": \"baz\" } } }",
manifest_url, document_url);
ASSERT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->action.GetString(),
"https://foo.com/share/");
EXPECT_EQ(manifest->share_target->params->text, "foo");
EXPECT_EQ(manifest->share_target->params->title, "bar");
EXPECT_EQ(manifest->share_target->params->url, "baz");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
}
// Backwards compatibility test: Contains share_target, url_template and
// action, and action is valid, params is populated.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"url_template\": "
"\"foo.com/share?title={title}\", "
"\"action\": \"share/\", \"params\": { \"text\": "
"\"foo\", \"title\": \"bar\", \"url\": \"baz\" } } }",
manifest_url, document_url);
ASSERT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->action.GetString(),
"https://foo.com/share/");
EXPECT_EQ(manifest->share_target->params->text, "foo");
EXPECT_EQ(manifest->share_target->params->title, "bar");
EXPECT_EQ(manifest->share_target->params->url, "baz");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
}
// Smoke test: Contains share_target, action and params. action is
// valid and is absolute.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"params\": "
"{ \"title\": \"mytitle\" } } "
"}",
manifest_url, document_url);
ASSERT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->action.GetString(), "https://foo.com/#");
EXPECT_TRUE(manifest->share_target->params->text.IsNull());
EXPECT_EQ(manifest->share_target->params->title, "mytitle");
EXPECT_TRUE(manifest->share_target->params->url.IsNull());
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ(
"Method should be set to either GET or POST. It currently defaults to "
"GET.",
errors()[0]);
EXPECT_EQ(
"Enctype should be set to either application/x-www-form-urlencoded or "
"multipart/form-data. It currently defaults to "
"application/x-www-form-urlencoded",
errors()[1]);
}
// Return undefined if method or enctype is not string.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"10, \"enctype\": 10, \"params\": "
"{ \"title\": \"mytitle\" } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"invalid method. Allowed methods are:"
"GET and POST.",
errors()[0]);
}
// Valid method and enctype.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"GET\", \"enctype\": \"application/x-www-form-urlencoded\", "
"\"params\": "
"{ \"title\": \"mytitle\" } } "
"}",
manifest_url, document_url);
EXPECT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->method,
mojom::blink::ManifestShareTarget::Method::kGet);
EXPECT_EQ(manifest->share_target->enctype,
mojom::blink::ManifestShareTarget::Enctype::kFormUrlEncoded);
}
// Auto-fill in "GET" for method and "application/x-www-form-urlencoded" for
// enctype.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"params\": "
"{ \"title\": \"mytitle\" } } "
"}",
manifest_url, document_url);
EXPECT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->method,
mojom::blink::ManifestShareTarget::Method::kGet);
EXPECT_EQ(manifest->share_target->enctype,
mojom::blink::ManifestShareTarget::Enctype::kFormUrlEncoded);
}
// Invalid method values, return undefined.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"\", \"enctype\": \"application/x-www-form-urlencoded\", \"params\": "
"{ \"title\": \"mytitle\" } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"invalid method. Allowed methods are:"
"GET and POST.",
errors()[0]);
}
// When method is "GET", enctype cannot be anything other than
// "application/x-www-form-urlencoded".
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"GET\", \"enctype\": \"RANDOM\", \"params\": "
"{ \"title\": \"mytitle\" } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"invalid enctype. Allowed enctypes are:"
"application/x-www-form-urlencoded and multipart/form-data.",
errors()[0]);
}
// When method is "POST", enctype cannot be anything other than
// "application/x-www-form-urlencoded" or "multipart/form-data".
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"random\", \"params\": "
"{ \"title\": \"mytitle\" } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"invalid enctype. Allowed enctypes are:"
"application/x-www-form-urlencoded and multipart/form-data.",
errors()[0]);
}
// Valid enctype for when method is "POST".
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"application/x-www-form-urlencoded\", "
"\"params\": "
"{ \"title\": \"mytitle\" } } "
"}",
manifest_url, document_url);
EXPECT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->method,
mojom::blink::ManifestShareTarget::Method::kPost);
EXPECT_EQ(manifest->share_target->enctype,
mojom::blink::ManifestShareTarget::Enctype::kFormUrlEncoded);
EXPECT_EQ(0u, GetErrorCount());
}
// Valid enctype for when method is "POST".
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"multipart/form-data\", \"params\": "
"{ \"title\": \"mytitle\" } } "
"}",
manifest_url, document_url);
EXPECT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->method,
mojom::blink::ManifestShareTarget::Method::kPost);
EXPECT_EQ(manifest->share_target->enctype,
mojom::blink::ManifestShareTarget::Enctype::kMultipartFormData);
EXPECT_EQ(0u, GetErrorCount());
}
// Ascii in-sensitive.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"PosT\", \"enctype\": \"mUltIparT/Form-dAta\", \"params\": "
"{ \"title\": \"mytitle\" } } "
"}",
manifest_url, document_url);
EXPECT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->method,
mojom::blink::ManifestShareTarget::Method::kPost);
EXPECT_EQ(manifest->share_target->enctype,
mojom::blink::ManifestShareTarget::Enctype::kMultipartFormData);
EXPECT_EQ(0u, GetErrorCount());
}
// No files is okay.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"multipart/form-data\", \"params\": "
"{ \"title\": \"mytitle\", \"files\": [] } } "
"}",
manifest_url, document_url);
EXPECT_TRUE(manifest->share_target.get());
EXPECT_EQ(manifest->share_target->method,
mojom::blink::ManifestShareTarget::Method::kPost);
EXPECT_EQ(manifest->share_target->enctype,
mojom::blink::ManifestShareTarget::Enctype::kMultipartFormData);
EXPECT_EQ(0u, GetErrorCount());
}
// Nonempty file must have POST method and multipart/form-data enctype.
// GET method, for example, will cause an error in this case.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"GET\", \"enctype\": \"multipart/form-data\", \"params\": "
"{ \"title\": \"mytitle\", \"files\": [{ \"name\": \"name\", "
"\"accept\": [\"text/plain\"]}] } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"invalid enctype for GET method. Only "
"application/x-www-form-urlencoded is allowed.",
errors()[0]);
}
// Nonempty file must have POST method and multipart/form-data enctype.
// Enctype other than multipart/form-data will cause an error.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"application/x-www-form-urlencoded\", "
"\"params\": "
"{ \"title\": \"mytitle\", \"files\": [{ \"name\": \"name\", "
"\"accept\": [\"text/plain\"]}] } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("files are only supported with multipart/form-data POST.",
errors()[0]);
}
// Nonempty file must have POST method and multipart/form-data enctype.
// This case is valid.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"multipart/form-data\", \"params\": "
"{ \"title\": \"mytitle\", \"files\": [{ \"name\": \"name\", "
"\"accept\": [\"text/plain\"]}] } } "
"}",
manifest_url, document_url);
EXPECT_TRUE(manifest->share_target.get());
EXPECT_TRUE(manifest->share_target->params->files.has_value());
EXPECT_EQ(1u, manifest->share_target->params->files->size());
EXPECT_EQ(0u, GetErrorCount());
}
// Invalid mimetype.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"multipart/form-data\", \"params\": "
"{ \"title\": \"mytitle\", \"files\": [{ \"name\": \"name\", "
"\"accept\": [\"\"]}] } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("invalid mime type inside files.", errors()[0]);
}
// Invalid mimetype.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"multipart/form-data\", \"params\": "
"{ \"title\": \"mytitle\", \"files\": [{ \"name\": \"name\", "
"\"accept\": [\"helloworld\"]}] } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("invalid mime type inside files.", errors()[0]);
}
// Invalid mimetype.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"multipart/form-data\", \"params\": "
"{ \"title\": \"mytitle\", \"files\": [{ \"name\": \"name\", "
"\"accept\": [\"^$/@$\"]}] } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("invalid mime type inside files.", errors()[0]);
}
// Invalid mimetype.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"multipart/form-data\", \"params\": "
"{ \"title\": \"mytitle\", \"files\": [{ \"name\": \"name\", "
"\"accept\": [\"/\"]}] } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("invalid mime type inside files.", errors()[0]);
}
// Invalid mimetype.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"multipart/form-data\", \"params\": "
"{ \"title\": \"mytitle\", \"files\": [{ \"name\": \"name\", "
"\"accept\": [\" \"]}] } } "
"}",
manifest_url, document_url);
EXPECT_FALSE(manifest->share_target.get());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("invalid mime type inside files.", errors()[0]);
}
// Accept field is empty.
{
auto& manifest = ParseManifestWithURLs(
"{ \"share_target\": { \"action\": \"https://foo.com/#\", \"method\": "
"\"POST\", \"enctype\": \"multipart/form-data\", \"params\": "
"{ \"title\": \"mytitle\", \"files\": [{ \"name\": \"name\", "
"\"accept\": []}] } } "
"}",
manifest_url, document_url);
EXPECT_TRUE(manifest->share_target.get());
EXPECT_FALSE(manifest->share_target->params->files.has_value());
EXPECT_EQ(0u, GetErrorCount());
}
// Accept sequence contains non-string elements.
{
auto& manifest = ParseManifestWithURLs(
"{"
" \"share_target\": {"
" \"action\": \"https://foo.com/#\","
" \"method\": \"POST\","
" \"enctype\": \"multipart/form-data\","
" \"params\": {"
" \"title\": \"mytitle\","
" \"files\": [{"
" \"name\": \"name\","
" \"accept\": [\"image/png\", 42]"
" }]"
" }"
" }"
"}",
manifest_url, document_url);
auto* share_target = manifest->share_target.get();
EXPECT_TRUE(share_target);
EXPECT_TRUE(share_target->params->files.has_value());
auto& files = share_target->params->files.value();
EXPECT_EQ(1u, files.size());
EXPECT_EQ(files[0]->name, "name");
auto& accept = files[0]->accept;
EXPECT_EQ(1u, accept.size());
EXPECT_EQ(accept[0], "image/png");
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("'accept' entry ignored, expected to be of type string.",
errors()[0]);
}
// Accept is just a single string.
{
auto& manifest = ParseManifestWithURLs(
"{"
" \"share_target\": {"
" \"action\": \"https://foo.com/#\","
" \"method\": \"POST\","
" \"enctype\": \"multipart/form-data\","
" \"params\": {"
" \"title\": \"mytitle\","
" \"files\": [{"
" \"name\": \"name\","
" \"accept\": \"image/png\""
" }]"
" }"
" }"
"}",
manifest_url, document_url);
auto* share_target = manifest->share_target.get();
EXPECT_TRUE(share_target);
EXPECT_TRUE(share_target->params->files.has_value());
auto& files = share_target->params->files.value();
EXPECT_EQ(1u, files.size());
EXPECT_EQ(files[0]->name, "name");
auto& accept = files[0]->accept;
EXPECT_EQ(1u, accept.size());
EXPECT_EQ(accept[0], "image/png");
EXPECT_EQ(0u, GetErrorCount());
}
// Accept is neither a string nor an array of strings.
{
auto& manifest = ParseManifestWithURLs(
"{"
" \"share_target\": {"
" \"action\": \"https://foo.com/#\","
" \"method\": \"POST\","
" \"enctype\": \"multipart/form-data\","
" \"params\": {"
" \"title\": \"mytitle\","
" \"files\": [{"
" \"name\": \"name\","
" \"accept\": true"
" }]"
" }"
" }"
"}",
manifest_url, document_url);
auto* share_target = manifest->share_target.get();
EXPECT_TRUE(share_target);
EXPECT_FALSE(share_target->params->files.has_value());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'accept' ignored, type array or string expected.",
errors()[0]);
}
// Files is just a single FileFilter (not an array).
{
auto& manifest = ParseManifestWithURLs(
"{"
" \"share_target\": {"
" \"action\": \"https://foo.com/#\","
" \"method\": \"POST\","
" \"enctype\": \"multipart/form-data\","
" \"params\": {"
" \"title\": \"mytitle\","
" \"files\": {"
" \"name\": \"name\","
" \"accept\": \"image/png\""
" }"
" }"
" }"
"}",
manifest_url, document_url);
EXPECT_TRUE(manifest->share_target.get());
auto* params = manifest->share_target->params.get();
EXPECT_TRUE(params->files.has_value());
auto& file = params->files.value();
EXPECT_EQ(1u, file.size());
EXPECT_EQ(file[0]->name, "name");
auto& accept = file[0]->accept;
EXPECT_EQ(1u, accept.size());
EXPECT_EQ(accept[0], "image/png");
EXPECT_EQ(0u, GetErrorCount());
}
// Files is neither array nor FileFilter.
{
auto& manifest = ParseManifestWithURLs(
"{"
" \"share_target\": {"
" \"action\": \"https://foo.com/#\","
" \"method\": \"POST\","
" \"enctype\": \"multipart/form-data\","
" \"params\": {"
" \"title\": \"mytitle\","
" \"files\": 3"
" }"
" }"
"}",
manifest_url, document_url);
auto* share_target = manifest->share_target.get();
EXPECT_TRUE(share_target);
EXPECT_FALSE(share_target->params->files.has_value());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'files' ignored, type array or FileFilter expected.",
errors()[0]);
}
// Files contains a non-dictionary entry.
{
auto& manifest = ParseManifestWithURLs(
"{"
" \"share_target\": {"
" \"action\": \"https://foo.com/#\","
" \"method\": \"POST\","
" \"enctype\": \"multipart/form-data\","
" \"params\": {"
" \"title\": \"mytitle\","
" \"files\": ["
" {"
" \"name\": \"name\","
" \"accept\": \"image/png\""
" },"
" 3"
" ]"
" }"
" }"
"}",
manifest_url, document_url);
auto* share_target = manifest->share_target.get();
EXPECT_TRUE(share_target);
EXPECT_TRUE(share_target->params->files.has_value());
auto& files = share_target->params->files.value();
EXPECT_EQ(1u, files.size());
EXPECT_EQ(files[0]->name, "name");
auto& accept = files[0]->accept;
EXPECT_EQ(1u, accept.size());
EXPECT_EQ(accept[0], "image/png");
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("files must be a sequence of non-empty file entries.",
errors()[0]);
}
// Files contains empty file.
{
auto& manifest = ParseManifestWithURLs(
"{"
" \"share_target\": {"
" \"action\": \"https://foo.com/#\","
" \"method\": \"POST\","
" \"enctype\": \"multipart/form-data\","
" \"params\": {"
" \"title\": \"mytitle\","
" \"files\": ["
" {"
" \"name\": \"name\","
" \"accept\": \"image/png\""
" },"
" {}"
" ]"
" }"
" }"
"}",
manifest_url, document_url);
auto* share_target = manifest->share_target.get();
EXPECT_TRUE(share_target);
EXPECT_TRUE(share_target->params->files.has_value());
auto& files = share_target->params->files.value();
EXPECT_EQ(1u, files.size());
EXPECT_EQ(files[0]->name, "name");
auto& accept = files[0]->accept;
EXPECT_EQ(1u, accept.size());
EXPECT_EQ(accept[0], "image/png");
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'name' missing.", errors()[0]);
}
}
TEST_F(ManifestParserTest, RelatedApplicationsParseRules) {
// If no application, empty list.
{
auto& manifest = ParseManifest("{ \"related_applications\": []}");
EXPECT_TRUE(manifest->related_applications.IsEmpty());
EXPECT_EQ(0u, GetErrorCount());
}
// If empty application, empty list.
{
auto& manifest = ParseManifest("{ \"related_applications\": [{}]}");
EXPECT_TRUE(manifest->related_applications.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("'platform' is a required field, related application ignored.",
errors()[0]);
}
// If invalid platform, application is ignored.
{
auto& manifest =
ParseManifest("{ \"related_applications\": [{\"platform\": 123}]}");
EXPECT_TRUE(manifest->related_applications.IsEmpty());
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'platform' ignored, type string expected.",
errors()[0]);
EXPECT_EQ(
"'platform' is a required field, "
"related application ignored.",
errors()[1]);
}
// If missing platform, application is ignored.
{
auto& manifest =
ParseManifest("{ \"related_applications\": [{\"id\": \"foo\"}]}");
EXPECT_TRUE(manifest->related_applications.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("'platform' is a required field, related application ignored.",
errors()[0]);
}
// If missing id and url, application is ignored.
{
auto& manifest = ParseManifest(
"{ \"related_applications\": [{\"platform\": \"play\"}]}");
EXPECT_TRUE(manifest->related_applications.IsEmpty());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("one of 'url' or 'id' is required, related application ignored.",
errors()[0]);
}
// Valid application, with url.
{
auto& manifest = ParseManifest(
"{ \"related_applications\": ["
"{\"platform\": \"play\", \"url\": \"http://www.foo.com\"}]}");
auto& related_applications = manifest->related_applications;
EXPECT_EQ(related_applications.size(), 1u);
EXPECT_EQ(related_applications[0]->platform, "play");
EXPECT_TRUE(related_applications[0]->url.has_value());
EXPECT_EQ(related_applications[0]->url->GetString(), "http://www.foo.com/");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Application with an invalid url.
{
auto& manifest = ParseManifest(
"{ \"related_applications\": ["
"{\"platform\": \"play\", \"url\": \"http://www.foo.com:co&uk\"}]}");
EXPECT_TRUE(manifest->related_applications.IsEmpty());
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("property 'url' ignored, URL is invalid.", errors()[0]);
EXPECT_EQ("one of 'url' or 'id' is required, related application ignored.",
errors()[1]);
}
// Valid application, with id.
{
auto& manifest = ParseManifest(
"{ \"related_applications\": ["
"{\"platform\": \"itunes\", \"id\": \"foo\"}]}");
auto& related_applications = manifest->related_applications;
EXPECT_EQ(related_applications.size(), 1u);
EXPECT_EQ(related_applications[0]->platform, "itunes");
EXPECT_EQ(related_applications[0]->id, "foo");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// All valid applications are in list.
{
auto& manifest = ParseManifest(
"{ \"related_applications\": ["
"{\"platform\": \"play\", \"id\": \"foo\"},"
"{\"platform\": \"itunes\", \"id\": \"bar\"}]}");
auto& related_applications = manifest->related_applications;
EXPECT_EQ(related_applications.size(), 2u);
EXPECT_EQ(related_applications[0]->platform, "play");
EXPECT_EQ(related_applications[0]->id, "foo");
EXPECT_EQ(related_applications[1]->platform, "itunes");
EXPECT_EQ(related_applications[1]->id, "bar");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Two invalid applications and one valid. Only the valid application should
// be in the list.
{
auto& manifest = ParseManifest(
"{ \"related_applications\": ["
"{\"platform\": \"itunes\"},"
"{\"platform\": \"play\", \"id\": \"foo\"},"
"{}]}");
auto& related_applications = manifest->related_applications;
EXPECT_EQ(related_applications.size(), 1u);
EXPECT_EQ(related_applications[0]->platform, "play");
EXPECT_EQ(related_applications[0]->id, "foo");
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("one of 'url' or 'id' is required, related application ignored.",
errors()[0]);
EXPECT_EQ("'platform' is a required field, related application ignored.",
errors()[1]);
}
}
TEST_F(ManifestParserTest, ParsePreferRelatedApplicationsParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest("{ \"prefer_related_applications\": true }");
EXPECT_TRUE(manifest->prefer_related_applications);
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if the property isn't a boolean.
{
auto& manifest = ParseManifest("{ \"prefer_related_applications\": {} }");
EXPECT_FALSE(manifest->prefer_related_applications);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'prefer_related_applications' "
"ignored, type boolean expected.",
errors()[0]);
}
{
auto& manifest =
ParseManifest("{ \"prefer_related_applications\": \"true\" }");
EXPECT_FALSE(manifest->prefer_related_applications);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'prefer_related_applications' "
"ignored, type boolean expected.",
errors()[0]);
}
{
auto& manifest = ParseManifest("{ \"prefer_related_applications\": 1 }");
EXPECT_FALSE(manifest->prefer_related_applications);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'prefer_related_applications' "
"ignored, type boolean expected.",
errors()[0]);
}
// "False" should set the boolean false without throwing errors.
{
auto& manifest =
ParseManifest("{ \"prefer_related_applications\": false }");
EXPECT_FALSE(manifest->prefer_related_applications);
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, ThemeColorParserRules) {
// Smoke test.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"#FF0000\" }");
EXPECT_TRUE(manifest->has_theme_color);
EXPECT_EQ(manifest->theme_color, 0xFFFF0000u);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest("{ \"theme_color\": \" blue \" }");
EXPECT_TRUE(manifest->has_theme_color);
EXPECT_EQ(manifest->theme_color, 0xFF0000FFu);
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if theme_color isn't a string.
{
auto& manifest = ParseManifest("{ \"theme_color\": {} }");
EXPECT_FALSE(manifest->has_theme_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'theme_color' ignored, type string expected.",
errors()[0]);
}
// Don't parse if theme_color isn't a string.
{
auto& manifest = ParseManifest("{ \"theme_color\": false }");
EXPECT_FALSE(manifest->has_theme_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'theme_color' ignored, type string expected.",
errors()[0]);
}
// Don't parse if theme_color isn't a string.
{
auto& manifest = ParseManifest("{ \"theme_color\": null }");
EXPECT_FALSE(manifest->has_theme_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'theme_color' ignored, type string expected.",
errors()[0]);
}
// Don't parse if theme_color isn't a string.
{
auto& manifest = ParseManifest("{ \"theme_color\": [] }");
EXPECT_FALSE(manifest->has_theme_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'theme_color' ignored, type string expected.",
errors()[0]);
}
// Don't parse if theme_color isn't a string.
{
auto& manifest = ParseManifest("{ \"theme_color\": 42 }");
EXPECT_FALSE(manifest->has_theme_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'theme_color' ignored, type string expected.",
errors()[0]);
}
// Parse fails if string is not in a known format.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"foo(bar)\" }");
EXPECT_FALSE(manifest->has_theme_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'theme_color' ignored,"
" 'foo(bar)' is not a valid color.",
errors()[0]);
}
// Parse fails if string is not in a known format.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"bleu\" }");
EXPECT_FALSE(manifest->has_theme_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'theme_color' ignored, 'bleu' is not a valid color.",
errors()[0]);
}
// Parse fails if string is not in a known format.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"FF00FF\" }");
EXPECT_FALSE(manifest->has_theme_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'theme_color' ignored, 'FF00FF'"
" is not a valid color.",
errors()[0]);
}
// Parse fails if multiple values for theme_color are given.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"#ABC #DEF\" }");
EXPECT_FALSE(manifest->has_theme_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'theme_color' ignored, "
"'#ABC #DEF' is not a valid color.",
errors()[0]);
}
// Parse fails if multiple values for theme_color are given.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"#AABBCC #DDEEFF\" }");
EXPECT_FALSE(manifest->has_theme_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'theme_color' ignored, "
"'#AABBCC #DDEEFF' is not a valid color.",
errors()[0]);
}
// Accept CSS color keyword format.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"blue\" }");
EXPECT_EQ(manifest->theme_color, 0xFF0000FFu);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept CSS color keyword format.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"chartreuse\" }");
EXPECT_EQ(manifest->theme_color, 0xFF7FFF00u);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept CSS RGB format.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"#FFF\" }");
EXPECT_EQ(manifest->theme_color, 0xFFFFFFFFu);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept CSS RGB format.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"#ABC\" }");
EXPECT_EQ(manifest->theme_color, 0xFFAABBCCu);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept CSS RRGGBB format.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"#FF0000\" }");
EXPECT_EQ(manifest->theme_color, 0xFFFF0000u);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept translucent colors.
{
auto& manifest = ParseManifest(
"{ \"theme_color\": \"rgba(255,0,0,"
"0.4)\" }");
EXPECT_EQ(manifest->theme_color, 0x66FF0000u);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept transparent colors.
{
auto& manifest = ParseManifest("{ \"theme_color\": \"rgba(0,0,0,0)\" }");
EXPECT_EQ(manifest->theme_color, 0x00000000u);
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, BackgroundColorParserRules) {
// Smoke test.
{
auto& manifest = ParseManifest("{ \"background_color\": \"#FF0000\" }");
EXPECT_EQ(manifest->background_color, 0xFFFF0000u);
EXPECT_FALSE(IsManifestEmpty(manifest));
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest("{ \"background_color\": \" blue \" }");
EXPECT_EQ(manifest->background_color, 0xFF0000FFu);
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if background_color isn't a string.
{
auto& manifest = ParseManifest("{ \"background_color\": {} }");
EXPECT_FALSE(manifest->has_background_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'background_color' ignored, type string expected.",
errors()[0]);
}
// Don't parse if background_color isn't a string.
{
auto& manifest = ParseManifest("{ \"background_color\": false }");
EXPECT_FALSE(manifest->has_background_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'background_color' ignored, type string expected.",
errors()[0]);
}
// Don't parse if background_color isn't a string.
{
auto& manifest = ParseManifest("{ \"background_color\": null }");
EXPECT_FALSE(manifest->has_background_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'background_color' ignored, type string expected.",
errors()[0]);
}
// Don't parse if background_color isn't a string.
{
auto& manifest = ParseManifest("{ \"background_color\": [] }");
EXPECT_FALSE(manifest->has_background_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'background_color' ignored, type string expected.",
errors()[0]);
}
// Don't parse if background_color isn't a string.
{
auto& manifest = ParseManifest("{ \"background_color\": 42 }");
EXPECT_FALSE(manifest->has_background_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'background_color' ignored, type string expected.",
errors()[0]);
}
// Parse fails if string is not in a known format.
{
auto& manifest = ParseManifest("{ \"background_color\": \"foo(bar)\" }");
EXPECT_FALSE(manifest->has_background_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'background_color' ignored,"
" 'foo(bar)' is not a valid color.",
errors()[0]);
}
// Parse fails if string is not in a known format.
{
auto& manifest = ParseManifest("{ \"background_color\": \"bleu\" }");
EXPECT_FALSE(manifest->has_background_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'background_color' ignored,"
" 'bleu' is not a valid color.",
errors()[0]);
}
// Parse fails if string is not in a known format.
{
auto& manifest = ParseManifest("{ \"background_color\": \"FF00FF\" }");
EXPECT_FALSE(manifest->has_background_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'background_color' ignored,"
" 'FF00FF' is not a valid color.",
errors()[0]);
}
// Parse fails if multiple values for background_color are given.
{
auto& manifest = ParseManifest("{ \"background_color\": \"#ABC #DEF\" }");
EXPECT_FALSE(manifest->has_background_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'background_color' ignored, "
"'#ABC #DEF' is not a valid color.",
errors()[0]);
}
// Parse fails if multiple values for background_color are given.
{
auto& manifest =
ParseManifest("{ \"background_color\": \"#AABBCC #DDEEFF\" }");
EXPECT_FALSE(manifest->has_background_color);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'background_color' ignored, "
"'#AABBCC #DDEEFF' is not a valid color.",
errors()[0]);
}
// Accept CSS color keyword format.
{
auto& manifest = ParseManifest("{ \"background_color\": \"blue\" }");
EXPECT_EQ(manifest->background_color, 0xFF0000FFu);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept CSS color keyword format.
{
auto& manifest = ParseManifest("{ \"background_color\": \"chartreuse\" }");
EXPECT_EQ(manifest->background_color, 0xFF7FFF00u);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept CSS RGB format.
{
auto& manifest = ParseManifest("{ \"background_color\": \"#FFF\" }");
EXPECT_EQ(manifest->background_color, 0xFFFFFFFFu);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept CSS RGB format.
{
auto& manifest = ParseManifest("{ \"background_color\": \"#ABC\" }");
EXPECT_EQ(manifest->background_color, 0xFFAABBCCu);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept CSS RRGGBB format.
{
auto& manifest = ParseManifest("{ \"background_color\": \"#FF0000\" }");
EXPECT_EQ(manifest->background_color, 0xFFFF0000u);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept translucent colors.
{
auto& manifest = ParseManifest(
"{ \"background_color\": \"rgba(255,0,0,"
"0.4)\" }");
EXPECT_EQ(manifest->background_color, 0x66FF0000u);
EXPECT_EQ(0u, GetErrorCount());
}
// Accept transparent colors.
{
auto& manifest = ParseManifest(
"{ \"background_color\": \"rgba(0,0,0,"
"0)\" }");
EXPECT_EQ(manifest->background_color, 0x00000000u);
EXPECT_EQ(0u, GetErrorCount());
}
}
TEST_F(ManifestParserTest, GCMSenderIDParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest("{ \"gcm_sender_id\": \"foo\" }");
EXPECT_EQ(manifest->gcm_sender_id, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Trim whitespaces.
{
auto& manifest = ParseManifest("{ \"gcm_sender_id\": \" foo \" }");
EXPECT_EQ(manifest->gcm_sender_id, "foo");
EXPECT_EQ(0u, GetErrorCount());
}
// Don't parse if the property isn't a string.
{
auto& manifest = ParseManifest("{ \"gcm_sender_id\": {} }");
EXPECT_TRUE(manifest->gcm_sender_id.IsNull());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'gcm_sender_id' ignored, type string expected.",
errors()[0]);
}
{
auto& manifest = ParseManifest("{ \"gcm_sender_id\": 42 }");
EXPECT_TRUE(manifest->gcm_sender_id.IsNull());
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("property 'gcm_sender_id' ignored, type string expected.",
errors()[0]);
}
}
TEST_F(ManifestParserTest, CaptureLinksParseRules) {
// Feature not enabled, should not be parsed.
{
auto& manifest = ParseManifest(R"({ "capture_links": "none" })");
EXPECT_EQ(manifest->capture_links, mojom::blink::CaptureLinks::kUndefined);
EXPECT_EQ(0u, GetErrorCount());
}
}
class ManifestCaptureLinksParserTest : public ManifestParserTest {
public:
ManifestCaptureLinksParserTest() {
scoped_feature_list_.InitAndEnableFeature(
features::kWebAppEnableLinkCapturing);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(ManifestCaptureLinksParserTest, CaptureLinksParseRules) {
// Smoke test.
{
auto& manifest = ParseManifest(R"({ "capture_links": "none" })");
EXPECT_EQ(manifest->capture_links, mojom::blink::CaptureLinks::kNone);
EXPECT_EQ(0u, GetErrorCount());
}
{
auto& manifest = ParseManifest(R"({ "capture_links": ["new-client"] })");
EXPECT_EQ(manifest->capture_links, mojom::blink::CaptureLinks::kNewClient);
EXPECT_EQ(0u, GetErrorCount());
}
// Empty array is fine.
{
auto& manifest = ParseManifest(R"({ "capture_links": [] })");
EXPECT_EQ(manifest->capture_links, mojom::blink::CaptureLinks::kUndefined);
EXPECT_EQ(0u, GetErrorCount());
}
// Unknown single string.
{
auto& manifest = ParseManifest(R"({ "capture_links": "unknown" })");
EXPECT_EQ(manifest->capture_links, mojom::blink::CaptureLinks::kUndefined);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("capture_links value 'unknown' ignored, unknown value.",
errors()[0]);
}
// First known value in array is used.
{
auto& manifest = ParseManifest(
R"({ "capture_links": ["none", "existing-client-navigate"] })");
EXPECT_EQ(manifest->capture_links, mojom::blink::CaptureLinks::kNone);
EXPECT_EQ(0u, GetErrorCount());
}
{
auto& manifest = ParseManifest(R"({
"capture_links": [
"unknown",
"existing-client-navigate",
"also-unknown",
"none"
]
})");
EXPECT_EQ(manifest->capture_links,
mojom::blink::CaptureLinks::kExistingClientNavigate);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("capture_links value 'unknown' ignored, unknown value.",
errors()[0]);
}
{
auto& manifest = ParseManifest(R"({
"capture_links": [
1234,
"new-client",
null,
"none"
]
})");
EXPECT_EQ(manifest->capture_links, mojom::blink::CaptureLinks::kNewClient);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ("capture_links value '1234' ignored, string expected.",
errors()[0]);
}
// Don't parse if the property isn't a string or array of strings.
{
auto& manifest = ParseManifest(R"({ "capture_links": null })");
EXPECT_EQ(manifest->capture_links, mojom::blink::CaptureLinks::kUndefined);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'capture_links' ignored, type string or array of strings "
"expected.",
errors()[0]);
}
{
auto& manifest = ParseManifest(R"({ "capture_links": 1234 })");
EXPECT_EQ(manifest->capture_links, mojom::blink::CaptureLinks::kUndefined);
EXPECT_EQ(1u, GetErrorCount());
EXPECT_EQ(
"property 'capture_links' ignored, type string or array of strings "
"expected.",
errors()[0]);
}
{
auto& manifest = ParseManifest(R"({ "capture_links": [12, 34] })");
EXPECT_EQ(manifest->capture_links, mojom::blink::CaptureLinks::kUndefined);
EXPECT_EQ(2u, GetErrorCount());
EXPECT_EQ("capture_links value '12' ignored, string expected.",
errors()[0]);
EXPECT_EQ("capture_links value '34' ignored, string expected.",
errors()[1]);
}
}
} // namespace blink