From f65fa74057d0d145c28b9bbfd8261e4b9f7824c9 Mon Sep 17 00:00:00 2001 From: Kiran Niranjan Date: Mon, 8 Apr 2019 13:05:14 +0530 Subject: [PATCH] Typescript (Fix bound changes and update child window security) (#628) * Typescript - Update child window security and permissions * Typescript - Update bound change logic * Typescript - Update unit tests * Typescript - Update aip files and package.json * Typescript - Fix unit tests for child window handler --- demo/index.html | 21 ++++++++----- installer/win/Symphony-x64.aip | 20 +++++------- installer/win/Symphony-x86.aip | 18 +++++------ package.json | 10 +++--- spec/childWindowHandle.spec.ts | 17 +++++++++-- src/app/child-window-handler.ts | 45 +++++++++++++++++++++++---- src/app/window-actions.ts | 54 ++++++++++++++++++++------------- src/app/window-handler.ts | 8 +++++ src/renderer/preload-main.ts | 10 +++--- 9 files changed, 136 insertions(+), 67 deletions(-) diff --git a/demo/index.html b/demo/index.html index 9352064d..a2196214 100644 --- a/demo/index.html +++ b/demo/index.html @@ -294,13 +294,6 @@ } }); - // register callback to be notified when size/position changes for win. - // ssf.registerBoundsChange(onBoundsChange); - - function onBoundsChange(arg) { - console.log('bounds changed for=', arg) - } - // crash the renderer process const crash = document.getElementById('crash'); crash.addEventListener('click', function () { @@ -384,6 +377,20 @@ } }); + /** + * Bound Changes + */ + // register callback to be notified when size/position changes for win. + if (window.ssf) { + ssf.registerBoundsChange(onBoundsChange); + } else { + postMessage(apiCmds.registerBoundsChange); + } + + function onBoundsChange(arg) { + console.log('bounds changed for=', arg) + } + /** * Protocol handler */ diff --git a/installer/win/Symphony-x64.aip b/installer/win/Symphony-x64.aip index c023785a..55183514 100644 --- a/installer/win/Symphony-x64.aip +++ b/installer/win/Symphony-x64.aip @@ -76,7 +76,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -116,7 +116,7 @@ - + @@ -125,21 +125,19 @@ - - - + - + @@ -164,8 +162,8 @@ - - + + @@ -208,7 +206,6 @@ - @@ -226,7 +223,7 @@ - + @@ -585,7 +582,6 @@ - diff --git a/installer/win/Symphony-x86.aip b/installer/win/Symphony-x86.aip index 5878e5a9..7db2fce5 100644 --- a/installer/win/Symphony-x86.aip +++ b/installer/win/Symphony-x86.aip @@ -75,7 +75,7 @@ - + @@ -85,7 +85,7 @@ - + @@ -115,7 +115,7 @@ - + @@ -125,12 +125,11 @@ - - + @@ -154,7 +153,7 @@ - + @@ -162,8 +161,8 @@ - - + + @@ -206,7 +205,6 @@ - @@ -224,7 +222,7 @@ - + diff --git a/package.json b/package.json index 36d3a8ed..4d1e3246 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,15 @@ }, "build": { "asarUnpack": [ - "node_modules/@paulcbetts/cld/build/Release/cld.node" + "node_modules/@nornagon/cld/build/Release/cld.node" ], "files": [ "!coverage/*", "!installer/*", "!tests/*", - "!node_modules/@paulcbetts/cld/deps/cld${/*}", - "!node_modules/@paulcbetts/cld/build/deps${/*}", - "!node_modules/@paulcbetts/spellchecker/vendor${/*}" + "!node_modules/@nornagon/cld/deps/cld${/*}", + "!node_modules/@nornagon/cld/build/deps${/*}", + "!node_modules/@nornagon/spellchecker/vendor${/*}" ], "extraFiles": [ "config/Symphony.config", @@ -157,6 +157,6 @@ }, "optionalDependencies": { "screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet.git#v1.0.5", - "swift-search": "3.0.2" + "swift-search": "5.0.0-beta.1" } } diff --git a/spec/childWindowHandle.spec.ts b/spec/childWindowHandle.spec.ts index ab1021c7..e694719b 100644 --- a/spec/childWindowHandle.spec.ts +++ b/spec/childWindowHandle.spec.ts @@ -1,4 +1,5 @@ import { handleChildWindow } from '../src/app/child-window-handler'; +import { config } from '../src/app/config-handler'; import { windowHandler } from '../src/app/window-handler'; import { injectStyles } from '../src/app/window-utils'; import { ipcRenderer } from './__mocks__/electron'; @@ -68,17 +69,29 @@ describe('child window handle', () => { }); it('should call `did-finish-load` correctly on WindowOS', () => { + config.getGlobalConfigFields = jest.fn(() => { + return { + url: 'https://foundation-dev.symphony.com', + }; + }); const newWinUrl = 'about:blank'; const args = [newWinUrl, frameName, disposition, newWinOptions]; const spy = jest.spyOn(newWinOptions.webContents.webContents, 'send'); handleChildWindow(ipcRenderer as any); ipcRenderer.send('new-window', ...args); ipcRenderer.send('did-finish-load'); - expect(spy).lastCalledWith('page-load', { isWindowsOS: true}); + expect(spy).lastCalledWith('page-load', { + enableCustomTitleBar: false, + isMainWindow: false, + isWindowsOS: true, + locale: 'en-US', + origin: 'https://foundation-dev.symphony.com', + resources: {}, + }); expect(injectStyles).toBeCalled(); }); - it('should call `windowHandler.openUrlInDefaultBrowser` when url in invalid', () => { + it('should call `windowHandler.openUrlInDefaultBrowser` when url in invalid', () => { const newWinUrl = 'invalid'; const args = [newWinUrl, frameName, disposition, newWinOptions]; const spy = jest.spyOn(windowHandler, 'openUrlInDefaultBrowser'); diff --git a/src/app/child-window-handler.ts b/src/app/child-window-handler.ts index 9e38da4e..94b85d90 100644 --- a/src/app/child-window-handler.ts +++ b/src/app/child-window-handler.ts @@ -3,7 +3,9 @@ import { BrowserWindow, WebContents } from 'electron'; import { parse as parseQuerystring } from 'querystring'; import { format, parse, Url } from 'url'; import { isDevEnv, isWindowsOS } from '../common/env'; +import { i18n } from '../common/i18n'; import { getGuid } from '../common/utils'; +import { config } from './config-handler'; import { monitorWindowActions, removeWindowEventListener } from './window-actions'; import { ICustomBrowserWindow, windowHandler } from './window-handler'; import { @@ -58,9 +60,24 @@ export const handleChildWindow = (webContents: WebContents): void => { const emptyUrlString = 'about:blank'; const dispositionWhitelist = ['new-window', 'foreground-tab']; + const fullMainUrl = `${mainWinParsedUrl.protocol}//${mainWinParsedUrl.host}/`; + // If the main url and new window url are the same, + // we open that in a browser rather than a separate window + if (newWinUrl === fullMainUrl) { + event.preventDefault(); + windowHandler.openUrlInDefaultBrowser(newWinUrl); + return; + } + // only allow window.open to succeed is if coming from same hsot, // otherwise open in default browser. - if ((newWinHost === mainWinHost || newWinUrl === emptyUrlString) && dispositionWhitelist.includes(disposition)) { + if ((newWinHost === mainWinHost + || newWinUrl === emptyUrlString + || (newWinHost + && mainWinHost + && newWinHost.indexOf(mainWinHost) !== -1 + && frameName !== '')) + && dispositionWhitelist.includes(disposition)) { const newWinKey = getGuid(); if (!frameName) { @@ -117,7 +134,15 @@ export const handleChildWindow = (webContents: WebContents): void => { return; } windowHandler.addWindow(newWinKey, browserWin); - browserWin.webContents.send('page-load', { isWindowsOS }); + const { url } = config.getGlobalConfigFields([ 'url' ]); + browserWin.webContents.send('page-load', { + isWindowsOS, + locale: i18n.getLocale(), + resources: i18n.loadedResources, + origin: url, + enableCustomTitleBar: false, + isMainWindow: false, + }); // Inserts css on to the window await injectStyles(browserWin, false); browserWin.winName = frameName; @@ -133,11 +158,19 @@ export const handleChildWindow = (webContents: WebContents): void => { removeWindowEventListener(browserWin); }); - // Certificate verification proxy - if (!isDevEnv) { - browserWin.webContents.session.setCertificateVerifyProc(handleCertificateProxyVerification); + if (browserWin.webContents) { + // validate link and create a child window or open in browser + handleChildWindow(browserWin.webContents); + + // Certificate verification proxy + if (!isDevEnv) { + browserWin.webContents.session.setCertificateVerifyProc(handleCertificateProxyVerification); + } + + // Updates media permissions for preload context + const { permissions } = config.getGlobalConfigFields([ 'permissions' ]); + browserWin.webContents.send('is-screen-share-enabled', permissions.media); } - // TODO: handle Permission Requests }); } else { event.preventDefault(); diff --git a/src/app/window-actions.ts b/src/app/window-actions.ts index e399a2f2..56f4d562 100644 --- a/src/app/window-actions.ts +++ b/src/app/window-actions.ts @@ -5,9 +5,9 @@ import { isMac, isWindowsOS } from '../common/env'; import { throttle } from '../common/utils'; import { config } from './config-handler'; import { ICustomBrowserWindow, windowHandler } from './window-handler'; -import { showPopupMenu } from './window-utils'; +import { showPopupMenu, windowExists } from './window-utils'; -export const saveWindowSettings = (): void => { +const saveWindowSettings = (): void => { const browserWindow = BrowserWindow.getFocusedWindow() as ICustomBrowserWindow; if (browserWindow && !browserWindow.isDestroyed()) { @@ -15,32 +15,38 @@ export const saveWindowSettings = (): void => { const [ width, height ] = browserWindow.getSize(); if (x && y && width && height) { browserWindow.webContents.send('boundsChange', { x, y, width, height, windowName: browserWindow.winName } as IBoundsChange); - - if (browserWindow.winName === apiName.mainWindowName) { - const isMaximized = browserWindow.isMaximized(); - const isFullScreen = browserWindow.isFullScreen(); - config.updateUserConfig({ mainWinPos: { x, y, width, height, isMaximized, isFullScreen } }); - } } } }; -export const enterFullScreen = () => { +const windowMaximized = async (): Promise => { const browserWindow = BrowserWindow.getFocusedWindow() as ICustomBrowserWindow; - if (browserWindow && !browserWindow.isDestroyed() && browserWindow.winName === apiName.mainWindowName) { - browserWindow.webContents.send('window-enter-full-screen'); + if (browserWindow && windowExists(browserWindow) && browserWindow.winName === apiName.mainWindowName) { + const isMaximized = browserWindow.isMaximized(); + const isFullScreen = browserWindow.isFullScreen(); + if (isFullScreen) { + browserWindow.webContents.send('window-enter-full-screen'); + } + const { mainWinPos } = config.getUserConfigFields([ 'mainWinPos' ]); + await config.updateUserConfig( { mainWinPos: { ...mainWinPos, ...{ isMaximized, isFullScreen } } }); } }; -export const leaveFullScreen = () => { +const windowUnmaximized = async (): Promise => { const browserWindow = BrowserWindow.getFocusedWindow() as ICustomBrowserWindow; - if (browserWindow && !browserWindow.isDestroyed() && browserWindow.winName === apiName.mainWindowName) { - browserWindow.webContents.send('window-leave-full-screen'); + if (browserWindow && windowExists(browserWindow) && browserWindow.winName === apiName.mainWindowName) { + const isMaximized = browserWindow.isMaximized(); + const isFullScreen = browserWindow.isFullScreen(); + if (!isFullScreen) { + browserWindow.webContents.send('window-leave-full-screen'); + } + const { mainWinPos } = config.getUserConfigFields([ 'mainWinPos' ]); + await config.updateUserConfig( { mainWinPos: { ...mainWinPos, ...{ isMaximized, isFullScreen } } }); } }; -export const throttledWindowChanges = throttle(saveWindowSettings, 1000); +const throttledWindowChanges = throttle(saveWindowSettings, 1000); /** * Tries finding a window we have created with given name. If found, then @@ -140,15 +146,18 @@ export const monitorWindowActions = (window: BrowserWindow): void => { if (!window || window.isDestroyed()) { return; } - const eventNames = [ 'move', 'resize', 'maximize', 'unmaximize' ]; + const eventNames = [ 'move', 'resize' ]; eventNames.forEach((event: string) => { if (window) { // @ts-ignore window.on(event, throttledWindowChanges); } }); - window.on('enter-full-screen', enterFullScreen); - window.on('leave-full-screen', leaveFullScreen); + window.on('enter-full-screen', windowMaximized); + window.on('maximize', windowMaximized); + + window.on('leave-full-screen', windowUnmaximized); + window.on('unmaximize', windowUnmaximized); }; /** @@ -160,13 +169,16 @@ export const removeWindowEventListener = (window: BrowserWindow): void => { if (!window || window.isDestroyed()) { return; } - const eventNames = [ 'move', 'resize', 'maximize', 'unmaximize' ]; + const eventNames = [ 'move', 'resize' ]; eventNames.forEach((event: string) => { if (window) { // @ts-ignore window.removeListener(event, throttledWindowChanges); } }); - window.removeListener('enter-full-screen', enterFullScreen); - window.removeListener('leave-full-screen', leaveFullScreen); + window.removeListener('enter-full-screen', windowMaximized); + window.removeListener('maximize', windowMaximized); + + window.removeListener('leave-full-screen', windowUnmaximized); + window.removeListener('unmaximize', windowUnmaximized); }; diff --git a/src/app/window-handler.ts b/src/app/window-handler.ts index 9ccd26ea..a5d17768 100644 --- a/src/app/window-handler.ts +++ b/src/app/window-handler.ts @@ -229,6 +229,13 @@ export class WindowHandler { ...this.windowOpts, ...getBounds(this.config.mainWinPos, DEFAULT_WIDTH, DEFAULT_HEIGHT), }) as ICustomBrowserWindow; this.mainWindow.winName = apiName.mainWindowName; + const { isFullScreen, isMaximized } = this.config.mainWinPos; + if (isMaximized) { + this.mainWindow.maximize(); + } + if (isFullScreen) { + this.mainWindow.setFullScreen(true); + } // Event needed to hide native menu bar on Windows 10 as we use custom menu bar this.mainWindow.webContents.once('did-start-loading', () => { @@ -260,6 +267,7 @@ export class WindowHandler { resources: i18n.loadedResources, origin: this.globalConfig.url, enableCustomTitleBar: this.isCustomTitleBar, + isMainWindow: true, }); this.appMenu = new AppMenu(); const { permissions } = config.getGlobalConfigFields([ 'permissions' ]); diff --git a/src/renderer/preload-main.ts b/src/renderer/preload-main.ts index 26e290a8..3c47cf62 100644 --- a/src/renderer/preload-main.ts +++ b/src/renderer/preload-main.ts @@ -42,7 +42,7 @@ const createAPI = () => { createAPI(); // When the window is completely loaded -ipcRenderer.on('page-load', (_event, { locale, resources, origin, enableCustomTitleBar }) => { +ipcRenderer.on('page-load', (_event, { locale, resources, origin, enableCustomTitleBar, isMainWindow }) => { // origin for postMessage targetOrigin communication if (origin) { appBridge.origin = origin; @@ -83,7 +83,9 @@ ipcRenderer.on('page-load', (_event, { locale, resources, origin, enableCustomTi ReactDOM.render(downloadManager, footerSFE); } - ipcRenderer.send(apiName.symphonyApi, { - cmd: apiCmds.initMainWindow, - }); + if (isMainWindow) { + ipcRenderer.send(apiName.symphonyApi, { + cmd: apiCmds.initMainWindow, + }); + } });