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