2edbd5866ec53d806134ea3f8f982b50f1d6c8c9
[platform/framework/web/crosswalk.git] / src / xwalk / application / common / manifest_handlers / tizen_appwidget_handler.cc
1 // Copyright (c) 2014 Samsung Electronics Co., Ltd 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 "xwalk/application/common/manifest_handlers/tizen_appwidget_handler.h"
6
7 #include <limits>
8 #include <set>
9
10 #include "base/macros.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/values.h"
14 #include "third_party/re2/re2/re2.h"
15 #include "xwalk/application/common/application_manifest_constants.h"
16 #include "xwalk/application/common/manifest_handlers/tizen_application_handler.h"
17
18 namespace xwalk {
19
20 namespace keys = application_widget_keys;
21
22 namespace application {
23
24 namespace {
25
26 const char kErrMsgInvalidDictionary[] =
27     "Cannot get key value as a dictionary. Key name: ";
28 const char kErrMsgInvalidList[] =
29     "Cannot get key value as a list. Key name: ";
30 const char kErrMsgNoMandatoryKey[] =
31     "Cannot find mandatory key. Key name: ";
32 const char kErrMsgInvalidKeyValue[] =
33     "Invalid key value. Key name: ";
34 const char kErrMsgMultipleKeys[] =
35     "Too many keys found. Key name: ";
36 const char kErrMsgNoNamespace[] =
37     "Element pointed by key has no namespace specified. Key name: ";
38 const char kErrMsgInvalidNamespace[] =
39     "Invalid namespace of element pointed by key. Key name: ";
40 const char kErrMsgAppWidgetInfoNotFound[] =
41     "Cannot access app-widget info object.";
42 const char kErrMsgApplicationInfoNotFound[] =
43     "Cannot access application info object.";
44 const char kErrMsgDuplicatedAppWidgetId[] =
45     "Duplicated value of an id attribute in app-widget element. The value: ";
46 const char kErrMsgInvalidAppWidgetIdBeginning[] =
47     "Invalid beginning of an id attribute value in app-widget element."
48     " The value: ";
49 const char kErrMsgInvalidAppWidgetIdFormat[] =
50     "Invalid format of an id attribute value in app-widget element."
51     " The value: ";
52 const char kErrMsgUpdatePeriodOutOfDomain[] =
53     "Value of an update-period attribute in app-widget element out of domain."
54     " The value: ";
55 const char kErrMsgNoLabel[] =
56     "No box-label element in app-widget element.";
57 const char kErrMsgInvalidIconSrc[] =
58     "Invalid path in a src attribute of box-icon element. The value: ";
59 const char kErrMsgInvalidContentSrc[] =
60     "Invalid path or url in a src attribute of box-content element."
61     " The value: ";
62 const char kErrMsgInvalidContentSizePreview[] =
63     "Invalid path in a preview attribute of box-size element. The value: ";
64 const char kErrMsgNoMandatoryContentSize1x1[] =
65     "No mandatory box-size element (1x1) in box-content element.";
66 const char kErrMsgInvalidContentDropViewSrc[] =
67     "Invalid path or url in a src attribute of pd element. The value: ";
68 const char kErrMsgContentDropViewHeightOutOfDomain[] =
69     "Value of a height attribute in box-content element out of domain."
70     " The value: ";
71
72 // If the error parameter is specified, it is filled with the given message
73 // otherwise it does nothing.
74 void SetError(const std::string& message,
75     std::string* error) {
76   if (error)
77     *error = message;
78 }
79
80 // If the error parameter is specified, it is filled with concatenation
81 // of message and arg parameters otherwise it does nothing.
82 void SetError(const std::string& message,
83     const std::string& arg, std::string* error) {
84   if (error)
85     *error = message + arg;
86 }
87
88 // If the error parameter is specified, it is filled with the given message
89 // otherwise it does nothing.
90 void SetError(const std::string& message,
91     base::string16* error) {
92   if (error)
93     *error = base::ASCIIToUTF16(message);
94 }
95
96 // If the error parameter is specified, it is filled with concatenation
97 // of message and arg parameters otherwise it does nothing.
98 void SetError(const std::string& message,
99     const std::string& arg, base::string16* error) {
100   if (error)
101     *error = base::ASCIIToUTF16(message + arg);
102 }
103
104 // Retrieves a mandatory dictionary from specified manifest and specified key.
105 // Returns true, if the ditionary is found or false otherwise. If the error
106 // parameter is specified, it is also filled with proper message.
107 bool GetMandatoryDictionary(const Manifest& manifest, const std::string& key,
108     const base::DictionaryValue** dict, base::string16* error) {
109   if (!manifest.HasPath(key)) {
110     SetError(kErrMsgNoMandatoryKey, key, error);
111     return false;
112   }
113   if (!manifest.GetDictionary(key, dict) || !*dict) {
114     SetError(kErrMsgInvalidDictionary, key, error);
115     return false;
116   }
117   return true;
118 }
119
120 // Converts given text value to a value of specific type. Returns true
121 // if convertion is successful or false otherwise.
122 template <typename ValueType>
123 bool ConvertValue(const std::string& str_value, ValueType* value) {
124   NOTREACHED() << "Use one of already defined template specializations"
125                   " or define a new one.";
126   return false;
127 }
128
129 // Converts given text value to a string value. Returns true
130 // if convertion is successful or false otherwise.
131 template <>
132 bool ConvertValue(const std::string& str_value, std::string* value) {
133   DCHECK(value);
134   *value = str_value;
135   return true;
136 }
137
138 // Converts given text value to a boolean value. Returns true
139 // if convertion is successful or false otherwise.
140 template <>
141 bool ConvertValue(const std::string& str_value, bool* value) {
142   DCHECK(value);
143   if (str_value == "true") {
144     *value = true;
145     return true;
146   }
147   if (str_value == "false") {
148     *value = false;
149     return true;
150   }
151   return false;
152 }
153
154 // Converts given text value to an integer value. Returns true
155 // if convertion is successful or false otherwise.
156 template <>
157 bool ConvertValue(const std::string& str_value, int* value) {
158   DCHECK(value);
159   return base::StringToInt(str_value, value);
160 }
161
162 // Converts given text value to a floating point value. Returns true
163 // if convertion is successful or false otherwise.
164 template <>
165 bool ConvertValue(const std::string& str_value, double* value) {
166   DCHECK(value);
167   return base::StringToDouble(str_value, value);
168 }
169
170 // Retrieves a mandatory value from specified dictionary and specified key.
171 // Returns true, if the value is found or false otherwise. If the error
172 // parameter is specified, it is also filled with proper message.
173 template <typename ValueType>
174 bool GetMandatoryValue(const base::DictionaryValue& dict,
175     const std::string& key, ValueType* value, base::string16* error) {
176   DCHECK(value);
177
178   std::string tmp;
179   if (!dict.GetString(key, &tmp)) {
180     SetError(kErrMsgNoMandatoryKey, key, error);
181     return false;
182   }
183
184   bool result = ConvertValue(tmp, value);
185   if (!result)
186     SetError(kErrMsgInvalidKeyValue, key, error);
187   return result;
188 }
189
190 // Retrieves an optional value from specified dictionary and specified key.
191 // If the value is found, the function returns true and fills value
192 // parameter. If the value is not found, the function returns true and fills
193 // value parameter with default value. If an error occurs, it returns false
194 // and fills error parameter if it is set.
195 template <typename ValueType>
196 bool GetOptionalValue(const base::DictionaryValue& dict,
197     const std::string& key, ValueType default_value, ValueType* value,
198     base::string16* error) {
199   DCHECK(value);
200
201   std::string tmp;
202   if (!dict.GetString(key, &tmp)) {
203     *value = default_value;
204     return true;
205   }
206
207   bool result = ConvertValue(tmp, value);
208   if (!result)
209     SetError(kErrMsgInvalidKeyValue, key, error);
210   return result;
211 }
212
213 // Helper function for ParseEach. Do not use directly.
214 template <typename ParseSingleType, typename DataContainerType>
215 bool ParseEachInternal(const base::Value& value, const std::string& key,
216     ParseSingleType parse_single, DataContainerType* data_container,
217     base::string16* error) {
218   DCHECK(data_container);
219
220   const base::DictionaryValue* inner_dict;
221   if (!value.GetAsDictionary(&inner_dict)) {
222     SetError(kErrMsgInvalidDictionary, key, error);
223     return false;
224   }
225   if (!parse_single(*inner_dict, key, data_container, error))
226     return false;
227
228   return true;
229 }
230
231 // Parsing helper function calling 'parse_single' for each dictionary contained
232 // in 'dict' under a 'key'. This helper function takes two template arguments:
233 //  - a function with following prototype:
234 //    bool ParseSingleExample(const base::Value& value, const std::string& key,
235 //        DataContainerType* data_container, base::string16* error);
236 //  - a DataContainerType object where the above function stores data
237 template <typename ParseSingleType, typename DataContainerType>
238 bool ParseEach(const base::DictionaryValue& dict, const std::string& key,
239     bool mandatory, ParseSingleType parse_single,
240     DataContainerType* data_container, base::string16* error) {
241   DCHECK(data_container);
242
243   const base::Value* value = nullptr;
244   if (!dict.Get(key, &value) || !value) {
245     if (mandatory) {
246       SetError(kErrMsgNoMandatoryKey, key, error);
247       return false;
248     }
249     return true;
250   }
251
252   if (value->IsType(base::Value::TYPE_DICTIONARY)) {
253     if (!ParseEachInternal(*value, key, parse_single, data_container, error))
254       return false;
255   } else if (value->IsType(base::Value::TYPE_LIST)) {
256     const base::ListValue* list;
257     if (!value->GetAsList(&list)) {
258       SetError(kErrMsgInvalidList, key, error);
259       return false;
260     }
261     for (const base::Value* value : *list)
262       if (!ParseEachInternal(*value, key, parse_single, data_container, error))
263         return false;
264   }
265
266   return true;
267 }
268
269 // Verifies whether specified dictionary represents an element in specified
270 // namespace. Returns true, if the namespace is set and equal to the specified
271 // one or false otherwise. If the error parameter is specified, it is also
272 // filled with proper message.
273 bool VerifyElementNamespace(const base::DictionaryValue& dict,
274     const std::string& key, const std::string& desired_namespace_value,
275     base::string16* error) {
276   std::string namespace_value;
277   if (!GetMandatoryValue(dict, keys::kNamespaceKey,
278       &namespace_value, nullptr)) {
279     SetError(kErrMsgNoNamespace, key, error);
280     return false;
281   }
282   if (namespace_value != desired_namespace_value) {
283     SetError(kErrMsgInvalidNamespace, key, error);
284     return false;
285   }
286   return true;
287 }
288
289 // Parses box-label part
290 bool ParseLabel(const base::DictionaryValue& dict,
291     const std::string& key, TizenAppWidget* app_widget, base::string16* error) {
292   DCHECK(app_widget);
293
294   if (!VerifyElementNamespace(dict, key, keys::kTizenNamespacePrefix, error))
295     return false;
296
297   std::string lang;
298   if (!GetOptionalValue(dict, keys::kTizenAppWidgetBoxLabelLangKey,
299       std::string(), &lang, error))
300     return false;
301
302   std::string text;
303   if (!GetMandatoryValue(dict, keys::kTizenAppWidgetBoxLabelTextKey,
304       &text, error))
305     return false;
306
307   if (lang.empty()) {
308     // Note: Tizen 2.2 WRT Core Spec does not determine how many times the value
309     // without lang attribute can appear in one app-widget, so overwrite.
310     app_widget->label.default_value = text;
311   } else {
312     // Note: Tizen 2.2 WRT Core Spec does not determine how many times the value
313     // with specific lang attribute can appear in one app-widget, so overwrite.
314     app_widget->label.lang_value_map[lang] = text;
315   }
316
317   return true;
318 }
319
320 // Parses box-icon part
321 bool ParseIcon(const base::DictionaryValue& dict,
322     const std::string& key, TizenAppWidget* app_widget, base::string16* error) {
323   DCHECK(app_widget);
324
325   if (!VerifyElementNamespace(dict, key, keys::kTizenNamespacePrefix, error))
326     return false;
327
328   if (!app_widget->icon_src.empty()) {
329     SetError(kErrMsgMultipleKeys, key, error);
330     return false;
331   }
332   if (!GetMandatoryValue(dict, keys::kTizenAppWidgetBoxIconSrcKey,
333       &app_widget->icon_src, error))
334     return false;
335
336   return true;
337 }
338
339 // Converts size type from text to enum representation
340 bool StringToSizeType(const std::string& str_type,
341     TizenAppWidgetSizeType* enum_type) {
342   DCHECK(enum_type);
343   if (str_type == "1x1") {
344     *enum_type = TizenAppWidgetSizeType::k1x1;
345     return true;
346   }
347   if (str_type == "2x1") {
348     *enum_type = TizenAppWidgetSizeType::k2x1;
349     return true;
350   }
351   if (str_type == "2x2") {
352     *enum_type = TizenAppWidgetSizeType::k2x2;
353     return true;
354   }
355   return false;
356 }
357
358 // Parses box-size part
359 bool ParseContentSizes(const base::DictionaryValue& dict,
360     const std::string& key, TizenAppWidget* app_widget, base::string16* error) {
361   DCHECK(app_widget);
362
363   if (!VerifyElementNamespace(dict, key, keys::kTizenNamespacePrefix, error))
364     return false;
365
366   TizenAppWidgetSize size;
367
368   std::string str_type;
369   if (!GetMandatoryValue(dict, keys::kTizenAppWidgetBoxContentSizeTextKey,
370       &str_type, error))
371     return false;
372
373   TizenAppWidgetSizeType type;
374   if (!StringToSizeType(str_type, &type)) {
375     SetError(kErrMsgInvalidKeyValue,
376         keys::kTizenAppWidgetBoxContentSizeTextKey, error);
377     return false;
378   }
379   size.type = type;
380
381   if (!GetOptionalValue(dict, keys::kTizenAppWidgetBoxContentSizePreviewKey,
382       std::string(), &size.preview, error))
383     return false;
384
385   if (!GetOptionalValue(dict,
386       keys::kTizenAppWidgetBoxContentSizeUseDecorationKey,
387       true, &size.use_decoration, error))
388     return false;
389
390   app_widget->content_size.push_back(size);
391
392   return true;
393 }
394
395 // Parses pd part
396 bool ParseContentDropView(const base::DictionaryValue& dict,
397     const std::string& key, TizenAppWidget* app_widget, base::string16* error) {
398   DCHECK(app_widget);
399
400   if (!VerifyElementNamespace(dict, key, keys::kTizenNamespacePrefix, error))
401     return false;
402
403   if (!app_widget->content_drop_view.empty()) {
404     SetError(kErrMsgMultipleKeys, key, error);
405     return false;
406   }
407
408   TizenAppWidgetDropView drop_view;
409
410   if (!GetMandatoryValue(dict, keys::kTizenAppWidgetBoxContentDropViewSrcKey,
411       &drop_view.src, error))
412     return false;
413
414   if (!GetMandatoryValue(dict,
415       keys::kTizenAppWidgetBoxContentDropViewWidthKey,
416       &drop_view.width, error))
417     return false;
418
419   if (!GetMandatoryValue(dict,
420       keys::kTizenAppWidgetBoxContentDropViewHeightKey,
421       &drop_view.height, error))
422     return false;
423
424   app_widget->content_drop_view.push_back(drop_view);
425
426   return true;
427 }
428
429 // Parses box-content part
430 bool ParseContent(const base::DictionaryValue& dict,
431     const std::string& key, TizenAppWidget* app_widget, base::string16* error) {
432   DCHECK(app_widget);
433
434   if (!VerifyElementNamespace(dict, key, keys::kTizenNamespacePrefix, error))
435     return false;
436
437   if (!app_widget->content_src.empty()) {
438     SetError(kErrMsgMultipleKeys, key, error);
439     return false;
440   }
441   if (!GetMandatoryValue(dict, keys::kTizenAppWidgetBoxContentSrcKey,
442       &app_widget->content_src, error))
443     return false;
444
445   if (!GetOptionalValue(dict, keys::kTizenAppWidgetBoxContentMouseEventKey,
446       false, &app_widget->content_mouse_event, error))
447     return false;
448
449   if (!GetOptionalValue(dict, keys::kTizenAppWidgetBoxContentTouchEffectKey,
450       true, &app_widget->content_touch_effect, error))
451     return false;
452
453   if (!ParseEach(dict, keys::kTizenAppWidgetBoxContentSizeKey,
454       true, ParseContentSizes, app_widget, error))
455     return false;
456
457   if (!ParseEach(dict, keys::kTizenAppWidgetBoxContentDropViewKey,
458       false, ParseContentDropView, app_widget, error))
459     return false;
460
461   return true;
462 }
463
464 // Parses app-widget part
465 bool ParseAppWidget(const base::DictionaryValue& dict,
466     const std::string& key, TizenAppWidgetVector* app_widgets,
467     base::string16* error) {
468   DCHECK(app_widgets);
469
470   if (!VerifyElementNamespace(dict, key, keys::kTizenNamespacePrefix, error))
471     return false;
472
473   TizenAppWidget app_widget;
474
475   if (!GetMandatoryValue(dict, keys::kTizenAppWidgetIdKey,
476       &app_widget.id, error))
477     return false;
478
479   if (!GetMandatoryValue(dict, keys::kTizenAppWidgetPrimaryKey,
480       &app_widget.primary, error))
481     return false;
482
483   double update_period;
484   double no_update_period = std::numeric_limits<double>::min();
485   if (!GetOptionalValue(dict, keys::kTizenAppWidgetUpdatePeriodKey,
486       no_update_period, &update_period, error))
487     return false;
488   if (update_period != no_update_period)
489     app_widget.update_period.push_back(update_period);
490
491   if (!GetOptionalValue(dict, keys::kTizenAppWidgetAutoLaunchKey,
492       false, &app_widget.auto_launch, error))
493     return false;
494
495   if (!ParseEach(dict, keys::kTizenAppWidgetBoxLabelKey,
496       true, ParseLabel, &app_widget, error))
497     return false;
498
499   if (!ParseEach(dict, keys::kTizenAppWidgetBoxIconKey,
500       false, ParseIcon, &app_widget, error))
501     return false;
502
503   if (!ParseEach(dict, keys::kTizenAppWidgetBoxContentKey,
504       true, ParseContent, &app_widget, error))
505     return false;
506
507   app_widgets->push_back(app_widget);
508
509   return true;
510 }
511
512 // Validates all app-widget ids
513 bool ValidateEachId(const TizenAppWidgetVector& app_widgets,
514     const std::string& app_id, std::string* error) {
515   std::set<std::string> unique_values;
516
517   for (const TizenAppWidget& app_widget : app_widgets) {
518     if (!unique_values.insert(app_widget.id).second) {
519       SetError(kErrMsgDuplicatedAppWidgetId, app_widget.id, error);
520       return false;
521     }
522
523     const size_t app_id_len = app_id.length();
524
525     if (app_widget.id.find(app_id) != 0) {
526       SetError(kErrMsgInvalidAppWidgetIdBeginning, app_widget.id, error);
527       return false;
528     }
529
530     const char kStringPattern[] = "[.][0-9a-zA-Z]+";
531     if (!RE2::FullMatch(app_widget.id.substr(app_id_len), kStringPattern)) {
532       SetError(kErrMsgInvalidAppWidgetIdFormat, app_widget.id, error);
533       return false;
534     }
535   }
536
537   return true;
538 }
539
540 // Tests if specified string represents valid remote url
541 bool IsValidUrl(const std::string& value) {
542   // TODO(tweglarski): implement me (it's not crucial atm)
543   return true;
544 }
545
546 // Tests if specified string represents valid path
547 bool IsValidPath(const std::string& value) {
548   // TODO(tweglarski): implement me (it's not crucial atm)
549   return true;
550 }
551
552 // Tests if specified string represents valid path or remote url
553 bool IsValidPathOrUrl(const std::string& value) {
554   return IsValidPath(value) || IsValidUrl(value);
555 }
556
557 // Validates all content sizes in an app-widget
558 bool ValidateContentSize(const TizenAppWidgetSizeVector& content_size,
559     std::string* error) {
560   bool mandatory_1x1_found = false;
561
562   for (const TizenAppWidgetSize& size : content_size) {
563     mandatory_1x1_found |= size.type == TizenAppWidgetSizeType::k1x1;
564
565     if (!size.preview.empty() && !IsValidPath(size.preview)) {
566       SetError(kErrMsgInvalidContentSizePreview, size.preview, error);
567       return false;
568     }
569   }
570
571   if (!mandatory_1x1_found) {
572     SetError(kErrMsgNoMandatoryContentSize1x1, error);
573     return false;
574   }
575
576   return true;
577 }
578
579 }  // namespace
580
581 TizenAppWidgetInfo::TizenAppWidgetInfo(const TizenAppWidgetVector& app_widgets)
582     : app_widgets_(app_widgets) {
583 }
584
585 TizenAppWidgetInfo::~TizenAppWidgetInfo() {
586 }
587
588 TizenAppWidgetHandler::TizenAppWidgetHandler() {
589 }
590
591 TizenAppWidgetHandler::~TizenAppWidgetHandler() {
592 }
593
594 bool TizenAppWidgetHandler::Parse(scoped_refptr<ApplicationData> application,
595     base::string16* error) {
596   const Manifest* manifest = application->GetManifest();
597   DCHECK(manifest);
598
599   const base::DictionaryValue* dict = nullptr;
600   if (!GetMandatoryDictionary(*manifest, keys::kTizenWidgetKey, &dict, error))
601     return false;
602
603   TizenAppWidgetVector app_widgets;
604
605   if (!ParseEach(*dict, keys::kTizenAppWidgetKey,
606       false, ParseAppWidget, &app_widgets, error))
607     return false;
608
609   scoped_ptr<TizenAppWidgetInfo> info(new TizenAppWidgetInfo(app_widgets));
610   application->SetManifestData(keys::kTizenAppWidgetFullKey, info.release());
611
612   return true;
613 }
614
615 bool TizenAppWidgetHandler::Validate(
616     scoped_refptr<const ApplicationData> application,
617     std::string* error) const {
618   const TizenAppWidgetInfo* app_widget_info =
619       static_cast<const TizenAppWidgetInfo*>(
620           application->GetManifestData(keys::kTizenAppWidgetFullKey));
621   const TizenApplicationInfo* app_info =
622       static_cast<const TizenApplicationInfo*>(
623           application->GetManifestData(keys::kTizenApplicationKey));
624
625   if (!app_widget_info) {
626     SetError(kErrMsgAppWidgetInfoNotFound, error);
627     return false;
628   }
629   if (!app_info) {
630     SetError(kErrMsgApplicationInfoNotFound, error);
631     return false;
632   }
633
634   const TizenAppWidgetVector& app_widgets = app_widget_info->app_widgets();
635
636   if (!ValidateEachId(app_widgets, app_info->id(), error))
637     return false;
638
639   for (const TizenAppWidget& app_widget : app_widgets) {
640     if (!app_widget.update_period.empty()
641         && app_widget.update_period.front() < 1800) {
642       SetError(kErrMsgUpdatePeriodOutOfDomain,
643           base::DoubleToString(app_widget.update_period.front()), error);
644       return false;
645     }
646
647     if (app_widget.label.default_value.empty()
648         && app_widget.label.lang_value_map.empty()) {
649       SetError(kErrMsgNoLabel, error);
650       return false;
651     }
652
653     if (!app_widget.icon_src.empty()
654         && !IsValidPathOrUrl(app_widget.icon_src)) {
655       SetError(kErrMsgInvalidIconSrc, app_widget.icon_src, error);
656       return false;
657     }
658
659     if (!IsValidPathOrUrl(app_widget.content_src)) {
660       SetError(kErrMsgInvalidContentSrc, app_widget.content_src, error);
661       return false;
662     }
663
664     if (!ValidateContentSize(app_widget.content_size, error))
665       return false;
666
667     if (!app_widget.content_drop_view.empty()) {
668       const TizenAppWidgetDropView& drop_view
669           = app_widget.content_drop_view.front();
670
671       if (!IsValidPathOrUrl(drop_view.src)) {
672         SetError(kErrMsgInvalidContentDropViewSrc, drop_view.src, error);
673         return false;
674       }
675
676       if (drop_view.height < 1 || drop_view.height > 380) {
677         SetError(kErrMsgContentDropViewHeightOutOfDomain,
678             base::IntToString(drop_view.height), error);
679         return false;
680       }
681     }
682   }
683
684   return true;
685 }
686
687 std::vector<std::string> TizenAppWidgetHandler::Keys() const {
688   return std::vector<std::string>(1, keys::kTizenAppWidgetFullKey);
689 }
690
691 }  // namespace application
692 }  // namespace xwalk