Merge "[Service][UI] Expose primitives and standard objects" into tizen_5.5
[platform/framework/web/wrtjs.git] / wrt_app / src / runtime.js
1 /*
2  * Copyright (c) 2019 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 'use strict';
18
19 const wrt = require('../browser/wrt');  // Load first for log
20 const AddonManager = require('./addon_manager');
21 const {app, ipcMain} = require('electron');
22 const IPC_MESSAGE = require('./ipc_message');
23 const TimerManager = require('../service/timer_manager');
24 const TizenExtension = require('../service/tizen_extension');
25 const WAS_EVENT = require('./was_event');
26 const WebApplication = require('./web_application');
27
28 class Runtime {
29     constructor(options) {
30         this.webApplication = null;
31         this.handleIpcMessages();
32         this.addonManager = null;
33         this.isLaunched = false;
34         this.inspectorEnabledByVconf = false;
35         this.sandbox = [];
36         this.webContents = null;
37         this.addonPkgs = [];
38         this.isTVProfile = (wrt.getPlatformType() === 'product_tv');
39
40         var _this = this;
41         app.on('before-quit', function(event) {
42             console.log('before-quit');
43             if (!wrt.isElectronApp()) {
44                 _this.webApplication.quit();
45                 _this.webApplication.finalize();
46                 _this.webApplication = null;
47             }
48         });
49         app.on('will-quit', function(event) {
50             console.log('will-quit');
51             _this.addonManager.deactivateAll(app);
52         });
53         app.on('quit', function(event) {
54             console.log('quit');
55             wrt.exit();
56         });
57         app.on('browser-window-blur', function() {
58             console.log('browser-window-blur');
59         });
60         app.on('browser-window-focus', function() {
61             console.log('browser-window-focus');
62         });
63         app.on('browser-window-created', function() {
64             console.log('browser-window-created');
65             if (!_this.isLaunched) {
66                 _this.addonManager.activateAll(app);
67                 _this.isLaunched = true;
68             }
69         });
70         app.on('gpu-process-crashed', function() {
71             console.error('gpu-process-crashed');
72         });
73         app.on('window-all-closed', function(event) {
74             console.log('window-all-closed');
75             app.quit();
76         });
77         app.on('will-finish-launching', function(event) {
78             console.log('will-finish-launching');
79         });
80         app.on('web-contents-created', function(event, webContents) {
81             console.log('web-contents-created');
82             _this.webContents = webContents;
83             _this.webContents.on('before-input-event', function(event, input) {
84                 if (_this.isLaunched && _this.webApplication) {
85                     _this.handleKeyEvents(input.key);
86                 }
87             });
88         });
89         app.once('ready', function(event) {
90             console.log('ready');
91             _this.addonManager = new AddonManager();
92             if (!options.noAddons) {
93                 _this.addonManager.build();
94             }
95             wrt.importCertificate('');
96         });
97         wrt.on('app-control', function(event, appControl) {
98             console.log('app-control');
99             let loadInfo = appControl.getLoadInfo();
100             let src = loadInfo.getSrc();
101
102             if (wrt.isElectronApp()) {
103                 console.log('Electron App launch');
104                 const Module = require('module');
105                 Module.globalPaths.push(wrt.getAppPath());
106                 let filePath = src[7] === '/' ? src.substr(8) : src.substr(7); // strip "file://"
107                 let pkgJson = require(filePath);
108                 let pos = filePath.lastIndexOf('/');
109
110                 let mainJsPath = (pos !== -1 ? filePath.substr(0, pos + 1) : '') +
111                                  (pkgJson.main || 'index.js');
112                 console.log('loading path:', mainJsPath);
113                 Module._load(mainJsPath, Module, true);
114                 app.emit('ready');
115             } else {
116                 console.log('Tizen Web App launch');
117                 let launchMode = appControl.getData('http://samsung.com/appcontrol/data/launch_mode');
118                 if (!_this.webApplication) {
119                     console.log('Creating WebApplication');
120                     options.isAddonAvailable = !options.noAddons &&
121                             _this.addonManager.isAddonAvailable();
122                     options.launchMode = launchMode;
123                     _this.webApplication = new WebApplication(options);
124                     _this.webApplication.addonEmitter =
125                           _this.addonManager.evt_emitter_;
126                     _this.inspectorEnabledByVconf = wrt.needUseInspector();
127                     if (_this.inspectorEnabledByVconf && launchMode != 'backgroundExecution') {
128                         _this.webApplication.inspectorSrc = src;
129                         src = "about:blank";
130                     }
131                     _this.webApplication.mainWindow.loadURL(src);
132                     _this.webApplication.prelaunch(src);
133                 } else {
134                     console.log('Handling app-control event');
135                     if (_this.webApplication.preloadStatus == 'readyToShow') {
136                         _this.webApplication.show();
137                     } else {
138                         if (launchMode != 'backgroundAtStartup') {
139                             _this.webApplication.preloadStatus = 'none';
140                         }
141                     }
142
143                     let skipReload = appControl.getData('SkipReload');
144                     if (skipReload == 'Yes') {
145                         console.log('skipping reload');
146                         // TODO : Need to care this situation and decide to pass the addon event emitter to resume()
147                         _this.webApplication.resume();
148                         return;
149                     }
150
151                     let reload = loadInfo.getReload() || _this.webApplication.isAlwaysReload;
152                     if (!reload) {
153                         if (_this.isTVProfile) {
154                             console.log(`src = ${src}, app-control uri = ${_this.webApplication.mainWindow.getURL()}`);
155                             const url = require('url');
156                             let appcontrolUrl = url.parse(src);
157                             let originUrl = url.parse(_this.webApplication.mainWindow.getURL());
158                             if (appcontrolUrl.protocol !== originUrl.protocol ||
159                                 appcontrolUrl.host !== originUrl.host ||
160                                 appcontrolUrl.pathname !== originUrl.pathname) {
161                                 reload = true;
162                             }
163                         } else {
164                             if (src != _this.webApplication.mainWindow.getURL()) {
165                                 reload = true;
166                             }
167                         }
168                     }
169                     // handle http://tizen.org/appcontrol/operation/main operation specially.
170                     // only menu-screen app can send launch request with main operation.
171                     // in this case, web app should have to resume web app not reset.
172                     if (reload && appControl.getOperation() == 'http://tizen.org/appcontrol/operation/main')
173                         reload = false;
174                     if (reload) {
175                         _this.webApplication.closeWindows();
176                         _this.webApplication.mainWindow.loadURL(src);
177                     } else {
178                         _this.webApplication.sendAppControlEvent();
179                     }
180                 }
181             }
182             _this.configureRuntime(appControl);
183         });
184         wrt.on('suspend', function() {
185             console.log('suspend');
186             if (_this.webApplication)
187                 _this.webApplication.suspend();
188         });
189         wrt.on('resume', function() {
190             console.log('resume');
191             if (_this.webApplication)
192                 _this.webApplication.resume();
193         });
194         wrt.on('low-memory', function() {
195             console.log('low-memory');
196             if (_this.webApplication)
197                 _this.webApplication.lowMemory();
198         });
199         wrt.on('message', function(event, type, params) {
200             console.log('message type(' + type + ') params : ' + params);
201             const app_id = params[0];
202             const vm = require('vm');
203             if (type === 'startService') {
204                 if (_this.sandbox[app_id] === undefined) {
205                     const fs = require('fs');
206                     const Module = require('module');
207                     _this.sandbox[app_id] = {
208                         console: console,
209                         module: new Module,
210                         require: require,
211                         tizen: tizen,
212                     };
213                     for(let key in global) {
214                         _this.sandbox[app_id][key] = global[key];
215                     }
216                     _this.sandbox[app_id]['timer_manager'] = new TimerManager();
217                     const timer_api = _this.sandbox[app_id]['timer_manager'].getTimerAPI();
218                     for(let key in timer_api) {
219                         _this.sandbox[app_id][key] = timer_api[key];
220                     }
221                     let standard_object_list = [ Error, EvalError, RangeError, ReferenceError,
222                             SyntaxError, TypeError, URIError, Number, BigInt, Math, Date,
223                             String, RegExp, Array, Int8Array, Uint8Array, Uint8ClampedArray,
224                             Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array,
225                             Float64Array, BigInt64Array, BigUint64Array, Map, Set, WeakMap,
226                             WeakSet, ArrayBuffer, DataView, JSON, Promise, Reflect, Proxy,
227                             Intl, Intl.Collator, Intl.DateTimeFormat, Intl.NumberFormat, Intl.PluralRules,
228                             WebAssembly, WebAssembly.Module, WebAssembly.Instance, WebAssembly.Memory,
229                             WebAssembly.Table, WebAssembly.CompileError, WebAssembly.LinkError,
230                             WebAssembly.RuntimeError, Boolean, Function, Object, Symbol ];
231                     for (let idx in standard_object_list) {
232                         _this.sandbox[app_id][standard_object_list[idx].name] = standard_object_list[idx];
233                     }
234                     let options = {};
235                     let code = fs.readFileSync(params[1]);
236                     vm.runInNewContext(code, _this.sandbox[app_id], options);
237                 }
238                 if (_this.sandbox[app_id]['started'] === undefined) {
239                     _this.sandbox[app_id]['started'] = true;
240                     _this.sandbox[app_id]['stopped'] = undefined;
241                     const start_callback_string = 'if (module.exports.onStart !== undefined) { module.exports.onStart(); }';
242                     vm.runInContext(start_callback_string, _this.sandbox[app_id]);
243                 } else {
244                     console.log('UI service has been started.');
245                 }
246                 event.preventDefault();
247             } else if (type === 'stopService') {
248                 if (_this.sandbox[app_id]['stopped'] === undefined) {
249                     _this.sandbox[app_id]['stopped'] = true;
250                     _this.sandbox[app_id]['started'] = undefined;
251                     const stop_callback_string = 'if (module.exports.onStop !== undefined) { module.exports.onStop(); }';
252                     vm.runInContext(stop_callback_string, _this.sandbox[app_id]);
253                     _this.sandbox[app_id]['timer_manager'].releaseRemainingTimers();
254                     _this.sandbox[app_id] = undefined;
255                 } else {
256                     console.log('UI service has been stopped.');
257                 }
258                 event.preventDefault();
259             }
260         });
261         wrt.on('addon-installed', function(event, path) {
262             console.log('addon-installed at ' + path);
263             let dbInfo = AddonManager.checkAddon(path);
264             if (dbInfo) {
265                 _this.addonPkgs.push(dbInfo);
266             }
267         });
268         wrt.on('addon-uninstalled', function(event, id) {
269             console.log('addon-unistalled named ' + id);
270         });
271         wrt.on('wgt-checking-done', function(event) {
272             console.log('wgt-checking-done');
273             AddonManager.updateDB(_this.addonPkgs);
274         });
275         /* FIXME: will uncheck after chromium-efl released */
276         if (wrt.getPlatformType() !== "product_tv") {
277             wrt.getInstalledPkg();
278         }
279     }
280     handleIpcMessages() {
281         var _this = this;
282         ipcMain.on(IPC_MESSAGE.ADDONS.INSTALLED, (sender, name) => {
283             console.log('handleIpcMessages: INSTALLED ' + name);
284             _this.addonManager.build();
285             return _this.addonManager.activate(app, name);
286         });
287         ipcMain.on(IPC_MESSAGE.ADDONS.UNINSTALLED, (sender, name, pkg) => {
288             console.log('handleIpcMessages: UNINSTALLED ' + name);
289             _this.addonManager.deactivate(app, name);
290             /* FIXME: will uncheck after chromium-efl released */
291             if (wrt.getPlatformType() !== "product_tv") {
292                wrt.reqUninstallPkg(pkg);
293             }
294             return true;
295         });
296         ipcMain.on(IPC_MESSAGE.ADDONS.ACTIVATE, (sender, name) => {
297             console.log('handleIpcMessages: ACTIVATE ' + name);
298             return _this.addonManager.activate(app, name);
299         });
300         ipcMain.on(IPC_MESSAGE.ADDONS.DEACTIVATE, (sender, name) => {
301             console.log('handleIpcMessages: DEACTIVATE ' + name);
302             return _this.addonManager.deactivate(app, name);
303         });
304     }
305     checkInspectorCondition(appControl) {
306         let bundleDebug = (appControl.getData('__AUL_DEBUG__') === "1");
307         return (bundleDebug || this.inspectorEnabledByVconf);
308     }
309     launchInspector(appControl) {
310         var debugPort = wrt.startInspectorServer();
311         var data    = { "port" :  [ debugPort.toString() ] };
312         if (this.webApplication)
313             this.webApplication.debugPort = debugPort;
314         appControl.reply(data);
315     }
316     configureRuntime(appControl) {
317         this.configureRuntime = (param) => {}; // call once
318
319         // FIX ME : It must be supplemented to set a specific path
320         wrt.setCookiePath();
321
322         // AUL public key/Vconf - To support inspector
323         if (this.checkInspectorCondition(appControl)) {
324             this.launchInspector(appControl);
325         }
326     }
327     handleKeyEvents(key) {
328         let valid = false;
329         let _this = this;
330
331         console.log(key + ' is pressed');
332         switch(key) {
333             case "ArrowUp":
334             case "Up":
335             case "ArrowDown":
336             case "Down":
337                 valid = true;
338                 break;
339             default:
340                 console.log('No handler for the key ' + key);
341                 break;
342         }
343
344         if (valid) {
345             _this.webApplication.keyEvent(key);
346         }
347     }
348 }
349 module.exports = Runtime;