[VD] Fix issue for app deeplink that page is not reload
[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                         let originalUrl = _this.webApplication.mainWindow.getURL();
161                         if (wrt.tv) {
162                             console.log(`appcontrol src = ${src}, original url = ${originalUrl}`);
163                             if (src && originalUrl) {
164                                 let appcontrolUrl = new URL(src);
165                                 let oldUrl = new URL(originalUrl);
166                                 if ('file:' !== appcontrolUrl.protocol &&
167                                     (appcontrolUrl.protocol !== oldUrl.protocol ||
168                                      appcontrolUrl.host !== oldUrl.host ||
169                                      appcontrolUrl.pathname !== oldUrl.pathname)) {
170                                      reload = true;
171                                 } else if ('file:' === appcontrolUrl.protocol && (src !== originalUrl)) {
172                                      reload = true;
173                                 }
174                             } else if (src !== originalUrl) {
175                                 reload = true;
176                             }
177                         } else if (src !== originalUrl) {
178                             reload = true;
179                         }
180                     }
181                     // handle http://tizen.org/appcontrol/operation/main operation specially.
182                     // only menu-screen app can send launch request with main operation.
183                     // in this case, web app should have to resume web app not reset.
184                     if (reload && appControl.getOperation() == 'http://tizen.org/appcontrol/operation/main')
185                         reload = false;
186                     if (reload) {
187                         _this.webApplication.closeWindows();
188                         _this.webApplication.mainWindow.loadURL(src);
189                     } else {
190                         _this.webApplication.sendAppControlEvent();
191                     }
192                 }
193             }
194             _this.setCookiePath();
195             _this.launchInspector(appControl);
196         });
197         wrt.on('suspend', function() {
198             console.log('suspend');
199             if (_this.webApplication)
200                 _this.webApplication.suspend();
201         });
202         wrt.on('resume', function() {
203             console.log('resume');
204             if (_this.webApplication)
205                 _this.webApplication.resume();
206         });
207         wrt.on('low-memory', function() {
208             console.log('low-memory');
209             if (_this.webApplication)
210                 _this.webApplication.lowMemory();
211         });
212         wrt.on('message', function(event, type, params) {
213             console.log('message type(' + type + ') params : ' + params);
214             const app_id = params[0];
215             const vm = require('vm');
216             if (type === 'startService') {
217                 if (_this.sandbox[app_id] === undefined) {
218                     if (_this.sandbox_count === 0) {
219                         new TizenExtension();
220                     }
221                     _this.sandbox_count++;
222                     const fs = require('fs');
223                     const Module = require('module');
224                     _this.sandbox[app_id] = {
225                         console: console,
226                         module: new Module,
227                         require: require,
228                         tizen: tizen,
229                     };
230                     for(let key in global) {
231                         _this.sandbox[app_id][key] = global[key];
232                     }
233                     _this.sandbox[app_id]['timer_manager'] = new TimerManager();
234                     const timer_api = _this.sandbox[app_id]['timer_manager'].getTimerAPI();
235                     for(let key in timer_api) {
236                         _this.sandbox[app_id][key] = timer_api[key];
237                     }
238                     let standard_object_list = [ Error, EvalError, RangeError, ReferenceError,
239                             SyntaxError, TypeError, URIError, Number, BigInt, Math, Date,
240                             String, RegExp, Array, Int8Array, Uint8Array, Uint8ClampedArray,
241                             Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array,
242                             Float64Array, BigInt64Array, BigUint64Array, Map, Set, WeakMap,
243                             WeakSet, ArrayBuffer, DataView, JSON, Promise, Reflect, Proxy,
244                             Intl, Intl.Collator, Intl.DateTimeFormat, Intl.NumberFormat, Intl.PluralRules,
245                             WebAssembly, WebAssembly.Module, WebAssembly.Instance, WebAssembly.Memory,
246                             WebAssembly.Table, WebAssembly.CompileError, WebAssembly.LinkError,
247                             WebAssembly.RuntimeError, Boolean, Function, Object, Symbol ];
248                     for (let idx in standard_object_list) {
249                         _this.sandbox[app_id][standard_object_list[idx].name] = standard_object_list[idx];
250                     }
251                     let options = {};
252                     let code = fs.readFileSync(params[1]);
253                     vm.runInNewContext(code, _this.sandbox[app_id], options);
254                 }
255                 if (_this.sandbox[app_id]['started'] === undefined) {
256                     _this.sandbox[app_id]['started'] = true;
257                     _this.sandbox[app_id]['stopped'] = undefined;
258                     const start_callback_string = 'if (module.exports.onStart !== undefined) { module.exports.onStart(); }';
259                     vm.runInContext(start_callback_string, _this.sandbox[app_id]);
260                 } else {
261                     console.log('UI service has been started.');
262                 }
263                 event.preventDefault();
264             } else if (type === 'stopService') {
265                 if (_this.sandbox[app_id]['stopped'] === undefined) {
266                     _this.sandbox_count--;
267                     _this.sandbox[app_id]['stopped'] = true;
268                     _this.sandbox[app_id]['started'] = undefined;
269                     const stop_callback_string = 'if (module.exports.onStop !== undefined) { module.exports.onStop(); }';
270                     vm.runInContext(stop_callback_string, _this.sandbox[app_id]);
271                     _this.sandbox[app_id]['timer_manager'].releaseRemainingTimers();
272                     for(let key in _this.sandbox[app_id]) {
273                         delete _this.sandbox[app_id][key];
274                     }
275                     delete _this.sandbox[app_id];
276                     if (_this.sandbox_count === 0) {
277                         tizen = null;
278                     }
279                 } else {
280                     console.log('UI service has been stopped.');
281                 }
282                 event.preventDefault();
283             }
284         });
285         wrt.on('addon-installed', function(event, path) {
286             console.log('addon-installed at ' + path);
287             let dbInfo = AddonManager.checkAddon(path);
288             if (dbInfo) {
289                 _this.addonPkgs.push(dbInfo);
290             }
291         });
292         wrt.on('addon-uninstalled', function(event, id) {
293             console.log('addon-unistalled named ' + id);
294         });
295         wrt.on('wgt-checking-done', function(event) {
296             console.log('wgt-checking-done');
297             AddonManager.updateDB(_this.addonPkgs);
298         });
299         /* FIXME: will uncheck after chromium-efl released */
300         if (wrt.getPlatformType() !== "product_tv") {
301             wrt.getInstalledPkg();
302         }
303     }
304     handleIpcMessages() {
305         var _this = this;
306         ipcMain.on(IPC_MESSAGE.ADDONS.INSTALLED, (sender, name) => {
307             console.log('handleIpcMessages: INSTALLED ' + name);
308             _this.addonManager.build();
309             return _this.addonManager.activate(app, name);
310         });
311         ipcMain.on(IPC_MESSAGE.ADDONS.UNINSTALLED, (sender, name, pkg) => {
312             console.log('handleIpcMessages: UNINSTALLED ' + name);
313             _this.addonManager.deactivate(app, name);
314             /* FIXME: will uncheck after chromium-efl released */
315             if (wrt.getPlatformType() !== "product_tv") {
316                wrt.reqUninstallPkg(pkg);
317             }
318             return true;
319         });
320         ipcMain.on(IPC_MESSAGE.ADDONS.ACTIVATE, (sender, name) => {
321             console.log('handleIpcMessages: ACTIVATE ' + name);
322             return _this.addonManager.activate(app, name);
323         });
324         ipcMain.on(IPC_MESSAGE.ADDONS.DEACTIVATE, (sender, name) => {
325             console.log('handleIpcMessages: DEACTIVATE ' + name);
326             return _this.addonManager.deactivate(app, name);
327         });
328     }
329     checkInspectorCondition(appControl) {
330         let bundleDebug = (appControl.getData('__AUL_DEBUG__') === "1");
331         return (bundleDebug || this.inspectorEnabledByVconf);
332     }
333     setCookiePath() {
334         this.setCookiePath = () => {}; // call once
335         console.log('setCookiePath');
336
337         // FIX ME : It must be supplemented to set a specific path
338         wrt.setCookiePath();
339     }
340     launchInspector(appControl) {
341         this.launchInspector = (param) => {}; // call once
342         console.log('launchInspector');
343
344         // AUL public key/Vconf - To support inspector
345         if (this.checkInspectorCondition(appControl)) {
346             var debugPort = wrt.startInspectorServer();
347             var data    = { "port" :  [ debugPort.toString() ] };
348             if (this.webApplication)
349                 this.webApplication.debugPort = debugPort;
350             appControl.reply(data);
351         }
352     }
353     handleKeyEvents(key) {
354         let valid = false;
355         let _this = this;
356
357         console.log(key + ' is pressed');
358         switch(key) {
359             case "ArrowUp":
360             case "Up":
361             case "ArrowDown":
362             case "Down":
363                 valid = true;
364                 break;
365             default:
366                 console.log('No handler for the key ' + key);
367                 break;
368         }
369
370         if (valid) {
371             _this.webApplication.keyEvent(key);
372         }
373     }
374 }
375 module.exports = Runtime;