[Release] wrt-plugins-common_0.3.83
[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 installStandardFunctions();
105      void installRootPlugins();
106     void installRequestedFeatures();
107
108     //returns true if success or false if failed
109     bool installPlugin(PluginModelPtr plugin);
110     bool installPluginOnDemand(PluginModelPtr plugin,
111                                JavaScriptObject& parentObject,
112                                JSGlobalContextRef context);
113
114     void unloadPluginsFromSession();
115
116   public:
117     Impl(const PluginContainerSupportPtr& containerSupport);
118     ~Impl();
119
120     // Widget session
121     void startSession(int widgetHandle,
122                       JSGlobalContextRef view,
123                       double scaleFactor,
124                       const char* encodedBundle,
125                       const char* theme);
126
127     void stopSession();
128
129     void performLibrariesUnload();
130
131     bool loadPluginOnDemand(const WrtDB::DbPluginHandle &pluginHandle,
132                             JavaScriptObject& parentObject,
133                             JSGlobalContextRef context);
134
135     void loadFrame(JSGlobalContextRef context);
136     void unloadFrame(JSGlobalContextRef context);
137
138     void setCustomProperties(double scaleFactor,
139                              const char* encodedBundle,
140                              const char* theme);
141
142     void dispatchJavaScriptEvent(CustomEventType eventType, void* data);
143 };
144
145 JSPageSession::Impl::Impl(const PluginContainerSupportPtr& support) :
146         m_widgetHandle(0),
147         m_context(NULL),
148         m_sessionStarted(false),
149         m_objectExplorer(NULL)
150 {
151     //    DPL::Log::LogSystemSingleton::Instance().SetTag("WRT_PLUGINS");
152     LogDebug("Initializing Page Session");
153     m_pluginsSupport = support;
154
155     // explicit call to keep singleton's lifetime until calling destructor.
156     //    JsFunctionManagerSingleton::Instance();
157     //    JavaScriptInterfaceSingleton::Instance();
158 }
159
160 JSPageSession::Impl::~Impl()
161 {
162     if (m_sessionStarted) {
163         LogError("Must stop widget session before exit!");
164         stopSession();
165     }
166
167     LogDebug("Deinitializing plugin Logic...");
168 }
169
170  void JSPageSession::Impl::installStandardFunctions()
171  {
172     LogInfo("Installing standard functions...");
173
174     //add standard functions
175     FOREACH(it, JsFunctionManagerSingleton::Instance().getFunctions()) {
176         m_objectExplorer->registerObject(*it, NULL);
177     }
178
179      LogInfo("Standard functions installed.");
180  }
181
182 void JSPageSession::Impl::installRootPlugins()
183 {
184     LogInfo("Installing requested root plugins...");
185
186     PluginContainerSupport::PluginsList rootPlugins =
187                     m_pluginsSupport->getRootPlugins();
188     FOREACH(it, rootPlugins)
189     {
190         installPlugin(*it);
191     }
192
193     LogInfo("requested root plugins installed.");
194 }
195
196 bool JSPageSession::Impl::installPlugin(PluginModelPtr plugin)
197 {
198     Assert(plugin && "Plugin Model is NULL");
199     PluginPtr library = loadLibrary(plugin);
200
201     if (!library) {
202         LogError("Loading library failed");
203         return false;
204     }
205
206     LogInfo("Install Plugin '" << library->GetFileName());
207
208     // Register new class
209     FOREACH(it, *(library->GetClassList()))
210     {
211         if (!m_objectExplorer->registerObject(*it, NULL)) {
212             LogError("Object Registration failed : " << (*it)->getName());
213         }
214     }
215
216     LogDebug("Registered feature.");
217     return true;
218 }
219
220 void JSPageSession::Impl::installRequestedFeatures()
221 {
222     LogInfo("Installing requested widget features...");
223
224     std::list<std::string> allowedFeatures =
225         m_pluginsSupport->getAllowedFeatures(m_widgetHandle);
226
227     PluginContainerSupport::PluginsList allowedPlugins;
228
229     FOREACH(feature, allowedFeatures)
230     {
231         LogDebug("Processing feature: " << *feature);
232
233         PluginModelPtr plugin = m_pluginsSupport->getPluginForFeature(*feature);
234
235         if (!plugin)
236         {
237             LogDebug("It didn't have plugins! : " << *feature);
238             continue;
239         }
240
241         ImplementedObjectsList implObjs =
242             PluginDAOReadOnly::
243                 getImplementedObjectsForPluginHandle(plugin->Handle.Get());
244
245         FOREACH(obj, implObjs)
246         {
247             LogDebug("Processing object: " << *obj);
248             /* This can be optimalized, but would need extra data in database.
249              * There should be a list of features that are allowed to be
250              * installed at widget start */
251             if (obj->find(".") == obj->rfind(".")) {
252                 allowedPlugins.push_back(plugin);
253                 LogWarning("Plugin will be added: "
254                            << plugin->LibraryName.Get());
255                 break;
256             }
257         }
258     }
259
260     FOREACH(plugin, allowedPlugins)
261     {
262         LogDebug("Installation plugin: " << (*plugin)->LibraryName.Get());
263         installPlugin(*plugin);
264     }
265
266     LogInfo("requested features installed.");
267 }
268
269 bool JSPageSession::Impl::loadPluginOnDemand(
270     const WrtDB::DbPluginHandle &pluginHandle,
271     JavaScriptObject& parentObject,
272     JSGlobalContextRef context)
273 {
274     LogDebug("load plugin with feature");
275
276     Assert(parentObject.instance &&
277            !parentObject.name.empty()
278            && "Wrong arguments");
279
280     if (!m_sessionStarted) {
281         LogError("Session not started");
282         return false;
283     }
284     //    //TODO here may be a bug. if plugin contains feature rejected and
285     // accepted
286     //    LogInfo("Installing feature : " << widgetFeature.name);
287     //    if (widgetFeature.rejected) {
288     //        LogWarning("This api-feature was rejected");
289     //        return;
290     //    }
291     //
292     //    auto plugin = m_pluginsSupport->getPluginModelById(pluginHandle);
293     //    if (!plugin) {
294     //        LogError("Failed to load plugin. plugin handle: " <<
295     // pluginHandle);
296     //        return false;
297     //    }
298     m_pluginsSupport->registerPluginModel(pluginHandle);
299     return installPluginOnDemand(
300                m_pluginsSupport->getPluginModelById(pluginHandle),
301                parentObject,
302                context);
303 }
304
305 bool JSPageSession::Impl::installPluginOnDemand(PluginModelPtr plugin,
306                                                 JavaScriptObject& parentObject,
307                                                 JSGlobalContextRef context)
308 {
309     Assert(plugin && "Plugin Model is NULL");
310     PluginPtr library = loadLibrary(plugin);
311
312     if (!library) {
313         LogError("Loading library failed");
314         return false;
315     }
316
317     LogInfo("Install Plugin '" << library->GetFileName());
318
319     if (!(parentObject.instance)) {
320         LogError("NULL pointer value");
321         return false;
322     }
323
324     JSObjectPtr parent(new JSObject(parentObject.instance));
325
326     if (!parent->getObject()) {
327         LogError("NULL pointer value");
328         assert(false);
329         return false;
330     }
331
332     FOREACH(it, *(library->GetClassList()))
333     {
334         bool installationStatus =
335             m_objectExplorer->registerObject(*it,
336                                              parentObject.name,
337                                              parent,
338                                              context);
339
340         if (!installationStatus) {
341             LogError(
342                 "Object Registration failed : " << (*it)->getName()
343                                                 <<
344                 "; Parent object name: " << parentObject.name);
345             return false;
346         }
347     }
348
349     LogDebug("Plugin on demand registration completed");
350     return true;
351 }
352
353 void JSPageSession::Impl::setCustomProperties(double scaleFactor,
354                                               const char* encodedBundle,
355                                               const char* theme)
356 {
357     LogInfo(
358         "set properties of window object " << scaleFactor << ", "
359                                            << encodedBundle << ", " <<
360         theme);
361
362     m_objectExplorer->getWindowPropertySupport()
363         ->setScaleToNavigatorProperty(scaleFactor);
364     m_objectExplorer->getWindowPropertySupport()
365         ->setBundleToWindowProperty(encodedBundle);
366     m_objectExplorer->getWindowPropertySupport()
367         ->setThemeToNavigatorProperty(theme);
368 }
369
370 void JSPageSession::Impl::dispatchJavaScriptEvent(CustomEventType eventType,
371                                                   void* data)
372 {
373     // Check if session is already started
374     if (!m_sessionStarted) {
375         LogWarning("Session not started!");
376         return;
377     }
378
379     LogInfo("Request dispatching javascript event");
380     m_objectExplorer->callEventListeners(eventType, data);
381 }
382
383 void JSPageSession::Impl::loadInjectedJavaScript()
384 {
385     LogInfo("Entered");
386
387     std::string DIR_PATH = "/usr/etc/wrt/injected-javascript";
388     std::string JS_EXTENSION = ".js";
389
390     DIR *dir = opendir(DIR_PATH.c_str());
391
392     if (!dir) {
393         LogError("opendir(\"" << DIR_PATH << "\") error!");
394         return;
395     }
396
397     int return_code;
398     struct dirent libdir;
399     struct dirent* result;
400     std::list<std::string> jsFiles;
401
402     // make file list from DIR_PATH
403     for (return_code = readdir_r(dir, &libdir, &result);
404             result != NULL && return_code == 0;
405             return_code = readdir_r(dir, &libdir, &result)) {
406         if (strncmp(libdir.d_name, ".", 2) == 0 ||
407             strncmp(libdir.d_name, "..", 3) == 0)
408         {
409             continue;
410         }
411
412         std::string filepath = DIR_PATH;
413         filepath += "/";
414         filepath += libdir.d_name;
415
416         std::string lowercase = filepath;
417         std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(),
418                        towlower);
419
420         if (lowercase.rfind(JS_EXTENSION) == std::string::npos ||
421             lowercase.length() !=
422             lowercase.rfind(JS_EXTENSION) + JS_EXTENSION.length() )
423         {
424             LogError("This is not js file" << filepath);
425             continue;
426         }
427
428         struct stat tmp;
429
430         if (stat(filepath.c_str(), &tmp) == -1) {
431             LogError("Failed to open file " << filepath);
432             continue;
433         }
434
435         if (!S_ISREG(tmp.st_mode)) {
436             LogError("This is not a regular file " << filepath);
437             continue;
438         }
439
440         LogInfo("Added : " << filepath);
441         jsFiles.push_back(filepath);
442     }
443     if (0 != return_code)
444         LogError("Error while reading directory.");
445
446     closedir(dir);
447
448     FOREACH(it, jsFiles)
449     {
450         LogDebug("load file : " << (*it));
451         // load file
452         std::string content;
453         std::ifstream fin(it->c_str());
454
455         while (fin.good()) {
456             string line;
457             std::getline(fin, line);
458             content += line + "\n";
459         }
460
461         fin.close();
462         // execute
463         if (!content.empty()) {
464             JSValueRef exception = NULL;
465             JSStringRef script =
466                 JSStringCreateWithUTF8CString(content.c_str());
467
468             JSEvaluateScript(m_context, script, NULL, NULL, 1, &exception);
469
470             JSStringRelease(script);
471
472             if (exception) {
473                 LogDebug("Exception Occured while injecting javascript "
474                          "file. : " << *it);
475
476                 JSStringRef exceptionJSString =
477                     JSValueToStringCopy(m_context, exception, NULL);
478                 size_t size =
479                     JSStringGetMaximumUTF8CStringSize(exceptionJSString);
480                 char* exceptionString = new char[size];
481                 JSStringGetUTF8CString(exceptionJSString,
482                                        exceptionString, size);
483                 LogDebug("Exception : " << exceptionString);
484
485                 delete[] exceptionString;
486                 JSStringRelease(exceptionJSString);
487             }
488         }
489     }
490 }
491
492 void JSPageSession::Impl::startSession(int widgetHandle,
493                                        JSGlobalContextRef context,
494                                        double scaleFactor,
495                                        const char* encodedBundle,
496                                        const char* theme)
497 {
498     LogInfo("Starting widget session...");
499
500     // Check if corresponding session if not already created
501     if (m_sessionStarted) {
502         LogWarning("Session already started!");
503         return;
504     }
505
506     // Create js object explorer object
507     m_objectExplorer = new Explorer(context);
508
509     m_sessionStarted = true;
510     m_widgetHandle = widgetHandle;
511     m_loadedPlugins.clear();
512     m_context = context;
513
514     // Register standard features
515     installStandardFunctions();
516
517     WidgetDAOReadOnly dao(m_widgetHandle);
518     WidgetType appType = dao.getWidgetType();
519     if (appType == WrtDB::APP_TYPE_TIZENWEBAPP) {
520         installRootPlugins();
521     }
522     // Register special features
523     installRequestedFeatures();
524
525     // set scale, bundle as window's property
526     setCustomProperties(scaleFactor, encodedBundle, theme);
527
528     // Load injected javascript files
529     loadInjectedJavaScript();
530     LogInfo("Widget session started.");
531 }
532
533 void JSPageSession::Impl::stopSession()
534 {
535     LogInfo("Stopping widget session...");
536
537     if (!m_sessionStarted) {
538         LogWarning("Session not started!");
539         return;
540     }
541
542     unloadPluginsFromSession();
543     m_sessionStarted = false;
544
545     LogInfo("Widget session stopped.");
546 }
547
548 void JSPageSession::Impl::unloadPluginsFromSession()
549 {
550     LogDebug("Unload plugins from session");
551
552     m_objectExplorer->removePluginsFromIframes();
553     m_objectExplorer->cleanIframesData();
554
555     // delete js object for overlayed js functions
556     FOREACH(it, JsFunctionManagerSingleton::Instance().getFunctions())
557     {
558         m_objectExplorer->deregisterObject(*it);
559     }
560
561     // delete js object for plugins
562     FOREACH(pluginIt, m_loadedPlugins)
563     {
564         LogDebug("Unregistering plugin " << (*pluginIt)->GetFileName());
565
566         (*pluginIt)->OnWidgetStop(m_widgetHandle);
567         LogDebug("Emitted WidgetStop for plugin: " <<
568                  (*pluginIt)->GetFileName());
569
570         FOREACH(it, *((*pluginIt)->GetClassList()))
571         {
572             m_objectExplorer->deregisterObject(*it);
573         }
574     }
575
576     JavaScriptInterfaceSingleton::Instance().invokeGarbageCollector(m_context);
577
578     m_loadedPlugins.clear();
579
580     delete m_objectExplorer;
581     m_objectExplorer = NULL;
582 }
583
584 void JSPageSession::Impl::performLibrariesUnload()
585 {
586 #if 0
587     LogDebug("Perform library unload");
588
589     size_t unloadedLibraries = 0;
590
591     FOREACH(pluginIt, m_loadedPlugins)
592     {
593         LogDebug("Preparing library: " << (*pluginIt)->LibraryName.Get());
594
595         PluginPtr plugin = (*pluginIt)->LibraryInstance.Get();
596         if (!plugin) {
597             LogWarning("Library not loaded " << (*pluginIt)->LibraryName.Get());
598             continue;
599         }
600         unloadedLibraries++;
601         (*pluginIt)->LibraryInstance.Set(PluginPtr());
602     }
603
604     LogInfo("unloaded " << unloadedLibraries << " unreferenced libraries!");
605 #endif
606 }
607
608 PluginPtr JSPageSession::Impl::loadLibrary(PluginModelPtr& pluginModel)
609 {
610     PluginPtr pluginLib = pluginModel->LibraryInstance.Get();
611     if (!pluginLib) {
612         std::string path = pluginModel->LibraryPath.Get() +
613             std::string(LIBRARY_PATH_SEPARATOR) +
614             pluginModel->LibraryName.Get();
615
616         pluginLib = Plugin::LoadFromFile(path);
617
618         if (!pluginLib) {
619             LogError("Loading library failed");
620         } else {
621             pluginModel->LibraryInstance.Set(pluginLib);
622
623             LogDebug("On widget start");
624             // This is first time for this plugin, start widget Session
625             pluginLib->OnWidgetStart(
626                 m_widgetHandle);
627             m_loadedPlugins.insert(pluginLib);
628
629             FOREACH(context, m_loadedContexts)
630             {
631                 pluginLib->OnFrameLoad(*context);
632             }
633         }
634     } else {
635         LogDebug("Get from LibraryInstance");
636         LogDebug("On widget start");
637         // This is first time for this plugin, start widget Session
638         pluginLib->OnWidgetStart(
639             m_widgetHandle);
640         m_loadedPlugins.insert(pluginLib);
641
642         FOREACH(context, m_loadedContexts)
643         {
644             pluginLib->OnFrameLoad(*context);
645         }
646     }
647
648     return pluginLib;
649 }
650
651 void JSPageSession::Impl::loadFrame(JSGlobalContextRef context)
652 {
653     LogDebug("Load a frame");
654
655     if (!m_sessionStarted) {
656         LogWarning("Session NOT started!");
657         return;
658     }
659
660     m_loadedContexts.insert(context);
661
662     FOREACH(pluginIt, m_loadedPlugins)
663     {
664         LogDebug("load plugin to frame" << (*pluginIt)->GetFileName());
665
666         (*pluginIt)->OnFrameLoad(context);
667     }
668
669     m_objectExplorer->loadFrame(context);
670 }
671
672 void JSPageSession::Impl::unloadFrame(JSGlobalContextRef context)
673 {
674     LogDebug("Unload a frame");
675
676     if (!m_sessionStarted) {
677         LogWarning("Session NOT started!");
678         return;
679     }
680
681     m_loadedContexts.erase(context);
682
683     FOREACH(pluginIt, m_loadedPlugins)
684     {
685         LogDebug("unload plugin to frame" << (*pluginIt)->GetFileName());
686
687         (*pluginIt)->OnFrameUnload(context);
688     }
689
690     m_objectExplorer->unloadFrame(context);
691 }
692
693 void JSPageSession::startSession(int widgetHandle,
694                                  JSGlobalContextRef ctx,
695                                  double scaleFactor,
696                                  const char* encodedBundle,
697                                  const char* theme)
698 {
699     m_impl->startSession(widgetHandle, ctx, scaleFactor, encodedBundle, theme);
700 }
701
702 void JSPageSession::stopSession()
703 {
704     m_impl->stopSession();
705 }
706
707 void JSPageSession::performLibrariesUnload()
708 {
709     m_impl->performLibrariesUnload();
710 }
711
712 bool JSPageSession::loadPluginOnDemand(
713     const WrtDB::DbPluginHandle &pluginHandle,
714     JavaScriptObject& parentObject,
715     JSGlobalContextRef context)
716 {
717     return m_impl->loadPluginOnDemand(pluginHandle, parentObject, context);
718 }
719
720 void JSPageSession::setCustomProperties(double scaleFactor,
721                                         const char* encodedBundle,
722                                         const char* theme)
723 {
724     m_impl->setCustomProperties(scaleFactor, encodedBundle, theme);
725 }
726
727 void JSPageSession::dispatchJavaScriptEvent(CustomEventType eventType,
728                                             void* data)
729 {
730     m_impl->dispatchJavaScriptEvent(eventType, data);
731 }
732
733 void JSPageSession::loadFrame(JSGlobalContextRef context)
734 {
735     m_impl->loadFrame(context);
736 }
737
738 void JSPageSession::unloadFrame(JSGlobalContextRef context)
739 {
740     m_impl->unloadFrame(context);
741 }
742
743 JSPageSession::JSPageSession(const PluginContainerSupportPtr& containerSupport)
744     :
745     m_impl(new JSPageSession::Impl(containerSupport))
746 {}
747
748 JSPageSession::~JSPageSession()
749 {}