2 * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 const { app, protocol } = require('electron');
20 const WAS_EVENT = require('./was_event');
21 const wrt = require('../browser/wrt');
22 const WRTWebContents = require('../browser/wrt_web_contents');
23 const WRTWindow = require('../browser/wrt_window');
25 class WebApplication {
26 constructor(options) {
27 this.initialize(options);
28 this.createMainWindow(options);
32 this.pendingCallbacks = new Map();
34 this.firstRendered = false;
35 this.backgroundSupport = wrt.getBackgroundSupport();
36 this.multitaskingSupport = wrt.getMultitaskingSupport();
38 this.inspectorSrc = '';
39 if (options.launchMode == 'backgroundAtStartup') {
40 console.log('backgroundAtStartup');
41 this.preloadStatus = 'preload';
43 this.preloadStatus = 'none';
45 if (options.launchMode == 'backgroundExecution') {
46 console.log('backgroundExecution');
47 this.backgroundExecution = true;
49 this.backgroundExecution = false;
51 this.accessiblePath = wrt.getAccessiblePath();
52 this.isAlwaysReload = wrt.isAlwaysReload();
53 this.suspended = true;
54 this.loadFinished = false;
55 this.runningStatus = 'none';
56 this.isTVProfile = (wrt.getPlatformType() === "product_tv");
57 this.defaultBackgroundColor = (this.isTVProfile
58 || (wrt.getPlatformType() === "product_wearable") ? '#000' : '#FFF');
59 this.defaultTransparent = (this.isTVProfile ? true : false);
62 app.on('browser-window-created', function(event, window) {
63 if (self.windowList.length > 0)
64 self.windowList[self.windowList.length - 1].hide();
65 self.windowList.push(window);
66 console.log(`window created : #${self.windowList.length}`);
68 window.on('closed', function() {
69 console.log(`window closed : #${self.windowList.length}`);
70 let index = self.windowList.indexOf(window);
71 self.windowList.splice(index, 1);
72 if (index === self.windowList.length && self.windowList.length > 0)
73 self.windowList[self.windowList.length - 1].show();
76 app.on('web-contents-created', function(event, webContents) {
77 webContents.on('crashed', function() {
78 console.error('webContents crashed');
81 webContents.session.setPermissionRequestHandler(function(webContents, permission, callback) {
82 console.log(`handlePermissionRequests for ${permission}`);
83 if (permission === 'notifications') {
84 const id = ++self.pendingID;
85 console.log(`Raising a notification permission request with id: ${id}`);
86 self.pendingCallbacks.set(id, callback);
87 wrt.handleNotificationPermissionRequest(id, webContents);
88 } else if (permission === 'media') {
89 const id = ++self.pendingID;
90 console.log(`Raising a media permission request with id: ${id}`);
91 self.pendingCallbacks.set(id, callback);
92 wrt.handleMediaPermissionRequest(id, webContents);
93 } else if (permission === 'geolocation') {
94 const id = ++self.pendingID;
95 console.log(`Raising a geolocation permission request with id: ${id}`);
96 self.pendingCallbacks.set(id, callback);
97 wrt.handleGeolocationPermissionRequest(id, webContents);
99 /* electron by default allows permission for all if no request handler
100 is there; so granting permission only temporarily to not have any
106 app.on('certificate-error', function(event, webContents, url, error, certificate, callback) {
107 console.log('A certificate error has occurred');
108 event.preventDefault();
109 if (certificate.data) {
110 const id = ++self.pendingID;
111 console.log(`Raising a certificate error response with id: ${id}`);
112 self.pendingCallbacks.set(id, callback);
113 wrt.handleCertificateError(id, webContents, certificate.data, url);
115 console.log('Certificate could not be opened');
119 app.on('login', function(event, webContents, request, authInfo, callback) {
120 console.log('Login info is required');
121 event.preventDefault();
122 const id = ++self.pendingID;
123 console.log(`Raising a login info request with id: ${id}`);
124 self.pendingCallbacks.set(id, callback);
125 wrt.handleAuthRequest(id, webContents);
127 if (this.accessiblePath) {
128 console.log('accessiblePath : ' + this.accessiblePath);
129 protocol.interceptFileProtocol('file', (request, callback) => {
130 const url = require('url');
131 let access_path, parsed_info = url.parse(request.url);
132 access_path = parsed_info.host + parsed_info.pathname;
133 console.log("check path: " + access_path);
135 for (let p in self.accessiblePath) {
136 if (access_path.startsWith(self.accessiblePath[p])) {
137 callback(access_path);
141 if (access_path.indexOf("/shared/res/") > -1) {
142 callback(access_path);
146 console.log("invalid access: " + access_path);
153 wrt.on('permission-response', function(event, id, result) {
154 console.log(`permission-response for ${id} is ${result}`);
155 let callback = self.pendingCallbacks.get(id);
156 if (typeof callback === 'function') {
157 console.log('calling permission response callback');
159 self.pendingCallbacks.delete(id);
162 wrt.on('auth-response', function(event, id, submit, user, passwd) {
163 let callback = self.pendingCallbacks.get(id);
164 if (typeof callback === 'function') {
165 console.log('calling auth response callback');
167 callback(user, passwd);
171 self.pendingCallbacks.delete(id);
174 wrt.on('app-status-changed', function(event, status) {
175 console.log(`runningStatus: ${status}, ${self.loadFinished}`);
176 if (!self.isTVProfile) {
179 self.runningStatus = status;
180 if (self.runningStatus === 'DialogClose' && self.inspectorSrc) {
181 console.log(`runningStatus is DialogClose, src is ${self.inspectorSrc}`);
182 self.mainWindow.loadURL(self.inspectorSrc);
183 self.inspectorSrc = '';
184 } else if (self.runningStatus == 'behind' && self.loadFinished) {
185 // TODO : Need to care this situation and decide to pass the addon event emitter to suspend()
190 backgroundRunnable() {
191 return this.backgroundSupport || this.backgroundExecution;
193 getWindowOption(options) {
196 backgroundColor: this.defaultBackgroundColor,
197 transparent: this.defaultTransparent,
200 nodeIntegration: options.isAddonAvailable,
201 nodeIntegrationInWorker: false
203 webContents: WRTWebContents.create(),
205 'direct-write': true,
206 'subpixel-font-scaling': false,
207 'web-security': false
211 createMainWindow(options) {
212 let winopt = this.getWindowOption(options);
213 this.mainWindow = new WRTWindow(winopt);
214 if (options.devMode) {
215 this.mainWindow.webContents.openDevTools({
220 if (!self.isTVProfile) {
221 self.showTimer = setTimeout(() => {
222 if (!self.suspended) {
223 console.log('FrameRendered not obtained from engine. To show window, timer fired');
224 self.mainWindow.emit('ready-to-show');
229 this.mainWindow.once('ready-to-show', function() {
230 console.log('mainWindow ready-to-show');
232 clearTimeout(self.showTimer);
233 wrt.hideSplashScreen(1);
234 self.firstRendered = true;
235 if (self.preloadStatus == 'preload') {
236 self.preloadStatus = 'readyToShow';
237 console.log('preloading show is skipped!');
242 this.mainWindow.webContents.on('did-start-loading', function() {
243 console.log('webContents did-start-loading');
244 self.loadFinished = false;
246 this.mainWindow.webContents.on('did-finish-load', function() {
247 console.log('webContents did-finish-load');
248 self.loadFinished = true;
249 wrt.hideSplashScreen(2);
250 if (wrt.isIMEWebApp()) {
251 self.activateIMEWebHelperClient();
252 } else if (self.isTVProfile) {
253 if (self.inspectorSrc) {
254 self.showInspectorGuide();
256 self.suspendByStatus();
262 if (this.preloadStatus === 'readyToShow' ||
263 this.preloadStatus === 'preload' ||
264 this.runningStatus === 'behind') {
265 console.log(`preloadStatus: ${this.preloadStatus}, runningStatus: ${this.runningStatus}`);
266 // TODO : Need to care this situation and decide to pass the addon event emitter to suspend()
268 if (this.runningStatus !== 'behind')
269 wrt.notifyAppStatus('preload');
272 showInspectorGuide() {
273 this.showInspectorGuide = () => {}; // call once
274 let message = this.debugPort.toString() +
275 "\r\nFast RWI is used, [about:blank] is loaded fist instead of \r\n[" +
277 "]\r\nClick OK button will start the real loading.\r\nNotes:\r\nPlease " +
278 "connect to RWI in PC before click OK button.\r\nThen you can get " +
279 "network log from the initial loading.\r\nPlease click Record button " +
280 "in Timeline panel in PC before click OK button,\r\nThen you can get " +
281 "profile log from the initial loading."
282 wrt.modalDialog(this.mainWindow.webContents, message);
284 suspend(evtEmitter) {
285 console.log('WebApplication : suspend');
287 console.log('WebApplication : suspend - Found event emitter');
288 evtEmitter.emit('lcSuspend', this.mainWindow.id);
290 console.log('WebApplication : suspend - Invalid event emitter');
292 this.suspended = true;
293 if (this.isTVProfile) {
295 this.windowList.forEach((window) => window.webContents.session.flushStorageData());
297 if (!this.multitaskingSupport) {
298 // FIXME : terminate app after visibilitychange event handling
300 console.log('multitasking is not supported; quitting app')
303 } else if (!this.backgroundRunnable()) {
304 this.windowList.forEach((window) => window.setEnabled(false));
306 this.windowList[this.windowList.length - 1].hide();
309 console.log('WebApplication : resume');
311 this.suspended = false;
314 console.log('WebApplication : resume - Found event emitter');
315 evtEmitter.emit('lcResume', this.mainWindow.id);
317 console.log('WebApplication : resume - Invalid event emitter');
320 if (!this.firstRendered) {
321 console.log('WebApplication : resume firstRendered is false');
324 if (!this.backgroundRunnable()) {
325 this.windowList.forEach((window) => window.setEnabled(true));
327 this.windowList[this.windowList.length - 1].show();
330 this.inspectorSrc = '';
331 this.windowList.forEach((window) => {
332 window.removeAllListeners();
336 console.log('WebApplication : quit');
338 console.log('WebApplication : quit - Found event emitter');
339 evtEmitter.emit('lcQuit', this.mainWindow.id);
341 console.log('WebApplication : quit - Invalid event emitter');
344 sendAppControlEvent() {
345 const kAppControlEventScript =
347 'var __event = document.createEvent("CustomEvent");' +
348 '__event.initCustomEvent("appcontrol", true, true, null);' +
349 'document.dispatchEvent(__event);' +
350 'for (var i=0; i < window.frames.length; i++)' +
351 'window.frames[i].document.dispatchEvent(__event);' +
353 wrt.executeJS(this.mainWindow.webContents, kAppControlEventScript);
355 activateIMEWebHelperClient() {
356 console.log('webApplication : activateIMEWebHelperClient');
357 const kImeActivateFunctionCallScript =
358 '(function(){WebHelperClient.impl.activate();})()';
359 wrt.executeJS(this.mainWindow.webContents, kImeActivateFunctionCallScript);
362 console.log('WebApplication : show');
363 this.preloadStatus = 'none';
364 if (this.backgroundExecution) {
365 console.log('skip showing while backgroundExecution mode');
366 } else if (!this.mainWindow.isVisible()) {
367 console.log('show window');
368 this.mainWindow.show();
372 this.windowList.forEach((window) => {
373 if (window != this.mainWindow)
377 keyEvent(evtEmitter, key) {
378 console.log('WebApplication : keyEvent');
380 console.log('Invalid event emitter for key hook');
383 console.log('key event is ' + key);
387 evtEmitter.emit('hwUpkey', this.mainWindow.id);
391 evtEmitter.emit('hwDownkey', this.mainWindow.id);
394 console.log('No handler for ' + key);
398 prelaunch(evtEmitter, origURL) {
399 console.log('WebApplication : prelaunch');
401 console.log('WebApplication : prelaunch - Found event emitter');
402 evtEmitter.emit('lcPrelaunch', this.mainWindow.id, origURL);
404 console.log('WebApplication : prelaunch - Invalid event emitter');
408 console.log('WebApplication : lowMemory to clearcache');
409 if (this.isTVProfile) {
410 this.windowList.forEach((window) => {
411 //clear webframe cache
412 wrt.clearWebCache(window.webContents);
413 /* FIXME: will unblock after chromium-efl released
414 window.webContents.session.clearCache(function() {
415 console.log('clear session Cache complete');
422 module.exports = WebApplication;