1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/renderer/manifest/manifest_parser.h"
6
7#include "base/strings/string_util.h"
8#include "content/public/common/manifest.h"
9#include "testing/gtest/include/gtest/gtest.h"
10
11namespace content {
12
13class ManifestParserTest : public testing::Test  {
14 protected:
15  ManifestParserTest() {}
16  virtual ~ManifestParserTest() {}
17
18  Manifest ParseManifest(const base::StringPiece& json,
19                         const GURL& document_url = default_document_url,
20                         const GURL& manifest_url = default_manifest_url) {
21    return ManifestParser::Parse(json, document_url, manifest_url);
22  }
23
24  static const GURL default_document_url;
25  static const GURL default_manifest_url;
26
27 private:
28  DISALLOW_COPY_AND_ASSIGN(ManifestParserTest);
29};
30
31const GURL ManifestParserTest::default_document_url(
32    "http://foo.com/index.html");
33const GURL ManifestParserTest::default_manifest_url(
34    "http://foo.com/manifest.json");
35
36TEST_F(ManifestParserTest, EmptyStringNull) {
37  Manifest manifest = ParseManifest("");
38
39  // A parsing error is equivalent to an empty manifest.
40  ASSERT_TRUE(manifest.IsEmpty());
41  ASSERT_TRUE(manifest.name.is_null());
42  ASSERT_TRUE(manifest.short_name.is_null());
43  ASSERT_TRUE(manifest.start_url.is_empty());
44  ASSERT_EQ(manifest.display, Manifest::DISPLAY_MODE_UNSPECIFIED);
45  ASSERT_EQ(manifest.orientation, blink::WebScreenOrientationLockDefault);
46}
47
48TEST_F(ManifestParserTest, ValidNoContentParses) {
49  Manifest manifest = ParseManifest("{}");
50
51  // Check that all the fields are null in that case.
52  ASSERT_TRUE(manifest.IsEmpty());
53  ASSERT_TRUE(manifest.name.is_null());
54  ASSERT_TRUE(manifest.short_name.is_null());
55  ASSERT_TRUE(manifest.start_url.is_empty());
56  ASSERT_EQ(manifest.display, Manifest::DISPLAY_MODE_UNSPECIFIED);
57  ASSERT_EQ(manifest.orientation, blink::WebScreenOrientationLockDefault);
58}
59
60TEST_F(ManifestParserTest, NameParseRules) {
61  // Smoke test.
62  {
63    Manifest manifest = ParseManifest("{ \"name\": \"foo\" }");
64    ASSERT_TRUE(EqualsASCII(manifest.name.string(), "foo"));
65    ASSERT_FALSE(manifest.IsEmpty());
66  }
67
68  // Trim whitespaces.
69  {
70    Manifest manifest = ParseManifest("{ \"name\": \"  foo  \" }");
71    ASSERT_TRUE(EqualsASCII(manifest.name.string(), "foo"));
72  }
73
74  // Don't parse if name isn't a string.
75  {
76    Manifest manifest = ParseManifest("{ \"name\": {} }");
77    ASSERT_TRUE(manifest.name.is_null());
78  }
79
80  // Don't parse if name isn't a string.
81  {
82    Manifest manifest = ParseManifest("{ \"name\": 42 }");
83    ASSERT_TRUE(manifest.name.is_null());
84  }
85}
86
87TEST_F(ManifestParserTest, ShortNameParseRules) {
88  // Smoke test.
89  {
90    Manifest manifest = ParseManifest("{ \"short_name\": \"foo\" }");
91    ASSERT_TRUE(EqualsASCII(manifest.short_name.string(), "foo"));
92    ASSERT_FALSE(manifest.IsEmpty());
93  }
94
95  // Trim whitespaces.
96  {
97    Manifest manifest = ParseManifest("{ \"short_name\": \"  foo  \" }");
98    ASSERT_TRUE(EqualsASCII(manifest.short_name.string(), "foo"));
99  }
100
101  // Don't parse if name isn't a string.
102  {
103    Manifest manifest = ParseManifest("{ \"short_name\": {} }");
104    ASSERT_TRUE(manifest.short_name.is_null());
105  }
106
107  // Don't parse if name isn't a string.
108  {
109    Manifest manifest = ParseManifest("{ \"short_name\": 42 }");
110    ASSERT_TRUE(manifest.short_name.is_null());
111  }
112}
113
114TEST_F(ManifestParserTest, StartURLParseRules) {
115  // Smoke test.
116  {
117    Manifest manifest = ParseManifest("{ \"start_url\": \"land.html\" }");
118    ASSERT_EQ(manifest.start_url.spec(),
119              default_document_url.Resolve("land.html").spec());
120    ASSERT_FALSE(manifest.IsEmpty());
121  }
122
123  // Whitespaces.
124  {
125    Manifest manifest = ParseManifest("{ \"start_url\": \"  land.html  \" }");
126    ASSERT_EQ(manifest.start_url.spec(),
127              default_document_url.Resolve("land.html").spec());
128  }
129
130  // Don't parse if property isn't a string.
131  {
132    Manifest manifest = ParseManifest("{ \"start_url\": {} }");
133    ASSERT_TRUE(manifest.start_url.is_empty());
134  }
135
136  // Don't parse if property isn't a string.
137  {
138    Manifest manifest = ParseManifest("{ \"start_url\": 42 }");
139    ASSERT_TRUE(manifest.start_url.is_empty());
140  }
141
142  // Absolute start_url, same origin with document.
143  {
144    Manifest manifest =
145        ParseManifest("{ \"start_url\": \"http://foo.com/land.html\" }",
146                      GURL("http://foo.com/manifest.json"),
147                      GURL("http://foo.com/index.html"));
148    ASSERT_EQ(manifest.start_url.spec(), "http://foo.com/land.html");
149  }
150
151  // Absolute start_url, cross origin with document.
152  {
153    Manifest manifest =
154        ParseManifest("{ \"start_url\": \"http://bar.com/land.html\" }",
155                      GURL("http://foo.com/manifest.json"),
156                      GURL("http://foo.com/index.html"));
157    ASSERT_TRUE(manifest.start_url.is_empty());
158  }
159
160  // Resolving has to happen based on the manifest_url.
161  {
162    Manifest manifest =
163        ParseManifest("{ \"start_url\": \"land.html\" }",
164                      GURL("http://foo.com/landing/manifest.json"),
165                      GURL("http://foo.com/index.html"));
166    ASSERT_EQ(manifest.start_url.spec(), "http://foo.com/landing/land.html");
167  }
168}
169
170TEST_F(ManifestParserTest, DisplayParserRules) {
171  // Smoke test.
172  {
173    Manifest manifest = ParseManifest("{ \"display\": \"browser\" }");
174    EXPECT_EQ(manifest.display, Manifest::DISPLAY_MODE_BROWSER);
175    EXPECT_FALSE(manifest.IsEmpty());
176  }
177
178  // Trim whitespaces.
179  {
180    Manifest manifest = ParseManifest("{ \"display\": \"  browser  \" }");
181    EXPECT_EQ(manifest.display, Manifest::DISPLAY_MODE_BROWSER);
182  }
183
184  // Don't parse if name isn't a string.
185  {
186    Manifest manifest = ParseManifest("{ \"display\": {} }");
187    EXPECT_EQ(manifest.display, Manifest::DISPLAY_MODE_UNSPECIFIED);
188  }
189
190  // Don't parse if name isn't a string.
191  {
192    Manifest manifest = ParseManifest("{ \"display\": 42 }");
193    EXPECT_EQ(manifest.display, Manifest::DISPLAY_MODE_UNSPECIFIED);
194  }
195
196  // Parse fails if string isn't known.
197  {
198    Manifest manifest = ParseManifest("{ \"display\": \"browser_something\" }");
199    EXPECT_EQ(manifest.display, Manifest::DISPLAY_MODE_UNSPECIFIED);
200  }
201
202  // Accept 'fullscreen'.
203  {
204    Manifest manifest = ParseManifest("{ \"display\": \"fullscreen\" }");
205    EXPECT_EQ(manifest.display, Manifest::DISPLAY_MODE_FULLSCREEN);
206  }
207
208  // Accept 'fullscreen'.
209  {
210    Manifest manifest = ParseManifest("{ \"display\": \"standalone\" }");
211    EXPECT_EQ(manifest.display, Manifest::DISPLAY_MODE_STANDALONE);
212  }
213
214  // Accept 'minimal-ui'.
215  {
216    Manifest manifest = ParseManifest("{ \"display\": \"minimal-ui\" }");
217    EXPECT_EQ(manifest.display, Manifest::DISPLAY_MODE_MINIMAL_UI);
218  }
219
220  // Accept 'browser'.
221  {
222    Manifest manifest = ParseManifest("{ \"display\": \"browser\" }");
223    EXPECT_EQ(manifest.display, Manifest::DISPLAY_MODE_BROWSER);
224  }
225
226  // Case insensitive.
227  {
228    Manifest manifest = ParseManifest("{ \"display\": \"BROWSER\" }");
229    EXPECT_EQ(manifest.display, Manifest::DISPLAY_MODE_BROWSER);
230  }
231}
232
233TEST_F(ManifestParserTest, OrientationParserRules) {
234  // Smoke test.
235  {
236    Manifest manifest = ParseManifest("{ \"orientation\": \"natural\" }");
237    EXPECT_EQ(manifest.orientation, blink::WebScreenOrientationLockNatural);
238    EXPECT_FALSE(manifest.IsEmpty());
239  }
240
241  // Trim whitespaces.
242  {
243    Manifest manifest = ParseManifest("{ \"orientation\": \"natural\" }");
244    EXPECT_EQ(manifest.orientation, blink::WebScreenOrientationLockNatural);
245  }
246
247  // Don't parse if name isn't a string.
248  {
249    Manifest manifest = ParseManifest("{ \"orientation\": {} }");
250    EXPECT_EQ(manifest.orientation, blink::WebScreenOrientationLockDefault);
251  }
252
253  // Don't parse if name isn't a string.
254  {
255    Manifest manifest = ParseManifest("{ \"orientation\": 42 }");
256    EXPECT_EQ(manifest.orientation, blink::WebScreenOrientationLockDefault);
257  }
258
259  // Parse fails if string isn't known.
260  {
261    Manifest manifest = ParseManifest("{ \"orientation\": \"naturalish\" }");
262    EXPECT_EQ(manifest.orientation, blink::WebScreenOrientationLockDefault);
263  }
264
265  // Accept 'any'.
266  {
267    Manifest manifest = ParseManifest("{ \"orientation\": \"any\" }");
268    EXPECT_EQ(manifest.orientation, blink::WebScreenOrientationLockAny);
269  }
270
271  // Accept 'natural'.
272  {
273    Manifest manifest = ParseManifest("{ \"orientation\": \"natural\" }");
274    EXPECT_EQ(manifest.orientation, blink::WebScreenOrientationLockNatural);
275  }
276
277  // Accept 'landscape'.
278  {
279    Manifest manifest = ParseManifest("{ \"orientation\": \"landscape\" }");
280    EXPECT_EQ(manifest.orientation, blink::WebScreenOrientationLockLandscape);
281  }
282
283  // Accept 'landscape-primary'.
284  {
285    Manifest manifest =
286        ParseManifest("{ \"orientation\": \"landscape-primary\" }");
287    EXPECT_EQ(manifest.orientation,
288              blink::WebScreenOrientationLockLandscapePrimary);
289  }
290
291  // Accept 'landscape-secondary'.
292  {
293    Manifest manifest =
294        ParseManifest("{ \"orientation\": \"landscape-secondary\" }");
295    EXPECT_EQ(manifest.orientation,
296              blink::WebScreenOrientationLockLandscapeSecondary);
297  }
298
299  // Accept 'portrait'.
300  {
301    Manifest manifest = ParseManifest("{ \"orientation\": \"portrait\" }");
302    EXPECT_EQ(manifest.orientation, blink::WebScreenOrientationLockPortrait);
303  }
304
305  // Accept 'portrait-primary'.
306  {
307    Manifest manifest =
308      ParseManifest("{ \"orientation\": \"portrait-primary\" }");
309    EXPECT_EQ(manifest.orientation,
310              blink::WebScreenOrientationLockPortraitPrimary);
311  }
312
313  // Accept 'portrait-secondary'.
314  {
315    Manifest manifest =
316        ParseManifest("{ \"orientation\": \"portrait-secondary\" }");
317    EXPECT_EQ(manifest.orientation,
318              blink::WebScreenOrientationLockPortraitSecondary);
319  }
320
321  // Case insensitive.
322  {
323    Manifest manifest = ParseManifest("{ \"orientation\": \"LANDSCAPE\" }");
324    EXPECT_EQ(manifest.orientation, blink::WebScreenOrientationLockLandscape);
325  }
326}
327
328TEST_F(ManifestParserTest, IconsParseRules) {
329  // Smoke test: if no icon, empty list.
330  {
331    Manifest manifest = ParseManifest("{ \"icons\": [] }");
332    EXPECT_EQ(manifest.icons.size(), 0u);
333    EXPECT_TRUE(manifest.IsEmpty());
334  }
335
336  // Smoke test: if empty icon, empty list.
337  {
338    Manifest manifest = ParseManifest("{ \"icons\": [ {} ] }");
339    EXPECT_EQ(manifest.icons.size(), 0u);
340    EXPECT_TRUE(manifest.IsEmpty());
341  }
342
343  // Smoke test: icon with invalid src, empty list.
344  {
345    Manifest manifest = ParseManifest("{ \"icons\": [ { \"icons\": [] } ] }");
346    EXPECT_EQ(manifest.icons.size(), 0u);
347    EXPECT_TRUE(manifest.IsEmpty());
348  }
349
350  // Smoke test: if icon with empty src, it will be present in the list.
351  {
352    Manifest manifest = ParseManifest("{ \"icons\": [ { \"src\": \"\" } ] }");
353    EXPECT_EQ(manifest.icons.size(), 1u);
354    EXPECT_EQ(manifest.icons[0].src.spec(), "http://foo.com/index.html");
355    EXPECT_FALSE(manifest.IsEmpty());
356  }
357
358  // Smoke test: if one icons with valid src, it will be present in the list.
359  {
360    Manifest manifest =
361        ParseManifest("{ \"icons\": [{ \"src\": \"foo.jpg\" }] }");
362    EXPECT_EQ(manifest.icons.size(), 1u);
363    EXPECT_EQ(manifest.icons[0].src.spec(), "http://foo.com/foo.jpg");
364    EXPECT_FALSE(manifest.IsEmpty());
365  }
366}
367
368TEST_F(ManifestParserTest, IconSrcParseRules) {
369  // Smoke test.
370  {
371    Manifest manifest =
372        ParseManifest("{ \"icons\": [ {\"src\": \"foo.png\" } ] }");
373    EXPECT_EQ(manifest.icons[0].src.spec(),
374              default_document_url.Resolve("foo.png").spec());
375  }
376
377  // Whitespaces.
378  {
379    Manifest manifest =
380        ParseManifest("{ \"icons\": [ {\"src\": \"   foo.png   \" } ] }");
381    EXPECT_EQ(manifest.icons[0].src.spec(),
382              default_document_url.Resolve("foo.png").spec());
383  }
384
385  // Don't parse if property isn't a string.
386  {
387    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": {} } ] }");
388    EXPECT_TRUE(manifest.icons.empty());
389  }
390
391  // Don't parse if property isn't a string.
392  {
393    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": 42 } ] }");
394    EXPECT_TRUE(manifest.icons.empty());
395  }
396
397  // Resolving has to happen based on the document_url.
398  {
399    Manifest manifest =
400        ParseManifest("{ \"icons\": [ {\"src\": \"icons/foo.png\" } ] }",
401                      GURL("http://foo.com/landing/index.html"));
402    EXPECT_EQ(manifest.icons[0].src.spec(),
403              "http://foo.com/landing/icons/foo.png");
404  }
405}
406
407TEST_F(ManifestParserTest, IconTypeParseRules) {
408  // Smoke test.
409  {
410    Manifest manifest =
411        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"type\": \"foo\" } ] }");
412    EXPECT_TRUE(EqualsASCII(manifest.icons[0].type.string(), "foo"));
413  }
414
415  // Trim whitespaces.
416  {
417    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
418                                      " \"type\": \"  foo  \" } ] }");
419    EXPECT_TRUE(EqualsASCII(manifest.icons[0].type.string(), "foo"));
420  }
421
422  // Don't parse if property isn't a string.
423  {
424    Manifest manifest =
425        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"type\": {} } ] }");
426    EXPECT_TRUE(manifest.icons[0].type.is_null());
427  }
428
429  // Don't parse if property isn't a string.
430  {
431    Manifest manifest =
432        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"type\": 42 } ] }");
433    EXPECT_TRUE(manifest.icons[0].type.is_null());
434  }
435}
436
437TEST_F(ManifestParserTest, IconDensityParseRules) {
438  // Smoke test.
439  {
440    Manifest manifest =
441        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": 42 } ] }");
442    EXPECT_EQ(manifest.icons[0].density, 42);
443  }
444
445  // Decimal value.
446  {
447    Manifest manifest =
448        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": 2.5 } ] }");
449    EXPECT_EQ(manifest.icons[0].density, 2.5);
450  }
451
452  // Parse fail if it isn't a float.
453  {
454    Manifest manifest =
455        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": {} } ] }");
456    EXPECT_EQ(manifest.icons[0].density, Manifest::Icon::kDefaultDensity);
457  }
458
459  // Parse fail if it isn't a float.
460  {
461    Manifest manifest =
462        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\":\"2\" } ] }");
463    EXPECT_EQ(manifest.icons[0].density, Manifest::Icon::kDefaultDensity);
464  }
465
466  // Edge case: 1.0.
467  {
468    Manifest manifest =
469        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": 1.00 } ] }");
470    EXPECT_EQ(manifest.icons[0].density, 1);
471  }
472
473  // Edge case: values between 0.0 and 1.0 are allowed.
474  {
475    Manifest manifest =
476        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": 0.42 } ] }");
477    EXPECT_EQ(manifest.icons[0].density, 0.42);
478  }
479
480  // 0 is an invalid value.
481  {
482    Manifest manifest =
483        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": 0.0 } ] }");
484    EXPECT_EQ(manifest.icons[0].density, Manifest::Icon::kDefaultDensity);
485  }
486
487  // Negative values are invalid.
488  {
489    Manifest manifest =
490        ParseManifest("{ \"icons\": [ {\"src\": \"\", \"density\": -2.5 } ] }");
491    EXPECT_EQ(manifest.icons[0].density, Manifest::Icon::kDefaultDensity);
492  }
493}
494
495TEST_F(ManifestParserTest, IconSizesParseRules) {
496  // Smoke test.
497  {
498    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
499        "\"sizes\": \"42x42\" } ] }");
500    EXPECT_EQ(manifest.icons[0].sizes.size(), 1u);
501  }
502
503  // Trim whitespaces.
504  {
505    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
506        "\"sizes\": \"  42x42  \" } ] }");
507    EXPECT_EQ(manifest.icons[0].sizes.size(), 1u);
508  }
509
510  // Don't parse if name isn't a string.
511  {
512    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
513        "\"sizes\": {} } ] }");
514    EXPECT_EQ(manifest.icons[0].sizes.size(), 0u);
515  }
516
517  // Don't parse if name isn't a string.
518  {
519    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
520        "\"sizes\": 42 } ] }");
521    EXPECT_EQ(manifest.icons[0].sizes.size(), 0u);
522  }
523
524  // Smoke test: value correctly parsed.
525  {
526    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
527        "\"sizes\": \"42x42  48x48\" } ] }");
528    EXPECT_EQ(manifest.icons[0].sizes[0], gfx::Size(42, 42));
529    EXPECT_EQ(manifest.icons[0].sizes[1], gfx::Size(48, 48));
530  }
531
532  // <WIDTH>'x'<HEIGHT> and <WIDTH>'X'<HEIGHT> are equivalent.
533  {
534    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
535        "\"sizes\": \"42X42  48X48\" } ] }");
536    EXPECT_EQ(manifest.icons[0].sizes[0], gfx::Size(42, 42));
537    EXPECT_EQ(manifest.icons[0].sizes[1], gfx::Size(48, 48));
538  }
539
540  // Twice the same value is parsed twice.
541  {
542    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
543        "\"sizes\": \"42X42  42x42\" } ] }");
544    EXPECT_EQ(manifest.icons[0].sizes[0], gfx::Size(42, 42));
545    EXPECT_EQ(manifest.icons[0].sizes[1], gfx::Size(42, 42));
546  }
547
548  // Width or height can't start with 0.
549  {
550    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
551        "\"sizes\": \"004X007  042x00\" } ] }");
552    EXPECT_EQ(manifest.icons[0].sizes.size(), 0u);
553  }
554
555  // Width and height MUST contain digits.
556  {
557    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
558        "\"sizes\": \"e4X1.0  55ax1e10\" } ] }");
559    EXPECT_EQ(manifest.icons[0].sizes.size(), 0u);
560  }
561
562  // 'any' is correctly parsed and transformed to gfx::Size(0,0).
563  {
564    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
565        "\"sizes\": \"any AnY ANY aNy\" } ] }");
566    gfx::Size any = gfx::Size(0, 0);
567    EXPECT_EQ(manifest.icons[0].sizes.size(), 4u);
568    EXPECT_EQ(manifest.icons[0].sizes[0], any);
569    EXPECT_EQ(manifest.icons[0].sizes[1], any);
570    EXPECT_EQ(manifest.icons[0].sizes[2], any);
571    EXPECT_EQ(manifest.icons[0].sizes[3], any);
572  }
573
574  // Some invalid width/height combinations.
575  {
576    Manifest manifest = ParseManifest("{ \"icons\": [ {\"src\": \"\","
577        "\"sizes\": \"x 40xx 1x2x3 x42 42xx42\" } ] }");
578    gfx::Size any = gfx::Size(0, 0);
579    EXPECT_EQ(manifest.icons[0].sizes.size(), 0u);
580  }
581}
582
583} // namespace content
584