--- /dev/null
+/*
+ * Copyright (c) 2021 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 { protocol } from 'electron';
+import { wrt } from '../../browser/wrt';
+import { WebApplication } from '../web_application';
+import { WebApplicationDelegate } from '../../common/web_application_delegate';
+
+export class WebApplicationDelegateTV extends WebApplicationDelegate {
+ accessiblePath?: string[];
+ backgroundExecution: boolean = false;
+ inspectorSrc: string = '';
+ isAlwaysReload: boolean = false;
+ preloadStatus: string = 'none';
+ runningStatus: string = 'none';
+ tv: any = (wrt.tv as NativeWRTjs.TVExtension);
+
+ constructor(webApplication: WebApplication) {
+ super(webApplication);
+ }
+
+ initialize(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.isAlwaysReload = this.tv.isAlwaysReload();
+ this.accessiblePath = this.tv.getAccessiblePath();
+
+ this.webApplication.multitaskingSupport = this.tv.getMultitaskingSupport();
+ this.webApplication.defaultBackgroundColor = '#0000';
+ this.webApplication.defaultTransparent = true;
+
+ this.initAccessiblePath();
+ this.initEventListener();
+ }
+
+ private initAccessiblePath() {
+ if (this.accessiblePath) {
+ console.log(`accessiblePath: ${this.accessiblePath}`);
+ protocol.interceptFileProtocol('file', (request: any, callback: any) => {
+ 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: Error) => {
+ console.log(error);
+ });
+ }
+ }
+
+ initEventListener() {
+ wrt.on('app-status-changed', (event: any, status: string) => {
+ console.log(`runningStatus: ${status}, ${this.webApplication.loadFinished}`);
+ this.runningStatus = status;
+ if (this.runningStatus === 'DialogClose' && this.inspectorSrc) {
+ console.log(`runningStatus is DialogClose, src is ${this.inspectorSrc}`);
+ this.webApplication.mainWindow.loadURL(this.inspectorSrc);
+ this.inspectorSrc = '';
+ } else if (this.runningStatus == 'behind' && this.webApplication.loadFinished) {
+ // TODO : Need to care this situation and decide to pass the addon event emitter to suspend()
+ this.webApplication.suspend();
+ }
+ });
+ }
+
+ onDidFinishLoad() {
+ if (this.inspectorSrc)
+ this.showInspectorGuide();
+ else
+ this.suspendByStatus();
+ }
+
+ private showInspectorGuide() {
+ console.log('WebApplication : showInspectorGuide');
+ this.showInspectorGuide = () => { }; // call once
+ const message = `${this.webApplication.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 webContents = this.webApplication.mainWindow.webContents;
+ this.tv.showDialog(webContents, message);
+ if (this.preloadStatus !== 'none') {
+ setTimeout(() => {
+ this.tv.cancelDialogs(webContents);
+ }, 5000);
+ }
+ }
+
+ private suspendByStatus() {
+ if (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.webApplication.suspend();
+ if (this.preloadStatus === 'preload')
+ this.runningStatus = 'preload';
+ this.tv.notifyAppStatus(this.runningStatus);
+ }
+ }
+
+ private handlePreloadState(launchMode: string) {
+ if (this.preloadStatus === 'preload') {
+ this.show();
+ } else {
+ if (launchMode != 'backgroundAtStartup')
+ this.preloadStatus = 'none';
+ }
+ }
+
+ backgroundExecutable() {
+ return this.backgroundExecution;
+ }
+
+ isPreloading() {
+ if (this.preloadStatus == 'preload') {
+ console.log('preloading show is skipped!');
+ return true;
+ }
+ return false;
+ }
+
+ show() {
+ this.preloadStatus = 'none';
+ }
+
+ beforeQuit() {
+ this.inspectorSrc = '';
+ this.tv.cancelDialogs(this.webApplication.mainWindow.webContents);
+ }
+
+ clearSurface(webContents: any) {
+ this.tv.clearSurface(webContents);
+ }
+
+ clearCache() {
+ console.log('clearcache with low-memory');
+ this.webApplication.windowList.forEach((window) => {
+ //clear webframe cache
+ this.tv.clearWebCache(window.webContents);
+ window.webContents.session.clearCache(function() {
+ console.log('clear session Cache complete');
+ })
+ });
+ }
+
+ needInpectorGuide() {
+ let inspectorEnabledByVconf = this.tv.needUseInspector();
+ if (inspectorEnabledByVconf && !this.backgroundExecution) {
+ this.inspectorSrc = this.webApplication.contentSrc;
+ this.webApplication.contentSrc = 'about:blank';
+ return true;
+ }
+ return false;
+ }
+
+ needReload(src: string) {
+ if (this.isAlwaysReload) {
+ return true;
+ }
+ let reload = false;
+ let originalUrl = this.webApplication.mainWindow.webContents.getURL();
+ 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;
+ }
+ return reload;
+ }
+
+ needShowTimer() {
+ return false;
+ }
+
+ handleAppControlEvent(appControl: any) {
+ let launchMode = appControl.getData('http://samsung.com/appcontrol/data/launch_mode');
+ this.handlePreloadState(launchMode);
+
+ 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 false;
+ }
+ return true;
+ }
+
+ handleProxyInfo(authInfo: any, callback: any) {
+ if (!authInfo.isProxy)
+ return false;
+
+ let usrname = '';
+ let passwd = '';
+ let vconfProxy = this.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('', '');
+ }
+ return true;
+ }
+
+ profileName() {
+ return 'TV';
+ }
+}
'use strict';
-import { app, protocol } from 'electron';
+import { app } 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';
+import { WebApplicationDelegate } from '../common/web_application_delegate';
+import { WebApplicationDelegateTV } from './tv/web_application_tv';
export class WebApplication {
- accessiblePath?: string[];
- backgroundExecution: boolean;
- defaultBackgroundColor: string;
- defaultTransparent: boolean;
- isAlwaysReload: boolean;
+ defaultBackgroundColor: string = (wrt.getPlatformType() === "product_wearable") ? '#000' : '#FFF';
+ defaultTransparent: boolean = false;
mainWindow: Electron.BrowserWindow;
- multitaskingSupport: boolean;
+ multitaskingSupport: boolean = true;
notificationPermissionMap?: Map<Electron.WebContents, boolean>;
- preloadStatus: string;
showTimer?: NodeJS.Timeout;
- backgroundSupport = wrt.getBackgroundSupport();
- debugPort = 0;
- firstRendered = false;
- contentSrc = '';
- inspectorSrc = '';
- loadFinished = false;
+ backgroundSupport: boolean = wrt.getBackgroundSupport();
+ debugPort: number = 0;
+ firstRendered: boolean = false;
+ contentSrc: string = '';
+ loadFinished: boolean = false;
pendingCallbacks: Map<number, any> = new Map();
- pendingID = 0;
- runningStatus = 'none';
- suspended = false;
+ pendingID: number = 0;
+ suspended: boolean = false;
windowList: Electron.BrowserWindow[] = [];
- inQuit = false;
+ inQuit: boolean = false;
+ profileDelegate: WebApplicationDelegate;
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;
+ if (wrt.tv) {
+ this.profileDelegate = new WebApplicationDelegateTV(this);
+ this.profileDelegate.initialize(options);
} else {
- this.backgroundExecution = false;
+ this.profileDelegate = new WebApplicationDelegate(this);
}
- 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();
this.setupMainWindowEventListener();
app.on('login', (event: any, webContents: any, request: any, authInfo: any, callback: any) => {
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 {
+ if (!this.profileDelegate.handleProxyInfo(authInfo, callback)) {
const id = ++this.pendingID;
console.log(`Raising a login info request with id: ${id}`);
this.pendingCallbacks.set(id, callback);
}
});
- if (this.accessiblePath) {
- console.log(`accessiblePath: ${this.accessiblePath}`);
- protocol.interceptFileProtocol('file', (request: any, callback: any) => {
- 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: Error) => {
- console.log(error);
- });
- }
-
wrt.on('permission-response', (event: any, id: number, result: boolean) => {
console.log(`permission-response for ${id} is ${result}`);
let callback = this.pendingCallbacks.get(id);
this.pendingCallbacks.delete(id);
}
});
-
- wrt.on('app-status-changed', (event: any, status: string) => {
- 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 {
clearTimeout(this.showTimer);
wrt.hideSplashScreen(0);
this.firstRendered = true;
- if (this.preloadStatus == 'preload') {
- this.preloadStatus = 'readyToShow';
- console.log('preloading show is skipped!');
+ if (this.profileDelegate.isPreloading())
return;
- }
this.show();
});
addonManager.emit('contentDidFinishLoad', this.mainWindow.id);
if (wrt.isIMEWebApp()) {
this.activateIMEWebHelperClient();
- } else if (wrt.tv) {
- if (this.inspectorSrc)
- this.showInspectorGuide();
- else
- this.suspendByStatus();
+ } else {
+ this.profileDelegate.onDidFinishLoad();
}
});
}
}
private initDisplayDelay() {
- let splashShown = this.preloadStatus !== 'preload' && wrt.showSplashScreen();
- if (splashShown || wrt.tv)
+ if (this.profileDelegate.isPreloading())
+ return;
+
+ let splashShown = wrt.showSplashScreen();
+ if (splashShown || !this.profileDelegate.needShowTimer())
return;
this.showTimer = setTimeout(() => {
}, 2000);
}
- private backgroundRunnable(): boolean {
- return this.backgroundSupport || this.backgroundExecution;
- }
-
- 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);
- }
+ private backgroundRunnable() {
+ return this.backgroundSupport || this.profileDelegate.backgroundExecutable();
}
handleAppControlEvent(appControl: any) {
- let launchMode = appControl.getData('http://samsung.com/appcontrol/data/launch_mode');
- this.handlePreloadState(launchMode);
-
- 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.resume();
+ if (!this.profileDelegate.handleAppControlEvent(appControl)) {
return;
}
let loadInfo = appControl.getLoadInfo();
let src = loadInfo.getSrc();
- let reload = loadInfo.getReload() || this.needReload(src);
+ let reload = loadInfo.getReload() || this.profileDelegate.needReload(src);
// 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.
private launchInspectorIfNeeded(appControl: NativeWRTjs.AppControl) {
console.log('launchInspectorIfNeeded');
- let inspectorEnabledByVconf = wrt.tv ? wrt.tv.needUseInspector() : false;
- if (inspectorEnabledByVconf && !this.backgroundExecution) {
- this.inspectorSrc = this.contentSrc;
- this.contentSrc = 'about:blank';
- }
+ let needInpectorGuide = this.profileDelegate.needInpectorGuide();
let hasAulDebug = (appControl.getData('__AUL_DEBUG__') === '1');
- if (hasAulDebug || inspectorEnabledByVconf) {
+
+ if (hasAulDebug || needInpectorGuide) {
let debugPort = wrt.startInspectorServer();
let data = { "port": [debugPort.toString()] };
this.debugPort = debugPort;
beforeQuit() {
console.log('WebApplication : beforeQuit');
+ this.profileDelegate.beforeQuit();
addonManager.emit('lcQuit', this.mainWindow.id);
- if (wrt.tv) {
- this.inspectorSrc = '';
- wrt.tv.cancelDialogs(this.mainWindow.webContents);
- }
if (this.debugPort) {
console.log('stop inspector server');
this.debugPort = 0;
this.inQuit = true;
}
- private needReload(src: string) {
- if (this.isAlwaysReload) {
- return true;
- }
- let reload = false;
- let originalUrl = this.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;
- }
- return reload;
- }
-
private handleAppControlReload(url: string) {
console.log('WebApplication : handleAppControlReload');
this.closeWindows();
this.mainWindow.loadURL(url);
}
- private handlePreloadState(launchMode: string) {
- if (this.preloadStatus == 'readyToShow') {
- this.show();
- } else {
- if (launchMode != 'backgroundAtStartup')
- this.preloadStatus = 'none';
- }
- }
-
private flushData() {
console.log('WebApplication : FlushData');
this.windowList.forEach((window) => window.webContents.session.flushStorageData());
show() {
console.log('WebApplication : show');
- this.preloadStatus = 'none';
- if (this.backgroundExecution) {
+ if (this.profileDelegate.backgroundExecutable()) {
console.log('skip showing while backgroundExecution mode');
} else if (!this.mainWindow.isVisible()) {
console.log('show window');
this.mainWindow.show();
}
+ this.profileDelegate.show();
}
private closeWindows() {
- wrt.tv?.clearSurface(this.mainWindow.webContents);
+ this.profileDelegate.clearSuface(this.mainWindow.webContents);
this.windowList.forEach((window) => {
if (window != this.mainWindow)
window.destroy();
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');
- })
- });
+ clearCache() {
+ this.profileDelegate.clearCache();
}
ambientTick() {