Apply typescript 64/236564/6
authorSangYong Park <sy302.park@samsung.com>
Thu, 28 May 2020 23:04:54 +0000 (08:04 +0900)
committerSangYong Park <sy302.park@samsung.com>
Tue, 7 Jul 2020 02:47:47 +0000 (11:47 +0900)
Apply typescript to maintain consistency with js api from chromium-efl.
If you change with wrtjs and chromium-efl together,
please append '-R {GBS-ROOT of chromium-efl}/local/repos/ -C' to build command.

Change-Id: I0231b02d4d3f79e79b0275a130929e7c46bf7d62
Signed-off-by: SangYong Park <sy302.park@samsung.com>
48 files changed:
.gitignore
packaging/wrtjs.spec
tsconfig.json [new file with mode: 0644]
wrt_app/addon/browser/addonapi.ts [moved from wrt_app/addon/browser/addonapi.js with 70% similarity]
wrt_app/addon/browser/module-list.ts [moved from wrt_app/addon/browser/module-list.js with 88% similarity]
wrt_app/addon/browser/modules/messaging.js [deleted file]
wrt_app/addon/browser/modules/messaging.ts [new file with mode: 0644]
wrt_app/addon/browser/modules/window.js [deleted file]
wrt_app/addon/browser/modules/window.ts [new file with mode: 0644]
wrt_app/addon/common/addonapi.ts [moved from wrt_app/addon/common/addonapi.js with 60% similarity]
wrt_app/addon/common/module-list.ts [moved from wrt_app/addon/common/module-list.js with 85% similarity]
wrt_app/addon/common/modules/options.ts [moved from wrt_app/addon/common/modules/options.js with 77% similarity]
wrt_app/addon/common/utils.ts [moved from wrt_app/addon/common/utils.js with 52% similarity]
wrt_app/addon/renderer/addonapi.ts [moved from wrt_app/addon/renderer/addonapi.js with 70% similarity]
wrt_app/addon/renderer/module-list.ts [moved from wrt_app/addon/renderer/module-list.js with 86% similarity]
wrt_app/addon/renderer/modules/messaging.js [deleted file]
wrt_app/addon/renderer/modules/messaging.ts [new file with mode: 0644]
wrt_app/browser/init.ts [moved from wrt_app/browser/init.js with 86% similarity]
wrt_app/browser/wrt.ts [moved from wrt_app/browser/wrt.js with 70% similarity]
wrt_app/browser/wrt_web_contents.ts [moved from wrt_app/browser/wrt_web_contents.js with 65% similarity]
wrt_app/browser/wrt_window.ts [moved from wrt_app/browser/wrt_window.js with 50% similarity]
wrt_app/common/config-search-paths.js [deleted file]
wrt_app/common/config-search-paths.ts [new file with mode: 0644]
wrt_app/common/exception_handling.ts [moved from wrt_app/common/exception_handling.js with 100% similarity]
wrt_app/common/init.js [deleted file]
wrt_app/common/init.ts [new file with mode: 0644]
wrt_app/common/service_manager.ts [moved from wrt_app/common/service_manager.js with 66% similarity]
wrt_app/common/wrt_xwalk_extension.ts [moved from wrt_app/common/wrt_xwalk_extension.js with 75% similarity]
wrt_app/package.json
wrt_app/renderer/init.js [deleted file]
wrt_app/renderer/init.ts [moved from wrt_app/src/main.js with 79% similarity]
wrt_app/renderer/wrt_renderer.js [deleted file]
wrt_app/renderer/wrt_renderer.ts [new file with mode: 0644]
wrt_app/service/access_control_manager.ts [moved from wrt_app/service/access_control_manager.js with 95% similarity]
wrt_app/service/builtins/wasm_builder.js [deleted file]
wrt_app/service/builtins/wasm_builder.ts [new file with mode: 0644]
wrt_app/service/main.ts [moved from wrt_app/service/main.js with 93% similarity]
wrt_app/service/timer_manager.js [deleted file]
wrt_app/service/timer_manager.ts [new file with mode: 0644]
wrt_app/src/addon_manager.js [deleted file]
wrt_app/src/addon_manager.ts [new file with mode: 0644]
wrt_app/src/ipc_message.js [deleted file]
wrt_app/src/runtime.js [deleted file]
wrt_app/src/runtime.ts [new file with mode: 0755]
wrt_app/src/web_application.js [deleted file]
wrt_app/src/web_application.ts [new file with mode: 0644]
wrt_app/worker/init.ts [moved from wrt_app/worker/init.js with 100% similarity]
wrtjs.d.ts [new file with mode: 0644]

index 5188e93..e6ce948 100644 (file)
@@ -1 +1,5 @@
 /tizen/build/gbs.conf
+out/
+*list
+wrtjs*manifest
+
index 6e3d0b2..7d748d4 100755 (executable)
@@ -7,8 +7,11 @@ License:    Apache-2.0
 URL:        https://www.tizen.org
 Source:     %{name}-%{version}.tar.gz
 
+%define app_dir out/gen/app
 %define crosswalk_extensions_service tizen-extensions-crosswalk-service
 
+BuildRequires: pkgconfig(chromium-efl)
+
 %if "%{?tizen_profile_name}" != "tv"
 BuildRequires: ninja
 BuildRequires: pkgconfig(dlog)
@@ -27,6 +30,10 @@ Web Runtime Engine based on Electron
   ninja -C %{_outdir} wrt-loader
 %endif
 
+./build/tools/node ./node_modules/.bin/tsc
+absolute_appdir=$PWD/%{app_dir}
+(cd wrt_app/ && find . -type f ! -name '*.ts' -exec cp --parents {} ${absolute_appdir} \;)
+
 %install
 %define _resourcedir /usr/share/wrt/app
 install -d %{buildroot}%{_bindir}
@@ -50,7 +57,7 @@ install -d %{buildroot}%{_resourcedir}
 
 mkdir -p %{buildroot}%{_libdir}/%{crosswalk_extensions_service}
 cp packaging/plugins.json %{buildroot}%{_libdir}/%{crosswalk_extensions_service}
-cp -r wrt_app/* %{buildroot}%{_resourcedir}/
+cp -r %{app_dir}/* %{buildroot}%{_resourcedir}/
 
 %post
 
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644 (file)
index 0000000..50deefa
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "compilerOptions": {
+    "module": "commonjs",
+    "target": "es2017",
+    "outDir": "out/gen/app",
+    "allowJs": true,
+    "strict": true,
+  },
+  "include": [
+    "/usr/include/wrt/*.d.ts",
+    "wrt_app/**/*",
+    "wrtjs.d.ts"
+  ]
+}
similarity index 70%
rename from wrt_app/addon/browser/addonapi.js
rename to wrt_app/addon/browser/addonapi.ts
index ea6340b..73512ed 100644 (file)
@@ -1,10 +1,9 @@
 'use strict';
 
-const common = require('../common/addonapi');
-common.defineProperties(exports);
-
-const moduleList = require('./module-list');
+import * as common from '../common/addonapi';
+import moduleList from './module-list';
 
