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