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