From: SangYong Park Date: Thu, 28 May 2020 23:04:54 +0000 (+0900) Subject: Apply typescript X-Git-Tag: submit/tizen/20200709.080654~1 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=28a81ff5b6bb4180eddc75747651f4f83fe10819;p=platform%2Fframework%2Fweb%2Fwrtjs.git Apply typescript 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 --- diff --git a/.gitignore b/.gitignore index 5188e93f..e6ce948b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ /tizen/build/gbs.conf +out/ +*list +wrtjs*manifest + diff --git a/packaging/wrtjs.spec b/packaging/wrtjs.spec index 6e3d0b27..7d748d43 100755 --- a/packaging/wrtjs.spec +++ b/packaging/wrtjs.spec @@ -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 index 00000000..50deefaa --- /dev/null +++ b/tsconfig.json @@ -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" + ] +} diff --git a/wrt_app/addon/browser/addonapi.js b/wrt_app/addon/browser/addonapi.js deleted file mode 100644 index ea6340b9..00000000 --- a/wrt_app/addon/browser/addonapi.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -const common = require('../common/addonapi'); -common.defineProperties(exports); - -const moduleList = require('./module-list'); - -for (const module of moduleList) { - Object.defineProperty(exports, module.name, { - get: common.memoizedGetter(() => require(`./modules/${module.file}`)) - }); -} diff --git a/wrt_app/addon/browser/addonapi.ts b/wrt_app/addon/browser/addonapi.ts new file mode 100644 index 00000000..73512ede --- /dev/null +++ b/wrt_app/addon/browser/addonapi.ts @@ -0,0 +1,11 @@ +'use strict'; + +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}`)) + }); +} diff --git a/wrt_app/addon/browser/module-list.js b/wrt_app/addon/browser/module-list.js deleted file mode 100644 index 63cf2c83..00000000 --- a/wrt_app/addon/browser/module-list.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict' - -// Browser side add-on modules, please sort alphabetically -module.exports = [ - { name: 'messaging', file: 'messaging' }, - { name: 'window', file: 'window' } -] diff --git a/wrt_app/addon/browser/module-list.ts b/wrt_app/addon/browser/module-list.ts new file mode 100644 index 00000000..0b49a9b6 --- /dev/null +++ b/wrt_app/addon/browser/module-list.ts @@ -0,0 +1,7 @@ +'use strict' + +// Browser side add-on modules, please sort alphabetically +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 index 26511c72..00000000 --- a/wrt_app/addon/browser/modules/messaging.js +++ /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 index 00000000..779480cf --- /dev/null +++ b/wrt_app/addon/browser/modules/messaging.ts @@ -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 index 44e779da..00000000 --- a/wrt_app/addon/browser/modules/window.js +++ /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 index 00000000..6cbd37c3 --- /dev/null +++ b/wrt_app/addon/browser/modules/window.ts @@ -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(); + } +}; diff --git a/wrt_app/addon/common/addonapi.js b/wrt_app/addon/common/addonapi.js deleted file mode 100644 index 96a7c8cb..00000000 --- a/wrt_app/addon/common/addonapi.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -const moduleList = require('./module-list'); - -exports.memoizedGetter = (getter) => { - let memoizedValue = null; - - return () => { - if (memoizedValue === null) { - memoizedValue = getter(); - } - return memoizedValue; - } -} - -exports.defineProperties = function (targetExports) { - const descriptors = {}; - for (const module of moduleList) { - descriptors[module.name] = { - get: exports.memoizedGetter(() => require(`./modules/${module.file}`)) - } - } - return Object.defineProperties(targetExports, descriptors); -} diff --git a/wrt_app/addon/common/addonapi.ts b/wrt_app/addon/common/addonapi.ts new file mode 100644 index 00000000..b9b11c58 --- /dev/null +++ b/wrt_app/addon/common/addonapi.ts @@ -0,0 +1,24 @@ +'use strict'; + +import moduleList from './module-list'; + +export let memoizedGetter = (getter: () => any) => { + let memoizedValue: any = null; + + return () => { + if (memoizedValue === null) { + memoizedValue = getter(); + } + return memoizedValue; + } +} + +export let defineProperties = function (targetExports: Object) { + const descriptors: PropertyDescriptorMap = {}; + for (const module of moduleList) { + descriptors[module.name] = { + get: exports.memoizedGetter(() => require(`./modules/${module.file}`)) + } + } + return Object.defineProperties(targetExports, descriptors); +} diff --git a/wrt_app/addon/common/module-list.js b/wrt_app/addon/common/module-list.js deleted file mode 100644 index fc160714..00000000 --- a/wrt_app/addon/common/module-list.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -// Common add-on modules, please sort alphabetically -module.exports = [ - { name: 'options', file: 'options' } -]; diff --git a/wrt_app/addon/common/module-list.ts b/wrt_app/addon/common/module-list.ts new file mode 100644 index 00000000..4aebfcb7 --- /dev/null +++ b/wrt_app/addon/common/module-list.ts @@ -0,0 +1,6 @@ +'use strict'; + +// Common add-on modules, please sort alphabetically +export default [ + { name: 'options', file: 'options' } +]; diff --git a/wrt_app/addon/common/modules/options.js b/wrt_app/addon/common/modules/options.js deleted file mode 100644 index f6304d93..00000000 --- a/wrt_app/addon/common/modules/options.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const utils = require('../utils'); - -const REPO_PATH = require('path').join(require('os').homedir(), 'data/electron/runtime_addon/'); -const DB_FILE = '_db.json'; - -function getDBPath() { - const addon_name = utils.getPackageID(); - if (addon_name) { - const DB_PATH = REPO_PATH + addon_name + DB_FILE; - console.log(`DB_PATH: ${DB_PATH}`); - return DB_PATH; - } else { - console.error('Package ID not found'); - return ''; - } -} - -function get(key) { - const DB_PATH = getDBPath(); - if (!DB_PATH) - return undefined; - try { - const fileContents = fs.readFileSync(DB_PATH); - const jsonObject = JSON.parse(fileContents); - console.log(`Read value: ${JSON.stringify(jsonObject)}`); - return jsonObject[key]; - } catch (err) { - console.log(`An error has occurred: ${err}`); - return undefined; - } -} - -function set(key, value) { - const DB_PATH = getDBPath(); - if (!DB_PATH) - return false; - try { - let jsonObject; - try { - const fileContents = fs.readFileSync(DB_PATH); - jsonObject = JSON.parse(fileContents); - console.log(`Current value: ${JSON.stringify(jsonObject)}`); - } catch { - console.log(`Add-on DB file doesn't exist`); - jsonObject = {}; - } - jsonObject[key] = value; - console.log(`New value: ${JSON.stringify(jsonObject)}`); - const jsonString = JSON.stringify(jsonObject); - fs.writeFileSync(DB_PATH, jsonString); - return true; - } catch (err) { - console.log(`An error has occurred: ${err}`); - return false; - } -} - -function clearAll() { - const DB_PATH = getDBPath(); - if (!DB_PATH) - return false; - try { - fs.statSync(DB_PATH); - } catch (err) { - console.log(`An error has occurred: ${err}`); - return true; - } - try { - fs.unlinkSync(DB_PATH); - } catch (err) { - console.log(`An error has occurred: ${err}`); - return false; - } - return true; -} - -module.exports = { - clearAll, - get, - set -} \ No newline at end of file diff --git a/wrt_app/addon/common/modules/options.ts b/wrt_app/addon/common/modules/options.ts new file mode 100644 index 00000000..11bd9647 --- /dev/null +++ b/wrt_app/addon/common/modules/options.ts @@ -0,0 +1,80 @@ +'use strict'; + +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import utils from '../utils'; + +const REPO_PATH = path.join(os.homedir(), 'data/electron/runtime_addon/'); +const DB_FILE = '_db.json'; + +function getDBPath(): string { + const addon_name = utils.getPackageID(); + if (addon_name) { + const DB_PATH = REPO_PATH + addon_name + DB_FILE; + console.log(`DB_PATH: ${DB_PATH}`); + return DB_PATH; + } else { + console.error('Package ID not found'); + return ''; + } +} + +export function get(key: string) { + const DB_PATH = getDBPath(); + if (!DB_PATH) + return undefined; + try { + const fileContents = fs.readFileSync(DB_PATH, 'utf-8'); + const jsonObject = JSON.parse(fileContents); + console.log(`Read value: ${JSON.stringify(jsonObject)}`); + return jsonObject[key]; + } catch (err) { + console.log(`An error has occurred: ${err}`); + return undefined; + } +} + +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, 'utf-8'); + jsonObject = JSON.parse(fileContents); + console.log(`Current value: ${JSON.stringify(jsonObject)}`); + } catch { + console.log(`Add-on DB file doesn't exist`); + jsonObject = {}; + } + jsonObject[key] = value; + console.log(`New value: ${JSON.stringify(jsonObject)}`); + const jsonString = JSON.stringify(jsonObject); + fs.writeFileSync(DB_PATH, jsonString); + return true; + } catch (err) { + console.log(`An error has occurred: ${err}`); + return false; + } +} + +export function clearAll() { + const DB_PATH = getDBPath(); + if (!DB_PATH) + return false; + try { + fs.statSync(DB_PATH); + } catch (err) { + console.log(`An error has occurred: ${err}`); + return true; + } + try { + fs.unlinkSync(DB_PATH); + } catch (err) { + console.log(`An error has occurred: ${err}`); + return false; + } + return true; +} diff --git a/wrt_app/addon/common/utils.js b/wrt_app/addon/common/utils.js deleted file mode 100644 index d8f1c403..00000000 --- a/wrt_app/addon/common/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -const keyTerm = 'globalapps'; -const delimiter = '/'; - -function extractID(line) { - let keyTermAt = line.indexOf(keyTerm); - let delimiterAt = line.indexOf(delimiter, keyTermAt); - return line.substr(delimiterAt + 1, 10); -} - -module.exports = { - 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]); - } - return ''; - } -} \ No newline at end of file diff --git a/wrt_app/addon/common/utils.ts b/wrt_app/addon/common/utils.ts new file mode 100644 index 00000000..3ed10ce6 --- /dev/null +++ b/wrt_app/addon/common/utils.ts @@ -0,0 +1,20 @@ +const keyTerm = 'globalapps'; +const delimiter = '/'; + +function extractID(line: string) { + let keyTermAt = line.indexOf(keyTerm); + let delimiterAt = line.indexOf(delimiter, keyTermAt); + return line.substr(delimiterAt + 1, 10); +} + +export default { + getPackageID() { + let stack = new Error().stack; + 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 ''; + } +}; diff --git a/wrt_app/addon/renderer/addonapi.js b/wrt_app/addon/renderer/addonapi.js deleted file mode 100644 index ea6340b9..00000000 --- a/wrt_app/addon/renderer/addonapi.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -const common = require('../common/addonapi'); -common.defineProperties(exports); - -const moduleList = require('./module-list'); - -for (const module of moduleList) { - Object.defineProperty(exports, module.name, { - get: common.memoizedGetter(() => require(`./modules/${module.file}`)) - }); -} diff --git a/wrt_app/addon/renderer/addonapi.ts b/wrt_app/addon/renderer/addonapi.ts new file mode 100644 index 00000000..73512ede --- /dev/null +++ b/wrt_app/addon/renderer/addonapi.ts @@ -0,0 +1,11 @@ +'use strict'; + +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}`)) + }); +} diff --git a/wrt_app/addon/renderer/module-list.js b/wrt_app/addon/renderer/module-list.js deleted file mode 100644 index c65f5386..00000000 --- a/wrt_app/addon/renderer/module-list.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -// Renderer side add-on modules, please sort alphabetically -module.exports = [ - { name: 'messaging', file: 'messaging' } -]; diff --git a/wrt_app/addon/renderer/module-list.ts b/wrt_app/addon/renderer/module-list.ts new file mode 100644 index 00000000..569fdf25 --- /dev/null +++ b/wrt_app/addon/renderer/module-list.ts @@ -0,0 +1,6 @@ +'use strict'; + +// Renderer side add-on modules, please sort alphabetically +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 index 40fe6846..00000000 --- a/wrt_app/addon/renderer/modules/messaging.js +++ /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 index 00000000..8e31b0ae --- /dev/null +++ b/wrt_app/addon/renderer/modules/messaging.ts @@ -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); +}; diff --git a/wrt_app/browser/init.js b/wrt_app/browser/init.js deleted file mode 100755 index 9fccd6f8..00000000 --- a/wrt_app/browser/init.js +++ /dev/null @@ -1,19 +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') -const wrt = require('./wrt') -require(wrt.getElectronPath() + '/browser/init') diff --git a/wrt_app/browser/init.ts b/wrt_app/browser/init.ts new file mode 100755 index 00000000..c3b9a54a --- /dev/null +++ b/wrt_app/browser/init.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +import '../common/init'; +import { wrt } from './wrt'; +import (wrt.getElectronPath() + '/browser/init'); diff --git a/wrt_app/browser/wrt.js b/wrt_app/browser/wrt.js deleted file mode 100755 index 288cb5b9..00000000 --- a/wrt_app/browser/wrt.js +++ /dev/null @@ -1,39 +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/exception_handling'); -const {wrt} = process.wrtBinding('wrt'); -const {EventEmitter} = require('events'); -const util = require('util'); - -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.logd = console.logv = console.loge = console.log; - -process.stdout.write = process.stderr.write = function (chunk, encoding, callback) { - if (Buffer.isBuffer(chunk)) { - chunk = chunk.toString(encoding); - } - wrt.log(chunk); - if (callback) { - callback(); - } - return true; -} diff --git a/wrt_app/browser/wrt.ts b/wrt_app/browser/wrt.ts new file mode 100755 index 00000000..180dcfe7 --- /dev/null +++ b/wrt_app/browser/wrt.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ +import { EventEmitter } from 'events'; +import * as util from 'util'; + +export const { wrt }: NativeWRTjs.WRTBinding = process.wrtBinding('wrt'); +Object.setPrototypeOf(Object.getPrototypeOf(wrt), EventEmitter.prototype); + +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; + +function write(chunk: Uint8Array | string, encoding?: any, callback?: (err?: Error) => void): boolean { + if (Buffer.isBuffer(chunk)) { + chunk = chunk.toString(encoding); + } + wrt.log(chunk as string); + if (callback) { + callback(); + } + return true; +} +process.stdout.write = process.stderr.write = write; diff --git a/wrt_app/browser/wrt_web_contents.js b/wrt_app/browser/wrt_web_contents.js deleted file mode 100644 index 5c2535a7..00000000 --- a/wrt_app/browser/wrt_web_contents.js +++ /dev/null @@ -1,34 +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. - */ - -const binding = 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 - -Object.setPrototypeOf(WRTWebContents.prototype, parent) - -WRTWebContents.prototype._init = function () { - parent._init.call(this) -} - -module.exports = { - create (options = {}) { - return binding.create(options) - } -} diff --git a/wrt_app/browser/wrt_web_contents.ts b/wrt_app/browser/wrt_web_contents.ts new file mode 100644 index 00000000..0b358738 --- /dev/null +++ b/wrt_app/browser/wrt_web_contents.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +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; + +Object.setPrototypeOf(WRTWebContents.prototype, parent); + +WRTWebContents.prototype._init = function () { + parent._init.call(this); +} + +export const create = (options = {}) => { + return binding.create(options); +} diff --git a/wrt_app/browser/wrt_window.js b/wrt_app/browser/wrt_window.js deleted file mode 100644 index 004f26d2..00000000 --- a/wrt_app/browser/wrt_window.js +++ /dev/null @@ -1,39 +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. - */ - -const { WRTWindow } = process.wrtBinding('wrt_window') -const { BrowserWindow } = require('electron') -const WRTWebContents = require('../browser/wrt_web_contents'); - -Object.setPrototypeOf(WRTWindow.prototype, BrowserWindow.prototype) - -WRTWindow.prototype._init = function () { - BrowserWindow.prototype._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() - if (!options.webContents || options.webContents === self) { - options.webContents = WRTWebContents() - options.webContents.loadURL(url) - } - event.newGuest = new WRTWindow(options) - }) -} - -module.exports = WRTWindow diff --git a/wrt_app/browser/wrt_window.ts b/wrt_app/browser/wrt_window.ts new file mode 100644 index 00000000..41c16901 --- /dev/null +++ b/wrt_app/browser/wrt_window.ts @@ -0,0 +1,42 @@ +/* + * 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. + */ + +import { BrowserWindow } from 'electron'; +import * as WRTWebContents from '../browser/wrt_web_contents'; + +export const { WRTWindow } = process.wrtBinding('wrt_window'); + +Object.setPrototypeOf(WRTWindow.prototype, BrowserWindow.prototype); + +WRTWindow.prototype._init = function () { + (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: 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.create(); + options.webContents.loadURL(url); + } + event.newGuest = new WRTWindow(options); + }); +} diff --git a/wrt_app/common/config-search-paths.js b/wrt_app/common/config-search-paths.js deleted file mode 100644 index 8f6f7391..00000000 --- a/wrt_app/common/config-search-paths.js +++ /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 index 00000000..080fae81 --- /dev/null +++ b/wrt_app/common/config-search-paths.ts @@ -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/exception_handling.js b/wrt_app/common/exception_handling.js deleted file mode 100644 index 028846d5..00000000 --- a/wrt_app/common/exception_handling.js +++ /dev/null @@ -1,6 +0,0 @@ -process.on('uncaughtException', function (error) { - // Do nothing if the user has a custom uncaught exception handler. - var ref, stack - stack = (ref = error.stack) != null ? ref : error.name + ': ' + error.message - console.error('A JavaScript error occurred in pid ' + process.pid + '\n' + stack) -}); diff --git a/wrt_app/common/exception_handling.ts b/wrt_app/common/exception_handling.ts new file mode 100644 index 00000000..028846d5 --- /dev/null +++ b/wrt_app/common/exception_handling.ts @@ -0,0 +1,6 @@ +process.on('uncaughtException', function (error) { + // Do nothing if the user has a custom uncaught exception handler. + var ref, stack + stack = (ref = error.stack) != null ? ref : error.name + ': ' + error.message + console.error('A JavaScript error occurred in pid ' + process.pid + '\n' + stack) +}); diff --git a/wrt_app/common/init.js b/wrt_app/common/init.js deleted file mode 100644 index b407422a..00000000 --- a/wrt_app/common/init.js +++ /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 index 00000000..570eb2a6 --- /dev/null +++ b/wrt_app/common/init.ts @@ -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; + } + } +} diff --git a/wrt_app/common/service_manager.js b/wrt_app/common/service_manager.js deleted file mode 100644 index 3e77f260..00000000 --- a/wrt_app/common/service_manager.js +++ /dev/null @@ -1,137 +0,0 @@ -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'); - -let sandbox = {}; -let is_global_service = !!wrt.getStartServiceFile; - -function callFunctionInContext(name, sandbox) { - const script = `if (typeof ${name} === 'function') { ${name}(); }`; - vm.runInContext(script, sandbox); -} - -function startService(id, filename) { - if (sandbox[id] === undefined) { - XWalkExtension.initialize(); - XWalkExtension.setRuntimeMessageHandler((type, data) => { - if (type === 'tizen://exit') - setTimeout(() => {wrt.stopService(id)}, 500); - }); - sandbox[id] = { - console: console, - module: new Module, - require: require, - tizen: tizen, - }; - sandbox[id].module.exports.onStop = () => { - callFunctionInContext('module.exports.onExit', sandbox[id]); - }; - if (wrt.tv) { - sandbox[id].webapis = 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); - if (app_info) - return app_info.packageId; - return ''; - } - } - - if (is_global_service) { - const permissions = wrt.getPrivileges(id); - console.log(`permissions : ${permissions}`); - const AccessControlManager = require('../service/access_control_manager'); - AccessControlManager.initialize(permissions, sandbox[id]); - } - for (let key in global) - sandbox[id][key] = global[key]; - - sandbox[id].timer_manager = new TimerManager(); - const timer_api = sandbox[id].timer_manager.getTimerAPI(); - 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 code; - if (is_global_service) { - options.filename = id; - if (wrt.tv) { - let extension_resolver = function (module, file_path) { - console.log(`resolved path: ${file_path}`); - let content = wrt.tv.decryptFile(id, file_path); - if (content) { - // Remove BOM - if (content.charCodeAt(0) === 0xFEFF) - content = content.slice(1); - module._compile(content, file_path); - } - }; - sandbox[id].require.extensions['.js.spm'] = extension_resolver; - sandbox[id].require.extensions['.spm'] = extension_resolver; - } - let start_service_file = wrt.getStartServiceFile(id); - console.log(`start service file: ${start_service_file}`); - code = `const app = require('${start_service_file}')`; - } else { - code = `const app = require('${filename}')`; - } - - vm.runInNewContext(code, sandbox[id], options); - } - - if (sandbox[id]['started'] === undefined) { - sandbox[id]['started'] = true; - sandbox[id]['stopped'] = undefined; - callFunctionInContext('app.onStart', sandbox[id]); - if (is_global_service) - wrt.finishStartingService(id); - } else { - console.log(id + ' service has been started.'); - } - - callFunctionInContext('app.onRequest', sandbox[id]); -} - -function stopService(id) { - console.log('stopService') - if (sandbox[id]['stopped']) { - console.log(id + ' service has been already stopped.'); - return; - } - - sandbox[id]['stopped'] = true; - sandbox[id]['started'] = undefined; - callFunctionInContext('app.onStop', sandbox[id]); - - sandbox[id].timer_manager.releaseRemainingTimers(); - for (let key in sandbox[id]) - delete sandbox[id][key]; - delete sandbox[id]; - - if (Object.keys(sandbox).length === 0) - XWalkExtension.cleanup(); -} - -module.exports = { - startService, - stopService -}; diff --git a/wrt_app/common/service_manager.ts b/wrt_app/common/service_manager.ts new file mode 100644 index 00000000..65c2e974 --- /dev/null +++ b/wrt_app/common/service_manager.ts @@ -0,0 +1,137 @@ +const Module = require('module'); +import { TimerManager } from '../service/timer_manager'; +import * as XWalkExtension from './wrt_xwalk_extension'; +import * as vm from 'vm'; +import { wrt } from '../browser/wrt'; + +interface ContextMap { + [id: string]: vm.Context; +} + +interface ContextOption { + [key: string]: any; +} + +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); +} + +export function startService(id: string, filename?: string) { + if (sandbox[id] === undefined) { + XWalkExtension.initialize(); + XWalkExtension.setRuntimeMessageHandler((type, data) => { + if (type === 'tizen://exit') + setTimeout(() => {wrt.stopService(id)}, 500); + }); + sandbox[id] = { + console: console, + module: new Module, + require: require, + tizen: global.tizen, + }; + sandbox[id].module.exports.onStop = () => { + callFunctionInContext('module.exports.onExit', sandbox[id]); + }; + if (wrt.tv) { + 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 = global.tizen.application.getAppInfo(service_id); + if (app_info) + return app_info.packageId; + return ''; + } + } + + if (is_global_service) { + const permissions = wrt.getPrivileges(id); + console.log(`permissions : ${permissions}`); + const AccessControlManager = require('../service/access_control_manager'); + AccessControlManager.initialize(permissions, sandbox[id]); + } + for (let key in global) + sandbox[id][key] = global[key]; + + sandbox[id].timer_manager = new TimerManager(); + const timer_api = sandbox[id].timer_manager.getTimerAPI(); + for (let key in timer_api) + sandbox[id][key] = timer_api[key]; + + 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: any, file_path: string) { + console.log(`resolved path: ${file_path}`); + let content = (wrt.tv as NativeWRTjs.TVExtension).decryptFile(id, file_path); + if (content) { + // Remove BOM + if (content.charCodeAt(0) === 0xFEFF) + content = content.slice(1); + module._compile(content, file_path); + } + }; + sandbox[id].require.extensions['.js.spm'] = extension_resolver; + sandbox[id].require.extensions['.spm'] = extension_resolver; + } + let start_service_file = wrt.getStartServiceFile(id); + console.log(`start service file: ${start_service_file}`); + code = `const app = require('${start_service_file}')`; + } else { + code = `const app = require('${filename}')`; + } + + vm.runInNewContext(code, sandbox[id], options); + } + + if (sandbox[id]['started'] === undefined) { + sandbox[id]['started'] = true; + sandbox[id]['stopped'] = undefined; + callFunctionInContext('app.onStart', sandbox[id]); + if (is_global_service) + wrt.finishStartingService(id); + } else { + console.log(id + ' service has been started.'); + } + + callFunctionInContext('app.onRequest', sandbox[id]); +} + +export function stopService(id: string) { + console.log('stopService') + if (sandbox[id]['stopped']) { + console.log(id + ' service has been already stopped.'); + return; + } + + sandbox[id]['stopped'] = true; + sandbox[id]['started'] = undefined; + callFunctionInContext('app.onStop', sandbox[id]); + + sandbox[id].timer_manager.releaseRemainingTimers(); + for (let key in sandbox[id]) + delete sandbox[id][key]; + delete sandbox[id]; + + if (Object.keys(sandbox).length === 0) + XWalkExtension.cleanup(); +} diff --git a/wrt_app/common/wrt_xwalk_extension.js b/wrt_app/common/wrt_xwalk_extension.js deleted file mode 100644 index 5e58a188..00000000 --- a/wrt_app/common/wrt_xwalk_extension.js +++ /dev/null @@ -1,232 +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('./exception_handling'); - -let instance; -let api_ = {}; -let extensions_ = {}; - -class XWalkExtension { - constructor() { - const binding = process.wrtBinding('wrt_xwalk_extension') - var extensions = binding.getExtensions(); - for (var i = 0; i < extensions.length; i++) { - extensions[i].loaded = false; - console.log("Load extension : " + extensions[i].name); - extensions_[extensions[i].name] = extensions[i]; - } - for (var name in extensions_) { - if (!extensions_[name].use_trampoline) { - this.load(extensions_[name]); - } - } - for (var name in extensions_) { - if (extensions_[name].use_trampoline) { - this.installTrampoline(extensions_[name]); - } - } - } - - /** - * Creates namespace for 'name' in given object. - * Eg. this.createNamespace(global, 'tizen.contact') will create: - * global.tizen.contact = {} - * - * @param {Object} object - * @param {String} name - */ - createNamespace(object, name) { - var obj = object; - var arr = name.split('.'); - for (var i = 0; i < arr.length; i++) { - obj[arr[i]] = obj[arr[i]] || {}; - obj = obj[arr[i]]; - } - } - - exposeApi(ext) { - var i, entry_points, entry_point, tmp, parent_name, base_name; - - // additional entry points are installed in global context by eval() - // so we need to move it to protected api_ object first - entry_points = [...new Set(ext.entry_points)]; - for (i = 0; i < entry_points.length; i++) { - entry_point = entry_points[i]; - tmp = entry_point.split('.'); - parent_name = tmp[0]; - base_name = tmp[tmp.length - 1]; - - api_[parent_name][base_name] = global[parent_name][base_name]; - delete global[parent_name][base_name]; - } - - entry_points.push(ext.name); - - for (i = 0; i < entry_points.length; i++) { - entry_point = entry_points[i]; - tmp = entry_point.split('.'); - parent_name = tmp[0]; - base_name = tmp[tmp.length - 1]; - - Object.defineProperty(global[parent_name], base_name, { - get: function (parent_name, base_name) { - return function () { - return api_[parent_name][base_name]; - } - }(parent_name, base_name), - configurable: false, - enumerable: true - }); - } - } - - runtimeMessageHandler(type, data, callback) { - console.log('This is prototype of runtimeMessageHandler'); - } - - /** - * @param {Object} ext - */ - load(ext) { - if (ext.loaded) - return; - - ext.loadInstance(); - ext.loaded = true; - - this.createNamespace(api_, ext.name); - this.createNamespace(global, ext.name); - - var api = (ext.use_trampoline) ? api_ : global; - - const jscode = - '(function(extension) {' + - ' extension.internal = {};' + - ' extension.internal.sendSyncMessage = extension.sendSyncMessage;' + - ' delete extension.sendSyncMessage;' + - ' var exports = {}; ' + - ' (function() {\'use strict\'; ' + ext.jsapi + '})();' + - ' api.' + ext.name + ' = exports; ' + - '});'; - - try { - var func = eval(jscode); - func({ - postMessage: function(msg) { - return ext.postMessage(msg); - }, - sendSyncMessage: function(msg) { - return ext.sendSyncMessage(msg); - }, - setMessageListener: function(fn) { - return ext.setMessageListener(fn); - }, - sendRuntimeMessage: function(type, data) { - return XWalkExtension.runtimeMessageHandler(type, data); - }, - sendRuntimeSyncMessage: function(type, data) { - return XWalkExtension.runtimeMessageHandler(type, data); - }, - sendRuntimeAsyncMessage: function(type, data, callback) { - 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); - } - }); - - if (ext.use_trampoline) { - this.exposeApi(ext); - } - } catch (err) { - console.log('Error loading extension "' + ext.name + '": ' + err.message); - } - } - - /** - * This is used to defer extension loading to it's first usage. - * Eg. First access to tizen.contact will load extension's 'jsapi' through eval(). - * - * @param {Object} ext - */ - installTrampoline(ext) { - var entry_points = [...new Set(ext.entry_points)]; - entry_points.push(ext.name); - for (var i = 0; i < entry_points.length; i++) { - var tmp = entry_points[i].split('.'); - var parent_name = tmp[0]; - var base_name = tmp[tmp.length - 1]; - - this.createNamespace(global, entry_points[i]); - - Object.defineProperty(global[parent_name], base_name, { - get: function (parent_name, base_name) { - return function() { - try { - this.deleteTrampoline(ext); - this.load(ext); - return api_[parent_name][base_name]; - } catch (e) { - console.log(e.stack); - } - }.bind(this); - }.call(this, parent_name, base_name), - enumerable: true - }); - } - } - - deleteTrampoline(ext) { - var entry_points = [...new Set(ext.entry_points)]; - entry_points.push(ext.name); - - for (var i = 0; i < entry_points.length; i++) { - var tmp = entry_points[i].split('.'); - var parent_name = tmp[0]; - var base_name = tmp[tmp.length - 1]; - delete global[parent_name][base_name]; - } - } -} - -const extension_api = { - initialize: () => { - if (!instance) - instance = new XWalkExtension; - }, - setRuntimeMessageHandler: (handler) => { - XWalkExtension.runtimeMessageHandler = handler; - }, - cleanup: () => { - delete global.tizen; - instance = undefined; - }, - preventCleanup: () => { - extension_api.cleanup = () => {}; - } -} - -module.exports = extension_api; diff --git a/wrt_app/common/wrt_xwalk_extension.ts b/wrt_app/common/wrt_xwalk_extension.ts new file mode 100644 index 00000000..ea8ad964 --- /dev/null +++ b/wrt_app/common/wrt_xwalk_extension.ts @@ -0,0 +1,223 @@ +/* + * 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. + */ + +import './exception_handling'; + +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: 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); + extensions_[extensions[i].name] = extensions[i]; + } + for (var name in extensions_) { + if (!extensions_[name].use_trampoline) { + this.load(extensions_[name]); + } + } + for (var name in extensions_) { + if (extensions_[name].use_trampoline) { + this.installTrampoline(extensions_[name]); + } + } + } + + /** + * Creates namespace for 'name' in given object. + * Eg. this.createNamespace(global, 'tizen.contact') will create: + * global.tizen.contact = {} + * + * @param {Object} object + * @param {String} name + */ + createNamespace(object: { [key: string]: any }, name: string) { + var obj = object; + var arr = name.split('.'); + for (var i = 0; i < arr.length; i++) { + obj[arr[i]] = obj[arr[i]] || {}; + obj = obj[arr[i]]; + } + } + + exposeApi(ext: NativeXWalkExtension) { + var i, entry_points, entry_point, tmp, parent_name, base_name; + + // additional entry points are installed in global context by eval() + // so we need to move it to protected api_ object first + entry_points = [...new Set(ext.entry_points)]; + for (i = 0; i < entry_points.length; i++) { + entry_point = entry_points[i]; + tmp = entry_point.split('.'); + parent_name = tmp[0]; + base_name = tmp[tmp.length - 1]; + + api_[parent_name][base_name] = global[parent_name][base_name]; + delete global[parent_name][base_name]; + } + + entry_points.push(ext.name); + + for (i = 0; i < entry_points.length; i++) { + entry_point = entry_points[i]; + tmp = entry_point.split('.'); + parent_name = tmp[0]; + base_name = tmp[tmp.length - 1]; + + Object.defineProperty(global[parent_name], base_name, { + get: function (parent_name, base_name) { + return function () { + return api_[parent_name][base_name]; + } + }(parent_name, base_name), + configurable: false, + enumerable: true + }); + } + } + + static runtimeMessageHandler(type: string, data?: string, callback?: (message: string) => void): void { + console.log('This is prototype of runtimeMessageHandler'); + } + + /** + * @param {Object} ext + */ + load(ext: NativeXWalkExtension) { + if (ext.loaded) + return; + + ext.loadInstance(); + ext.loaded = true; + + this.createNamespace(api_, ext.name); + this.createNamespace(global, ext.name); + + var api = (ext.use_trampoline) ? api_ : global; + + const jscode = + '(function(extension) {' + + ' extension.internal = {};' + + ' extension.internal.sendSyncMessage = extension.sendSyncMessage;' + + ' delete extension.sendSyncMessage;' + + ' var exports = {}; ' + + ' (function() {\'use strict\'; ' + ext.jsapi + '})();' + + ' api.' + ext.name + ' = exports; ' + + '});'; + + try { + var func = eval(jscode); + func({ + postMessage: function(msg: string) { + return ext.postMessage(msg); + }, + sendSyncMessage: function(msg: string) { + return ext.sendSyncMessage(msg); + }, + setMessageListener: function(fn: (message: string) => void) { + return ext.setMessageListener(fn); + }, + sendRuntimeMessage: function(type: string, data?: string) { + return XWalkExtension.runtimeMessageHandler(type, data); + }, + sendRuntimeSyncMessage: function(type: string, data?: string) { + return XWalkExtension.runtimeMessageHandler(type, data); + }, + sendRuntimeAsyncMessage: function(type: string, data?: string, callback?: (message: string) => void) { + return XWalkExtension.runtimeMessageHandler(type, data, callback); + } + }); + + if (ext.use_trampoline) { + this.exposeApi(ext); + } + } catch (err) { + console.log('Error loading extension "' + ext.name + '": ' + err.message); + } + } + + /** + * This is used to defer extension loading to it's first usage. + * Eg. First access to tizen.contact will load extension's 'jsapi' through eval(). + * + * @param {Object} 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++) { + var tmp = entry_points[i].split('.'); + var parent_name = tmp[0]; + var base_name = tmp[tmp.length - 1]; + + this.createNamespace(global, entry_points[i]); + + Object.defineProperty(global[parent_name], base_name, { + get: function (this: XWalkExtension, parent_name: string, base_name: string) { + return function(this: XWalkExtension) { + try { + this.deleteTrampoline(ext); + this.load(ext); + return api_[parent_name][base_name]; + } catch (e) { + console.log(e.stack); + } + }.bind(this); + }.call(this, parent_name, base_name), + enumerable: true + }); + } + } + + deleteTrampoline(ext: NativeXWalkExtension) { + var entry_points = [...new Set(ext.entry_points)]; + entry_points.push(ext.name); + + for (var i = 0; i < entry_points.length; i++) { + var tmp = entry_points[i].split('.'); + var parent_name = tmp[0]; + var base_name = tmp[tmp.length - 1]; + delete global[parent_name][base_name]; + } + } +} + +export const initialize = () => { + if (!instance) + instance = new XWalkExtension; +} + +export const setRuntimeMessageHandler = (handler: (type: string, data?: string, callback?: (message: string) => void) => void) => { + XWalkExtension.runtimeMessageHandler = handler; +} + +export let cleanup = () => { + delete global.tizen; + instance = undefined; +} + +export const preventCleanup = () => { + cleanup = () => {}; +} diff --git a/wrt_app/package.json b/wrt_app/package.json index 0858c86d..e7c2d184 100755 --- a/wrt_app/package.json +++ b/wrt_app/package.json @@ -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 index e207bcd3..00000000 --- a/wrt_app/renderer/init.js +++ /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') diff --git a/wrt_app/renderer/init.ts b/wrt_app/renderer/init.ts new file mode 100755 index 00000000..f53c5d1f --- /dev/null +++ b/wrt_app/renderer/init.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +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 index c1b183a0..00000000 --- a/wrt_app/renderer/wrt_renderer.js +++ /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 index 00000000..c8468534 --- /dev/null +++ b/wrt_app/renderer/wrt_renderer.ts @@ -0,0 +1 @@ +export const { wrtRenderer }: NativeWRTjs.WRTRendererBinding = process.wrtBinding('wrt_renderer'); diff --git a/wrt_app/service/access_control_manager.js b/wrt_app/service/access_control_manager.js deleted file mode 100644 index b0270b57..00000000 --- a/wrt_app/service/access_control_manager.js +++ /dev/null @@ -1,119 +0,0 @@ -function initialize(permissions, sandbox) { - let tizen = sandbox.tizen; - if (!permissions.includes("http://tizen.org/privilege/alarm")) { - tizen.alarm.add = - tizen.alarm.remove = - tizen.alarm.removeAll = - tizen.alarm.get = - tizen.alarm.getAll = - tizen.alarm.getAlarmNotification = - tizen.alarm.addAlarmNotification = function() { - console.log('The alarm permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/apphistory.read")) { - tizen.application.getAppsUsageInfo = - tizen.application.getBatteryUsageInfo = function() { - console.log('The application.read permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/application.launch") && - !permissions.includes("http://tizen.org/privilege/appmanager.launch")) { - tizen.application.launch = - tizen.application.launchAppControl = function() { - console.log('The application.launch and appmanager.launch permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/application.info")) { - tizen.application.getAppMetaData = function() { - console.log('The application.info permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/appmanager.certificate")) { - tizen.application.getAppCerts = function() { - console.log('The application.certificate permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/appmanager.kill")) { - tizen.application.kill = function() { - console.log('The application.kill permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/appmanager.launch") || - !permissions.includes("http://tizen.org/privilege/datasharing")) { - tizen.datacontrol.addChangeListener = - tizen.datacontrol.removeChangeListener = function() { - console.log('The appmanager.launch or datasharing permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/datacontrol.consumer")) { - tizen.datacontrol.getValue = - tizen.datacontrol.updateValue = - tizen.datacontrol.insert = - tizen.datacontrol.update = - tizen.datacontrol.remove = - tizen.datacontrol.select = - tizen.datacontrol.addValue = - tizen.datacontrol.removeValue = - tizen.datacontrol.getDataControlConsumer = function() { - console.log('The datacontrol.consumer permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/filesystem.read")) { - tizen.filesystem.listDirectory = - tizen.filesystem.isFile = - tizen.filesystem.isDirectory = - tizen.filesystem.pathExists = - tizen.filesystem.copyFile = - tizen.filesystem.copyDirectory = - tizen.filesystem.moveFile = - tizen.filesystem.moveDirectory = - tizen.filesystem.resolve = function() { - console.log('The filesystem.read permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/filesystem.write")) { - tizen.filesystem.createDirectory = - tizen.filesystem.deleteFile = - tizen.filesystem.deleteDirectory = - tizen.filesystem.copyFile = - tizen.filesystem.copyDirectory = - tizen.filesystem.moveFile = - tizen.filesystem.moveDirectory = - tizen.filesystem.rename = function() { - console.log('The filesystem.write permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/notification")) { - tizen.alarm.addAlarmNotification = function() { - console.log('The notification permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/package.info")) { - tizen.package.setPackageInfoEventListener = - tizen.package.unsetPackageInfoEventListener = - tizen.package.getPackageInfo = - tizen.package.getPackagesInfo = function() { - console.log('The package.info permission is missing.'); - } - } - if (!permissions.includes("http://tizen.org/privilege/packagemanager.install")) { - tizen.package.install = - tizen.package.uninstall = function() { - console.log('The packagemanager.install permission is missing.'); - } - } - // systeminfo : Runtime privilege validation is required, based on parameters - let getPropertyValue = tizen.systeminfo.getPropertyValue; - tizen.systeminfo.getPropertyValue = function(type, onSuccessCallback, onErrorCallback) { - 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/access_control_manager.ts b/wrt_app/service/access_control_manager.ts new file mode 100644 index 00000000..6a559468 --- /dev/null +++ b/wrt_app/service/access_control_manager.ts @@ -0,0 +1,117 @@ +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 = + tizen.alarm.remove = + tizen.alarm.removeAll = + tizen.alarm.get = + tizen.alarm.getAll = + tizen.alarm.getAlarmNotification = + tizen.alarm.addAlarmNotification = function() { + console.log('The alarm permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/apphistory.read")) { + tizen.application.getAppsUsageInfo = + tizen.application.getBatteryUsageInfo = function() { + console.log('The application.read permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/application.launch") && + !permissions.includes("http://tizen.org/privilege/appmanager.launch")) { + tizen.application.launch = + tizen.application.launchAppControl = function() { + console.log('The application.launch and appmanager.launch permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/application.info")) { + tizen.application.getAppMetaData = function() { + console.log('The application.info permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/appmanager.certificate")) { + tizen.application.getAppCerts = function() { + console.log('The application.certificate permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/appmanager.kill")) { + tizen.application.kill = function() { + console.log('The application.kill permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/appmanager.launch") || + !permissions.includes("http://tizen.org/privilege/datasharing")) { + tizen.datacontrol.addChangeListener = + tizen.datacontrol.removeChangeListener = function() { + console.log('The appmanager.launch or datasharing permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/datacontrol.consumer")) { + tizen.datacontrol.getValue = + tizen.datacontrol.updateValue = + tizen.datacontrol.insert = + tizen.datacontrol.update = + tizen.datacontrol.remove = + tizen.datacontrol.select = + tizen.datacontrol.addValue = + tizen.datacontrol.removeValue = + tizen.datacontrol.getDataControlConsumer = function() { + console.log('The datacontrol.consumer permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/filesystem.read")) { + tizen.filesystem.listDirectory = + tizen.filesystem.isFile = + tizen.filesystem.isDirectory = + tizen.filesystem.pathExists = + tizen.filesystem.copyFile = + tizen.filesystem.copyDirectory = + tizen.filesystem.moveFile = + tizen.filesystem.moveDirectory = + tizen.filesystem.resolve = function() { + console.log('The filesystem.read permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/filesystem.write")) { + tizen.filesystem.createDirectory = + tizen.filesystem.deleteFile = + tizen.filesystem.deleteDirectory = + tizen.filesystem.copyFile = + tizen.filesystem.copyDirectory = + tizen.filesystem.moveFile = + tizen.filesystem.moveDirectory = + tizen.filesystem.rename = function() { + console.log('The filesystem.write permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/notification")) { + tizen.alarm.addAlarmNotification = function() { + console.log('The notification permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/package.info")) { + tizen.package.setPackageInfoEventListener = + tizen.package.unsetPackageInfoEventListener = + tizen.package.getPackageInfo = + tizen.package.getPackagesInfo = function() { + console.log('The package.info permission is missing.'); + } + } + if (!permissions.includes("http://tizen.org/privilege/packagemanager.install")) { + tizen.package.install = + tizen.package.uninstall = function() { + console.log('The packagemanager.install permission is missing.'); + } + } + // systeminfo : Runtime privilege validation is required, based on parameters + let getPropertyValue = tizen.systeminfo.getPropertyValue; + 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); + }; +} diff --git a/wrt_app/service/builtins/wasm_builder.js b/wrt_app/service/builtins/wasm_builder.js deleted file mode 100644 index 9cadd6b5..00000000 --- a/wrt_app/service/builtins/wasm_builder.js +++ /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 index 00000000..2403ee3f --- /dev/null +++ b/wrt_app/service/builtins/wasm_builder.ts @@ -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); + }); +} diff --git a/wrt_app/service/main.js b/wrt_app/service/main.js deleted file mode 100755 index 03d86b79..00000000 --- a/wrt_app/service/main.js +++ /dev/null @@ -1,61 +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'; - -require('../common/init') -const wrt = require('../browser/wrt'); -const ServiceManager = require('../common/service_manager'); - -wrt.on('start-service', (event, internal_id) => { - console.log('start service app : ' + internal_id); - ServiceManager.startService(internal_id); -}); - -wrt.on('stop-service', (event, internal_id) => { - ServiceManager.stopService(internal_id); - if (wrt.getServiceModel() == "STANDALONE") - process.exit(); -}); - -wrt.on('builtin-service', (event, internal_id, service_name) => { - console.log(`service_name: ${service_name}`); - const vm = require('vm'); - let builtin_service = ''; - if (service_name === 'wasm_builder') { - builtin_service = './builtins/wasm_builder.js'; - } - if (builtin_service) { - console.log(`Builtin service is ${builtin_service}`); - let sandbox = { - console: console, - require: require, - }; - let options = { - filename: internal_id, - }; - let code = `require('${builtin_service}').run('${internal_id}')`; - vm.runInNewContext(code, sandbox, options); - } -}); - -wrt.on('quit', (event) => { - process.exit(); -}); - -process.on('exit', (code) => { - console.log('Exit with code : ' + code); -}); diff --git a/wrt_app/service/main.ts b/wrt_app/service/main.ts new file mode 100755 index 00000000..7d3ff1ab --- /dev/null +++ b/wrt_app/service/main.ts @@ -0,0 +1,61 @@ +/* + * 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 '../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); + ServiceManager.startService(internal_id); +}); + +wrt.on('stop-service', (event, internal_id) => { + ServiceManager.stopService(internal_id); + if (wrt.getServiceModel() == "STANDALONE") + process.exit(); +}); + +wrt.on('builtin-service', (event, internal_id, service_name) => { + console.log(`service_name: ${service_name}`); + const vm = require('vm'); + let builtin_service = ''; + if (service_name === 'wasm_builder') { + builtin_service = './builtins/wasm_builder.js'; + } + if (builtin_service) { + console.log(`Builtin service is ${builtin_service}`); + let sandbox = { + console: console, + require: require, + }; + let options = { + filename: internal_id, + }; + let code = `require('${builtin_service}').run('${internal_id}')`; + vm.runInNewContext(code, sandbox, options); + } +}); + +wrt.on('quit', (event) => { + process.exit(); +}); + +process.on('exit', (code) => { + console.log('Exit with code : ' + code); +}); diff --git a/wrt_app/service/timer_manager.js b/wrt_app/service/timer_manager.js deleted file mode 100644 index e17cd685..00000000 --- a/wrt_app/service/timer_manager.js +++ /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 index 00000000..deace6e1 --- /dev/null +++ b/wrt_app/service/timer_manager.ts @@ -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 index 0eb9b55e..00000000 --- a/wrt_app/src/addon_manager.js +++ /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 index 00000000..c4f1b662 --- /dev/null +++ b/wrt_app/src/addon_manager.ts @@ -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 index 01f620cd..00000000 --- a/wrt_app/src/ipc_message.js +++ /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/main.js b/wrt_app/src/main.js deleted file mode 100755 index e3341202..00000000 --- a/wrt_app/src/main.js +++ /dev/null @@ -1,25 +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'; - -require('../common/exception_handling'); -require('../common/config-search-paths'); - -new (require('./runtime'))({ - devMode: false, - noAddons: false -}); diff --git a/wrt_app/src/runtime.js b/wrt_app/src/runtime.js deleted file mode 100755 index b0d97ffc..00000000 --- a/wrt_app/src/runtime.js +++ /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 index 00000000..2348fd11 --- /dev/null +++ b/wrt_app/src/runtime.ts @@ -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 index 46d83c72..00000000 --- a/wrt_app/src/web_application.js +++ /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 index 00000000..61bbfd74 --- /dev/null +++ b/wrt_app/src/web_application.ts @@ -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; + preloadStatus: string; + showTimer?: NodeJS.Timeout; + + backgroundSupport = wrt.getBackgroundSupport(); + debugPort = 0; + firstRendered = false; + inspectorSrc = ''; + loadFinished = false; + pendingCallbacks: Map = 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).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/wrt_app/worker/init.js b/wrt_app/worker/init.js deleted file mode 100755 index 676b55a6..00000000 --- a/wrt_app/worker/init.js +++ /dev/null @@ -1,17 +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('../../electron.asar/worker/init') diff --git a/wrt_app/worker/init.ts b/wrt_app/worker/init.ts new file mode 100755 index 00000000..676b55a6 --- /dev/null +++ b/wrt_app/worker/init.ts @@ -0,0 +1,17 @@ +/* + * 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('../../electron.asar/worker/init') diff --git a/wrtjs.d.ts b/wrtjs.d.ts new file mode 100644 index 00000000..c1a1449b --- /dev/null +++ b/wrtjs.d.ts @@ -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; + } +}