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