summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormlamouri <mlamouri@chromium.org>2014-09-24 03:37:06 -0700
committerCommit bot <commit-bot@chromium.org>2014-09-24 10:37:17 +0000
commitc00b78f53f2bc38d19c0dd671a6558160d98f808 (patch)
treef150b2f2e9aa232a7a0417c830ba0b4ac3319abe
parent3fdb73284d2b947f3a81ebe81501635b220b4dc1 (diff)
downloadchromium_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.cc10
-rw-r--r--content/common/manifest_manager_messages.h7
-rw-r--r--content/public/common/manifest.cc11
-rw-r--r--content/public/common/manifest.h29
-rw-r--r--content/renderer/manifest/manifest_manager.cc6
-rw-r--r--content/renderer/manifest/manifest_parser.cc82
-rw-r--r--content/renderer/manifest/manifest_parser_unittest.cc167
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