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