diff options
author | mlamouri <mlamouri@chromium.org> | 2014-09-24 03:37:06 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-24 10:37:17 +0000 |
commit | c00b78f53f2bc38d19c0dd671a6558160d98f808 (patch) | |
tree | f150b2f2e9aa232a7a0417c830ba0b4ac3319abe | |
parent | 3fdb73284d2b947f3a81ebe81501635b220b4dc1 (diff) | |
download | chromium_src-c00b78f53f2bc38d19c0dd671a6558160d98f808.zip chromium_src-c00b78f53f2bc38d19c0dd671a6558160d98f808.tar.gz chromium_src-c00b78f53f2bc38d19c0dd671a6558160d98f808.tar.bz2 |
Add support for icons.{src,type,density} in Manifest.
This is not yet supporting icons.sizes. It will be
implemented in a separate CL because the algorithm might be
more complex and could require some code sharing with Blink.
BUG=366145
Review URL: https://codereview.chromium.org/590563002
Cr-Commit-Position: refs/heads/master@{#296382}
-rw-r--r-- | content/browser/manifest/manifest_manager_host.cc | 10 | ||||
-rw-r--r-- | content/common/manifest_manager_messages.h | 7 | ||||
-rw-r--r-- | content/public/common/manifest.cc | 11 | ||||
-rw-r--r-- | content/public/common/manifest.h | 29 | ||||
-rw-r--r-- | content/renderer/manifest/manifest_manager.cc | 6 | ||||
-rw-r--r-- | content/renderer/manifest/manifest_parser.cc | 82 | ||||
-rw-r--r-- | content/renderer/manifest/manifest_parser_unittest.cc | 167 |
7 files changed, 303 insertions, 9 deletions
diff --git a/content/browser/manifest/manifest_manager_host.cc b/content/browser/manifest/manifest_manager_host.cc index 796d0a9..ebab903 100644 --- a/content/browser/manifest/manifest_manager_host.cc +++ b/content/browser/manifest/manifest_manager_host.cc @@ -109,8 +109,16 @@ void ManifestManagerHost::OnRequestManifestResponse( manifest.short_name = base::NullableString16( manifest.short_name.string().substr(0, Manifest::kMaxIPCStringLength), manifest.short_name.is_null()); - if (!manifest.start_url.is_empty() && !manifest.start_url.is_valid()) + if (!manifest.start_url.is_valid()) manifest.start_url = GURL(); + for (size_t i = 0; i < manifest.icons.size(); ++i) { + if (!manifest.icons[i].src.is_valid()) + manifest.icons[i].src = GURL(); + manifest.icons[i].type = base::NullableString16( + manifest.icons[i].type.string().substr(0, + Manifest::kMaxIPCStringLength), + manifest.icons[i].type.is_null()); + } callback->Run(manifest); callbacks->Remove(request_id); diff --git a/content/common/manifest_manager_messages.h b/content/common/manifest_manager_messages.h index b199088..a447de9 100644 --- a/content/common/manifest_manager_messages.h +++ b/content/common/manifest_manager_messages.h @@ -17,12 +17,19 @@ IPC_ENUM_TRAITS_MAX_VALUE(content::Manifest::DisplayMode, content::Manifest::DISPLAY_MODE_BROWSER) +IPC_STRUCT_TRAITS_BEGIN(content::Manifest::Icon) + IPC_STRUCT_TRAITS_MEMBER(src) + IPC_STRUCT_TRAITS_MEMBER(type) + IPC_STRUCT_TRAITS_MEMBER(density) +IPC_STRUCT_TRAITS_END() + IPC_STRUCT_TRAITS_BEGIN(content::Manifest) IPC_STRUCT_TRAITS_MEMBER(name) IPC_STRUCT_TRAITS_MEMBER(short_name) IPC_STRUCT_TRAITS_MEMBER(start_url) IPC_STRUCT_TRAITS_MEMBER(display) IPC_STRUCT_TRAITS_MEMBER(orientation) + IPC_STRUCT_TRAITS_MEMBER(icons) IPC_STRUCT_TRAITS_END() // The browser process requests for the manifest linked with the associated diff --git a/content/public/common/manifest.cc b/content/public/common/manifest.cc index 9aeedd4..71b2cd2 100644 --- a/content/public/common/manifest.cc +++ b/content/public/common/manifest.cc @@ -6,8 +6,16 @@ namespace content { +const double Manifest::Icon::kDefaultDensity = 1; const size_t Manifest::kMaxIPCStringLength = 4 * 1024; +Manifest::Icon::Icon() + : density(kDefaultDensity) { +} + +Manifest::Icon::~Icon() { +} + Manifest::Manifest() : display(DISPLAY_MODE_UNSPECIFIED), orientation(blink::WebScreenOrientationLockDefault) { @@ -21,7 +29,8 @@ bool Manifest::IsEmpty() const { short_name.is_null() && start_url.is_empty() && display == DISPLAY_MODE_UNSPECIFIED && - orientation == blink::WebScreenOrientationLockDefault; + orientation == blink::WebScreenOrientationLockDefault && + icons.empty(); } } // namespace content diff --git a/content/public/common/manifest.h b/content/public/common/manifest.h index 35a49b1..949c610 100644 --- a/content/public/common/manifest.h +++ b/content/public/common/manifest.h @@ -5,6 +5,8 @@ #ifndef CONTENT_PUBLIC_COMMON_MANIFEST_H_ #define CONTENT_PUBLIC_COMMON_MANIFEST_H_ +#include <vector> + #include "base/strings/nullable_string16.h" #include "content/common/content_export.h" #include "third_party/WebKit/public/platform/WebScreenOrientationLockType.h" @@ -24,6 +26,29 @@ struct CONTENT_EXPORT Manifest { DISPLAY_MODE_BROWSER }; + // Structure representing an icon as per the Manifest specification, see: + // http://w3c.github.io/manifest/#dfn-icon-object + struct CONTENT_EXPORT Icon { + Icon(); + ~Icon(); + + // MUST be a valid url. If an icon doesn't have a valid URL, it will not be + // successfully parsed, thus will not be represented in the Manifest. + GURL src; + + // Null if the parsing failed or the field was not present. The type can be + // any string and doesn't have to be a valid image MIME type at this point. + // It is up to the consumer of the object to check if the type matches a + // supported type. + base::NullableString16 type; + + // Default value is 1.0 if the value is missing or invalid. + double density; + + // Default density. Set to 1.0. + static const double kDefaultDensity; + }; + Manifest(); ~Manifest(); @@ -48,6 +73,10 @@ struct CONTENT_EXPORT Manifest { // field was not present. blink::WebScreenOrientationLockType orientation; + // Empty if the parsing failed, the field was not present, empty or all the + // icons inside the JSON array were invalid. + std::vector<Icon> icons; + // Maximum length for all the strings inside the Manifest when it is sent over // IPC. The renderer process should truncate the strings before sending the // Manifest and the browser process must do the same when receiving it. diff --git a/content/renderer/manifest/manifest_manager.cc b/content/renderer/manifest/manifest_manager.cc index 88ef6ac..08612b8 100644 --- a/content/renderer/manifest/manifest_manager.cc +++ b/content/renderer/manifest/manifest_manager.cc @@ -59,6 +59,12 @@ void ManifestManager::OnRequestManifestComplete( ipc_manifest.short_name.string().substr(0, Manifest::kMaxIPCStringLength), ipc_manifest.short_name.is_null()); + for (size_t i = 0; i < ipc_manifest.icons.size(); ++i) { + ipc_manifest.icons[i].type = base::NullableString16( + ipc_manifest.icons[i].type.string().substr( + 0, Manifest::kMaxIPCStringLength), + ipc_manifest.icons[i].type.is_null()); + } Send(new ManifestManagerHostMsg_RequestManifestResponse( routing_id(), request_id, ipc_manifest)); diff --git a/content/renderer/manifest/manifest_parser.cc b/content/renderer/manifest/manifest_parser.cc index 3f340ba..3f2f989 100644 --- a/content/renderer/manifest/manifest_parser.cc +++ b/content/renderer/manifest/manifest_parser.cc @@ -38,6 +38,20 @@ base::NullableString16 ParseString(const base::DictionaryValue& dictionary, return base::NullableString16(value, false); } +// Helper function to parse URLs present on a given |dictionary| in a given +// field identified by its |key|. The URL is first parsed as a string then +// resolved using |base_url|. +// Returns a GURL. If the parsing failed, the GURL will not be valid. +GURL ParseURL(const base::DictionaryValue& dictionary, + const std::string& key, + const GURL& base_url) { + base::NullableString16 url_str = ParseString(dictionary, key, NoTrim); + if (url_str.is_null()) + return GURL(); + + return base_url.Resolve(url_str.string()); +} + // Parses the 'name' field of the manifest, as defined in: // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-name-member // Returns the parsed string if any, a null string if the parsing failed. @@ -59,13 +73,7 @@ base::NullableString16 ParseShortName( GURL ParseStartURL(const base::DictionaryValue& dictionary, const GURL& manifest_url, const GURL& document_url) { - base::NullableString16 start_url_str = - ParseString(dictionary, "start_url", NoTrim); - - if (start_url_str.is_null()) - return GURL(); - - GURL start_url = manifest_url.Resolve(start_url_str.string()); + GURL start_url = ParseURL(dictionary, "start_url", manifest_url); if (!start_url.is_valid()) return GURL(); @@ -131,6 +139,65 @@ blink::WebScreenOrientationLockType ParseOrientation( return blink::WebScreenOrientationLockDefault; } +// Parses the 'src' field of an icon, as defined in: +// http://w3c.github.io/manifest/#dfn-steps-for-processing-the-src-member-of-an-icon +// Returns the parsed GURL if any, an empty GURL if the parsing failed. +GURL ParseIconSrc(const base::DictionaryValue& icon, + const GURL& manifest_url) { + return ParseURL(icon, "src", manifest_url); +} + +// Parses the 'type' field of an icon, as defined in: +// http://w3c.github.io/manifest/#dfn-steps-for-processing-the-type-member-of-an-icon +// Returns the parsed string if any, a null string if the parsing failed. +base::NullableString16 ParseIconType(const base::DictionaryValue& icon) { + return ParseString(icon, "type", Trim); +} + +// Parses the 'density' field of an icon, as defined in: +// http://w3c.github.io/manifest/#dfn-steps-for-processing-a-density-member-of-an-icon +// Returns the parsed double if any, Manifest::Icon::kDefaultDensity if the +// parsing failed. +double ParseIconDensity(const base::DictionaryValue& icon) { + double density; + if (!icon.GetDouble("density", &density) || density <= 0) + return Manifest::Icon::kDefaultDensity; + return density; +} + +std::vector<Manifest::Icon> ParseIcons(const base::DictionaryValue& dictionary, + const GURL& manifest_url) { + std::vector<Manifest::Icon> icons; + if (!dictionary.HasKey("icons")) + return icons; + + const base::ListValue* icons_list = 0; + if (!dictionary.GetList("icons", &icons_list)) { + // TODO(mlamouri): provide a custom message to the developer console about + // the property being incorrectly set. + return icons; + } + + for (size_t i = 0; i < icons_list->GetSize(); ++i) { + const base::DictionaryValue* icon_dictionary = 0; + if (!icons_list->GetDictionary(i, &icon_dictionary)) + continue; + + Manifest::Icon icon; + icon.src = ParseIconSrc(*icon_dictionary, manifest_url); + // An icon MUST have a valid src. If it does not, it MUST be ignored. + if (!icon.src.is_valid()) + continue; + icon.type = ParseIconType(*icon_dictionary); + icon.density = ParseIconDensity(*icon_dictionary); + // TODO(mlamouri): icon.sizes + + icons.push_back(icon); + } + + return icons; +} + } // anonymous namespace Manifest ManifestParser::Parse(const base::StringPiece& json, @@ -162,6 +229,7 @@ Manifest ManifestParser::Parse(const base::StringPiece& json, manifest.start_url = ParseStartURL(*dictionary, manifest_url, document_url); manifest.display = ParseDisplay(*dictionary); manifest.orientation = ParseOrientation(*dictionary); + manifest.icons = ParseIcons(*dictionary, manifest_url); return manifest; } diff --git a/content/renderer/manifest/manifest_parser_unittest.cc b/content/renderer/manifest/manifest_parser_unittest.cc index 12ea5d6..c2ce8da 100644 --- a/content/renderer/manifest/manifest_parser_unittest.cc +++ b/content/renderer/manifest/manifest_parser_unittest.cc @@ -325,4 +325,171 @@ TEST_F(ManifestParserTest, OrientationParserRules) { } } +TEST_F(ManifestParserTest, IconsParseRules) { + // Smoke test: if no icon, empty list. + { + Manifest manifest = ParseManifest("{ \"icons\": [] }"); + EXPECT_EQ(manifest.icons.size(), 0u); + EXPECT_TRUE(manifest.IsEmpty()); + } + + // Smoke test: if empty icon, empty list. + { + Manifest manifest = ParseManifest("{ \"icons\": [ {} ] }"); + EXPECT_EQ(manifest.icons.size(), 0u); + EXPECT_TRUE(manifest.IsEmpty()); + } + + // Smoke test: icon with invalid src, empty list. + { + Manifest manifest = ParseManifest("{ \"icons\": [ { \"icons\": [] } ] }"); + EXPECT_EQ(manifest.icons.size(), 0u); + EXPECT_TRUE(manifest.IsEmpty()); + } + + // Smoke test: if icon with empty src, it will be present in the list. + { + Manifest manifest = ParseManifest("{ \"icons\": [ { \"src\": \"\" } ] }"); + EXPECT_EQ(manifest.icons.size(), 1u); + EXPECT_EQ(manifest.icons[0].src.spec(), "http://foo.com/index.html"); + EXPECT_FALSE(manifest.IsEmpty()); + } + + // Smoke test: if one icons with valid src, it will be present in the list. + { + Manifest manifest = + ParseManifest("{ \"icons\": [{ \"src\": \"foo.jpg\" }] }"); + EXPECT_EQ(manifest.icons.size(), 1u); + EXPECT_EQ(manifest.icons[0].src.spec(), "http://foo.com/foo.jpg"); + EXPECT_FALSE(manifest.IsEmpty()); + } +} + +TEST_F(ManifestParserTest, IconSrcParseRules) { + // Smoke test. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"foo.png\" } ] }"); + EXPECT_EQ(manifest.icons[0].src.spec(), + default_document_url.Resolve("foo.png").spec()); + } + + // Whitespaces. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \" foo.png \" } ] }"); + EXPECT_EQ(manifest.icons[0].src.spec(), + default_document_url.Resolve("foo.png").spec()); + } + + // Don't parse if property isn't a string. + { + Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": {} } ] }"); + EXPECT_TRUE(manifest.icons.empty()); + } + + // Don't parse if property isn't a string. + { + Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": 42 } ] }"); + EXPECT_TRUE(manifest.icons.empty()); + } + + // Resolving has to happen based on the document_url. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"icons/foo.png\" } ] }", + GURL("http://foo.com/landing/index.html")); + EXPECT_EQ(manifest.icons[0].src.spec(), + "http://foo.com/landing/icons/foo.png"); + } +} + +TEST_F(ManifestParserTest, IconTypeParseRules) { + // Smoke test. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"type\": \"foo\" } ] }"); + EXPECT_TRUE(EqualsASCII(manifest.icons[0].type.string(), "foo")); + } + + // Trim whitespaces. + { + Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\"," + " \"type\": \" foo \" } ] }"); + EXPECT_TRUE(EqualsASCII(manifest.icons[0].type.string(), "foo")); + } + + // Don't parse if property isn't a string. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"type\": {} } ] }"); + EXPECT_TRUE(manifest.icons[0].type.is_null()); + } + + // Don't parse if property isn't a string. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"type\": 42 } ] }"); + EXPECT_TRUE(manifest.icons[0].type.is_null()); + } +} + +TEST_F(ManifestParserTest, IconDensityParseRules) { + // Smoke test. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": 42 } ] }"); + EXPECT_EQ(manifest.icons[0].density, 42); + } + + // Decimal value. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": 2.5 } ] }"); + EXPECT_EQ(manifest.icons[0].density, 2.5); + } + + // Parse fail if it isn't a float. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": {} } ] }"); + EXPECT_EQ(manifest.icons[0].density, Manifest::Icon::kDefaultDensity); + } + + // Parse fail if it isn't a float. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\":\"2\" } ] }"); + EXPECT_EQ(manifest.icons[0].density, Manifest::Icon::kDefaultDensity); + } + + // Edge case: 1.0. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": 1.00 } ] }"); + EXPECT_EQ(manifest.icons[0].density, 1); + } + + // Edge case: values between 0.0 and 1.0 are allowed. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": 0.42 } ] }"); + EXPECT_EQ(manifest.icons[0].density, 0.42); + } + + // 0 is an invalid value. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": 0.0 } ] }"); + EXPECT_EQ(manifest.icons[0].density, Manifest::Icon::kDefaultDensity); + } + + // Negative values are invalid. + { + Manifest manifest = + ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": -2.5 } ] }"); + EXPECT_EQ(manifest.icons[0].density, Manifest::Icon::kDefaultDensity); + } +} + } // namespace content |