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