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