+common.defineProperties(exports);
 for (const module of moduleList) {
   Object.defineProperty(exports, module.name, {
     get: common.memoizedGetter(() => require(`./modules/${module.file}`))
similarity index 88%
rename from wrt_app/addon/browser/module-list.js
rename to wrt_app/addon/browser/module-list.ts
index 63cf2c8..0b49a9b 100644 (file)
@@ -1,7 +1,7 @@
 'use strict'
 
 // Browser side add-on modules, please sort alphabetically
-module.exports = [
+export default [
   { name: 'messaging', file: 'messaging' },
   { name: 'window', file: 'window' }
-]
+];
diff --git a/wrt_app/addon/browser/modules/messaging.js b/wrt_app/addon/browser/modules/messaging.js
deleted file mode 100644 (file)
index 26511c7..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-'use strict';
-
-const { ipcMain } = require('electron');
-
-module.exports = {
-  on: function (channel, listener) {
-    ipcMain.on(channel, listener);
-  }
-};
\ No newline at end of file
diff --git a/wrt_app/addon/browser/modules/messaging.ts b/wrt_app/addon/browser/modules/messaging.ts
new file mode 100644 (file)
index 0000000..779480c
--- /dev/null
@@ -0,0 +1,7 @@
+'use strict';
+
+import { ipcMain } from 'electron';
+
+export const on = function (channel: string, listener: (event: Electron.IpcMainEvent, ...args: any[]) => void) {
+  ipcMain.on(channel, listener);
+};
diff --git a/wrt_app/addon/browser/modules/window.js b/wrt_app/addon/browser/modules/window.js
deleted file mode 100644 (file)
index 44e779d..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-'use strict';
-
-const { dialog, TopLevelWindow } = require('electron');
-
-module.exports = {
-  loadURL: function (winId, url) {
-    let window = TopLevelWindow.fromId(winId);
-    window.loadURL(url);
-  },
-
-  showMessageBox: function (winId, options) {
-    let window = TopLevelWindow.fromId(winId);
-    let showMessageBox = (dialog.showMessageBoxSync || dialog.showMessageBox);
-    showMessageBox(window, options);
-  },
-
-  show: function (winId) {
-    let window = TopLevelWindow.fromId(winId);
-    window.show();
-  }
-}
diff --git a/wrt_app/addon/browser/modules/window.ts b/wrt_app/addon/browser/modules/window.ts
new file mode 100644 (file)
index 0000000..6cbd37c
--- /dev/null
@@ -0,0 +1,21 @@
+'use strict';
+
+import { dialog, BrowserWindow } from 'electron';
+
+export default {
+  loadURL: function (winId: number, url: string) {
+    let window = BrowserWindow.fromId(winId);
+    window.loadURL(url);
+  },
+
+  showMessageBox: function (winId: number, options: Electron.MessageBoxOptions) {
+    let window = BrowserWindow.fromId(winId);
+    let showMessageBox = (dialog.showMessageBoxSync || dialog.showMessageBox);
+    showMessageBox(window, options);
+  },
+
+  show: function (winId: number) {
+    let window = BrowserWindow.fromId(winId);
+    window.show();
+  }
+};
similarity index 60%
rename from wrt_app/addon/common/addonapi.js
rename to wrt_app/addon/common/addonapi.ts
index 96a7c8c..b9b11c5 100644 (file)
@@ -1,9 +1,9 @@
 'use strict';
 
-const moduleList = require('./module-list');
+import moduleList from './module-list';
 
-exports.memoizedGetter = (getter) => {
-  let memoizedValue = null;
+export let memoizedGetter = (getter: () => any) => {
+  let memoizedValue: any = null;
 
   return () => {
     if (memoizedValue === null) {
@@ -13,8 +13,8 @@ exports.memoizedGetter = (getter) => {
   }
 }
 
-exports.defineProperties = function (targetExports) {
-  const descriptors = {};
+export let defineProperties = function (targetExports: Object) {
+  const descriptors: PropertyDescriptorMap = {};
   for (const module of moduleList) {
     descriptors[module.name] = {
       get: exports.memoizedGetter(() => require(`./modules/${module.file}`))
similarity index 85%
rename from wrt_app/addon/common/module-list.js
rename to wrt_app/addon/common/module-list.ts
index fc16071..4aebfcb 100644 (file)
@@ -1,6 +1,6 @@
 'use strict';
 
 // Common add-on modules, please sort alphabetically
-module.exports = [
+export default [
   { name: 'options', file: 'options' }
 ];
similarity index 77%
rename from wrt_app/addon/common/modules/options.js
rename to wrt_app/addon/common/modules/options.ts
index f6304d9..11bd964 100644 (file)
@@ -1,12 +1,14 @@
 'use strict';
 
-const fs = require('fs');
-const utils = require('../utils');
+import * as fs from 'fs';
+import * as path from 'path';
+import * as os from 'os';
+import utils from '../utils';
 
-const REPO_PATH = require('path').join(require('os').homedir(), 'data/electron/runtime_addon/');
+const REPO_PATH = path.join(os.homedir(), 'data/electron/runtime_addon/');
 const DB_FILE = '_db.json';
 
-function getDBPath() {
+function getDBPath(): string {
   const addon_name = utils.getPackageID();
   if (addon_name) {
     const DB_PATH = REPO_PATH + addon_name + DB_FILE;
@@ -18,12 +20,12 @@ function getDBPath() {
   }
 }
 
-function get(key) {
+export function get(key: string) {
   const DB_PATH = getDBPath();
   if (!DB_PATH)
     return undefined;
   try {
-    const fileContents = fs.readFileSync(DB_PATH);
+    const fileContents = fs.readFileSync(DB_PATH, 'utf-8');
     const jsonObject = JSON.parse(fileContents);
     console.log(`Read value: ${JSON.stringify(jsonObject)}`);
     return jsonObject[key];
@@ -33,14 +35,14 @@ function get(key) {
   }
 }
 
-function set(key, value) {
+export function set(key: string, value: any) {
   const DB_PATH = getDBPath();
   if (!DB_PATH)
     return false;
   try {
     let jsonObject;
     try {
-      const fileContents = fs.readFileSync(DB_PATH);
+      const fileContents = fs.readFileSync(DB_PATH, 'utf-8');
       jsonObject = JSON.parse(fileContents);
       console.log(`Current value: ${JSON.stringify(jsonObject)}`);
     } catch {
@@ -58,7 +60,7 @@ function set(key, value) {
   }
 }
 
-function clearAll() {
+export function clearAll() {
   const DB_PATH = getDBPath();
   if (!DB_PATH)
     return false;
@@ -76,9 +78,3 @@ function clearAll() {
   }
   return true;
 }
-
-module.exports = {
-  clearAll,
-  get,
-  set
-}
\ No newline at end of file
similarity index 52%
rename from wrt_app/addon/common/utils.js
rename to wrt_app/addon/common/utils.ts
index d8f1c40..3ed10ce 100644 (file)
@@ -1,20 +1,20 @@
 const keyTerm = 'globalapps';
 const delimiter = '/';
 
-function extractID(line) {
+function extractID(line: string) {
   let keyTermAt = line.indexOf(keyTerm);
   let delimiterAt = line.indexOf(delimiter, keyTermAt);
   return line.substr(delimiterAt + 1, 10);
 }
 
-module.exports = {
+export default {
   getPackageID() {
     let stack = new Error().stack;
-    stack = stack.split('\n');
-    for (let i = 0; i < stack.length; ++i) {
-      if (stack[i].indexOf(keyTerm) !== -1)
-        return extractID(stack[i]);
+    let stack_list = stack ? stack.split('\n') : [];
+    for (let i = 0; i < stack_list.length; ++i) {
+      if (stack_list[i].indexOf(keyTerm) !== -1)
+        return extractID(stack_list[i]);
     }
     return '';
   }
-}
\ No newline at end of file
+};
similarity index 70%
rename from wrt_app/addon/renderer/addonapi.js
rename to wrt_app/addon/renderer/addonapi.ts
index ea6340b..73512ed 100644 (file)
@@ -1,10 +1,9 @@
 'use strict';
 
-const common = require('../common/addonapi');
-common.defineProperties(exports);
-
-const moduleList = require('./module-list');
+import * as common from '../common/addonapi';
+import moduleList from './module-list';
 
+common.defineProperties(exports);
 for (const module of moduleList) {
   Object.defineProperty(exports, module.name, {
     get: common.memoizedGetter(() => require(`./modules/${module.file}`))
similarity index 86%
rename from wrt_app/addon/renderer/module-list.js
rename to wrt_app/addon/renderer/module-list.ts
index c65f538..569fdf2 100644 (file)
@@ -1,6 +1,6 @@
 'use strict';
 
 // Renderer side add-on modules, please sort alphabetically
-module.exports = [
+export default [
   { name: 'messaging', file: 'messaging' }
 ];
diff --git a/wrt_app/addon/renderer/modules/messaging.js b/wrt_app/addon/renderer/modules/messaging.js
deleted file mode 100644 (file)
index 40fe684..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-'use strict';
-
-const { ipcRenderer } = require('electron');
-
-module.exports = {
-  on: function (channel, listener) {
-    ipcRenderer.on(channel, listener);
-  },
-
-  send: function (...args) {
-    ipcRenderer.send(args);
-  }
-}
\ No newline at end of file
diff --git a/wrt_app/addon/renderer/modules/messaging.ts b/wrt_app/addon/renderer/modules/messaging.ts
new file mode 100644 (file)
index 0000000..8e31b0a
--- /dev/null
@@ -0,0 +1,11 @@
+'use strict';
+
+import { ipcRenderer } from 'electron';
+
+export const on = function (channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) {
+  ipcRenderer.on(channel, listener);
+};
+
+export const send = function (channel: string, ...args: any[]) {
+  ipcRenderer.send(channel, args);
+};
similarity index 86%
rename from wrt_app/browser/init.js
rename to wrt_app/browser/init.ts
index 9fccd6f..c3b9a54 100755 (executable)
@@ -14,6 +14,6 @@
  *    limitations under the License.
  */
 
-require('../common/init')
-const wrt = require('./wrt')
-require(wrt.getElectronPath() + '/browser/init')
+import '../common/init';
+import { wrt } from './wrt';
+import (wrt.getElectronPath() + '/browser/init');
similarity index 70%
rename from wrt_app/browser/wrt.js
rename to wrt_app/browser/wrt.ts
index 288cb5b..180dcfe 100755 (executable)
  *    See the License for the specific language governing permissions and
  *    limitations under the License.
  */
-require('../common/exception_handling');
-const {wrt} = process.wrtBinding('wrt');
-const {EventEmitter} = require('events');
-const util = require('util');
+import { EventEmitter } from 'events';
+import * as util from 'util';
 
+export const { wrt }: NativeWRTjs.WRTBinding = process.wrtBinding('wrt');
 Object.setPrototypeOf(Object.getPrototypeOf(wrt), EventEmitter.prototype);
 
-module.exports = wrt;
-
-console.log = console.info = console.error = console.warn = function(...args) {
-  wrt.log(util.format(...args));
+console.log = console.info = console.error = console.warn = function(format: any, ...param: any[]) {
+  wrt.log(util.format(format, ...param));
 };
 console.logd = console.logv = console.loge = console.log;
 
-process.stdout.write = process.stderr.write = function (chunk, encoding, callback) {
+function write(chunk: Uint8Array | string, encoding?: any, callback?: (err?: Error) => void): boolean {
   if (Buffer.isBuffer(chunk)) {
     chunk = chunk.toString(encoding);
   }
-  wrt.log(chunk);
+  wrt.log(chunk as string);
   if (callback) {
     callback();
   }
   return true;
 }
+process.stdout.write = process.stderr.write = write;
similarity index 65%
rename from wrt_app/browser/wrt_web_contents.js
rename to wrt_app/browser/wrt_web_contents.ts
index 5c2535a..0b35873 100644 (file)
  *    limitations under the License.
  */
 
-const binding = process.wrtBinding('wrt_web_contents')
-const { WRTWebContents } = binding
-const { WebContents: AtomWebContents } = process.wrtBinding('atom_browser_web_contents')
+const binding: NativeWRTjs.WRTWebContentsBinding = process.wrtBinding('wrt_web_contents');
+const { WRTWebContents } = binding;
+const { WebContents: AtomWebContents } = process.wrtBinding('atom_browser_web_contents');
 
-const parent = AtomWebContents.prototype
-AtomWebContents.prototype = WRTWebContents.prototype
+const parent = AtomWebContents.prototype;
+AtomWebContents.prototype = WRTWebContents.prototype;
 
-Object.setPrototypeOf(WRTWebContents.prototype, parent)
+Object.setPrototypeOf(WRTWebContents.prototype, parent);
 
 WRTWebContents.prototype._init = function () {
-  parent._init.call(this)
+  parent._init.call(this);
 }
 
-module.exports = {
-  create (options = {}) {
-    return binding.create(options)
-  }
+export const create = (options = {}) => {
+  return binding.create(options);
 }
similarity index 50%
rename from wrt_app/browser/wrt_window.js
rename to wrt_app/browser/wrt_window.ts
index 004f26d..41c1690 100644 (file)
  *    limitations under the License.
  */
 
-const { WRTWindow } = process.wrtBinding('wrt_window')
-const { BrowserWindow } = require('electron')
-const WRTWebContents = require('../browser/wrt_web_contents');
+import { BrowserWindow } from 'electron';
+import * as WRTWebContents from '../browser/wrt_web_contents';
 
-Object.setPrototypeOf(WRTWindow.prototype, BrowserWindow.prototype)
+export const { WRTWindow } = process.wrtBinding('wrt_window');
+
+Object.setPrototypeOf(WRTWindow.prototype, BrowserWindow.prototype);
 
 WRTWindow.prototype._init = function () {
-  BrowserWindow.prototype._init.call(this)
+  (BrowserWindow.prototype as any)._init.call(this);
   if (typeof this.setup === 'function')
-    this.setup()
-  this.constructor = BrowserWindow
-  let self = this
-  this.webContents.on('new-window', (event, url, frameName, disposition, options) => {
-    event.preventDefault()
+    this.setup();
+  this.constructor = BrowserWindow;
+  let self = this;
+  this.webContents.on('new-window', (event: Electron.NewWindowWebContentsEvent,
+                                     url: string,
+                                     frameName: string,
+                                     disposition: ('default' | 'foreground-tab' | 'background-tab' | 'new-window' | 'save-to-disk' | 'other'),
+                                     options: any) => {
+    event.preventDefault();
     if (!options.webContents || options.webContents === self) {
-      options.webContents = WRTWebContents()
-      options.webContents.loadURL(url)
+      options.webContents = WRTWebContents.create();
+      options.webContents.loadURL(url);
     }
-    event.newGuest = new WRTWindow(options)
-  })
+    event.newGuest = new WRTWindow(options);
+  });
 }
-
-module.exports = WRTWindow
diff --git a/wrt_app/common/config-search-paths.js b/wrt_app/common/config-search-paths.js
deleted file mode 100644 (file)
index 8f6f739..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-'use strict';
-
-const Module = require('module');
-const originalResolveFilename = Module._resolveFilename;
-const ADDONS_PATH = require('path').join(__dirname, '..', 'addon', process.type, 'addonapi.js');
-
-Module._resolveFilename = function (request, parent, isMain) {
-    if (request === 'addonapi') {
-        return ADDONS_PATH;
-    } else {
-        return originalResolveFilename(request, parent, isMain);
-    }
-}
\ No newline at end of file
diff --git a/wrt_app/common/config-search-paths.ts b/wrt_app/common/config-search-paths.ts
new file mode 100644 (file)
index 0000000..080fae8
--- /dev/null
@@ -0,0 +1,12 @@
+'use strict';
+
+const Module = require('module');
+import * as path from 'path';
+
+const originalResolveFilename = Module._resolveFilename;
+Module._resolveFilename = function (request: string, parent: NodeModule, isMain: boolean) {
+  if (request === 'addonapi' && !process.type)
+    return path.join(__dirname, '..', 'addon', process.type as string, 'addonapi.js');
+  else
+    return originalResolveFilename(request, parent, isMain);
+}
\ No newline at end of file
diff --git a/wrt_app/common/init.js b/wrt_app/common/init.js
deleted file mode 100644 (file)
index b407422..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-process.wrtBinding = (name) => {
-  try {
-    return process._linkedBinding(name)
-  } catch (error) {
-    if (/No such module/.test(error.message)) {
-      return process.binding(name)
-    } else {
-      throw error
-    }
-  }
-}
diff --git a/wrt_app/common/init.ts b/wrt_app/common/init.ts
new file mode 100644 (file)
index 0000000..570eb2a
--- /dev/null
@@ -0,0 +1,14 @@
+import '../common/exception_handling';
+import '../common/config-search-paths';
+
+process.wrtBinding = (name) => {
+  try {
+    return process._linkedBinding(name);
+  } catch (error) {
+    if (/No such module/.test(error.message)) {
+      return process.binding(name);
+    } else {
+      throw error;
+    }
+  }
+}
similarity index 66%
rename from wrt_app/common/service_manager.js
rename to wrt_app/common/service_manager.ts
index 3e77f26..65c2e97 100644 (file)
@@ -1,18 +1,26 @@
 const Module = require('module');
-const TimerManager = require('../service/timer_manager');
-const XWalkExtension = require('./wrt_xwalk_extension');
-const vm = require('vm');
-const wrt = require('../browser/wrt');
+import { TimerManager } from '../service/timer_manager';
+import * as XWalkExtension from './wrt_xwalk_extension';
+import * as vm from 'vm';
+import { wrt } from '../browser/wrt';
 
-let sandbox = {};
-let is_global_service = !!wrt.getStartServiceFile;
+interface ContextMap {
+  [id: string]: vm.Context;
+}
+
+interface ContextOption {
+  [key: string]: any;
+}
 
-function callFunctionInContext(name, sandbox) {
+let sandbox: ContextMap = {};
+let is_global_service: boolean = !!wrt.getStartServiceFile;
+
+function callFunctionInContext(name: string, sandbox: vm.Context) {
   const script = `if (typeof ${name} === 'function') { ${name}(); }`;
   vm.runInContext(script, sandbox);
 }
 
-function startService(id, filename) {
+export function startService(id: string, filename?: string) {
   if (sandbox[id] === undefined) {
     XWalkExtension.initialize();
     XWalkExtension.setRuntimeMessageHandler((type, data) => {
@@ -23,20 +31,20 @@ function startService(id, filename) {
       console: console,
       module: new Module,
       require: require,
-      tizen: tizen,
+      tizen: global.tizen,
     };
     sandbox[id].module.exports.onStop = () => {
       callFunctionInContext('module.exports.onExit', sandbox[id]);
     };
     if (wrt.tv) {
-      sandbox[id].webapis = webapis;
+      sandbox[id].webapis = global.webapis;
       sandbox[id].webapis.getServiceId = () => {
         let service_id = id.split(':')[0];
         return service_id;
       }
       sandbox[id].webapis.getPackageId = () => {
         let service_id = id.split(':')[0];
-        let app_info = tizen.application.getAppInfo(service_id);
+        let app_info = global.tizen.application.getAppInfo(service_id);
         if (app_info)
           return app_info.packageId;
         return '';
@@ -57,27 +65,24 @@ function startService(id, filename) {
     for (let key in timer_api)
       sandbox[id][key] = timer_api[key];
 
-    let standard_object_list = [ Error, EvalError, RangeError, ReferenceError,
-        SyntaxError, TypeError, URIError, Number, BigInt, Math, Date,
-        String, RegExp, Array, Int8Array, Uint8Array, Uint8ClampedArray,
-        Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array,
-        Float64Array, BigInt64Array, BigUint64Array, Map, Set, WeakMap,
-        WeakSet, ArrayBuffer, DataView, JSON, Promise, Reflect, Proxy,
-        Intl, Intl.Collator, Intl.DateTimeFormat, Intl.NumberFormat, Intl.PluralRules,
-        WebAssembly, WebAssembly.Module, WebAssembly.Instance, WebAssembly.Memory,
-        WebAssembly.Table, WebAssembly.CompileError, WebAssembly.LinkError,
-        WebAssembly.RuntimeError, Boolean, Function, Object, Symbol ];
-    for (let idx in standard_object_list)
-      sandbox[id][standard_object_list[idx].name] = standard_object_list[idx];
-
-    let options = {};
+    let object_list = [ 'Error', 'EvalError', 'RangeError', 'ReferenceError',
+        'SyntaxError', 'TypeError', 'URIError', 'Number', 'BigInt', 'Math', 'Date',
+        'String', 'RegExp', 'Array', 'Int8Array', 'Uint8Array', 'Uint8ClampedArray',
+        'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array', 'Float32Array',
+        'Float64Array', 'BigInt64Array', 'BigUint64Array', 'Map', 'Set', 'WeakMap',
+        'WeakSet', 'ArrayBuffer', 'DataView', 'JSON', 'Promise', 'Reflect', 'Proxy',
+        'Intl', 'WebAssembly', 'Boolean', 'Function', 'Object', 'Symbol' ];
+    for (let prop of object_list)
+      sandbox[id][prop] = global[prop];
+
+    let options: ContextOption = {};
     let code;
     if (is_global_service) {
       options.filename = id;
       if (wrt.tv) {
-        let extension_resolver = function (module, file_path) {
+        let extension_resolver = function (module: any, file_path: string) {
           console.log(`resolved path: ${file_path}`);
-          let content = wrt.tv.decryptFile(id, file_path);
+          let content = (wrt.tv as NativeWRTjs.TVExtension).decryptFile(id, file_path);
           if (content) {
             // Remove BOM
             if (content.charCodeAt(0)  === 0xFEFF)
@@ -111,7 +116,7 @@ function startService(id, filename) {
   callFunctionInContext('app.onRequest', sandbox[id]);
 }
 
-function stopService(id) {
+export function stopService(id: string) {
   console.log('stopService')
   if (sandbox[id]['stopped']) {
     console.log(id + ' service has been already stopped.');
@@ -130,8 +135,3 @@ function stopService(id) {
   if (Object.keys(sandbox).length === 0)
     XWalkExtension.cleanup();
 }
-
-module.exports = {
-  startService,
-  stopService
-};
similarity index 75%
rename from wrt_app/common/wrt_xwalk_extension.js
rename to wrt_app/common/wrt_xwalk_extension.ts
index 5e58a18..ea8ad96 100644 (file)
  *    limitations under the License.
  */
 
-require('./exception_handling');
+import './exception_handling';
 
-let instance;
-let api_ = {};
-let extensions_ = {};
+interface NativeXWalkExtension extends NativeWRTjs.XWalkExtension {
+  loaded?: boolean
+}
+
+let instance: XWalkExtension | undefined;
+let api_: { [key: string]: any } = {};
+let extensions_: { [key: string]: NativeXWalkExtension } = {};
 
 class XWalkExtension {
   constructor() {
-    const binding = process.wrtBinding('wrt_xwalk_extension')
-    var extensions = binding.getExtensions();
+    const binding: NativeWRTjs.XWalkExtensionBinding = process.wrtBinding('wrt_xwalk_extension')
+    var extensions: NativeXWalkExtension[] = binding.getExtensions();
     for (var i = 0; i < extensions.length; i++) {
       extensions[i].loaded = false;
       console.log("Load extension : " + extensions[i].name);
@@ -49,7 +53,7 @@ class XWalkExtension {
    * @param {Object} object
    * @param {String} name
    */
-  createNamespace(object, name) {
+  createNamespace(object: { [key: string]: any }, name: string) {
     var obj = object;
     var arr = name.split('.');
     for (var i = 0; i < arr.length; i++) {
@@ -58,7 +62,7 @@ class XWalkExtension {
     }
   }
 
-  exposeApi(ext) {
+  exposeApi(ext: NativeXWalkExtension) {
     var i, entry_points, entry_point, tmp, parent_name, base_name;
 
     // additional entry points are installed in global context by eval()
@@ -94,14 +98,14 @@ class XWalkExtension {
     }
   }
 
-  runtimeMessageHandler(type, data, callback) {
+  static runtimeMessageHandler(type: string, data?: string, callback?: (message: string) => void): void {
     console.log('This is prototype of runtimeMessageHandler');
   }
 
   /**
    * @param {Object} ext
    */
-  load(ext) {
+  load(ext: NativeXWalkExtension) {
     if (ext.loaded)
       return;
 
@@ -126,35 +130,23 @@ class XWalkExtension {
     try {
       var func = eval(jscode);
       func({
-        postMessage: function(msg) {
+        postMessage: function(msg: string) {
           return ext.postMessage(msg);
         },
-        sendSyncMessage: function(msg) {
+        sendSyncMessage: function(msg: string) {
           return ext.sendSyncMessage(msg);
         },
-        setMessageListener: function(fn) {
+        setMessageListener: function(fn: (message: string) => void) {
           return ext.setMessageListener(fn);
         },
-        sendRuntimeMessage: function(type, data) {
+        sendRuntimeMessage: function(type: string, data?: string) {
           return XWalkExtension.runtimeMessageHandler(type, data);
         },
-        sendRuntimeSyncMessage: function(type, data) {
+        sendRuntimeSyncMessage: function(type: string, data?: string) {
           return XWalkExtension.runtimeMessageHandler(type, data);
         },
-        sendRuntimeAsyncMessage: function(type, data, callback) {
+        sendRuntimeAsyncMessage: function(type: string, data?: string, callback?: (message: string) => void) {
           return XWalkExtension.runtimeMessageHandler(type, data, callback);
-        },
-        postData: function(msg, chunk) {
-          return ext.postData(msg, chunk);
-        },
-        sendSyncData: function(msg, chunk) {
-          return ext.sendSyncData(msg, chunk);
-        },
-        setDataListener: function(fn) {
-          return ext.setDataListener(fn);
-        },
-        receiveChunkData: function(id, type) {
-          return ext.receiveChunkData(id, type);
         }
       });
 
@@ -172,7 +164,7 @@ class XWalkExtension {
    *
    * @param {Object} ext
    */
-  installTrampoline(ext) {
+  installTrampoline(ext: NativeXWalkExtension) {
     var entry_points = [...new Set(ext.entry_points)];
     entry_points.push(ext.name);
     for (var i = 0; i < entry_points.length; i++) {
@@ -183,8 +175,8 @@ class XWalkExtension {
       this.createNamespace(global, entry_points[i]);
 
       Object.defineProperty(global[parent_name], base_name, {
-        get: function (parent_name, base_name) {
-          return function() {
+        get: function (this: XWalkExtension, parent_name: string, base_name: string) {
+          return function(this: XWalkExtension) {
             try {
               this.deleteTrampoline(ext);
               this.load(ext);
@@ -199,7 +191,7 @@ class XWalkExtension {
     }
   }
 
-  deleteTrampoline(ext) {
+  deleteTrampoline(ext: NativeXWalkExtension) {
     var entry_points = [...new Set(ext.entry_points)];
     entry_points.push(ext.name);
 
@@ -212,21 +204,20 @@ class XWalkExtension {
   }
 }
 
-const extension_api = {
-  initialize: () => {
-    if (!instance)
-      instance = new XWalkExtension;
-  },
-  setRuntimeMessageHandler: (handler) => {
-    XWalkExtension.runtimeMessageHandler = handler;
-  },
-  cleanup: () => {
-    delete global.tizen;
-    instance = undefined;
-  },
-  preventCleanup: () => {
-    extension_api.cleanup = () => {};
-  }
+export const initialize = () => {
+  if (!instance)
+    instance = new XWalkExtension;
+}
+
+export const setRuntimeMessageHandler = (handler: (type: string, data?: string, callback?: (message: string) => void) => void) => {
+  XWalkExtension.runtimeMessageHandler = handler;
 }
 
-module.exports = extension_api;
+export let cleanup = () => {
+  delete global.tizen;
+  instance = undefined;
+}
+
+export const preventCleanup = () => {
+  cleanup = () => {};
+}
index 0858c86..e7c2d18 100755 (executable)
@@ -1,5 +1,5 @@
 {
   "name": "wrtjs",
   "version": "1.0.0",
-  "main": "src/main.js"
+  "main": "src/runtime.js"
 }
diff --git a/wrt_app/renderer/init.js b/wrt_app/renderer/init.js
deleted file mode 100755 (executable)
index e207bcd..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
- *
- *    Licensed under the Apache License, Version 2.0 (the "License");
- *    you may not use this file except in compliance with the License.
- *    You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS,
- *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *    See the License for the specific language governing permissions and
- *    limitations under the License.
- */
-
-require('../common/init')
-require('../common/exception_handling');
-require('../common/config-search-paths');
-const wrtRenderer = require('./wrt_renderer');
-require(wrtRenderer.getElectronPath() + '/renderer/init')
similarity index 79%
rename from wrt_app/src/main.js
rename to wrt_app/renderer/init.ts
index e334120..f53c5d1 100755 (executable)
  *    limitations under the License.
  */
 
-'use strict';
-
-require('../common/exception_handling');
-require('../common/config-search-paths');
-
-new (require('./runtime'))({
-  devMode: false,
-  noAddons: false
-});
+import '../common/init';
+import { wrtRenderer } from './wrt_renderer';
+import (wrtRenderer.getElectronPath() + '/renderer/init');
diff --git a/wrt_app/renderer/wrt_renderer.js b/wrt_app/renderer/wrt_renderer.js
deleted file mode 100644 (file)
index c1b183a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-const {wrtRenderer} = process.wrtBinding('wrt_renderer');
-
-module.exports = wrtRenderer
diff --git a/wrt_app/renderer/wrt_renderer.ts b/wrt_app/renderer/wrt_renderer.ts
new file mode 100644 (file)
index 0000000..c846853
--- /dev/null
@@ -0,0 +1 @@
+export const { wrtRenderer }: NativeWRTjs.WRTRendererBinding = process.wrtBinding('wrt_renderer');
similarity index 95%
rename from wrt_app/service/access_control_manager.js
rename to wrt_app/service/access_control_manager.ts
index b0270b5..6a55946 100644 (file)
@@ -1,4 +1,6 @@
-function initialize(permissions, sandbox) {
+import * as vm from 'vm';
+
+export function initialize(permissions: string[], sandbox: vm.Context) {
   let tizen = sandbox.tizen;
   if (!permissions.includes("http://tizen.org/privilege/alarm")) {
     tizen.alarm.add =
@@ -105,15 +107,11 @@ function initialize(permissions, sandbox) {
   }
   // systeminfo : Runtime privilege validation is required, based on parameters
   let getPropertyValue = tizen.systeminfo.getPropertyValue;
-  tizen.systeminfo.getPropertyValue = function(type, onSuccessCallback, onErrorCallback) {
+  tizen.systeminfo.getPropertyValue = (type: string, onSuccessCallback: any, onErrorCallback: any) => {
     if (type === "CELLULAR_NETWORK" && !permissions.includes("http://tizen.org/privilege/telephony")) {
       console.log('The telephony permission is missing.');
       return;
     }
     getPropertyValue.apply(tizen.systeminfo, arguments);
-  }.bind(this);
-}
-
-module.exports = {
-  initialize
+  };
 }
diff --git a/wrt_app/service/builtins/wasm_builder.js b/wrt_app/service/builtins/wasm_builder.js
deleted file mode 100644 (file)
index 9cadd6b..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-const fs = require('fs');
-const wrt = require('../../browser/wrt');
-
-async function compileWasmForCaching(file_path) {
-    console.log(`Requesting WASM compilation for building a cache, file_path:(${file_path})`);
-    try {
-        let source = fs.readFileSync(file_path);
-        let file = new Uint8Array(source);
-        await WebAssembly.compile(file);
-    } catch (e) {
-        console.error(`An error occurred while compiling a wasm module. error:(${e})`);
-    }
-}
-
-function run(app_id) {
-    console.log(`wasm_builder.js starts, app_id:(${app_id})`);
-    wrt.tv.setWasmFlags();
-    wrt.tv.setDiskCache(app_id);
-    let files = wrt.tv.getWasmFiles(app_id);
-    console.log(files);
-    files.forEach((file_path) => {
-        compileWasmForCaching(file_path);
-    });
-}
-
-module.exports = {
-    run
-}
diff --git a/wrt_app/service/builtins/wasm_builder.ts b/wrt_app/service/builtins/wasm_builder.ts
new file mode 100644 (file)
index 0000000..2403ee3
--- /dev/null
@@ -0,0 +1,25 @@
+import * as fs from 'fs';
+import { wrt } from '../../browser/wrt';
+
+async function compileWasmForCaching(file_path: string) {
+  console.log(`Requesting WASM compilation for building a cache, file_path:(${file_path})`);
+  try {
+    let source = fs.readFileSync(file_path);
+    let file = new Uint8Array(source);
+    await WebAssembly.compile(file);
+  } catch (e) {
+    console.error(`An error occurred while compiling a wasm module. error:(${e})`);
+  }
+}
+
+export function run(app_id: string) {
+  console.log(`wasm_builder.js starts, app_id:(${app_id})`);
+  let tv = wrt.tv as NativeWRTjs.TVExtension;
+  tv.setWasmFlags();
+  tv.setDiskCache(app_id);
+  let files = tv.getWasmFiles(app_id);
+  console.log(files);
+  files.forEach((file_path) => {
+    compileWasmForCaching(file_path);
+  });
+}
similarity index 93%
rename from wrt_app/service/main.js
rename to wrt_app/service/main.ts
index 03d86b7..7d3ff1a 100755 (executable)
@@ -16,9 +16,9 @@
 
 'use strict';
 
-require('../common/init')
-const wrt = require('../browser/wrt');
-const ServiceManager = require('../common/service_manager');
+import '../common/init';
+import { wrt } from '../browser/wrt';
+import * as ServiceManager from '../common/service_manager';
 
 wrt.on('start-service', (event, internal_id) => {
   console.log('start service app : ' + internal_id);
diff --git a/wrt_app/service/timer_manager.js b/wrt_app/service/timer_manager.js
deleted file mode 100644 (file)
index e17cd68..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-class TimerManager {
-  constructor() {
-    this.interval_handlers = [];
-    this.timeout_handlers = [];
-    // Prevent contaminating global APIs
-    this.timer_api = {};
-    const _this = this;
-
-    this.timer_api.clearInterval = function(handler) {
-      const index = Object.keys(_this.interval_handlers)[Object.values(_this.interval_handlers).indexOf(handler)]
-      clearInterval(_this.interval_handlers.splice(index, 1)[0]);
-    }
-    this.timer_api.clearTimeout = function(handler) {
-      const index = Object.keys(_this.timeout_handlers)[Object.values(_this.timeout_handlers).indexOf(handler)]
-      clearTimeout(_this.timeout_handlers.splice(index, 1)[0]);
-    }
-    this.timer_api.setInterval = function(func, delay) {
-      _this.interval_handlers.push(setInterval(func, delay));
-    }
-    this.timer_api.setTimeout = function(func, delay) {
-      _this.timeout_handlers.push(setTimeout(func, delay));
-    }
-    this.timer_api.setServiceInterval = function(func, after = 1000, repeat = 0) {
-      if (typeof func !== "function") {
-        console.log("Use function as the first parameter.");
-        return;
-      }
-      let count = 1;
-      let handler = this.timer_api.setInterval(function() {
-        func();
-        count++;
-        if (count > repeat && repeat !== 0) {
-          this.timer_api.clearInterval(handler);
-        }
-      }.bind(this), after);
-    }.bind(this);
-  }
-  getTimerAPI() {
-    return this.timer_api;
-  }
-  releaseRemainingTimers() {
-    console.log('Remaining interval(s) : ' + this.interval_handlers.length);
-    for (let index in this.interval_handlers) {
-      const handler = this.interval_handlers.splice(index, 1)[0];
-      clearInterval(handler);
-    }
-    console.log('Remaining timer(s) : ' + this.timeout_handlers.length);
-    for (let index in this.timeout_handlers) {
-      const handler = this.timeout_handlers.splice(index, 1)[0];
-      clearTimeout(handler);
-    }
-  }
-}
-module.exports = TimerManager;
diff --git a/wrt_app/service/timer_manager.ts b/wrt_app/service/timer_manager.ts
new file mode 100644 (file)
index 0000000..deace6e
--- /dev/null
@@ -0,0 +1,62 @@
+interface TimerAPI {
+  clearInterval(id: NodeJS.Timeout): void;
+  clearTimeout(id: NodeJS.Timeout): void;
+  setInterval(callback: (...args: any[]) => void, ms: number): NodeJS.Timeout;
+  setTimeout(callback: (...args: any[]) => void, ms: number): NodeJS.Timeout;
+  setServiceInterval(callback: (...args: any[]) => void, after:number, repeat:number): void;
+}
+
+export class TimerManager {
+  interval_handlers: NodeJS.Timeout[] = [];
+  timeout_handlers: NodeJS.Timeout[] = [];
+  timer_api: TimerAPI;
+  constructor() {
+    this.timer_api = {
+      clearInterval: (handler) => {
+        const index = this.interval_handlers.indexOf(handler);
+        clearInterval(this.interval_handlers.splice(index, 1)[0]);
+      },
+      clearTimeout: (handler) => {
+        const index = this.timeout_handlers.indexOf(handler);
+        clearTimeout(this.timeout_handlers.splice(index, 1)[0]);
+      },
+      setInterval: (func, delay) => {
+        let id = setInterval(func, delay);
+        this.interval_handlers.push(id);
+        return id;
+      },
+      setTimeout: (func, delay) => {
+        let id = setTimeout(func, delay);
+        this.timeout_handlers.push(id);
+        return id;
+      },
+      setServiceInterval: (func, after = 1000, repeat = 0) => {
+        if (typeof func !== "function") {
+          console.log("Use function as the first parameter.");
+          return;
+        }
+        let count = 1;
+        let handler = this.timer_api.setInterval(() => {
+          func();
+          count++;
+          if (count > repeat && repeat !== 0) {
+            this.timer_api.clearInterval(handler);
+          }
+        }, after);
+      }
+    }
+  }
+  getTimerAPI() {
+    return this.timer_api;
+  }
+  releaseRemainingTimers() {
+    console.log('Remaining interval(s) : ' + this.interval_handlers.length);
+    for (let id of this.interval_handlers)
+      clearInterval(id);
+    this.interval_handlers = [];
+    console.log('Remaining timer(s) : ' + this.timeout_handlers.length);
+    for (let id of this.timeout_handlers)
+      clearTimeout(id);
+    this.timeout_handlers = [];
+  }
+}
diff --git a/wrt_app/src/addon_manager.js b/wrt_app/src/addon_manager.js
deleted file mode 100644 (file)
index 0eb9b55..0000000
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
- *
- *    Licensed under the Apache License, Version 2.0 (the "License");
- *    you may not use this file except in compliance with the License.
- *    You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS,
- *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *    See the License for the specific language governing permissions and
- *    limitations under the License.
- */
-
-'use strict';
-
-const fs = require('fs');
-const path = require('path');
-const ADN_PATH =
-          path.join(require('os').homedir(), 'data/electron/runtime_addon');
-const MANIFEST_FILE = 'manifest.json';
-const ADDONS_DB_FILE = 'addons_db.json';
-
-// A set of predefined events for addons
-const EventList = [
-    'lcPrelaunch',      // An app is at just before launching
-    'lcResume',         // An app is resumed
-    'lcSuspend',        // An app is suspended
-    'lcQuit',           // An app is quitted
-    'hwDownkey',        // Down key is pressed
-    'hwUpkey',          // Up key is pressed
-    'contentDidFinishLoad' // The navigation is done and 'onload' was dispatched
-];
-Object.freeze(EventList);
-
-const {BrowserWindow} = require('electron');
-const {EventEmitter} = require('events');
-
-class AddonManager {
-    constructor() {
-        this.addons_list_ = null;
-        this.addons_ = null;
-        this.evt_emitter_ = null;
-        this.addons_listeners = {};
-    }
-
-    loadJsonDB(db_path) {
-        if (!db_path) {
-            db_path = path.join(ADN_PATH, ADDONS_DB_FILE);
-        }
-        var addons_list;
-        try {
-            addons_list = JSON.parse(fs.readFileSync(db_path));
-        } catch (e) {
-            console.error('LoadJsonDB - open error : ' + e);
-            return false;
-            // For DEBUG purpose (load addons from PATH, not via INSTALLER)
-            //return this.loadAddonsListFromAdnPath();
-        }
-        this.addons_list_ = addons_list;
-        return true;
-    }
-
-    build() {
-        this.initEventListener();
-        // 0. load addons_list_ from JSON DB
-        this.loadJsonDB();
-        var addons = [];
-        // 1. load addons from addon_list_
-        if (this.addons_list_) {
-            for (var i in this.addons_list_) {
-                var addon = this.addons_list_[i];
-                if (addon.activate == false) {
-                    continue;
-                }
-                try {
-                    var manifest_obj, manifest_path = path.join(addon.path, MANIFEST_FILE);
-                    manifest_obj = JSON.parse(fs.readFileSync(manifest_path));
-                    console.log('manifest_obj : ' + JSON.stringify(manifest_obj));
-
-                    if (addons[manifest_obj.name]) {
-                        console.log('addons[' + manifest_obj.name + '] already registered : ' + addons[manifest_obj.name]);
-                        continue;
-                    }
-                    if (manifest_obj.main) {
-                        addons[manifest_obj.name] = path.join(addon.path, manifest_obj.main);
-                    } else {
-                        addons[manifest_obj.name] = addon.path;
-                    }
-
-                    console.log('addons[' + manifest_obj.name + '] = ' + addons[manifest_obj.name] + ' registered');
-                } catch (e) {
-                    console.error('AddonManager.build error - ' + e);
-                }
-            }
-        }
-        if (this.addons_ != null) {
-            delete this.addons_;
-            this.addons_ = null;
-        }
-        this.addons_ = addons;
-        return this.addons_;
-    }
-
-    activate(app, name) {
-        if (!this.addons_) {
-            return;
-        }
-        var addon, addon_path = null;
-        if (this.addons_[name] !== undefined) {
-            addon_path = this.addons_[name];
-            console.log('activate: ' + addon_path + ' name:' + name);
-            try {
-                let Addon = require(addon_path);
-                addon = new Addon(this.wrappedEventEmitter);
-            } catch (e) {
-                console.error('activate - error on require() : ' + e);
-                return;
-            }
-            if (addon && addon.activate) {
-                addon.activate();
-            }
-            else console.log('addon.activate not defined!');
-        }
-    }
-
-    deactivate(app, name) {
-        if (!this.addons_) {
-            return;
-        }
-        console.log('deactivate: name:' + name);
-        var addon, addon_path = null;
-        if (this.addons_[name] !== undefined) {
-            try {
-                addon_path = this.addons_[name];
-            } catch (e) {
-                console.error('deactivate - error : ' + e);
-                return;
-            }
-            console.log('deactivate: path:' + addon_path);
-            try {
-                let Addon = require(addon_path);
-                addon = new Addon(this.wrappedEventEmitter);
-            } catch (e) {
-                console.error('deactivate - error on require() : ' + e);
-            }
-            if (addon && addon.deactivate) {
-                addon.deactivate();
-            } else {
-                console.log('addon.deactivate not defined!');
-            }
-        }
-    }
-
-    activateAll(app) {
-        if (!this.addons_) {
-            console.log('activateAll - addons not built');
-            return;
-        }
-        for (var name in this.addons_) {
-            this.activate(app, name);
-        }
-    }
-
-    deactivateAll(app) {
-        if (!this.addons_) {
-            console.log('deactivateAll - addons not built');
-            return;
-        }
-        for (var name in this.addons_) {
-            this.deactivate(app, name);
-        }
-    }
-
-    isAddonAvailable() {
-        return Object.keys(this.addons_).length != 0;
-    }
-
-    initEventListener() {
-        const getSafeFunction = function(fn) {
-            return function() {
-                try {
-                    fn.apply(this, arguments);
-                } catch (err) {
-                    console.log(`Exception from add-on: ${err.name} - ${err.message}`);
-                }
-            }
-        }
-        this.evt_emitter_ = new EventEmitter();
-        this.wrappedEventEmitter = {
-            on: (eventName, listener) => {
-                if (EventList.indexOf(eventName) === -1) {
-                    console.log(`Invalid Event: ${eventName}`);
-                } else if (!this.addons_listeners[listener]) {
-                    console.log(`A new listener for ${eventName} is added`);
-                    this.addons_listeners[listener] = getSafeFunction(listener);
-                    this.evt_emitter_.on(eventName, this.addons_listeners[listener]);
-                } else console.log('Listener is already registered');
-            },
-            off: (eventName, listener) => {
-                if (EventList.indexOf(eventName) === -1) {
-                    console.log(`Invalid Event: ${eventName}`);
-                } else if (this.addons_listeners[listener]) {
-                    console.log(`A listener for ${eventName} is removed`);
-                    this.evt_emitter_.off(eventName, this.addons_listeners[listener]);
-                    delete this.addons_listeners[listener];
-                } else console.log('Listener is not registered');
-            },
-            once: (eventName, listener) => {
-                if (EventList.indexOf(eventName) === -1) {
-                    console.log(`Invalid Event: ${eventName}`);
-                } else if (!this.addons_listeners[listener]) {
-                    console.log(`A new one-time listener for ${eventName} is added`);
-                    this.evt_emitter_.once(eventName, getSafeFunction(listener));
-                } else console.log('Listener is already registered for the multiple-time use');
-            }
-        }
-    }
-
-    static getManifestFile() {
-        return MANIFEST_FILE;
-    }
-
-    static getAddonsPath() {
-        return ADN_PATH;
-    }
-
-    static checkAddon(path) {
-        let tmpPath = require('path');
-        let mani_path = tmpPath.join(path, MANIFEST_FILE);
-        let tmpfs = require('fs');
-        let json;
-        let addon = null;
-
-        try {
-            json = JSON.parse(tmpfs.readFileSync(mani_path));
-        } catch (e) {
-            console.log('fail to read manifest');
-            return addon;
-        }
-
-        addon = new Object();
-        addon.name = json.name;
-        addon.version = json.version;
-        addon.settings = json.settings;
-        addon.description = json.description;
-        addon.path = path;
-        let arr = path.split("/");
-        let index = 0;
-        for (let i in arr) {
-            if (arr[i] == 'shared') {
-                index = i - 1;
-                break;
-            }
-        }
-        addon.pkgid = arr[index];
-        addon.type = 'WRT';
-        addon.activate = true;
-
-        return addon;
-    }
-
-    static updateDB(adnPkgs) {
-        let tmpPath = require('path');
-        let db_path = tmpPath.join(ADN_PATH, ADDONS_DB_FILE);
-        let tmpfs = require('fs');
-        let addon_list = [];
-        try {
-            addon_list = JSON.parse(tmpfs.readFileSync(db_path));
-        } catch (e) {
-            try {
-                tmpfs.statSync(ADN_PATH);
-            } catch (e) {
-                console.log('db path not exist');
-                let path_dep1 = tmpPath.join(require('os').homedir(),
-                                    'data/electron');
-                let path_dep2 = tmpPath.join(path_dep1, 'runtime_addon');
-                // fs.mkdirSync didn't work to create the directory recursively
-                // even with recursive as true.
-                // That's why creating a directory one by one here.
-                tmpfs.mkdirSync(path_dep1, {recursive: true});
-                tmpfs.mkdirSync(path_dep2, {recursive: true});
-            }
-        }
-
-        let addons_pkgids_ = [];
-        for (let addon of adnPkgs) {
-            addons_pkgids_.push(addon.pkgid);
-            for (let tmpAddon of addon_list) {
-                if (tmpAddon.name == addon.name) {
-                    console.log('already in db');
-                    adnPkgs[adnPkgs.indexOf(addon)].activate =
-                        tmpAddon.activate;
-                }
-            }
-        }
-
-        // Removes obsolete add-on DB files
-        try {
-            const DB_FILES = '_db.json';
-            tmpfs.readdirSync(ADN_PATH).forEach((file) => {
-                if (file != ADDONS_DB_FILE && file.endsWith(DB_FILES)) {
-                    let id = file.substr(0, file.indexOf(DB_FILES));
-                    if (addons_pkgids_.indexOf(id) === -1) {
-                        try {
-                            tmpfs.unlinkSync(path.join(ADN_PATH, file));
-                        } catch (err) {
-                            console.log(`${file} deletion failed`);
-                        }
-                    }
-                }
-            });
-        } catch (err) {
-            console.log(`An error occurred: ${err}`);
-        }
-
-        var fd;
-        try {
-            fd = tmpfs.openSync(db_path, 'w');
-        } catch (e) {
-            console.log('db file open error');
-            return false;
-        }
-        fs.writeSync(fd, JSON.stringify(adnPkgs));
-        fs.closeSync(fd);
-        fs.chmodSync(db_path, 0o666);
-        return true;
-    }
-}
-
-module.exports = AddonManager;
diff --git a/wrt_app/src/addon_manager.ts b/wrt_app/src/addon_manager.ts
new file mode 100644 (file)
index 0000000..c4f1b66
--- /dev/null
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+'use strict';
+
+import * as fs from 'fs';
+import * as os from 'os';
+import * as path from 'path';
+import { EventEmitter } from 'events';
+import { ipcMain } from 'electron';
+import { wrt } from '../browser/wrt';
+
+interface AddonModule {
+  activate: () => void;
+  deactivate: () => void;
+}
+
+interface AddonInfo {
+  name: string;
+  version: string;
+  path: string;
+  pkgid: string;
+  activate: boolean;
+  module?: AddonModule;
+}
+
+const ADN_PATH = path.join(os.homedir(), 'data/electron/runtime_addon');
+const MANIFEST_FILE = 'manifest.json';
+const ADDONS_DB_FILE = 'addons_db.json';
+
+// A set of predefined events for addons
+const EventList = [
+  'lcPrelaunch',      // An app is at just before launching
+  'lcResume',         // An app is resumed
+  'lcSuspend',        // An app is suspended
+  'lcQuit',           // An app is quitted
+  'hwDownkey',        // Down key is pressed
+  'hwUpkey',          // Up key is pressed
+  'contentDidFinishLoad' // The navigation is done and 'onload' was dispatched
+];
+Object.freeze(EventList);
+
+const InternalEvent = {
+  installed: 'ipc:addons:installed',
+  uninstalled: 'ipc:addons:uninstalled',
+  activate: 'ipc:addons:activate',
+  deactivate: 'ipc:addons:deactivate'
+};
+
+class AddonEventManager {
+  private emitter = new EventEmitter();
+  private listeners = new Map();
+  private bindListener = (listener: (...args: any[]) => void, addon: AddonInfo): (...args: any[]) => void => {
+    return (...args) => {
+      listener.call(addon.module, args);
+    }
+  }
+  on = (eventName: string, listener: (...args: any[]) => void, addon: AddonInfo): void => {
+    if (EventList.indexOf(eventName) === -1) {
+      console.log(`Invalid Event: ${eventName}`);
+    } else if (!this.listeners.has(listener)) {
+      console.log(`A new listener for ${eventName} is added`);
+      let fn = this.bindListener(listener, addon);
+      this.listeners.set(listener, fn);
+      this.emitter.on(eventName, fn);
+    } else {
+      console.log('Listener is already registered');
+    }
+  }
+  off = (eventName: string, listener: (...args: any[]) => void): void => {
+    if (EventList.indexOf(eventName) === -1) {
+      console.log(`Invalid Event: ${eventName}`);
+    } else if (this.listeners.has(listener)) {
+      console.log(`A listener for ${eventName} is removed`);
+      this.emitter.off(eventName, this.listeners.get(listener));
+      this.listeners.delete(listener);
+    } else {
+      console.log('Listener is not registered');
+    }
+  }
+  once = (eventName: string, listener: (...args: any[]) => void, addon: AddonInfo): void => {
+    if (EventList.indexOf(eventName) === -1) {
+      console.log(`Invalid Event: ${eventName}`);
+    } else if (!this.listeners.has(listener)) {
+      console.log(`A new one-time listener for ${eventName} is added`);
+      this.emitter.once(eventName, this.bindListener(listener, addon));
+    } else {
+      console.log('Listener is already registered for the multiple-time use');
+    }
+  }
+  createBinder = (addon: AddonInfo) => {
+    return {
+      on: (eventName: string, listener: (...args: any[]) => void): void => {
+        this.on(eventName, listener, addon);
+      },
+      off: (eventName: string, listener: (...args: any[]) => void): void => {
+        this.off(eventName, listener);
+      },
+      once: (eventName: string, listener: (...args: any[]) => void): void => {
+        this.once(eventName, listener, addon);
+      }
+    }
+  }
+  emit = (eventName: string, ...args: any[]): void => {
+    this.emitter.emit(eventName, args);
+  }
+}
+
+class AddonManager {
+  private addonList: AddonInfo[] = [];
+  private eventManager = new AddonEventManager;
+
+  constructor() {
+    ipcMain.on(InternalEvent.installed, (sender, name) => {
+      console.log('Addon event : INSTALLED ' + name);
+      this.build();
+      return this.activateByName(name);
+    });
+    ipcMain.on(InternalEvent.uninstalled, (sender, name, pkgid) => {
+      console.log('Addon event : UNINSTALLED ' + name);
+      this.deactivateByName(name);
+      /* FIXME: will uncheck after chromium-efl released */
+      if (wrt.getPlatformType() !== "product_tv")
+        wrt.reqUninstallPkg(pkgid);
+      return true;
+    });
+    ipcMain.on(InternalEvent.activate, (sender, name) => {
+      console.log('Addon event : ACTIVATE ' + name);
+      return this.activateByName(name);
+    });
+    ipcMain.on(InternalEvent.deactivate, (sender, name) => {
+      console.log('Addon event : DEACTIVATE ' + name);
+      return this.deactivateByName(name);
+    });
+  }
+
+  private loadJsonDB(dbPath?: string): void {
+    if (!dbPath)
+      dbPath = path.join(ADN_PATH, ADDONS_DB_FILE);
+    let list;
+    try {
+      list = JSON.parse(fs.readFileSync(dbPath, 'utf-8'));
+    } catch (e) {
+      console.error('LoadJsonDB - open error : ' + e);
+      list = [];
+    }
+    this.addonList = list;
+  }
+
+  build(): boolean {
+    this.loadJsonDB();
+    for (let addon of this.addonList) {
+      if (addon.activate == false)
+        continue;
+      console.log('addon ' + addon.name + '(' + addon.path + ') registered');
+    }
+    return this.isAddonAvailable();
+  }
+
+  private getModule(addon: AddonInfo): AddonModule | undefined {
+    if (addon.module)
+      return addon.module;
+    try {
+      const addonModule = require(addon.path);
+      let addonInstance = new addonModule(this.eventManager.createBinder(addon));
+      addon.module = addonInstance;
+    } catch (e) {
+      console.error('error on creating addon instance : ' + e);
+    }
+    return addon.module;
+  }
+
+  private activate(addon: AddonInfo) {
+    console.log('activate: ' + addon.path + ' name:' + addon.name);
+    let addonInstance = this.getModule(addon);
+    if (addonInstance && addonInstance.activate)
+      addonInstance.activate();
+    else
+      console.log('addon.activate not defined!');
+  }
+
+  private deactivate(addon: AddonInfo) {
+    console.log('deactivate: ' + addon.path + ' name:' + addon.name);
+    let addonInstance = this.getModule(addon);
+    if (addonInstance && addonInstance.deactivate)
+      addonInstance.deactivate();
+    else
+      console.log('addon.deactivate not defined!');
+  }
+
+  private getAddonInfo(name: string): AddonInfo | undefined {
+    for (let addon of this.addonList) {
+      if (addon.name === name)
+        return addon;
+    }
+    return undefined;
+  }
+
+  private activateByName(name: string): boolean {
+    let addon = this.getAddonInfo(name);
+    if (!addon)
+      return false;
+    this.activate(addon);
+    return true;
+  }
+
+  private deactivateByName(name: string): boolean {
+    let addon = this.getAddonInfo(name);
+    if (!addon)
+      return false;
+    this.deactivate(addon);
+    return true;
+  }
+
+  activateAll() {
+    for (let addon of this.addonList)
+      this.activate(addon);
+  }
+
+  deactivateAll() {
+    for (let addon of this.addonList)
+      this.deactivate(addon);
+  }
+
+  isAddonAvailable() {
+    return this.addonList.length != 0;
+  }
+
+  emit(eventName: string, ...args: any[]) {
+    this.eventManager.emit(eventName, args);
+  }
+
+  checkAddon(appPath: string) {
+    let manifest;
+    try {
+      let manifestPath = path.join(appPath, MANIFEST_FILE);
+      manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
+    } catch (e) {
+      console.log('fail to read manifest');
+      return;
+    }
+
+    let arr = appPath.split("/");
+    let index = 0;
+    for (let i in arr) {
+      if (arr[i] == 'shared') {
+        index = Number(i) - 1;
+        break;
+      }
+    }
+
+    this.addonList.push({
+      name: manifest.name,
+      version: manifest.version,
+      path: path.join(appPath, manifest.main),
+      pkgid: arr[index],
+      activate: true
+    });
+  }
+
+  updateDB() {
+    let dbPath = path.join(ADN_PATH, ADDONS_DB_FILE);
+    let currentAddonList;
+    try {
+      currentAddonList = JSON.parse(fs.readFileSync(dbPath, 'utf-8'));
+    } catch (e) {
+      try {
+        fs.statSync(ADN_PATH);
+      } catch (e) {
+        console.log('db path not exist');
+        fs.mkdirSync(ADN_PATH, { recursive: true });
+      }
+      currentAddonList = [];
+    }
+
+    let pkgids: string[] = [];
+    for (let addon of this.addonList) {
+      pkgids.push(addon.pkgid);
+      for (let addonInDB of currentAddonList) {
+        if (addonInDB.name == addon.name)
+          addon.activate = addonInDB.activate;
+      }
+    }
+
+    // Removes obsolete add-on DB files
+    try {
+      const DB_FILES = '_db.json';
+      fs.readdirSync(ADN_PATH).forEach((file) => {
+        if (file != ADDONS_DB_FILE && file.endsWith(DB_FILES)) {
+          let id = file.substr(0, file.indexOf(DB_FILES));
+          if (pkgids.indexOf(id) === -1) {
+            try {
+              fs.unlinkSync(path.join(ADN_PATH, file));
+            } catch (err) {
+              console.log(`${file} deletion failed`);
+            }
+          }
+        }
+      });
+    } catch (err) {
+      console.log(`An error occurred: ${err}`);
+    }
+
+    let fd;
+    try {
+      fd = fs.openSync(dbPath, 'w');
+    } catch (e) {
+      console.log('db file open error');
+      return false;
+    }
+    let replacer = (key: string, value: any): any => {
+      if (key === 'module')
+        return undefined;
+      return value;
+    };
+    fs.writeSync(fd, JSON.stringify(this.addonList, replacer));
+    fs.closeSync(fd);
+    fs.chmodSync(dbPath, 0o666);
+    return true;
+  }
+}
+
+export const addonManager = new AddonManager;
diff --git a/wrt_app/src/ipc_message.js b/wrt_app/src/ipc_message.js
deleted file mode 100755 (executable)
index 01f620c..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
- *
- *    Licensed under the Apache License, Version 2.0 (the "License");
- *    you may not use this file except in compliance with the License.
- *    You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS,
- *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *    See the License for the specific language governing permissions and
- *    limitations under the License.
- */
-
-'use strict';
-
-module.exports = Object.freeze({
-    ADDONS: {
-        INSTALLED: 'ipc:addons:installed',
-        UNINSTALLED: 'ipc:addons:uninstalled',
-        ACTIVATE: 'ipc:addons:activate',
-        DEACTIVATE: 'ipc:addons:deactivate'
-    }
-});
diff --git a/wrt_app/src/runtime.js b/wrt_app/src/runtime.js
deleted file mode 100755 (executable)
index b0d97ff..0000000
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
- *
- *    Licensed under the Apache License, Version 2.0 (the "License");
- *    you may not use this file except in compliance with the License.
- *    You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS,
- *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *    See the License for the specific language governing permissions and
- *    limitations under the License.
- */
-
-'use strict';
-
-const wrt = require('../browser/wrt');  // Load first for log
-const AddonManager = require('./addon_manager');
-const {app, ipcMain} = require('electron');
-const IPC_MESSAGE = require('./ipc_message');
-const WebApplication = require('./web_application');
-
-class Runtime {
-    constructor(options) {
-        this.webApplication = null;
-        this.handleIpcMessages();
-        this.addonManager = null;
-        this.isLaunched = false;
-        this.inspectorEnabledByVconf = false;
-        this.addonPkgs = [];
-
-        var _this = this;
-        app.on('before-quit', function(event) {
-            console.log('before-quit');
-            if (_this.webApplication) {
-                _this.webApplication.quit();
-            }
-        });
-        app.on('will-quit', function(event) {
-            console.log('will-quit');
-            _this.addonManager.deactivateAll(app);
-        });
-        app.on('quit', function(event) {
-            console.log('quit');
-            if (_this.webApplication) {
-                _this.webApplication.finalize();
-                _this.webApplication = null;
-            }
-            wrt.exit();
-        });
-        app.on('browser-window-blur', function() {
-            console.log('browser-window-blur');
-        });
-        app.on('browser-window-focus', function() {
-            console.log('browser-window-focus');
-        });
-        app.on('browser-window-created', function() {
-            console.log('browser-window-created');
-            if (!_this.isLaunched) {
-                _this.addonManager.activateAll(app);
-                _this.isLaunched = true;
-            }
-        });
-        app.on('gpu-process-crashed', function() {
-            console.error('gpu-process-crashed');
-        });
-        app.on('window-all-closed', function(event) {
-            console.log('window-all-closed');
-            app.quit();
-        });
-        app.on('will-finish-launching', function(event) {
-            console.log('will-finish-launching');
-        });
-        app.on('web-contents-created', function(event, webContents) {
-            console.log('web-contents-created');
-            if (wrt.tv)
-                _this.setCookiePath();
-            webContents.on('before-input-event', function(event, input) {
-                if (_this.isLaunched && _this.webApplication) {
-                    _this.handleKeyEvents(input.key);
-                }
-            });
-        });
-        app.once('ready', function(event) {
-            console.log('ready');
-            _this.addonManager = new AddonManager();
-            if (!options.noAddons) {
-                let addonBuilt = _this.addonManager.build();
-                console.log("addonBuild : " + addonBuilt.length);
-                if (addonBuilt.length) {
-                  const XWalkExtension = require('./wrt_xwalk_extension');
-                  XWalkExtension.initialize();
-                  XWalkExtension.preventCleanup();
-                }
-            }
-            if (wrt.tv) {
-                wrt.tv.importCertificate('');
-                wrt.tv.optimizeCache();
-                wrt.tv.clearDeadMount();
-            }
-        });
-        wrt.on('app-control', function(event, appControl) {
-            console.log('app-control');
-            let loadInfo = appControl.getLoadInfo();
-            let src = loadInfo.getSrc();
-
-            if (wrt.isElectronApp()) {
-                console.log('Electron App launch');
-                const Module = require('module');
-                Module.globalPaths.push(wrt.getAppPath());
-                let filePath = src[7] === '/' ? src.substr(8) : src.substr(7); // strip "file://"
-                let pkgJson = require(filePath);
-                let pos = filePath.lastIndexOf('/');
-
-                let mainJsPath = (pos !== -1 ? filePath.substr(0, pos + 1) : '') +
-                                 (pkgJson.main || 'index.js');
-                console.log('loading path:', mainJsPath);
-                Module._load(mainJsPath, Module, true);
-                app.emit('ready');
-            } else {
-                console.log('Tizen Web App launch');
-                let launchMode = appControl.getData('http://samsung.com/appcontrol/data/launch_mode');
-                if (!_this.webApplication) {
-                    console.log('Creating WebApplication');
-                    options.isAddonAvailable = !options.noAddons &&
-                            _this.addonManager.isAddonAvailable();
-                    options.launchMode = launchMode;
-                    _this.webApplication = new WebApplication(options);
-                    _this.webApplication.addonEmitter =
-                          _this.addonManager.evt_emitter_;
-                    if (wrt.tv) {
-                        _this.inspectorEnabledByVconf = wrt.tv.needUseInspector();
-                        if (_this.inspectorEnabledByVconf && launchMode != 'backgroundExecution') {
-                            _this.webApplication.inspectorSrc = src;
-                            src = "about:blank";
-                        }
-                        let useDiskCache = appControl.getData('USE_DISKCACHE');
-                        let halfWindowOption = appControl.getData('http://samsung.com/appcontrol/data/half_window_support');
-                        wrt.tv.setDiskCache(useDiskCache);
-                        wrt.tv.handleAppControlData(launchMode, halfWindowOption);
-                    }
-                    _this.webApplication.mainWindow.loadURL(src);
-                    _this.webApplication.prelaunch(src);
-                } else {
-                    console.log('Handling app-control event');
-                    if (_this.webApplication.preloadStatus == 'readyToShow') {
-                        _this.webApplication.show();
-                    } else {
-                        if (launchMode != 'backgroundAtStartup') {
-                            _this.webApplication.preloadStatus = 'none';
-                        }
-                    }
-
-                    let skipReload = appControl.getData('SkipReload');
-                    if (skipReload == 'Yes') {
-                        console.log('skipping reload');
-                        // TODO : Need to care this situation and decide to pass the addon event emitter to resume()
-                        _this.webApplication.resume();
-                        return;
-                    }
-
-                    let reload = loadInfo.getReload() || _this.webApplication.isAlwaysReload;
-                    if (!reload) {
-                        let originalUrl = _this.webApplication.mainWindow.getURL();
-                        if (wrt.tv) {
-                            console.log(`appcontrol src = ${src}, original url = ${originalUrl}`);
-                            if (src && originalUrl) {
-                                let appcontrolUrl = (new URL(src)).href;
-                                let oldUrl = (new URL(originalUrl)).href;
-                                console.log(`appcontrolUrl = ${appcontrolUrl}, oldUrl = ${oldUrl}`);
-                                // FIXME(dh81.song)
-                                // Below case it must be distinguishable for known cases
-                                //   from 'file:///index.htmlx' to 'file:///index.html'
-                                if (appcontrolUrl !== oldUrl.substr(0, appcontrolUrl.length)) {
-                                    reload = true;
-                                }
-                            } else {
-                                reload = true;
-                            }
-                        } else if (src !== originalUrl) {
-                            reload = true;
-                        }
-                    }
-                    // handle http://tizen.org/appcontrol/operation/main operation specially.
-                    // only menu-screen app can send launch request with main operation.
-                    // in this case, web app should have to resume web app not reset.
-                    if (reload && appControl.getOperation() == 'http://tizen.org/appcontrol/operation/main')
-                        reload = false;
-                    if (reload) {
-                        _this.webApplication.handleAppControlReload(src);
-                    } else {
-                        _this.webApplication.sendAppControlEvent();
-                    }
-                }
-            }
-            _this.setCookiePath();
-            _this.launchInspector(appControl);
-        });
-        wrt.on('suspend', function() {
-            console.log('suspend');
-            if (_this.webApplication)
-                _this.webApplication.suspend();
-        });
-        wrt.on('resume', function() {
-            console.log('resume');
-            if (_this.webApplication)
-                _this.webApplication.resume();
-        });
-        wrt.on('low-memory', function() {
-            console.log('low-memory');
-            if (_this.webApplication)
-                _this.webApplication.lowMemory();
-        });
-        wrt.on('message', function(event, type, params) {
-            console.log('message type(' + type + ') params : ' + params);
-            const app_id = params[0];
-            if (type === 'startService') {
-                require('../common/service_manager').startService(app_id, params[1]);
-                event.preventDefault();
-            } else if (type === 'stopService') {
-                require('../common/service_manager').stopService(app_id);
-                event.preventDefault();
-            }
-        });
-        wrt.on('ambient-tick', function() {
-            if (_this.webApplication)
-                _this.webApplication.ambientTick();
-        });
-        wrt.on('ambient-changed', function(event, ambient_mode) {
-            console.log('ambient-changed , ambient_mode:' + ambient_mode);
-            if (_this.webApplication)
-                _this.webApplication.ambientChanged(ambient_mode);
-        });
-        wrt.on('addon-installed', function(event, path) {
-            console.log('addon-installed at ' + path);
-            let dbInfo = AddonManager.checkAddon(path);
-            if (dbInfo) {
-                _this.addonPkgs.push(dbInfo);
-            }
-        });
-        wrt.on('addon-uninstalled', function(event, id) {
-            console.log('addon-unistalled named ' + id);
-        });
-        wrt.on('wgt-checking-done', function(event) {
-            console.log('wgt-checking-done');
-            AddonManager.updateDB(_this.addonPkgs);
-        });
-        /* FIXME: will uncheck after chromium-efl released */
-        if (wrt.getPlatformType() !== "product_tv") {
-            wrt.getInstalledPkg();
-        }
-    }
-    handleIpcMessages() {
-        var _this = this;
-        ipcMain.on(IPC_MESSAGE.ADDONS.INSTALLED, (sender, name) => {
-            console.log('handleIpcMessages: INSTALLED ' + name);
-            _this.addonManager.build();
-            return _this.addonManager.activate(app, name);
-        });
-        ipcMain.on(IPC_MESSAGE.ADDONS.UNINSTALLED, (sender, name, pkg) => {
-            console.log('handleIpcMessages: UNINSTALLED ' + name);
-            _this.addonManager.deactivate(app, name);
-            /* FIXME: will uncheck after chromium-efl released */
-            if (wrt.getPlatformType() !== "product_tv") {
-               wrt.reqUninstallPkg(pkg);
-            }
-            return true;
-        });
-        ipcMain.on(IPC_MESSAGE.ADDONS.ACTIVATE, (sender, name) => {
-            console.log('handleIpcMessages: ACTIVATE ' + name);
-            return _this.addonManager.activate(app, name);
-        });
-        ipcMain.on(IPC_MESSAGE.ADDONS.DEACTIVATE, (sender, name) => {
-            console.log('handleIpcMessages: DEACTIVATE ' + name);
-            return _this.addonManager.deactivate(app, name);
-        });
-    }
-    checkInspectorCondition(appControl) {
-        let bundleDebug = (appControl.getData('__AUL_DEBUG__') === "1");
-        return (bundleDebug || this.inspectorEnabledByVconf);
-    }
-    setCookiePath() {
-        this.setCookiePath = () => {}; // call once
-        console.log('setCookiePath');
-
-        // FIX ME : It must be supplemented to set a specific path
-        wrt.setCookiePath();
-    }
-    launchInspector(appControl) {
-        this.launchInspector = (param) => {}; // call once
-        console.log('launchInspector');
-
-        // AUL public key/Vconf - To support inspector
-        if (this.checkInspectorCondition(appControl)) {
-            var debugPort = wrt.startInspectorServer();
-            var data    = { "port" :  [ debugPort.toString() ] };
-            if (this.webApplication)
-                this.webApplication.debugPort = debugPort;
-            appControl.reply(data);
-        }
-    }
-    handleKeyEvents(key) {
-        let valid = false;
-        let _this = this;
-
-        console.log(key + ' is pressed');
-        switch(key) {
-            case "ArrowUp":
-            case "Up":
-            case "ArrowDown":
-            case "Down":
-                valid = true;
-                break;
-            default:
-                console.log('No handler for the key ' + key);
-                break;
-        }
-
-        if (valid) {
-            _this.webApplication.keyEvent(key);
-        }
-    }
-}
-module.exports = Runtime;
diff --git a/wrt_app/src/runtime.ts b/wrt_app/src/runtime.ts
new file mode 100755 (executable)
index 0000000..2348fd1
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+'use strict';
+
+import { wrt } from '../browser/wrt';  // Load first for log
+import { addonManager } from './addon_manager';
+import { app } from 'electron';
+import { WebApplication } from './web_application';
+
+class Runtime {
+  webApplication?: WebApplication = undefined;
+  isLaunched = false;
+  inspectorEnabledByVconf = false;
+
+  constructor() {
+    app.on('before-quit', (event) => {
+      console.log('before-quit');
+      this.webApplication?.quit();
+    });
+    app.on('will-quit', (event) => {
+      console.log('will-quit');
+      addonManager.deactivateAll();
+    });
+    app.on('quit', (event) => {
+      console.log('quit');
+      if (this.webApplication) {
+        this.webApplication.finalize();
+        this.webApplication = undefined;
+      }
+    });
+    app.on('browser-window-created', () => {
+      console.log('browser-window-created');
+      if (!this.isLaunched) {
+        addonManager.activateAll();
+        this.isLaunched = true;
+      }
+    });
+    app.on('window-all-closed', () => {
+      console.log('window-all-closed');
+      app.quit();
+    });
+    app.on('web-contents-created', (event, webContents) => {
+      console.log('web-contents-created');
+      if (wrt.tv)
+        this.setCookiePath();
+      webContents.on('before-input-event', (event, input) => {
+        if (this.isLaunched && this.webApplication)
+          this.handleKeyEvents(input.key);
+      });
+    });
+    app.once('ready', (event) => {
+      console.log('ready');
+      let addonAvailable = addonManager.build();
+      console.log("addonBuild : " + addonAvailable);
+      if (addonAvailable) {
+        const XWalkExtension = require('../common/wrt_xwalk_extension');
+        XWalkExtension.initialize();
+        XWalkExtension.preventCleanup();
+      }
+      if (wrt.tv) {
+        wrt.tv.importCertificate('');
+        wrt.tv.optimizeCache();
+        wrt.tv.clearDeadMount();
+      }
+    });
+    wrt.on('app-control', (event, appControl) => {
+      console.log('app-control');
+      let loadInfo = appControl.getLoadInfo();
+      let src = loadInfo.getSrc();
+
+      if (wrt.isElectronApp()) {
+        console.log('Electron App launch');
+        const Module = require('module');
+        Module.globalPaths.push(wrt.getAppPath());
+        let filePath = src[7] === '/' ? src.substr(8) : src.substr(7); // strip "file://"
+        let pkgJson = require(filePath);
+        let pos = filePath.lastIndexOf('/');
+
+        let mainJsPath = (pos !== -1 ? filePath.substr(0, pos + 1) : '') +
+                            (pkgJson.main || 'index.js');
+        console.log('loading path:', mainJsPath);
+        Module._load(mainJsPath, Module, true);
+        app.emit('ready');
+      } else {
+        console.log('Tizen Web App launch');
+        let launchMode = appControl.getData('http://samsung.com/appcontrol/data/launch_mode');
+        if (!this.webApplication) {
+          console.log('Creating WebApplication');
+          let options: RuntimeOption = {
+            isAddonAvailable: addonManager.isAddonAvailable(),
+            launchMode: launchMode
+          }
+          this.webApplication = new WebApplication(options);
+          if (wrt.tv) {
+            this.inspectorEnabledByVconf = wrt.tv.needUseInspector();
+            if (this.inspectorEnabledByVconf && launchMode != 'backgroundExecution') {
+              this.webApplication.inspectorSrc = src;
+              src = "about:blank";
+            }
+            let useDiskCache = appControl.getData('USE_DISKCACHE');
+            let halfWindowOption = appControl.getData('http://samsung.com/appcontrol/data/half_window_support');
+            wrt.tv.setDiskCache(useDiskCache);
+            wrt.tv.handleAppControlData(launchMode, halfWindowOption);
+          }
+          this.webApplication.mainWindow.loadURL(src);
+          this.webApplication.prelaunch(src);
+        } else {
+          console.log('Handling app-control event');
+          if (this.webApplication.preloadStatus == 'readyToShow') {
+            this.webApplication.show();
+          } else {
+            if (launchMode != 'backgroundAtStartup')
+              this.webApplication.preloadStatus = 'none';
+          }
+
+          let skipReload = appControl.getData('SkipReload');
+          if (skipReload == 'Yes') {
+            console.log('skipping reload');
+            // TODO : Need to care this situation and decide to pass the addon event emitter to resume()
+            this.webApplication.resume();
+            return;
+          }
+
+          let reload = loadInfo.getReload() || this.webApplication.isAlwaysReload;
+          if (!reload) {
+            let originalUrl = this.webApplication.mainWindow.webContents.getURL();
+            if (wrt.tv) {
+              console.log(`appcontrol src = ${src}, original url = ${originalUrl}`);
+              if (src && originalUrl) {
+                let appcontrolUrl = (new URL(src)).href;
+                let oldUrl = (new URL(originalUrl)).href;
+                console.log(`appcontrolUrl = ${appcontrolUrl}, oldUrl = ${oldUrl}`);
+                // FIXME(dh81.song)
+                // Below case it must be distinguishable for known cases
+                //   from 'file:///index.htmlx' to 'file:///index.html'
+                if (appcontrolUrl !== oldUrl.substr(0, appcontrolUrl.length))
+                  reload = true;
+              } else {
+                reload = true;
+              }
+            } else if (src !== originalUrl) {
+              reload = true;
+            }
+          }
+          // handle http://tizen.org/appcontrol/operation/main operation specially.
+          // only menu-screen app can send launch request with main operation.
+          // in this case, web app should have to resume web app not reset.
+          if (reload && appControl.getOperation() == 'http://tizen.org/appcontrol/operation/main')
+            reload = false;
+          if (reload)
+            this.webApplication.handleAppControlReload(src);
+          else
+            this.webApplication.sendAppControlEvent();
+        }
+      }
+      this.setCookiePath();
+      this.launchInspector(appControl);
+    });
+    wrt.on('suspend', () => {
+      console.log('suspend');
+      this.webApplication?.suspend();
+    });
+    wrt.on('resume', () => {
+      console.log('resume');
+      this.webApplication?.resume();
+    });
+    wrt.on('low-memory', () => {
+      console.log('low-memory');
+      this.webApplication?.lowMemory();
+    });
+    wrt.on('message', (event, type, params) => {
+      console.log('message type(' + type + ') params : ' + params);
+      const app_id = params[0];
+      if (type === 'startService') {
+        require('../common/service_manager').startService(app_id, params[1]);
+        event.preventDefault();
+      } else if (type === 'stopService') {
+        require('../common/service_manager').stopService(app_id);
+        event.preventDefault();
+      }
+    });
+    wrt.on('ambient-tick', () => {
+      this.webApplication?.ambientTick();
+    });
+    wrt.on('ambient-changed', (event, ambient_mode) => {
+      console.log('ambient-changed , ambient_mode:' + ambient_mode);
+      this.webApplication?.ambientChanged(ambient_mode);
+    });
+    wrt.on('addon-installed', (event, path) => {
+      console.log('addon-installed at ' + path);
+      addonManager.checkAddon(path);
+    });
+    wrt.on('addon-uninstalled', (event, id) => {
+      console.log('addon-unistalled named ' + id);
+    });
+    wrt.on('wgt-checking-done', (event) => {
+      console.log('wgt-checking-done');
+      addonManager.updateDB();
+    });
+
+    /* FIXME: will uncheck after chromium-efl released */
+    if (wrt.getPlatformType() !== "product_tv")
+      wrt.getInstalledPkg();
+  }
+
+  private launchInspector(appControl: NativeWRTjs.AppControl) {
+    this.launchInspector = (param) => {}; // call once
+    console.log('launchInspector');
+
+    // AUL public key/Vconf - To support inspector
+    if (this.checkInspectorCondition(appControl)) {
+      let debugPort = wrt.startInspectorServer();
+      let data = { "port" :  [ debugPort.toString() ] };
+      if (this.webApplication)
+        this.webApplication.debugPort = debugPort;
+      appControl.reply(data);
+    }
+  }
+
+  private checkInspectorCondition(appControl: NativeWRTjs.AppControl) {
+    let bundleDebug = (appControl.getData('__AUL_DEBUG__') === "1");
+    return (bundleDebug || this.inspectorEnabledByVconf);
+  }
+
+  private setCookiePath() {
+    this.setCookiePath = () => {}; // call once
+    console.log('setCookiePath');
+
+    // FIX ME : It must be supplemented to set a specific path
+    wrt.setCookiePath();
+  }
+
+  private handleKeyEvents(key: string) {
+    let valid = false;
+    console.log(key + ' is pressed');
+    switch(key) {
+      case "ArrowUp":
+      case "Up":
+      case "ArrowDown":
+      case "Down":
+        valid = true;
+        break;
+      default:
+        console.log('No handler for the key ' + key);
+        break;
+    }
+    if (valid)
+      this.webApplication?.keyEvent(key);
+  }
+}
+
+new Runtime();
diff --git a/wrt_app/src/web_application.js b/wrt_app/src/web_application.js
deleted file mode 100644 (file)
index 46d83c7..0000000
+++ /dev/null
@@ -1,514 +0,0 @@
-/*
- * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
- *
- *    Licensed under the Apache License, Version 2.0 (the "License");
- *    you may not use this file except in compliance with the License.
- *    You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS,
- *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *    See the License for the specific language governing permissions and
- *    limitations under the License.
- */
-
-'use strict';
-
-const { app, protocol } = require('electron');
-const wrt = require('../browser/wrt');
-const WRTWebContents = require('../browser/wrt_web_contents');
-const WRTWindow = require('../browser/wrt_window');
-
-class WebApplication {
-    constructor(options) {
-        this.initialize(options);
-        this.createMainWindow(options);
-    }
-    initialize(options) {
-        this.pendingID = 0;
-        this.pendingCallbacks = new Map();
-        this.windowList = [];
-        this.backgroundSupport = wrt.getBackgroundSupport();
-        this.debugPort = 0;
-        this.inspectorSrc = '';
-        if (options.launchMode == 'backgroundAtStartup') {
-            console.log('backgroundAtStartup');
-            this.preloadStatus = 'preload';
-        } else {
-            this.preloadStatus = 'none';
-        }
-        if (options.launchMode == 'backgroundExecution') {
-            console.log('backgroundExecution');
-            this.backgroundExecution = true;
-        } else {
-            this.backgroundExecution = false;
-        }
-        this.accessiblePath = (wrt.tv ? wrt.tv.getAccessiblePath() : null);
-        this.isAlwaysReload = (wrt.tv ? wrt.tv.isAlwaysReload() : false);
-        this.multitaskingSupport = (wrt.tv ? wrt.tv.getMultitaskingSupport() : true);
-        this.defaultBackgroundColor = (wrt.tv ? '#0000' :
-            ((wrt.getPlatformType() === "product_wearable") ? '#000' : '#FFF'));
-        this.defaultTransparent = (wrt.tv ? true : false);
-        this.loadFinished = false;
-        this.runningStatus = 'none';
-        this.addonEmitter = null;
-
-        let self = this;
-        app.on('browser-window-created', function(event, window) {
-            if (self.windowList.length > 0)
-                self.windowList[self.windowList.length - 1].hide();
-            self.windowList.push(window);
-            console.log(`window created : #${self.windowList.length}`);
-
-            window.on('closed', function() {
-                console.log(`window closed : #${self.windowList.length}`);
-                let index = self.windowList.indexOf(window);
-                self.windowList.splice(index, 1);
-                if (index === self.windowList.length && self.windowList.length > 0)
-                    self.windowList[self.windowList.length - 1].show();
-            });
-        });
-        app.on('web-contents-created', function(event, webContents) {
-            webContents.on('crashed', function() {
-                console.error('webContents crashed');
-                app.exit(100);
-            });
-            webContents.session.setPermissionRequestHandler(function(webContents, permission, callback) {
-                console.log(`handlePermissionRequests for ${permission}`);
-                if (permission === 'notifications') {
-                    if (!self.notificationPermissionMap)
-                      self.notificationPermissionMap = new Map();
-                    else if (self.notificationPermissionMap.has(webContents)) {
-                      process.nextTick(callback, self.notificationPermissionMap.get(webContents));
-                      return;
-                    }
-                    const id = ++self.pendingID;
-                    console.log(`Raising a notification permission request with id: ${id}`);
-                    self.pendingCallbacks.set(id, (result) => {
-                      self.notificationPermissionMap.set(webContents, result);
-                      callback(result);
-                    });
-                    wrt.handleNotificationPermissionRequest(id, webContents);
-                } else if (permission === 'media') {
-                    const id = ++self.pendingID;
-                    console.log(`Raising a media permission request with id: ${id}`);
-                    self.pendingCallbacks.set(id, callback);
-                    wrt.handleMediaPermissionRequest(id, webContents);
-                } else if (permission === 'geolocation') {
-                    const id = ++self.pendingID;
-                    console.log(`Raising a geolocation permission request with id: ${id}`);
-                    self.pendingCallbacks.set(id, callback);
-                    wrt.handleGeolocationPermissionRequest(id, webContents);
-                } else {
-                    /* electron by default allows permission for all if no request handler
-                       is there; so granting permission only temporarily to not have any
-                       side effects */
-                    callback(true);
-                }
-            });
-        });
-        app.on('certificate-error', function(event, webContents, url, error, certificate, callback) {
-            console.log('A certificate error has occurred');
-            event.preventDefault();
-            if (certificate.data) {
-                const id = ++self.pendingID;
-                console.log(`Raising a certificate error response with id: ${id}`);
-                self.pendingCallbacks.set(id, callback);
-                wrt.handleCertificateError(id, webContents, certificate.data, url, error);
-            } else {
-                console.log('Certificate could not be opened');
-                callback(false);
-            }
-        });
-        app.on('login', function(event, webContents, request, authInfo, callback) {
-            console.log(`Login info is required, isproxy: ${authInfo.isProxy}`);
-            event.preventDefault();
-            let usrname = '';
-            let passwd = '';
-            if (wrt.tv && authInfo.isProxy) {
-                let vconfProxy = wrt.tv.getProxy();
-                if (vconfProxy) {
-                    let proxyInfo = new URL(vconfProxy);
-                    usrname = proxyInfo.username;
-                    passwd = proxyInfo.password;
-                }
-                if (usrname && passwd) {
-                    callback(usrname, passwd);
-                } else {
-                    console.log('Login, but usrname and passwd is empty!!!');
-                    callback();
-                }
-            } else {
-                const id = ++self.pendingID;
-                console.log(`Raising a login info request with id: ${id}`);
-                self.pendingCallbacks.set(id, callback);
-                wrt.handleAuthRequest(id, webContents);
-            }
-        });
-        if (this.accessiblePath) {
-            console.log(`accessiblePath: ${this.accessiblePath}`);
-            protocol.interceptFileProtocol('file', (request, callback) => {
-                if (request.url) {
-                    let access_path, parsed_info = new URL(request.url);
-                    access_path = parsed_info.host + decodeURI(parsed_info.pathname);
-                    console.log(`check path: : ${access_path}`);
-                    for (let p in self.accessiblePath) {
-                        if (access_path.startsWith(self.accessiblePath[p])) {
-                            callback(access_path);
-                            return;
-                        }
-                    }
-                    if (access_path.indexOf("/shared/res/") > -1) {
-                        callback(access_path);
-                        return;
-                    }
-                    else {
-                        console.log(`invalid accesspath: ${access_path}`);
-                        callback(403);
-                    }
-                } else {
-                    console.log('request url is empty');
-                    callback(403);
-                }
-            }, (error) => {
-                console.log(error);
-            });
-        }
-        wrt.on('permission-response', function(event, id, result) {
-            console.log(`permission-response for ${id} is ${result}`);
-            let callback = self.pendingCallbacks.get(id);
-            if (typeof callback === 'function') {
-                console.log('calling permission response callback');
-                callback(result);
-                self.pendingCallbacks.delete(id);
-            }
-        });
-        wrt.on('auth-response', function(event, id, submit, user, passwd) {
-            let callback = self.pendingCallbacks.get(id);
-            if (typeof callback === 'function') {
-                console.log('calling auth response callback');
-                if (submit) {
-                    callback(user, passwd);
-                } else {
-                    callback();
-                }
-                self.pendingCallbacks.delete(id);
-            }
-        });
-        wrt.on('app-status-changed', function(event, status) {
-            console.log(`runningStatus: ${status}, ${self.loadFinished}`);
-            if (!wrt.tv) {
-                return;
-            }
-            self.runningStatus = status;
-            if (self.runningStatus === 'DialogClose' && self.inspectorSrc) {
-                console.log(`runningStatus is DialogClose, src is ${self.inspectorSrc}`);
-                self.mainWindow.loadURL(self.inspectorSrc);
-                self.inspectorSrc = '';
-            } else if (self.runningStatus == 'behind' && self.loadFinished) {
-                // TODO : Need to care this situation and decide to pass the addon event emitter to suspend()
-                self.suspend();
-            }
-        });
-    }
-    backgroundRunnable() {
-        return this.backgroundSupport || this.backgroundExecution;
-    }
-    getWindowOption(options) {
-        return {
-            fullscreen: false,
-            backgroundColor: this.defaultBackgroundColor,
-            transparent: this.defaultTransparent,
-            show: false,
-            webPreferences: {
-                nodeIntegration: options.isAddonAvailable,
-                nodeIntegrationInWorker: false
-            },
-            webContents: WRTWebContents.create(),
-            'web-preferences': {
-                'direct-write': true,
-                'subpixel-font-scaling': false,
-                'web-security': false
-            }
-        };
-    }
-    createMainWindow(options) {
-        let winopt = this.getWindowOption(options);
-        this.mainWindow = new WRTWindow(winopt);
-        if (options.devMode) {
-            this.mainWindow.webContents.openDevTools({
-                detached: true
-            });
-        }
-        this.initDisplayDelay(true);
-        let self = this;
-        this.mainWindow.once('ready-to-show', function() {
-            console.log('mainWindow ready-to-show');
-            if (self.showTimer)
-                clearTimeout(self.showTimer);
-            wrt.hideSplashScreen(0);
-            self.firstRendered = true;
-            if (self.preloadStatus == 'preload') {
-                self.preloadStatus = 'readyToShow';
-                console.log('preloading show is skipped!');
-                return;
-            }
-            self.show();
-        });
-        this.mainWindow.webContents.on('did-start-loading', function() {
-            console.log('webContents did-start-loading');
-            self.loadFinished = false;
-        });
-        this.mainWindow.webContents.on('did-finish-load', function() {
-            console.log('webContents did-finish-load');
-            self.loadFinished = true;
-            wrt.hideSplashScreen(1);
-            if (wrt.isIMEWebApp()) {
-                self.activateIMEWebHelperClient();
-            } else if (wrt.tv) {
-                if (self.inspectorSrc) {
-                    self.showInspectorGuide();
-                } else {
-                    self.suspendByStatus();
-                }
-            }
-            self.addonEmitter.emit('contentDidFinishLoad', self.mainWindow.id);
-        });
-    }
-    initDisplayDelay(firstLaunch) {
-        // TODO: On 6.0, this causes a black screen on relaunch
-        if (firstLaunch)
-            this.firstRendered = false;
-        this.suspended = false;
-        if (this.showTimer)
-            clearTimeout(this.showTimer);
-        let splashShown = firstLaunch && wrt.showSplashScreen();
-        if (!splashShown && !wrt.tv) {
-            let self = this;
-            self.showTimer = setTimeout(() => {
-                if (!self.suspended) {
-                    console.log('FrameRendered not obtained from engine. To show window, timer fired');
-                    self.mainWindow.emit('ready-to-show');
-                }
-            }, 2000);
-        }
-        if (!firstLaunch && !this.backgroundRunnable())
-            this.mainWindow.setEnabled(true);
-    }
-    handleAppControlReload(src) {
-        console.log('WebApplication : handleAppControlReload');
-        this.closeWindows();
-        this.initDisplayDelay(false);
-        this.mainWindow.loadURL(src);
-    }
-    suspendByStatus() {
-        if (this.preloadStatus === 'readyToShow' ||
-            this.preloadStatus === 'preload' ||
-            this.runningStatus === 'behind') {
-            console.log('WebApplication : suspendByStatus');
-            console.log(`preloadStatus: ${this.preloadStatus}, runningStatus: ${this.runningStatus}`);
-            // TODO : Need to care this situation and decide to pass the addon event emitter to suspend()
-            this.suspend();
-            if (this.runningStatus !== 'behind')
-                wrt.tv.notifyAppStatus('preload');
-        }
-    }
-    showInspectorGuide() {
-        console.log('WebApplication : showInspectorGuide');
-        this.showInspectorGuide = () => {}; // call once
-        let message = this.debugPort.toString() +
-            "\r\nFast RWI is used, [about:blank] is loaded fist instead of \r\n[" +
-            this.inspectorSrc +
-            "]\r\nClick OK button will start the real loading.\r\nNotes:\r\nPlease " +
-            "connect to RWI in PC before click OK button.\r\nThen you can get " +
-            "network log from the initial loading.\r\nPlease click Record button " +
-            "in Timeline panel in PC before click OK button,\r\nThen you can get " +
-            "profile log from the initial loading."
-        wrt.tv.showDialog(this.mainWindow.webContents, message);
-
-        if (this.preloadStatus !== 'none') {
-            setTimeout(() => {
-                wrt.tv.cancelDialogs(this.mainWindow.webContents);
-            }, 5000);
-        }
-    }
-    suspend() {
-        console.log('WebApplication : suspend');
-        if (this.addonEmitter) {
-            console.log('WebApplication : suspend - Found event emitter');
-            this.addonEmitter.emit('lcSuspend', this.mainWindow.id);
-        } else {
-            console.log('WebApplication : suspend - Invalid event emitter');
-        }
-        this.suspended = true;
-        this.windowList[this.windowList.length - 1].hide();
-        this.flushData();
-        if (!this.backgroundRunnable()) {
-            if (!this.multitaskingSupport) {
-                // FIXME : terminate app after visibilitychange event handling
-                setTimeout(() => {
-                    console.log('multitasking is not supported; quitting app')
-                    app.quit();
-                }, 1000);
-            } else {
-                this.windowList.forEach((window) => window.setEnabled(false));
-            }
-        }
-    }
-    resume() {
-        console.log('WebApplication : resume');
-        this.suspended = false;
-        if (this.addonEmitter) {
-            console.log('WebApplication : resume - Found event emitter');
-            this.addonEmitter.emit('lcResume', this.mainWindow.id);
-        } else {
-            console.log('WebApplication : resume - Invalid event emitter');
-        }
-
-        if (!this.firstRendered) {
-            console.log('WebApplication : resume firstRendered is false');
-            return;
-        }
-        if (!this.backgroundRunnable()) {
-            this.windowList.forEach((window) => window.setEnabled(true));
-        }
-        this.windowList[this.windowList.length - 1].show();
-    }
-    finalize() {
-        console.log('WebApplication : finalize');
-        if (wrt.tv) {
-            this.inspectorSrc = '';
-            wrt.tv.cancelDialogs(this.mainWindow.webContents);
-        }
-        this.flushData();
-        if (this.debugPort) {
-            console.log('stop inspector server');
-            this.debugPort = 0;
-            wrt.stopInspectorServer();
-        }
-        this.windowList.forEach((window) => {
-            window.removeAllListeners();
-        });
-    }
-    quit() {
-        console.log('WebApplication : quit');
-        if (this.addonEmitter) {
-            console.log('WebApplication : quit - Found event emitter');
-            this.addonEmitter.emit('lcQuit', this.mainWindow.id);
-        } else {
-            console.log('WebApplication : quit - Invalid event emitter');
-        }
-    }
-    flushData() {
-        console.log('WebApplication : FlushData');
-        if (wrt.tv) {
-            wrt.tv.flushCookie();
-            this.windowList.forEach((window) => window.webContents.session.flushStorageData());
-        }
-    }
-    sendAppControlEvent() {
-        const kAppControlEventScript =
-            '(function(){' +
-              'var __event = document.createEvent("CustomEvent");' +
-              '__event.initCustomEvent("appcontrol", true, true, null);' +
-              'document.dispatchEvent(__event);' +
-              'for (var i=0; i < window.frames.length; i++)' +
-                'window.frames[i].document.dispatchEvent(__event);' +
-            '})()';
-        wrt.executeJS(this.mainWindow.webContents, kAppControlEventScript);
-    }
-    activateIMEWebHelperClient() {
-        console.log('webApplication : activateIMEWebHelperClient');
-        const kImeActivateFunctionCallScript =
-            '(function(){WebHelperClient.impl.activate();})()';
-        wrt.executeJS(this.mainWindow.webContents, kImeActivateFunctionCallScript);
-    }
-    show() {
-        console.log('WebApplication : show');
-        this.preloadStatus = 'none';
-        if (this.backgroundExecution) {
-            console.log('skip showing while backgroundExecution mode');
-        } else if (!this.mainWindow.isVisible()) {
-            console.log('show window');
-            this.mainWindow.show();
-        }
-    }
-    closeWindows() {
-        if (wrt.tv)
-            wrt.tv.clearSurface(this.mainWindow.webContents);
-        this.windowList.forEach((window) => {
-            if (window != this.mainWindow)
-                window.destroy();
-        });
-    }
-    keyEvent(key) {
-        console.log('WebApplication : keyEvent');
-        if (!this.addonEmitter) {
-            console.log('Invalid event emitter for key hook');
-            return;
-        }
-        console.log('key event is ' + key);
-        switch(key) {
-            case "ArrowUp":
-            case "Up":
-                this.addonEmitter.emit('hwUpkey', this.mainWindow.id);
-                break;
-            case "ArrowDown":
-            case "Down":
-                this.addonEmitter.emit('hwDownkey', this.mainWindow.id);
-                break;
-            default:
-                console.log('No handler for ' + key);
-                break;
-        }
-    }
-    prelaunch(origURL) {
-        console.log('WebApplication : prelaunch');
-        if (this.addonEmitter) {
-            console.log('WebApplication : prelaunch - Found event emitter');
-            this.addonEmitter.emit('lcPrelaunch', this.mainWindow.id, origURL);
-        } else {
-            console.log('WebApplication : prelaunch - Invalid event emitter');
-        }
-    }
-    lowMemory() {
-        console.log('WebApplication : lowMemory to clearcache');
-        if (wrt.tv) {
-            this.windowList.forEach((window) => {
-                //clear webframe cache
-                wrt.tv.clearWebCache(window.webContents);
-                window.webContents.session.clearCache(function() {
-                    console.log('clear session Cache complete');
-                })
-            });
-        }
-    }
-    ambientTick() {
-        const kAmbientTickEventScript =
-            '(function(){' +
-            'var __event = document.createEvent(\"CustomEvent\");' +
-            '__event.initCustomEvent(\"timetick\", true, true);' +
-            'document.dispatchEvent(__event);' +
-            'for (var i=0; i < window.frames.length; i++)' +
-            '{ window.frames[i].document.dispatchEvent(__event); }' +
-            '})()';
-        wrt.executeJS(this.mainWindow.webContents, kAmbientTickEventScript);
-    }
-    ambientChanged(ambient_mode) {
-        const kAmbientChangedEventScript =
-            '(function(){' +
-            'var __event = document.createEvent(\"CustomEvent\");' +
-            'var __detail = {};' +
-            '__event.initCustomEvent(\"ambientmodechanged\",true,true,__detail);' +
-            '__event.detail.ambientMode = ' +
-            (ambient_mode ? 'true' : 'false') + ';' +
-            'document.dispatchEvent(__event);' +
-            'for (var i=0; i < window.frames.length; i++)' +
-            '{ window.frames[i].document.dispatchEvent(__event); }' +
-            '})()';
-        wrt.executeJS(this.mainWindow.webContents, kAmbientChangedEventScript);
-    }
-}
-module.exports = WebApplication;
diff --git a/wrt_app/src/web_application.ts b/wrt_app/src/web_application.ts
new file mode 100644 (file)
index 0000000..61bbfd7
--- /dev/null
@@ -0,0 +1,502 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+'use strict';
+
+import { app, protocol } from 'electron';
+import { wrt } from '../browser/wrt';
+import * as WRTWebContents from '../browser/wrt_web_contents';
+import { WRTWindow } from '../browser/wrt_window';
+import { addonManager } from './addon_manager';
+
+export class WebApplication {
+  accessiblePath?: string[];
+  backgroundExecution: boolean;
+  defaultBackgroundColor: string;
+  defaultTransparent: boolean;
+  isAlwaysReload: boolean;
+  mainWindow: Electron.BrowserWindow;
+  multitaskingSupport: boolean;
+  notificationPermissionMap?: Map<Electron.WebContents, boolean>;
+  preloadStatus: string;
+  showTimer?: NodeJS.Timeout;
+
+  backgroundSupport = wrt.getBackgroundSupport();
+  debugPort = 0;
+  firstRendered = false;
+  inspectorSrc = '';
+  loadFinished = false;
+  pendingCallbacks: Map<number, any> = new Map();
+  pendingID = 0;
+  runningStatus = 'none';
+  suspended = false;
+  windowList: Electron.BrowserWindow[] = [];
+
+  constructor(options: RuntimeOption) {
+    if (options.launchMode == 'backgroundAtStartup') {
+      console.log('backgroundAtStartup');
+      this.preloadStatus = 'preload';
+    } else {
+      this.preloadStatus = 'none';
+    }
+    if (options.launchMode == 'backgroundExecution') {
+      console.log('backgroundExecution');
+      this.backgroundExecution = true;
+    } else {
+      this.backgroundExecution = false;
+    }
+    this.accessiblePath = wrt.tv?.getAccessiblePath();
+    this.isAlwaysReload = (wrt.tv ? wrt.tv.isAlwaysReload() : false);
+    this.multitaskingSupport = (wrt.tv ? wrt.tv.getMultitaskingSupport() : true);
+    this.defaultBackgroundColor = (wrt.tv ? '#0000' :
+        ((wrt.getPlatformType() === "product_wearable") ? '#000' : '#FFF'));
+    this.defaultTransparent = (wrt.tv ? true : false);
+
+    this.setupEventListener(options);
+
+    this.mainWindow = new WRTWindow(this.getWindowOption(options));
+    this.initDisplayDelay(true);
+    this.setupMainWindowEventListener();
+  }
+
+  private setupEventListener(options: RuntimeOption) {
+    app.on('browser-window-created', (event, window) => {
+      if (this.windowList.length > 0)
+        this.windowList[this.windowList.length - 1].hide();
+      this.windowList.push(window);
+      console.log(`window created : #${this.windowList.length}`);
+
+      window.on('closed', () => {
+        console.log(`window closed : #${this.windowList.length}`);
+        let index = this.windowList.indexOf(window);
+        this.windowList.splice(index, 1);
+        if (index === this.windowList.length && this.windowList.length > 0)
+          this.windowList[this.windowList.length - 1].show();
+      });
+    });
+    app.on('web-contents-created', (event, webContents) => {
+      webContents.on('crashed', function() {
+        console.error('webContents crashed');
+        app.exit(100);
+      });
+      webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
+        console.log(`handlePermissionRequests for ${permission}`);
+        if (permission === 'notifications') {
+          if (!this.notificationPermissionMap)
+            this.notificationPermissionMap = new Map();
+          else if (this.notificationPermissionMap.has(webContents)) {
+            process.nextTick(callback, this.notificationPermissionMap.get(webContents));
+            return;
+          }
+          const id = ++this.pendingID;
+          console.log(`Raising a notification permission request with id: ${id}`);
+          this.pendingCallbacks.set(id, (result: boolean) => {
+            (this.notificationPermissionMap as Map<Electron.WebContents, boolean>).set(webContents, result);
+            callback(result);
+          });
+          wrt.handleNotificationPermissionRequest(id, webContents);
+        } else if (permission === 'media') {
+          const id = ++this.pendingID;
+          console.log(`Raising a media permission request with id: ${id}`);
+          this.pendingCallbacks.set(id, callback);
+          wrt.handleMediaPermissionRequest(id, webContents);
+        } else if (permission === 'geolocation') {
+          const id = ++this.pendingID;
+          console.log(`Raising a geolocation permission request with id: ${id}`);
+          this.pendingCallbacks.set(id, callback);
+          wrt.handleGeolocationPermissionRequest(id, webContents);
+        } else {
+          /* electron by default allows permission for all if no request handler
+             is there; so granting permission only temporarily to not have any
+             side effects */
+          callback(true);
+        }
+      });
+    });
+    app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
+      console.log('A certificate error has occurred');
+      event.preventDefault();
+      if (certificate.data) {
+        const id = ++this.pendingID;
+        console.log(`Raising a certificate error response with id: ${id}`);
+        this.pendingCallbacks.set(id, callback);
+        wrt.handleCertificateError(id, webContents, certificate.data, url, error);
+      } else {
+        console.log('Certificate could not be opened');
+        callback(false);
+      }
+    });
+    app.on('login', (event, webContents, request, authInfo, callback) => {
+      console.log(`Login info is required, isproxy: ${authInfo.isProxy}`);
+      event.preventDefault();
+      let usrname = '';
+      let passwd = '';
+      if (wrt.tv && authInfo.isProxy) {
+        let vconfProxy = wrt.tv.getProxy();
+        if (vconfProxy) {
+          let proxyInfo = new URL(vconfProxy);
+          usrname = proxyInfo.username;
+          passwd = proxyInfo.password;
+        }
+        if (usrname && passwd) {
+          callback(usrname, passwd);
+        } else {
+          console.log('Login, but usrname and passwd is empty!!!');
+          callback('', '');
+        }
+      } else {
+        const id = ++this.pendingID;
+        console.log(`Raising a login info request with id: ${id}`);
+        this.pendingCallbacks.set(id, callback);
+        wrt.handleAuthRequest(id, webContents);
+      }
+    });
+    if (this.accessiblePath) {
+      console.log(`accessiblePath: ${this.accessiblePath}`);
+      protocol.interceptFileProtocol('file', (request, callback) => {
+        if (request.url) {
+          let parsed_info = new URL(request.url);
+          let access_path = parsed_info.host + decodeURI(parsed_info.pathname);
+          console.log(`check path: : ${access_path}`);
+          for (let path of (this.accessiblePath as string[])) {
+            if (access_path.startsWith(path)) {
+              callback(access_path);
+              return;
+            }
+          }
+          if (access_path.indexOf("/shared/res/") > -1) {
+            callback(access_path);
+            return;
+          }
+          else {
+            console.log(`invalid accesspath: ${access_path}`);
+            (callback as any)(403);
+          }
+        } else {
+          console.log('request url is empty');
+          (callback as any)(403);
+        }
+      }, (error) => {
+        console.log(error);
+      });
+    }
+    wrt.on('permission-response', (event, id, result) => {
+      console.log(`permission-response for ${id} is ${result}`);
+      let callback = this.pendingCallbacks.get(id);
+      if (typeof callback === 'function') {
+        console.log('calling permission response callback');
+        callback(result);
+        this.pendingCallbacks.delete(id);
+      }
+    });
+    wrt.on('auth-response', (event, id, submit, user, password) => {
+      let callback = this.pendingCallbacks.get(id);
+      if (typeof callback === 'function') {
+        console.log('calling auth response callback');
+        if (submit)
+          callback(user, password);
+        else
+          callback();
+        this.pendingCallbacks.delete(id);
+      }
+    });
+    wrt.on('app-status-changed', (event, status) => {
+      console.log(`runningStatus: ${status}, ${this.loadFinished}`);
+      if (!wrt.tv)
+        return;
+      this.runningStatus = status;
+      if (this.runningStatus === 'DialogClose' && this.inspectorSrc) {
+        console.log(`runningStatus is DialogClose, src is ${this.inspectorSrc}`);
+        this.mainWindow.loadURL(this.inspectorSrc);
+        this.inspectorSrc = '';
+      } else if (this.runningStatus == 'behind' && this.loadFinished) {
+        // TODO : Need to care this situation and decide to pass the addon event emitter to suspend()
+        this.suspend();
+      }
+    });
+  }
+
+  private getWindowOption(options: RuntimeOption): NativeWRTjs.WRTWindowConstructorOptions {
+    return {
+      fullscreen: false,
+      backgroundColor: this.defaultBackgroundColor,
+      transparent: this.defaultTransparent,
+      show: false,
+      webPreferences: {
+        nodeIntegration: options.isAddonAvailable,
+        nodeIntegrationInWorker: false
+      },
+      webContents: WRTWebContents.create(),
+    };
+  }
+
+  private setupMainWindowEventListener() {
+    this.mainWindow.once('ready-to-show', () => {
+      console.log('mainWindow ready-to-show');
+      if (this.showTimer)
+        clearTimeout(this.showTimer);
+      wrt.hideSplashScreen(0);
+      this.firstRendered = true;
+      if (this.preloadStatus == 'preload') {
+        this.preloadStatus = 'readyToShow';
+        console.log('preloading show is skipped!');
+        return;
+      }
+      this.show();
+    });
+    this.mainWindow.webContents.on('did-start-loading', () => {
+      console.log('webContents did-start-loading');
+      this.loadFinished = false;
+    });
+    this.mainWindow.webContents.on('did-finish-load', () => {
+      console.log('webContents did-finish-load');
+      this.loadFinished = true;
+      wrt.hideSplashScreen(1);
+      if (wrt.isIMEWebApp()) {
+        this.activateIMEWebHelperClient();
+      } else if (wrt.tv) {
+        if (this.inspectorSrc)
+          this.showInspectorGuide();
+        else
+          this.suspendByStatus();
+      }
+      addonManager.emit('contentDidFinishLoad', this.mainWindow.id);
+    });
+  }
+
+  private initDisplayDelay(firstLaunch: boolean) {
+    // TODO: On 6.0, this causes a black screen on relaunch
+    if (firstLaunch)
+      this.firstRendered = false;
+    this.suspended = false;
+    if (this.showTimer)
+      clearTimeout(this.showTimer);
+    let splashShown = firstLaunch && wrt.showSplashScreen();
+    if (!splashShown && !wrt.tv) {
+      this.showTimer = setTimeout(() => {
+        if (!this.suspended) {
+          console.log('FrameRendered not obtained from engine. To show window, timer fired');
+          this.mainWindow.emit('ready-to-show');
+        }
+      }, 2000);
+    }
+    if (!firstLaunch && !this.backgroundRunnable())
+      this.mainWindow.setEnabled(true);
+  }
+
+  private backgroundRunnable(): boolean {
+    return this.backgroundSupport || this.backgroundExecution;
+  }
+
+  handleAppControlReload(url: string) {
+    console.log('WebApplication : handleAppControlReload');
+    this.closeWindows();
+    this.initDisplayDelay(false);
+    this.mainWindow.loadURL(url);
+  }
+
+  private suspendByStatus() {
+    if (this.preloadStatus === 'readyToShow' ||
+        this.preloadStatus === 'preload' ||
+        this.runningStatus === 'behind') {
+      console.log('WebApplication : suspendByStatus');
+      console.log(`preloadStatus: ${this.preloadStatus}, runningStatus: ${this.runningStatus}`);
+      // TODO : Need to care this situation and decide to pass the addon event emitter to suspend()
+      this.suspend();
+      if (this.runningStatus !== 'behind')
+        (wrt.tv as NativeWRTjs.TVExtension).notifyAppStatus('preload');
+    }
+  }
+
+  private showInspectorGuide() {
+    console.log('WebApplication : showInspectorGuide');
+    this.showInspectorGuide = () => {}; // call once
+    const message = `${this.debugPort.toString()}
+Fast RWI is used, [about:blank] is loaded fist instead of
+[${this.inspectorSrc}]
+Click OK button will start the real loading.
+Notes:
+Please connect to RWI in PC before click OK button.
+Then you can get network log from the initial loading.
+Please click Record button in Timeline panel in PC before click OK button,
+Then you can get profile log from the initial loading.`;
+    let tv = wrt.tv as NativeWRTjs.TVExtension;
+    tv.showDialog(this.mainWindow.webContents, message);
+
+    if (this.preloadStatus !== 'none') {
+      setTimeout(() => {
+        tv.cancelDialogs(this.mainWindow.webContents);
+      }, 5000);
+    }
+  }
+
+  suspend() {
+    console.log('WebApplication : suspend');
+    addonManager.emit('lcSuspend', this.mainWindow.id);
+    this.suspended = true;
+    this.windowList[this.windowList.length - 1].hide();
+    this.flushData();
+    if (!this.backgroundRunnable()) {
+      if (!this.multitaskingSupport) {
+        // FIXME : terminate app after visibilitychange event handling
+        setTimeout(() => {
+          console.log('multitasking is not supported; quitting app')
+          app.quit();
+        }, 1000);
+      } else {
+        this.windowList.forEach((window) => window.setEnabled(false));
+      }
+    }
+  }
+
+  resume() {
+    console.log('WebApplication : resume');
+    this.suspended = false;
+    addonManager.emit('lcResume', this.mainWindow.id);
+
+    if (!this.firstRendered) {
+      console.log('WebApplication : resume firstRendered is false');
+      return;
+    }
+    if (!this.backgroundRunnable())
+      this.windowList.forEach((window) => window.setEnabled(true));
+    this.windowList[this.windowList.length - 1].show();
+  }
+
+  finalize() {
+    console.log('WebApplication : finalize');
+    if (wrt.tv) {
+      this.inspectorSrc = '';
+      wrt.tv.cancelDialogs(this.mainWindow.webContents);
+    }
+    this.flushData();
+    if (this.debugPort) {
+      console.log('stop inspector server');
+      this.debugPort = 0;
+      wrt.stopInspectorServer();
+    }
+    this.windowList.forEach((window) => window.removeAllListeners());
+  }
+
+  quit() {
+    console.log('WebApplication : quit');
+    addonManager.emit('lcQuit', this.mainWindow.id);
+  }
+
+  private flushData() {
+    console.log('WebApplication : FlushData');
+    if (wrt.tv) {
+      wrt.tv.flushCookie();
+      this.windowList.forEach((window) => window.webContents.session.flushStorageData());
+    }
+  }
+
+  sendAppControlEvent() {
+    const kAppControlEventScript = `(function(){
+  var __event = document.createEvent("CustomEvent");
+  __event.initCustomEvent("appcontrol", true, true, null);
+  document.dispatchEvent(__event);
+  for (var i=0; i < window.frames.length; i++)
+    window.frames[i].document.dispatchEvent(__event);
+})()`;
+    wrt.executeJS(this.mainWindow.webContents, kAppControlEventScript);
+  }
+
+  private activateIMEWebHelperClient() {
+    console.log('webApplication : activateIMEWebHelperClient');
+    const kImeActivateFunctionCallScript =
+        '(function(){WebHelperClient.impl.activate();})()';
+    wrt.executeJS(this.mainWindow.webContents, kImeActivateFunctionCallScript);
+  }
+
+  show() {
+    console.log('WebApplication : show');
+    this.preloadStatus = 'none';
+    if (this.backgroundExecution) {
+      console.log('skip showing while backgroundExecution mode');
+    } else if (!this.mainWindow.isVisible()) {
+      console.log('show window');
+      this.mainWindow.show();
+    }
+  }
+
+  private closeWindows() {
+    wrt.tv?.clearSurface(this.mainWindow.webContents);
+    this.windowList.forEach((window) => {
+      if (window != this.mainWindow)
+        window.destroy();
+    });
+  }
+
+  keyEvent(key: string) {
+    console.log(`WebApplication : keyEvent[${key}]`);
+    switch(key) {
+      case "ArrowUp":
+      case "Up":
+        addonManager.emit('hwUpkey', this.mainWindow.id);
+        break;
+      case "ArrowDown":
+      case "Down":
+        addonManager.emit('hwDownkey', this.mainWindow.id);
+        break;
+      default:
+        console.log('No handler for ' + key);
+        break;
+    }
+  }
+
+  prelaunch(url: string) {
+    console.log('WebApplication : prelaunch');
+    addonManager.emit('lcPrelaunch', this.mainWindow.id, url);
+  }
+
+  lowMemory() {
+    console.log('WebApplication : lowMemory to clearcache');
+    if (!wrt.tv)
+      return;
+    this.windowList.forEach((window) => {
+      //clear webframe cache
+      (wrt.tv as NativeWRTjs.TVExtension).clearWebCache(window.webContents);
+      window.webContents.session.clearCache(function() {
+        console.log('clear session Cache complete');
+      })
+    });
+  }
+
+  ambientTick() {
+    const kAmbientTickEventScript = `(function(){
+  var __event = document.createEvent("CustomEvent");
+  __event.initCustomEvent("timetick", true, true);
+  document.dispatchEvent(__event);
+  for (var i=0; i < window.frames.length; i++)
+    window.frames[i].document.dispatchEvent(__event);
+})()`;
+    wrt.executeJS(this.mainWindow.webContents, kAmbientTickEventScript);
+  }
+
+  ambientChanged(ambient_mode: boolean) {
+    const kAmbientChangedEventScript = `(function(){
+  var __event = document.createEvent(\"CustomEvent\");
+  var __detail = {};
+  __event.initCustomEvent(\"ambientmodechanged\",true,true,__detail);
+  __event.detail.ambientMode = ${ambient_mode ? 'true' : 'false'};
+  document.dispatchEvent(__event);
+  for (var i=0; i < window.frames.length; i++)
+    window.frames[i].document.dispatchEvent(__event);
+})()`;
+    wrt.executeJS(this.mainWindow.webContents, kAmbientChangedEventScript);
+  }
+}
diff --git a/wrtjs.d.ts b/wrtjs.d.ts
new file mode 100644 (file)
index 0000000..c1a1449
--- /dev/null
@@ -0,0 +1,24 @@
+interface RuntimeOption {
+  launchMode: string;
+  isAddonAvailable: boolean;
+}
+
+interface Console {
+  logd(message?: any, ...optionalParams: any[]): void;
+  logv(message?: any, ...optionalParams: any[]): void;
+  loge(message?: any, ...optionalParams: any[]): void;
+}
+
+declare namespace NodeJS {
+  interface Process extends EventEmitter {
+    _linkedBinding(name: string): any;
+    binding(name: string): any;
+    wrtBinding(name: string): any;
+  }
+}
+
+declare namespace NodeJS {
+  interface Global {
+    [key: string]: any;
+  }
+}