39b23d261f00310b029903186dd66920730a794b
[platform/core/uifw/dali-toolkit.git] / plugins / dali-script-v8 / src / module-loader / module-loader.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd.
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
18 // CLASS HEADER
19 #include "module-loader.h"
20
21 // INTERNAL INCLUDES
22 #include <v8-utils.h>
23
24 namespace Dali
25 {
26
27 namespace V8Plugin
28 {
29
30 ModuleLoader::ModuleLoader()
31 {
32
33 }
34
35 ModuleLoader::~ModuleLoader()
36 {
37   VectorBase::SizeType count =  mModules.Count();
38   for( VectorBase::SizeType  i = 0; i < count ; ++i)
39   {
40     Module* module = mModules[i];
41     delete module;
42   }
43   mModules.Clear();
44 }
45
46 bool ModuleLoader::CompileAndRun(v8::Isolate* isolate,
47     const std::string& sourceCode,
48     const std::string& sourceFileName )
49 {
50
51   v8::HandleScope handleScope( isolate );
52   v8::TryCatch tryCatch;
53
54   // convert from string to v8 string
55   v8::Local<v8::String> source = v8::String::NewFromUtf8( isolate, sourceCode.c_str());
56   v8::Local<v8::String> file = v8::String::NewFromUtf8( isolate, sourceFileName.c_str());
57   v8::ScriptOrigin origin(file);
58
59   // Compile the script
60   v8::Local<v8::Script> script = v8::Script::Compile( source, &origin);
61
62   // See if an exception has been thrown
63   if( tryCatch.HasCaught() || script.IsEmpty() )
64   {
65     // Print errors that happened during compilation.
66     V8Utils::ReportException( isolate, &tryCatch );
67     return false;
68   }
69
70   // Run the script
71   v8::Local<v8::Value> result = script->Run();
72
73   // in V8 test code ( they check for an exception and empty return object )
74   if( tryCatch.HasCaught() || result.IsEmpty() )
75   {
76     // Print errors that happened during execution.
77     V8Utils::ReportException( isolate, &tryCatch);
78     return false;
79   }
80
81   return true;
82 }
83
84 bool ModuleLoader::ExecuteScript( v8::Isolate* isolate,
85                                   const std::string& sourceCode,
86                                   const std::string& sourceFileName )
87 {
88   StoreScriptInfo(  sourceFileName );
89
90   return CompileAndRun(isolate, sourceCode, sourceFileName );
91 }
92
93 bool ModuleLoader::ExecuteScriptFromFile( v8::Isolate* isolate,
94                                           const std::string& fileName  )
95 {
96   std::string contents;
97
98   V8Utils::GetFileContents( fileName, contents );
99
100   return ExecuteScript( isolate, contents, fileName );
101 }
102
103 /**
104  * ### var module = require("module-name");
105  *
106  *
107  * There is no standard for modules or the 'require' keyword in JavaScript.<br />
108  * However CommonJS have this: http://wiki.commonjs.org/wiki/Modules/1.1.1  ( used by Node.js).
109  * <br /> <br />
110  *
111  * The concept behind 'require' keyword is simple, it allows you to include another
112  * JavaScript file, which exports an API / function / constructor / singleton.
113  *
114  *
115  *     // example_module.js
116  *     exports.hello = function() { return "hello world" }
117  *
118  * <br />
119  *
120  *     // main.js
121  *     var example = require( "example_module.js");
122  *
123  *     log( example.hello() );
124  *
125  *
126
127  * ### Module writers guide:
128  *
129  *
130  * #### Exporting as a namespace
131  *
132  * Example of using a namespace to export functions / objects.
133  *
134  *     // filesystem-helper.js
135  *     exports.version = "FileSystem helper 1.0";
136  *     exports.open = function() { }
137  *     exports.close = function() { }
138  *     exports.read = function() { }
139  *     exports.write = function() { ... }
140  *     exports.fileSize = function() {...}
141  *
142  * <br />
143  *
144  *     // main.js
145  *     var fs = require( "filesystem-helper.js");
146  *
147  *     log( fs.version );
148  *
149  *     var file = fs.open("myfile.txt");
150  *     var data = fs.read( file );
151  *
152  *
153  *
154  * #### Exporting as a function
155  *
156  * In this example we are using module.exports directly to change it
157  * from an object literal with name-value pairs (exports object) to a function.
158  *
159  *     // my_first_module.js
160  *     module.exports = function() {  log("hello-world"); }
161  *
162  * <br />
163  *
164  *     // main.js
165  *     var func = require("my_first_module.js");
166  *     func();      // prints out hello-world
167  *
168  *
169  * #### Exporting as a constructor
170  *
171  *
172  *     // ImageActor.js
173  *     function ImageActor( position, orientation, image, name )
174  *     {
175  *         this = new dali.ImageActor( image );
176  *         this.position = position;
177  *         this.orientation = orientation;
178  *         this.name = name;
179  *     }
180  *     module.exports = ImageActor;
181  *
182  * <br />
183  *
184  *     // main.js
185  *
186  *     var ImageActor = require(" ImageActor.js");
187  *
188  *     var imageActor = new ImageActor( position, orientation, image, "my first image actor");
189  *
190  * #### Exporting as a singleton
191  *
192  * By exporting a singleton you have an object which has shared state between
193  * any modules using it.
194  *
195  * example:
196  *
197  *     // image-database.js
198  *
199  *     function ImageDatabase( )
200  *     {
201  *       this.addImage  = function() {  ... };
202  *       this.removeImage = function() { ... };
203  *       this.getImage = function()  { ...};
204  *       this.getImageCount = function() { ... };
205  *     }
206  *
207  *     module.exports = new ImageDatabase();
208  *
209  *
210  * <br />
211  *
212  *     // main.js
213  *
214  *     var database = require('image-database.js');
215  *
216  *     database.addImage( myImage );
217  *
218  * <br />
219  *
220  *     // another-module.js
221  *     var database = require('image-database.js');
222  *
223  *     // gets the same database object as main.js
224  *
225  *
226  * The first call to require('image-database.js') will create the image database.
227  * Further calls, will return the same instance, because require caches module.exports.
228  * Otherwise it would have to recompile and run the module every time require is called.
229  *
230  * ## Notes
231  *
232  * #### Automatic wrapping of a module by DALi:
233  *
234  * The module is automatically wrapped in a function by DALi before being executed ( similar technique to Node.js). </br>
235  * This is to prevent any functions / variables declared by the module entering the global namespace. </br>
236  * Currently the module will have access to all DALi global functions, like log, require and the DALi API ( actors / stage etc).</br>
237  *
238  *
239  *     // Parameters passed to the internally generated function
240  *     // module = reference to current module
241  *     // module.exports = defines what the module exports
242  *     // exports = reference to module.exports
243  *     // __filename = module filename
244  *     // __dirname = module directory
245  *
246  *     function createModuleXYZ( exports ( === module.exports), module, __filename, __dirname )
247  *     {
248  *       //
249  *       // Module code automatically inserted here.
250  *       //
251  *       log(" my first module ");
252  *       var version = "1.3";      // this won't pollute global namespace
253  *       exports.version = version;
254  *       exports.logActorPosition = function( actorName )
255  *       {
256  *         var actor = dali.stage.getRootLayer().findChildByName(actorName );
257  *         log( actor.x + "," + actor.y + "," + actor.z );
258  *        }
259  *       //
260  *       // End module code
261  *       //
262  *
263  *       return module.exports;
264  *     }
265
266  *
267  * Initially module.exports is an object literal with name-value pairs ( exports object).
268  * However it can be re-assigned to a constructor / function / singleton object as shown
269  * in the examples above.
270  *
271  *
272  *  ### Circular dependencies:
273  *
274  *  DALi JS supports circular dependencies as required by the CommonJS specification.
275  *
276  *  #### a.js
277  *
278  *
279  *     export.version = "1.3"
280  *     export.loaded = false;
281  *     var bModule = require('b.js')
282  *     export.loaded = true;
283  *
284  *  #### b.js
285  *
286  *     var aModule = require('a.js')
287  *     log( "aModule version = " + aModule.version + ", aModule loaded = " + aModule.loaded );
288  *
289  *     //prints  aModule = 1.3, aModule loaded = false
290  *
291  *  #### main.js
292  *
293  *      var aModule = require("a.js");
294  *
295  *
296  *  When b.js requires a.js, it is given everything that is exported from a.js, up to the point
297  *  b.js is required by a.js.
298  *
299  * ### 'require' background
300  *
301  * There is alternative to module spec in CommonJS called RequireJs ( http://requirejs.org/docs/node.html) <br />
302  * DALi JS tries to follows the CommonJS  specification (used by Node.js) as it
303  * is supposed to be better suited to server side development. <br /><br />
304  *
305  * @method require
306  * @for ModuleLoader
307  *
308  */
309 void ModuleLoader::Require(const v8::FunctionCallbackInfo< v8::Value >& args,
310
311                            v8::Persistent<v8::ObjectTemplate>& globalObjectTemplate )
312 {
313   v8::Isolate* isolate = args.GetIsolate();
314   v8::HandleScope handleScope( isolate );
315
316   bool found( false );
317   std::string fileName = V8Utils::GetStringParameter( PARAMETER_0, found, isolate , args );
318   if( !found )
319   {
320     DALI_SCRIPT_EXCEPTION( isolate, "require missing module name");
321     return;
322   }
323
324   // strip off any path / .js
325   std::string moduleName;
326   V8Utils::GetModuleName( fileName, moduleName );
327
328   // see if the module already exists
329   const Module* existingModule = FindModule( moduleName );
330   if( existingModule )
331   {
332     // printf(" using existing module %s \n",moduleName.c_str() );
333     args.GetReturnValue().Set( existingModule->mExportsObject );
334     return;
335   }
336
337   std::string path = mCurrentScriptPath;  // path of top level script being executed
338   std::string contents;
339   V8Utils::GetFileContents(path + fileName, contents);
340
341   // wrap the module in a function to protect global namespace.
342   // the create function itself is global so we make it unique for each module
343   // For reference nodeJs does this as an anonymous function, but we're calling it from native side
344   // so need to pass parameters  / get a name for it.
345   std::string functionName ="__createModule" +  moduleName;
346   std::string source = "function " + functionName + "( exports, module, __filename, __directory)  { ";
347   source+= contents;
348   source+=" \n };";  // close the function
349
350   CompileAndRun( isolate, source, fileName );
351
352   // We need to create module object, so that the module can read / write properties to it
353
354   v8::Local<v8::Object> moduleObject = v8::Object::New( isolate );
355   v8::Local<v8::Object> exportsObject = v8::Object::New( isolate );
356   moduleObject->Set( v8::String::NewFromUtf8( isolate, "exports"),  exportsObject );
357   moduleObject->Set( v8::String::NewFromUtf8( isolate, "id"), v8::String::NewFromUtf8( isolate ,moduleName.c_str() ) );
358
359   // store the module exports object now, this is to allow for circular dependencies.
360   // If this-module requires another module, which then requires this module ( creating a circle), it will be given an export object
361   // which contains everything exported so far.
362   Module* module = StoreModule( path, fileName, moduleName, isolate, exportsObject );
363
364   v8::Local<v8::Context> currentContext =  isolate->GetCurrentContext();
365
366   // get the CreateModule function
367   v8::Local<v8::Function> createModule = v8::Local<v8::Function>::Cast(currentContext->Global()->Get(v8::String::NewFromUtf8( isolate, functionName.c_str() )));
368
369   // add the arguments
370   std::vector< v8::Local<v8::Value> > arguments;
371   arguments.push_back( exportsObject );
372   arguments.push_back( moduleObject );
373   arguments.push_back( v8::String::NewFromUtf8( isolate, fileName.c_str() ));
374   arguments.push_back( v8::String::NewFromUtf8( isolate, path.c_str() ));
375
376
377   // call the CreateModule function
378   createModule->Call( createModule, arguments.size(), &arguments[0]); //[0]
379
380   // get the module.export object, the module writer may have re-assinged module.exports, so the exports object
381   // no longer references it.
382   v8::Local<v8::Value> moduleExportsValue = moduleObject->Get( v8::String::NewFromUtf8( isolate, "exports"));
383   v8::Local<v8::Object>  moduleExports = moduleExportsValue->ToObject();
384
385   // Re-store the export ( possible nothing happens, because exports hasn't been re-assigned).
386   module->mExportsObject.Reset( isolate, moduleExports);
387
388   args.GetReturnValue().Set( moduleExports );
389
390 }
391 void ModuleLoader::StoreScriptInfo( const std::string& sourceFileName )
392 {
393   V8Utils::GetFileDirectory( sourceFileName, mCurrentScriptPath);
394 }
395
396 Module* ModuleLoader::StoreModule( const std::string& path,
397                                 const std::string& fileName,
398                                 const std::string& moduleName,
399
400                                 v8::Isolate* isolate,
401                                 v8::Local<v8::Object>& moduleExportsObject )
402 {
403   Module* module = new Module( path, fileName, moduleName, isolate, moduleExportsObject );
404   mModules.PushBack( module );
405   return module;
406
407 }
408
409 const Module* ModuleLoader::FindModule( const std::string& moduleName )
410 {
411   VectorBase::SizeType count =  mModules.Count();
412   for( VectorBase::SizeType  i = 0; i < count ; ++i)
413   {
414     const Module* module = mModules[i];
415     if (module->mModuleName == moduleName )
416     {
417       return module;
418     }
419   }
420   return NULL;
421 }
422
423
424 } // V8Plugin
425
426 } // Dali