628263d56434b2e089948c76330eaf12445b8add
[platform/framework/web/crosswalk.git] / src / xwalk / application / browser / application_service.cc
1 // Copyright (c) 2013 Intel Corporation. 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/browser/application_service.h"
6
7 #include <set>
8 #include <string>
9
10 #include "base/files/file_enumerator.h"
11 #include "base/file_util.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/path_service.h"
15 #include "base/version.h"
16 #include "content/public/browser/storage_partition.h"
17 #include "content/public/browser/web_contents.h"
18 #include "content/public/browser/web_contents_observer.h"
19 #include "xwalk/application/browser/application_event_manager.h"
20 #include "xwalk/application/browser/application.h"
21 #include "xwalk/application/browser/application_storage.h"
22 #include "xwalk/application/browser/application_system.h"
23 #include "xwalk/application/browser/installer/package.h"
24 #include "xwalk/application/common/application_file_util.h"
25 #include "xwalk/application/common/event_names.h"
26 #include "xwalk/application/common/permission_policy_manager.h"
27 #include "xwalk/runtime/browser/runtime_context.h"
28 #include "xwalk/runtime/browser/runtime.h"
29 #include "xwalk/runtime/browser/xwalk_runner.h"
30 #include "xwalk/runtime/common/xwalk_paths.h"
31
32 #if defined(OS_TIZEN)
33 #include "xwalk/application/browser/application_tizen.h"
34 #include "xwalk/application/browser/installer/tizen/package_installer.h"
35 #endif
36
37 namespace xwalk {
38 namespace application {
39
40 namespace {
41
42 void WaitForEventAndClose(
43     const std::string& app_id,
44     const std::string& event_name,
45     ApplicationService* application_service,
46     ApplicationEventManager* event_manager) {
47
48   class CloseOnEventArrived : public EventObserver {
49    public:
50     CloseOnEventArrived(
51         const std::string& event_name,
52         ApplicationService* application_service,
53         ApplicationEventManager* event_manager)
54         : EventObserver(event_manager),
55           event_name_(event_name),
56           application_service_(application_service) {}
57
58     virtual void Observe(
59         const std::string& app_id,
60         scoped_refptr<Event> event) OVERRIDE {
61       DCHECK(kOnJavaScriptEventAck == event->name());
62       std::string ack_event_name;
63       event->args()->GetString(0, &ack_event_name);
64       if (ack_event_name != event_name_)
65         return;
66
67       if (Application* app = application_service_->GetApplicationByID(app_id))
68         app->Terminate(Application::Immediate);
69
70       delete this;
71     }
72
73    private:
74     std::string event_name_;
75     ApplicationService* application_service_;
76   };
77
78   DCHECK(event_manager);
79   CloseOnEventArrived* observer =
80       new CloseOnEventArrived(event_name, application_service, event_manager);
81   event_manager->AttachObserver(app_id,
82       kOnJavaScriptEventAck, observer);
83 }
84
85 void WaitForFinishLoad(
86     const std::string& app_id,
87     ApplicationService* application_service,
88     ApplicationEventManager* event_manager,
89     content::WebContents* contents) {
90   class CloseAfterLoadObserver : public content::WebContentsObserver {
91    public:
92     CloseAfterLoadObserver(
93         const std::string& app_id,
94         ApplicationService* application_service,
95         ApplicationEventManager* event_manager,
96         content::WebContents* contents)
97         : content::WebContentsObserver(contents),
98           id_(app_id),
99           application_service_(application_service),
100           event_manager_(event_manager) {
101       DCHECK(application_service_);
102       DCHECK(event_manager_);
103     }
104
105     virtual void DidFinishLoad(
106         int64 frame_id,
107         const GURL& validate_url,
108         bool is_main_frame,
109         content::RenderViewHost* render_view_host) OVERRIDE {
110       Application* app = application_service_->GetApplicationByID(id_);
111       if (!app) {
112         delete this;
113         return;
114       }
115
116       if (!IsEventHandlerRegistered(app->data(), kOnInstalled)) {
117           app->Terminate(Application::Immediate);
118       } else {
119         scoped_ptr<base::ListValue> event_args(new base::ListValue);
120         scoped_refptr<Event> event =
121             Event::CreateEvent(kOnInstalled, event_args.Pass());
122         event_manager_->SendEvent(id_, event);
123
124         WaitForEventAndClose(
125             id_, event->name(), application_service_, event_manager_);
126       }
127       delete this;
128     }
129
130    private:
131     bool IsEventHandlerRegistered(scoped_refptr<ApplicationData> app_data,
132                                   const std::string& event_name) const {
133       const std::set<std::string>& events = app_data->GetEvents();
134       return events.find(event_name) != events.end();
135     }
136
137     std::string id_;
138     ApplicationService* application_service_;
139     ApplicationEventManager* event_manager_;
140   };
141
142   // This object is self-destroyed when an event occurs.
143   new CloseAfterLoadObserver(
144       app_id, application_service, event_manager, contents);
145 }
146
147 void SaveSystemEventsInfo(
148     scoped_refptr<ApplicationData> application_data,
149     ApplicationService* application_service,
150     ApplicationEventManager* event_manager) {
151   // We need to run main document after installation in order to
152   // register system events.
153   if (application_data->HasMainDocument()) {
154     if (Application* application =
155         application_service->Launch(application_data->ID())) {
156       WaitForFinishLoad(application->id(), application_service, event_manager,
157                         application->GetMainDocumentRuntime()->web_contents());
158     }
159   }
160 }
161
162 bool CopyDirectoryContents(const base::FilePath& from,
163     const base::FilePath& to) {
164   base::FileEnumerator iter(from, false,
165       base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
166   for (base::FilePath path = iter.Next(); !path.empty(); path = iter.Next()) {
167     if (iter.GetInfo().IsDirectory()) {
168       if (!base::CopyDirectory(path, to, true))
169         return false;
170     } else if (!base::CopyFile(path, to.Append(path.BaseName()))) {
171         return false;
172     }
173   }
174
175   return true;
176 }
177
178 void RemoveWidgetStorageFiles(const base::FilePath& storage_path,
179                               const std::string& app_id) {
180   base::FileEnumerator iter(storage_path, true,
181                             base::FileEnumerator::FILES);
182   for (base::FilePath file = iter.Next(); !file.empty(); file = iter.Next()) {
183     if (file.MaybeAsASCII().find(app_id) != std::string::npos)
184       base::DeleteFile(file, false);
185   }
186 }
187
188 }  // namespace
189
190 const base::FilePath::CharType kApplicationsDir[] =
191     FILE_PATH_LITERAL("applications");
192
193 ApplicationService::ApplicationService(RuntimeContext* runtime_context,
194                                        ApplicationStorage* app_storage,
195                                        ApplicationEventManager* event_manager)
196     : runtime_context_(runtime_context),
197       application_storage_(app_storage),
198       event_manager_(event_manager),
199       permission_policy_handler_(new PermissionPolicyManager()) {
200   AddObserver(event_manager);
201 }
202
203 ApplicationService::~ApplicationService() {
204 }
205
206 bool ApplicationService::Install(const base::FilePath& path, std::string* id) {
207   // FIXME(leandro): Installation is not robust enough -- should any step
208   // fail, it can't roll back to a consistent state.
209   if (!base::PathExists(path))
210     return false;
211
212   const base::FilePath data_dir =
213       runtime_context_->GetPath().Append(kApplicationsDir);
214
215   // Make sure the kApplicationsDir exists under data_path, otherwise,
216   // the installation will always fail because of moving application
217   // resources into an invalid directory.
218   if (!base::DirectoryExists(data_dir) &&
219       !base::CreateDirectory(data_dir))
220     return false;
221
222   std::string app_id;
223   base::FilePath unpacked_dir;
224   scoped_ptr<Package> package;
225   if (!base::DirectoryExists(path)) {
226     package = Package::Create(path);
227     package->Extract(&unpacked_dir);
228     app_id = package->Id();
229   } else {
230     unpacked_dir = path;
231   }
232
233   std::string error;
234   scoped_refptr<ApplicationData> application_data = LoadApplication(
235       unpacked_dir, app_id, Manifest::COMMAND_LINE, &error);
236   if (!application_data) {
237     LOG(ERROR) << "Error during application installation: " << error;
238     return false;
239   }
240   if (!permission_policy_handler_->
241       InitApplicationPermission(application_data)) {
242     LOG(ERROR) << "Application permission data is invalid";
243     return false;
244   }
245
246   if (application_storage_->Contains(application_data->ID())) {
247     *id = application_data->ID();
248     LOG(INFO) << "Already installed: " << *id;
249     return false;
250   }
251
252   base::FilePath app_dir = data_dir.AppendASCII(application_data->ID());
253   if (base::DirectoryExists(app_dir)) {
254     if (!base::DeleteFile(app_dir, true))
255       return false;
256   }
257   if (!package) {
258     if (!base::CreateDirectory(app_dir))
259       return false;
260     if (!CopyDirectoryContents(unpacked_dir, app_dir))
261       return false;
262   } else {
263     if (!base::Move(unpacked_dir, app_dir))
264       return false;
265   }
266
267   application_data->SetPath(app_dir);
268
269   if (!application_storage_->AddApplication(application_data)) {
270     LOG(ERROR) << "Application with id " << application_data->ID()
271                << " couldn't be installed.";
272     return false;
273   }
274
275 #if defined(OS_TIZEN)
276   if (!PackageInstaller::InstallApplication(
277         application_data, runtime_context_->GetPath())) {
278     application_storage_->RemoveApplication(application_data->ID());
279     return false;
280   }
281 #endif
282
283   LOG(INFO) << "Application be installed in: " << app_dir.MaybeAsASCII();
284   LOG(INFO) << "Installed application with id: " << application_data->ID()
285             << " successfully.";
286   *id = application_data->ID();
287
288   SaveSystemEventsInfo(application_data, this, event_manager_);
289
290   FOR_EACH_OBSERVER(Observer, observers_,
291                     OnApplicationInstalled(application_data->ID()));
292
293   return true;
294 }
295
296 bool ApplicationService::Update(const std::string& id,
297                                 const base::FilePath& path) {
298   if (!base::PathExists(path)) {
299     LOG(ERROR) << "The XPK/WGT package file " << path.value() << " is invalid.";
300     return false;
301   }
302
303   if (base::DirectoryExists(path)) {
304     LOG(WARNING) << "Can not update an unpacked XPK/WGT package.";
305     return false;
306   }
307
308   base::FilePath unpacked_dir;
309   base::FilePath origin_dir;
310   std::string app_id;
311   scoped_ptr<Package> package = Package::Create(path);
312   if (!package) {
313     LOG(ERROR) << "XPK/WGT file is invalid.";
314     return false;
315   }
316
317   app_id = package->Id();
318
319   if (app_id.empty()) {
320     LOG(ERROR) << "XPK/WGT file is invalid, and the application id is empty.";
321     return false;
322   }
323
324   if (id.empty() ||
325       id.compare(app_id) != 0) {
326     LOG(ERROR) << "The XPK/WGT file is not the same as expecting.";
327     return false;
328   }
329
330   if (!package->Extract(&unpacked_dir))
331     return false;
332
333   std::string error;
334   scoped_refptr<ApplicationData> new_application =
335       LoadApplication(unpacked_dir,
336                       app_id,
337                       Manifest::COMMAND_LINE,
338                       &error);
339   if (!new_application) {
340     LOG(ERROR) << "An error occurred during application updating: " << error;
341     return false;
342   }
343
344   scoped_refptr<ApplicationData> old_application =
345       application_storage_->GetApplicationData(app_id);
346   if (!old_application) {
347     LOG(INFO) << "Application haven't installed yet: " << app_id;
348     return false;
349   }
350
351   if (old_application->Version()->CompareTo(
352           *(new_application->Version())) >= 0) {
353     LOG(INFO) << "The version number of new XPK/WGT package "
354                  "should be higher than "
355               << old_application->VersionString();
356     return false;
357   }
358
359   const base::FilePath& app_dir = old_application->Path();
360   const base::FilePath tmp_dir(app_dir.value()
361                                + FILE_PATH_LITERAL(".tmp"));
362
363   if (Application* app = GetApplicationByID(app_id)) {
364     LOG(INFO) << "Try to terminate the running application before update.";
365     app->Terminate(Application::Immediate);
366   }
367
368   if (!base::Move(app_dir, tmp_dir) ||
369       !base::Move(unpacked_dir, app_dir))
370     return false;
371
372   new_application = LoadApplication(app_dir,
373                                     app_id,
374                                     Manifest::COMMAND_LINE,
375                                     &error);
376   if (!new_application) {
377     LOG(ERROR) << "Error during loading new package: " << error;
378     base::DeleteFile(app_dir, true);
379     base::Move(tmp_dir, app_dir);
380     return false;
381   }
382
383   if (!application_storage_->UpdateApplication(new_application)) {
384     LOG(ERROR) << "Fail to update application, roll back to the old one.";
385     base::DeleteFile(app_dir, true);
386     base::Move(tmp_dir, app_dir);
387     return false;
388   }
389
390 #if defined(OS_TIZEN)
391   if (!PackageInstaller::UpdateApplication(
392         new_application, runtime_context_->GetPath())) {
393     LOG(ERROR) << "Fail to update package on Tizen, roll back to the old one.";
394     base::DeleteFile(app_dir, true);
395     if (!application_storage_->UpdateApplication(old_application)) {
396       LOG(ERROR) << "Fail to revert old application info, "
397                  << "remove the application as a last resort.";
398       application_storage_->RemoveApplication(old_application->ID());
399       return false;
400     }
401     base::Move(tmp_dir, app_dir);
402     return false;
403   }
404 #endif
405
406   base::DeleteFile(tmp_dir, true);
407
408   SaveSystemEventsInfo(new_application, this, event_manager_);
409
410   FOR_EACH_OBSERVER(Observer, observers_,
411                     OnApplicationUpdated(app_id));
412
413   return true;
414 }
415
416 bool ApplicationService::Uninstall(const std::string& id) {
417   bool result = true;
418
419   scoped_refptr<ApplicationData> application =
420       application_storage_->GetApplicationData(id);
421   if (!application) {
422     LOG(ERROR) << "Cannot uninstall application with id " << id
423                << "; invalid application id";
424     return false;
425   }
426
427   if (Application* app = GetApplicationByID(id)) {
428     LOG(INFO) << "Try to terminate the running application before uninstall.";
429     app->Terminate(Application::Immediate);
430   }
431
432 #if defined(OS_TIZEN)
433   if (!PackageInstaller::UninstallApplication(
434         application, runtime_context_->GetPath()))
435     result = false;
436 #endif
437
438   if (!application_storage_->RemoveApplication(id)) {
439     LOG(ERROR) << "Cannot uninstall application with id " << id
440                << "; application is not installed.";
441     result = false;
442   }
443
444   const base::FilePath resources =
445       runtime_context_->GetPath().Append(kApplicationsDir).AppendASCII(id);
446   if (base::DirectoryExists(resources) &&
447       !base::DeleteFile(resources, true)) {
448     LOG(ERROR) << "Error occurred while trying to remove application with id "
449                << id << "; Cannot remove all resources.";
450     result = false;
451   }
452
453   content::StoragePartition* partition =
454       content::BrowserContext::GetStoragePartitionForSite(
455           runtime_context_, application->GetBaseURLFromApplicationId(id));
456   partition->ClearDataForOrigin(
457       content::StoragePartition::REMOVE_DATA_MASK_ALL,
458       content::StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
459       application->URL(),
460       partition->GetURLRequestContext());
461
462   base::FilePath path;
463   PathService::Get(xwalk::DIR_WGT_STORAGE_PATH, &path);
464   RemoveWidgetStorageFiles(path, id);
465
466   FOR_EACH_OBSERVER(Observer, observers_, OnApplicationUninstalled(id));
467
468   return result;
469 }
470
471 void ApplicationService::ChangeLocale(const std::string& locale) {
472   const ApplicationData::ApplicationDataMap& apps =
473       application_storage_->GetInstalledApplications();
474   ApplicationData::ApplicationDataMap::const_iterator it;
475   for (it = apps.begin(); it != apps.end(); ++it) {
476     base::string16 error;
477     std::string old_name = it->second->Name();
478     if (!it->second->SetApplicationLocale(locale, &error)) {
479       LOG(ERROR) << "Error when set locale " << locale
480                  << " to application " << it->second->ID()
481                  << "error : " << error;
482     }
483     if (old_name != it->second->Name()) {
484       // After we has changed the application locale, we might get a new name in
485       // the new locale, so call all observer for this event.
486       FOR_EACH_OBSERVER(
487           Observer, observers_,
488           OnApplicationNameChanged(it->second->ID(), it->second->Name()));
489     }
490   }
491 }
492
493 Application* ApplicationService::Launch(
494     scoped_refptr<ApplicationData> application_data,
495     const Application::LaunchParams& launch_params) {
496   if (GetApplicationByID(application_data->ID()) != NULL) {
497     LOG(INFO) << "Application with id: " << application_data->ID()
498               << " is already running.";
499     // FIXME: we need to notify application that it had been attempted
500     // to invoke and let the application to define the further behavior.
501     return NULL;
502   }
503
504   event_manager_->AddEventRouterForApp(application_data);
505
506 #if defined(OS_TIZEN)
507   Application* application(new ApplicationTizen(application_data,
508     runtime_context_, this));
509 #else
510   Application* application(new Application(application_data,
511     runtime_context_, this));
512 #endif
513
514   ScopedVector<Application>::iterator app_iter =
515       applications_.insert(applications_.end(), application);
516
517   if (!application->Launch(launch_params)) {
518     event_manager_->RemoveEventRouterForApp(application_data);
519     applications_.erase(app_iter);
520     return NULL;
521   }
522
523   FOR_EACH_OBSERVER(Observer, observers_,
524                     DidLaunchApplication(application));
525
526   return application;
527 }
528
529 Application* ApplicationService::Launch(
530     const std::string& id, const Application::LaunchParams& params) {
531   Application* application = NULL;
532   scoped_refptr<ApplicationData> application_data =
533     application_storage_->GetApplicationData(id);
534   if (!application_data) {
535     LOG(ERROR) << "Application with id " << id << " is not installed.";
536     return NULL;
537   }
538
539   if ((application = Launch(application_data, params))) {
540     scoped_refptr<Event> event = Event::CreateEvent(
541         kOnLaunched, scoped_ptr<base::ListValue>(new base::ListValue));
542     event_manager_->SendEvent(application->id(), event);
543   }
544   return application;
545 }
546
547 Application* ApplicationService::Launch(
548     const base::FilePath& path, const Application::LaunchParams& params) {
549   Application* application = NULL;
550   if (!base::DirectoryExists(path))
551     return NULL;
552
553   std::string error;
554   scoped_refptr<ApplicationData> application_data =
555       LoadApplication(path, Manifest::COMMAND_LINE, &error);
556
557   if (!application_data) {
558     LOG(ERROR) << "Error occurred while trying to launch application: "
559                << error;
560     return NULL;
561   }
562
563   if ((application = Launch(application_data, params))) {
564     scoped_refptr<Event> event = Event::CreateEvent(
565         kOnLaunched, scoped_ptr<base::ListValue>(new base::ListValue));
566     event_manager_->SendEvent(application->id(), event);
567   }
568   return application;
569 }
570
571 namespace {
572
573 struct ApplicationRenderHostIDComparator {
574     explicit ApplicationRenderHostIDComparator(int id) : id(id) { }
575     bool operator() (Application* application) {
576       return id == application->GetRenderProcessHostID();
577     }
578     int id;
579 };
580
581 struct ApplicationIDComparator {
582     explicit ApplicationIDComparator(const std::string& app_id)
583       : app_id(app_id) { }
584     bool operator() (Application* application) {
585       return app_id == application->id();
586     }
587     std::string app_id;
588 };
589
590 }  // namespace
591
592 Application* ApplicationService::GetApplicationByRenderHostID(int id) const {
593   ApplicationRenderHostIDComparator comparator(id);
594   ScopedVector<Application>::const_iterator found = std::find_if(
595       applications_.begin(), applications_.end(), comparator);
596   if (found != applications_.end())
597     return *found;
598   return NULL;
599 }
600
601 Application* ApplicationService::GetApplicationByID(
602     const std::string& app_id) const {
603   ApplicationIDComparator comparator(app_id);
604   ScopedVector<Application>::const_iterator found = std::find_if(
605       applications_.begin(), applications_.end(), comparator);
606   if (found != applications_.end())
607     return *found;
608   return NULL;
609 }
610
611 void ApplicationService::AddObserver(Observer* observer) {
612   observers_.AddObserver(observer);
613 }
614
615 void ApplicationService::RemoveObserver(Observer* observer) {
616   observers_.RemoveObserver(observer);
617 }
618
619 void ApplicationService::OnApplicationTerminated(
620                                       Application* application) {
621   ScopedVector<Application>::iterator found = std::find(
622       applications_.begin(), applications_.end(), application);
623   CHECK(found != applications_.end());
624   FOR_EACH_OBSERVER(Observer, observers_,
625                     WillDestroyApplication(application));
626   applications_.erase(found);
627   if (!XWalkRunner::GetInstance()->is_running_as_service() &&
628       applications_.empty()) {
629     base::MessageLoop::current()->PostTask(
630             FROM_HERE, base::MessageLoop::QuitClosure());
631   }
632 }
633
634 void ApplicationService::CheckAPIAccessControl(const std::string& app_id,
635     const std::string& extension_name,
636     const std::string& api_name, const PermissionCallback& callback) {
637   Application* app = GetApplicationByID(app_id);
638   if (!app) {
639     LOG(ERROR) << "No running application found with ID: "
640       << app_id;
641     callback.Run(UNDEFINED_RUNTIME_PERM);
642     return;
643   }
644   if (!app->UseExtension(extension_name)) {
645     LOG(ERROR) << "Can not find extension: "
646       << extension_name << " of Application with ID: "
647       << app_id;
648     callback.Run(UNDEFINED_RUNTIME_PERM);
649     return;
650   }
651   // Permission name should have been registered at extension initialization.
652   std::string permission_name =
653       app->GetRegisteredPermissionName(extension_name, api_name);
654   if (permission_name.empty()) {
655     LOG(ERROR) << "API: " << api_name << " of extension: "
656       << extension_name << " not registered!";
657     callback.Run(UNDEFINED_RUNTIME_PERM);
658     return;
659   }
660   // Okay, since we have the permission name, let's get down to the policies.
661   // First, find out whether the permission is stored for the current session.
662   StoredPermission perm = app->GetPermission(
663       SESSION_PERMISSION, permission_name);
664   if (perm != UNDEFINED_STORED_PERM) {
665     // "PROMPT" should not be in the session storage.
666     DCHECK(perm != PROMPT);
667     if (perm == ALLOW) {
668       callback.Run(ALLOW_SESSION);
669       return;
670     }
671     if (perm == DENY) {
672       callback.Run(DENY_SESSION);
673       return;
674     }
675     NOTREACHED();
676   }
677   // Then, query the persistent policy storage.
678   perm = app->GetPermission(PERSISTENT_PERMISSION, permission_name);
679   // Permission not found in persistent permission table, normally this should
680   // not happen because all the permission needed by the application should be
681   // contained in its manifest, so it also means that the application is asking
682   // for something wasn't allowed.
683   if (perm == UNDEFINED_STORED_PERM) {
684     callback.Run(UNDEFINED_RUNTIME_PERM);
685     return;
686   }
687   if (perm == PROMPT) {
688     // TODO(Bai): We needed to pop-up a dialog asking user to chose one from
689     // either allow/deny for session/one shot/forever. Then, we need to update
690     // the session and persistent policy accordingly.
691     callback.Run(UNDEFINED_RUNTIME_PERM);
692     return;
693   }
694   if (perm == ALLOW) {
695     callback.Run(ALLOW_ALWAYS);
696     return;
697   }
698   if (perm == DENY) {
699     callback.Run(DENY_ALWAYS);
700     return;
701   }
702   NOTREACHED();
703 }
704
705 bool ApplicationService::RegisterPermissions(const std::string& app_id,
706     const std::string& extension_name,
707     const std::string& perm_table) {
708   Application* app = GetApplicationByID(app_id);
709   if (!app) {
710     LOG(ERROR) << "No running application found with ID: " << app_id;
711     return false;
712   }
713   if (!app->UseExtension(extension_name)) {
714     LOG(ERROR) << "Can not find extension: "
715                << extension_name << " of Application with ID: "
716                << app_id;
717     return false;
718   }
719   return app->RegisterPermissions(extension_name, perm_table);
720 }
721
722 }  // namespace application
723 }  // namespace xwalk