2262e2dce33174b21c15bcf958f60f792177ee7d
[platform/framework/web/wrt-plugins-common.git] / src / plugin-loading / js_page_session.cpp
1 /*
2  * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16 /**
17  * @file        plugin_logic.cpp
18  * @author      Piotr Fatyga (p.fatyga@samsung.com)
19  * @author      Grzegorz Krawczyk (g.krawczyk@samsung.com)
20  * @author      Przemyslaw Dobrowolski (p.dobrowolsk@samsung.com)
21  * @version     1.0
22  * @brief       This file is the implementation file of plugin and
23  *              feature loading routines
24  * @brief       This code is intended to work behind view controller
25  */
26
27 #include "plugin_logic.h"
28
29 #include <dpl/assert.h>
30 #include <dpl/scoped_array.h>
31 #include <dpl/log/log.h>
32 #include <dpl/foreach.h>
33 #include <dpl/singleton_impl.h>
34 #include <dpl/wrt-dao-ro/widget_dao_read_only.h>
35 #include <dpl/wrt-dao-ro/common_dao_types.h>
36 #include <dpl/wrt-dao-ro/global_config.h>
37
38 #include <JavaScriptCore/JavaScript.h>
39
40 #include <string>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <dirent.h>
44 #include <errno.h>
45 #include <fstream>
46 #include <map>
47 #include <list>
48 #include <vector>
49 #include <algorithm>
50 #include <cstring>
51
52 #include <wrt_plugin_export.h>
53 #include <js_overlay_types.h>
54
55 #include "explorer.h"
56 #include "plugin.h"
57 #include "plugin_model.h"
58 #include "javascript_interface.h"
59 #include "js_function_manager.h"
60 #include "plugin_container_support.h"
61
62 #include "js_page_session.h"
63
64 using namespace std;
65 using namespace WrtDB;
66 using namespace WrtPlugins::W3C;
67
68 namespace {
69 const char *LIBRARY_PATH_SEPARATOR = "/";
70 }
71
72 class JSPageSession::Impl
73 {
74   private:
75     typedef std::set<PluginPtr> LoadedPlugins;
76     typedef std::set<JSGlobalContextRef> LoadedContexts;
77
78   private:
79     ///< Widget handle using this session
80     int m_widgetHandle;
81
82     //view for this session
83     JSGlobalContextRef m_context;
84
85     bool m_sessionStarted;
86
87     ///< JS object explorer for this session
88     Explorer* m_objectExplorer;
89
90     PluginContainerSupportPtr m_pluginsSupport;
91
92     ///< All loaded plugins. Each one must be unloaded. Plugins means
93     //set of features connected with such plugin (library)
94     LoadedPlugins m_loadedPlugins;
95
96     // Set of currently loaded web pages' contexts. These contexts are
97     // exactly matching with each frames' global context.
98     LoadedContexts m_loadedContexts;
99
100   private:
101     PluginPtr loadLibrary(PluginModelPtr& pluginModel);
102
103     void loadInjectedJavaScript();
104     void installStandardFeatures();
105     void installRequestedFeatures();
106
107     //returns true if success or false if failed
108     bool installPlugin(PluginModelPtr plugin);
109     bool installPluginOnDemand(PluginModelPtr plugin,
110                                JavaScriptObject& parentObject,
111                                JSGlobalContextRef context);
112
113     void unloadPluginsFromSession();
114
115   public:
116     Impl(const PluginContainerSupportPtr& containerSupport);
117     ~Impl();
118
119     // Widget session
120     void startSession(int widgetHandle,
121                       JSGlobalContextRef view,
122                       double scaleFactor,
123                       const char* encodedBundle,
124                       const char* theme);
125
126     void stopSession();
127
128     void performLibrariesUnload();
129
130     bool loadPluginOnDemand(const WrtDB::DbPluginHandle &pluginHandle,
131                             JavaScriptObject& parentObject,
132                             JSGlobalContextRef context);
133
134     void loadFrame(JSGlobalContextRef context);
135     void unloadFrame(JSGlobalContextRef context);
136
137     void setCustomProperties(double scaleFactor,
138                              const char* encodedBundle,
139                              const char* theme);
140
141     void dispatchJavaScriptEvent(CustomEventType eventType, void* data);
142
143 };
144
145
146 JSPageSession::Impl::Impl(const PluginContainerSupportPtr& support) :
147     m_sessionStarted(false)
148 {
149 //    DPL::Log::LogSystemSingleton::Instance().SetTag("WRT_PLUGINS");
150     LogDebug("Initializing Page Session");
151     m_pluginsSupport = support;
152
153     // explicit call to keep singleton's lifetime until calling destructor.
154 //    JsFunctionManagerSingleton::Instance();
155 //    JavaScriptInterfaceSingleton::Instance();
156 }
157
158 JSPageSession::Impl::~Impl()
159 {
160     if(m_sessionStarted)
161     {
162         LogError("Must stop widget session before exit!");
163         stopSession();
164     }
165
166     LogDebug("Deinitializing plugin Logic...");
167 }
168
169 void JSPageSession::Impl::installStandardFeatures()
170 {
171     LogInfo("Installing standard widget features...");
172
173     //add standard functions
174     FOREACH(it, JsFunctionManagerSingleton::Instance().getFunctions())
175     {
176         m_objectExplorer->registerObject(*it, NULL);
177     }
178
179     //add standard objects
180     LogDebug("Installing standard extensions...");
181
182     auto standardPlugins = m_pluginsSupport->getStandardPlugins();
183     FOREACH(it, standardPlugins)
184     {
185         //loadFeatureToSession(*it);
186         installPlugin(*it);
187     }
188
189     LogInfo("Standard widget features installed.");
190 }
191
192 bool JSPageSession::Impl::installPlugin(PluginModelPtr plugin)
193 {
194     Assert(plugin && "Plugin Model is NULL");
195     auto library = loadLibrary(plugin);
196
197     LogInfo("Install Plugin '" << library->GetFileName());
198
199
200     if(!library)
201     {
202         LogError("Loading library failed");
203         return false;
204     }
205
206     // Register new class
207     FOREACH(it, *(library->GetClassList()))
208     {
209         if (!m_objectExplorer->registerObject(*it, NULL)) {
210             LogError("Object Registration failed : " << (*it)->getName());
211         }
212     }
213
214     LogDebug("Registered feature.");
215     return true;
216 }
217
218 void JSPageSession::Impl::installRequestedFeatures()
219 {
220     LogInfo("Installing requested widget features...");
221
222     std::list<std::string> allowedFeatures =
223             m_pluginsSupport->getAllowedFeatures(m_widgetHandle);
224
225     PluginContainerSupport::PluginsList allowedPlugins;
226
227     FOREACH(feature, allowedFeatures)
228     {
229         LogDebug("Processing feature: " << *feature);
230
231         auto plugin = m_pluginsSupport->getPluginForFeature(*feature);
232         ImplementedObjectsList implObjs =
233             PluginDAOReadOnly::
234             getImplementedObjectsForPluginHandle(plugin->Handle.Get());
235
236         FOREACH(obj, implObjs)
237         {
238             LogDebug("Processing object: " << *obj);
239             /* This can be optimalized, but would need extra data in database.
240                There should be a list of features that are allowed to be
241                installed at widget start */
242             if (obj->find(".") == obj->rfind(".")) {
243                 allowedPlugins.push_back(plugin);
244                 LogWarning("Plugin will be added: "
245                            << plugin->LibraryName.Get());
246                 break;
247             }
248         }
249     }
250
251     FOREACH(plugin, allowedPlugins)
252     {
253         LogDebug("Installation plugin: " << (*plugin)->LibraryName.Get());
254         installPlugin(*plugin);
255     }
256
257     LogInfo("requested features installed.");
258 }
259
260 bool JSPageSession::Impl::loadPluginOnDemand(
261     const WrtDB::DbPluginHandle &pluginHandle,
262     JavaScriptObject& parentObject,
263     JSGlobalContextRef context)
264 {
265     LogDebug("load plugin with feature");
266
267     Assert(parentObject.instance &&
268            !parentObject.name.empty()
269            && "Wrong arguments");
270
271     if (!m_sessionStarted) {
272         LogError("Session not started");
273         return false;
274     }
275 //    //TODO here may be a bug. if plugin contains feature rejected and accepted
276 //    LogInfo("Installing feature : " << widgetFeature.name);
277 //    if (widgetFeature.rejected) {
278 //        LogWarning("This api-feature was rejected");
279 //        return;
280 //    }
281 //
282     auto plugin = m_pluginsSupport->getPluginModelById(pluginHandle);
283     if (!plugin) {
284         LogError("Failed to load plugin. plugin handle: " << pluginHandle);
285         return false;
286     }
287
288     return installPluginOnDemand(plugin,parentObject,context);
289 }
290
291 bool JSPageSession::Impl::installPluginOnDemand(PluginModelPtr plugin,
292                                               JavaScriptObject& parentObject,
293                                               JSGlobalContextRef context)
294 {
295     Assert(plugin && "Plugin Model is NULL");
296     auto library = loadLibrary(plugin);
297
298     LogInfo("Install Plugin '" << library->GetFileName());
299
300     if(!library)
301     {
302         LogError("Loading library failed");
303         return false;
304     }
305
306     if(!(parentObject.instance))
307     {
308         LogError("NULL pointer value");
309         return false;
310     }
311
312     JSObjectPtr parent(new JSObject(parentObject.instance));
313
314     if (!parent->getObject())
315     {
316         LogError("NULL pointer value");
317         assert(false);
318         return false;
319     }
320
321     FOREACH(it, *(library->GetClassList()))
322     {
323         bool installationStatus =
324             m_objectExplorer->registerObject(*it,
325                                              parentObject.name,
326                                              parent,
327                                              context);
328
329         if(!installationStatus)
330         {
331             LogError("Object Registration failed : " << (*it)->getName()
332                      << "; Parent object name: " << parentObject.name);
333             return false;
334         }
335     }
336
337     LogDebug("Plugin on demand registration completed");
338     return true;
339 }
340
341 void JSPageSession::Impl::setCustomProperties(double scaleFactor,
342                                             const char* encodedBundle,
343                                             const char* theme)
344 {
345     LogInfo("set properties of window object " << scaleFactor << ", "
346             << encodedBundle << ", " << theme);
347
348     m_objectExplorer->getWindowPropertySupport()
349         ->setScaleToNavigatorProperty(scaleFactor);
350     m_objectExplorer->getWindowPropertySupport()
351         ->setBundleToWindowProperty(encodedBundle);
352     m_objectExplorer->getWindowPropertySupport()
353         ->setThemeToNavigatorProperty(theme);
354 }
355
356 void JSPageSession::Impl::dispatchJavaScriptEvent(CustomEventType eventType, void* data)
357 {
358     // Check if session is already started
359     if (!m_sessionStarted) {
360         LogWarning("Session not started!");
361         return;
362     }
363
364     LogInfo("Request dispatching javascript event");
365     m_objectExplorer->callEventListeners(eventType, data);
366 }
367
368 void JSPageSession::Impl::loadInjectedJavaScript()
369 {
370     LogInfo("Entered");
371
372     std::string DIR_PATH     = "/usr/etc/wrt/injected-javascript";
373     std::string JS_EXTENSION = ".js";
374
375     DIR *dir = opendir(DIR_PATH.c_str());
376
377     if (!dir) {
378         LogError("opendir(\"" << DIR_PATH << "\") error!" );
379         return;
380     }
381
382     struct dirent* libdir;
383     std::list<std::string> jsFiles;
384
385     // make file list from DIR_PATH
386     while ((libdir = readdir(dir)) != 0)
387     {
388         if (strncmp(libdir->d_name, ".", 2) == 0 ||
389             strncmp(libdir->d_name, "..", 3) == 0)
390         {
391             continue;
392         }
393
394         std::string filepath = DIR_PATH;
395         filepath += "/";
396         filepath += libdir->d_name;
397
398         std::string lowercase = filepath;
399         std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(),
400                        towlower);
401
402         if( lowercase.rfind(JS_EXTENSION) == std::string::npos ||
403             lowercase.length() !=
404             lowercase.rfind(JS_EXTENSION) + JS_EXTENSION.length() )
405         {
406             LogError("This is not js file" << filepath);
407             continue;
408         }
409
410         struct stat tmp;
411
412         if (stat(filepath.c_str(), &tmp) == -1) {
413             LogError("Failed to open file " << filepath);
414             continue;
415         }
416
417         if (!S_ISREG(tmp.st_mode)) {
418             LogError("This is not a regular file " << filepath);
419             continue;
420         }
421
422         LogInfo("Added : " << filepath);
423         jsFiles.push_back(filepath);
424     }
425
426     closedir(dir);
427
428
429     FOREACH(it, jsFiles)
430     {
431         LogDebug("load file : " << (*it));
432         // load file
433         std::string content;
434         std::ifstream fin(it->c_str());
435
436         while(fin.good())
437         {
438             string line;
439             std::getline(fin, line);
440             content += line + "\n";
441         }
442
443         fin.close();
444         // execute
445         if(!content.empty())
446         {
447             JSValueRef exception = NULL;
448             JSStringRef script =
449                 JSStringCreateWithUTF8CString(content.c_str());
450
451             JSEvaluateScript(m_context, script, NULL, NULL, 1, &exception);
452
453             JSStringRelease(script);
454
455             if(exception)
456             {
457                 LogDebug("Exception Occured while injecting javascript "
458                     "file. : " << *it);
459
460                 JSStringRef exceptionJSString =
461                     JSValueToStringCopy(m_context, exception, NULL);
462                 size_t size =
463                     JSStringGetMaximumUTF8CStringSize(exceptionJSString);
464                 char* exceptionString = new char[size];
465                 JSStringGetUTF8CString(exceptionJSString,
466                                        exceptionString, size);
467                 LogDebug("Exception : " << exceptionString);
468
469                 delete [] exceptionString;
470                 JSStringRelease(exceptionJSString);
471              }
472         }
473     }
474 }
475
476 void JSPageSession::Impl::startSession(int widgetHandle,
477                                      JSGlobalContextRef context,
478                                      double scaleFactor,
479                                      const char* encodedBundle,
480                                      const char* theme)
481 {
482     LogInfo("Starting widget session...");
483
484     // Check if corresponding session if not already created
485     if (m_sessionStarted) {
486         LogWarning("Session already started!");
487         return;
488     }
489
490     // Create js object explorer object
491     m_objectExplorer = new Explorer(context);
492
493     m_sessionStarted = true;
494     m_widgetHandle = widgetHandle;
495     m_loadedPlugins.clear();
496     m_context = context;
497
498     // Register standard features
499     installStandardFeatures();
500
501     // Register special features
502     installRequestedFeatures();
503
504     // set scale, bundle as window's property
505     setCustomProperties(scaleFactor, encodedBundle, theme);
506
507     // Load injected javascript files
508     loadInjectedJavaScript();
509     LogInfo("Widget session started.");
510 }
511
512 void JSPageSession::Impl::stopSession()
513 {
514     LogInfo("Stopping widget session...");
515
516     if (!m_sessionStarted) {
517         LogWarning("Session not started!");
518         return;
519     }
520
521     unloadPluginsFromSession();
522     m_sessionStarted = false;
523
524     LogInfo("Widget session stopped.");
525 }
526
527
528 void JSPageSession::Impl::unloadPluginsFromSession()
529 {
530     LogDebug("Unload plugins from session");
531
532     m_objectExplorer->removePluginsFromIframes();
533     m_objectExplorer->cleanIframesData();
534
535     // delete js object for overlayed js functions
536     FOREACH(it, JsFunctionManagerSingleton::Instance().getFunctions())
537     {
538         m_objectExplorer->deregisterObject(*it);
539     }
540
541     // delete js object for plugins
542     FOREACH(pluginIt, m_loadedPlugins)
543     {
544         LogDebug("Unregistering plugin " << (*pluginIt)->GetFileName());
545
546         (*pluginIt)->OnWidgetStop(m_widgetHandle);
547         LogDebug("Emitted WidgetStop for plugin: " <<
548                  (*pluginIt)->GetFileName());
549
550         FOREACH(it, *((*pluginIt)->GetClassList()))
551         {
552             m_objectExplorer->deregisterObject(*it);
553         }
554     }
555
556     JavaScriptInterfaceSingleton::Instance().invokeGarbageCollector(m_context);
557
558     m_loadedPlugins.clear();
559
560     delete m_objectExplorer;
561     m_objectExplorer = NULL;
562 }
563
564
565
566
567 void JSPageSession::Impl::performLibrariesUnload()
568 {
569 #if 0
570     LogDebug("Perform library unload");
571
572     size_t unloadedLibraries = 0;
573
574     FOREACH(pluginIt, m_loadedPlugins)
575     {
576         LogDebug("Preparing library: " << (*pluginIt)->LibraryName.Get());
577
578         PluginPtr plugin = (*pluginIt)->LibraryInstance.Get();
579         if (!plugin) {
580             LogWarning("Library not loaded " << (*pluginIt)->LibraryName.Get());
581             continue;
582         }
583         unloadedLibraries++;
584         (*pluginIt)->LibraryInstance.Set(PluginPtr());
585     }
586
587     LogInfo("unloaded " << unloadedLibraries << " unreferenced libraries!");
588 #endif
589 }
590
591 PluginPtr JSPageSession::Impl::loadLibrary(PluginModelPtr& pluginModel)
592 {
593     PluginPtr pluginLib = pluginModel->LibraryInstance.Get();
594     if (!pluginLib)
595     {
596         std::string path = pluginModel->LibraryPath.Get() +
597                            std::string(LIBRARY_PATH_SEPARATOR) +
598                            pluginModel->LibraryName.Get();
599
600         pluginLib = Plugin::LoadFromFile(path);
601
602         if (!pluginLib)
603         {
604             LogError("Loading library failed");
605         } else
606         {
607             pluginModel->LibraryInstance.Set(pluginLib);
608
609             LogDebug("On widget start");
610             // This is first time for this plugin, start widget Session
611             pluginLib->OnWidgetStart(
612                 m_widgetHandle);
613             m_loadedPlugins.insert(pluginLib);
614
615             FOREACH(context, m_loadedContexts)
616             {
617                 pluginLib->OnFrameLoad(*context);
618             }
619         }
620     }
621     else
622     {
623         LogDebug("Get from LibraryInstance");
624         LogDebug("On widget start");
625         // This is first time for this plugin, start widget Session
626         pluginLib->OnWidgetStart(
627             m_widgetHandle);
628         m_loadedPlugins.insert(pluginLib);
629
630         FOREACH(context, m_loadedContexts)
631         {
632             pluginLib->OnFrameLoad(*context);
633         }
634     }
635
636     return pluginLib;
637 }
638
639
640 void JSPageSession::Impl::loadFrame(JSGlobalContextRef context)
641 {
642     LogDebug("Load a frame");
643
644     if (!m_sessionStarted) {
645         LogWarning("Session NOT started!");
646         return;
647     }
648
649     m_loadedContexts.insert(context);
650
651     FOREACH(pluginIt, m_loadedPlugins)
652     {
653         LogDebug("load plugin to frame" << (*pluginIt)->GetFileName());
654
655         (*pluginIt)->OnFrameLoad(context);
656     }
657
658     m_objectExplorer->loadFrame(context);
659 }
660
661 void JSPageSession::Impl::unloadFrame(JSGlobalContextRef context)
662 {
663     LogDebug("Unload a frame");
664
665     if (!m_sessionStarted) {
666         LogWarning("Session NOT started!");
667         return;
668     }
669
670     m_loadedContexts.erase(context);
671
672     FOREACH(pluginIt, m_loadedPlugins)
673     {
674         LogDebug("unload plugin to frame" << (*pluginIt)->GetFileName());
675
676         (*pluginIt)->OnFrameUnload(context);
677     }
678
679     m_objectExplorer->unloadFrame(context);
680 }
681
682
683 void JSPageSession::startSession(int widgetHandle,
684                                JSGlobalContextRef ctx,
685                                double scaleFactor,
686                                const char* encodedBundle,
687                                const char* theme)
688 {
689     m_impl->startSession(widgetHandle, ctx, scaleFactor, encodedBundle, theme);
690 }
691
692 void JSPageSession::stopSession()
693 {
694     m_impl->stopSession();
695 }
696
697 void JSPageSession::performLibrariesUnload()
698 {
699     m_impl->performLibrariesUnload();
700 }
701
702 bool JSPageSession::loadPluginOnDemand(
703     const WrtDB::DbPluginHandle &pluginHandle,
704     JavaScriptObject& parentObject,
705     JSGlobalContextRef context)
706 {
707     return m_impl->loadPluginOnDemand(pluginHandle, parentObject, context);
708 }
709
710 void JSPageSession::setCustomProperties(double scaleFactor,
711                                       const char* encodedBundle,
712                                       const char* theme)
713 {
714     m_impl->setCustomProperties(scaleFactor, encodedBundle, theme);
715 }
716
717 void JSPageSession::dispatchJavaScriptEvent(CustomEventType eventType, void* data)
718 {
719     m_impl->dispatchJavaScriptEvent(eventType, data);
720 }
721
722 void JSPageSession::loadFrame(JSGlobalContextRef context)
723 {
724     m_impl->loadFrame(context);
725 }
726
727 void JSPageSession::unloadFrame(JSGlobalContextRef context)
728 {
729     m_impl->unloadFrame(context);
730 }
731
732 JSPageSession::JSPageSession(const PluginContainerSupportPtr& containerSupport)
733     : m_impl(new JSPageSession::Impl(containerSupport))
734 {
735 }
736
737 JSPageSession::~JSPageSession()
738 {
739 }