From 6d1d7a41113b0518b8b3fb8e293ea5a48b11560e Mon Sep 17 00:00:00 2001 From: "Grigorii K. Shartsev" Date: Thu, 21 Nov 2024 00:37:20 +0100 Subject: [PATCH 1/2] chore(app): add waitWindowMarkedReady Signed-off-by: Grigorii K. Shartsev --- src/app/utils.ts | 48 ++++++++++++++++++++++++++++++++++- src/preload.js | 4 +++ src/shared/markWindowReady.ts | 14 ++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/shared/markWindowReady.ts diff --git a/src/app/utils.ts b/src/app/utils.ts index 2a871cdc..764ae9e6 100644 --- a/src/app/utils.ts +++ b/src/app/utils.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { screen } from 'electron' +import type { IpcMainEvent } from 'electron' +import { BrowserWindow, ipcMain, screen } from 'electron' import { getAppConfig } from './AppConfig.ts' /** @@ -47,3 +48,48 @@ export function getScaledWindowMinSize({ minWidth, minHeight }: { minWidth: numb minHeight: height, } } + +/** + * Memoized promises of waitWindowMarkedReady. + * Using WeakMap so that the promises are garbage collected when the window is destroyed. + */ +const windowMarkedReadyPromises = new WeakMap>() + +/** + * Wait for the window to be marked as ready to show by its renderer process + * @param window - BrowserWindow + */ +export function waitWindowMarkedReady(window: BrowserWindow): Promise { + // As a window is marked ready only once. + // Awaiting promise is memoized to allow multiple calls and calls on a ready window. + + if (windowMarkedReadyPromises.has(window)) { + return windowMarkedReadyPromises.get(window)! + } + + const promise: Promise = new Promise((resolve) => { + const handleMarkWindowReady = (event: IpcMainEvent) => { + if (event.sender === window.webContents) { + ipcMain.removeListener('app:markWindowReady', handleMarkWindowReady) + resolve() + } + } + ipcMain.on('app:markWindowReady', handleMarkWindowReady) + }) + + windowMarkedReadyPromises.set(window, promise) + + return promise +} + +/** + * Show the window when it is marked as ready to show its renderer process + * @param window - The BrowserWindow + */ +export async function showWhenWindowMarkedReady(window: BrowserWindow): Promise { + if (window.isVisible()) { + return + } + await waitWindowMarkedReady(window) + window.show() +} diff --git a/src/preload.js b/src/preload.js index be1db176..b5727813 100644 --- a/src/preload.js +++ b/src/preload.js @@ -32,6 +32,10 @@ const TALK_DESKTOP = { * @type {typeof packageInfo} packageInfo */ packageInfo, + /** + * Mark the window as ready to show + */ + markWindowReady: () => ipcRenderer.send('app:markWindowReady'), /** * Get app name * diff --git a/src/shared/markWindowReady.ts b/src/shared/markWindowReady.ts new file mode 100644 index 00000000..493d3713 --- /dev/null +++ b/src/shared/markWindowReady.ts @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Mark the window as ready to show + */ +export function markWindowReady() { + // Wait for the next frame to ensure the window content is actually rendered + requestAnimationFrame(() => { + window.TALK_DESKTOP.markWindowReady() + }) +} From 2e23ee7357d908b2149f408fd077448709dbee85 Mon Sep 17 00:00:00 2001 From: "Grigorii K. Shartsev" Date: Thu, 21 Nov 2024 00:38:09 +0100 Subject: [PATCH 2/2] fix: show windows when the app is rendered Signed-off-by: Grigorii K. Shartsev --- src/authentication/authentication.window.js | 4 ++- .../renderer/authentication.main.js | 3 ++ src/help/help.window.js | 6 ++-- src/help/renderer/help.app.js | 12 +++---- src/main.js | 32 +++++++++---------- src/talk/renderer/talk.main.js | 3 ++ src/upgrade/renderer/upgrade.app.js | 3 ++ src/upgrade/upgrade.window.js | 6 ++-- src/welcome/welcome.js | 3 ++ src/welcome/welcome.window.js | 4 ++- 10 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/authentication/authentication.window.js b/src/authentication/authentication.window.js index 55b06697..0fcdef39 100644 --- a/src/authentication/authentication.window.js +++ b/src/authentication/authentication.window.js @@ -8,7 +8,7 @@ const { BASE_TITLE, TITLE_BAR_HEIGHT } = require('../constants.js') const { applyContextMenu } = require('../app/applyContextMenu.js') const { getBrowserWindowIcon } = require('../shared/icons.utils.js') const { getAppConfig } = require('../app/AppConfig.ts') -const { getScaledWindowSize } = require('../app/utils.ts') +const { getScaledWindowSize, showWhenWindowMarkedReady } = require('../app/utils.ts') /** * @return {import('electron').BrowserWindow} @@ -53,6 +53,8 @@ function createAuthenticationWindow() { window.loadURL(AUTHENTICATION_WINDOW_WEBPACK_ENTRY) + showWhenWindowMarkedReady(window) + return window } diff --git a/src/authentication/renderer/authentication.main.js b/src/authentication/renderer/authentication.main.js index 5b2c5f21..3536f7fb 100644 --- a/src/authentication/renderer/authentication.main.js +++ b/src/authentication/renderer/authentication.main.js @@ -8,7 +8,10 @@ import '../../shared/assets/global.styles.css' import Vue from 'vue' import AuthenticationApp from './AuthenticationApp.vue' import { setupWebPage } from '../../shared/setupWebPage.js' +import { markWindowReady } from '../../shared/markWindowReady.ts' await setupWebPage() new Vue(AuthenticationApp).$mount('#app') + +markWindowReady() diff --git a/src/help/help.window.js b/src/help/help.window.js index 61e06228..753e4be8 100644 --- a/src/help/help.window.js +++ b/src/help/help.window.js @@ -7,7 +7,7 @@ const { BASE_TITLE } = require('../constants.js') const { BrowserWindow } = require('electron') const { applyExternalLinkHandler } = require('../app/externalLinkHandlers.js') const { getBrowserWindowIcon } = require('../shared/icons.utils.js') -const { getScaledWindowSize } = require('../app/utils.ts') +const { getScaledWindowSize, showWhenWindowMarkedReady } = require('../app/utils.ts') const { applyContextMenu } = require('../app/applyContextMenu.js') /** @@ -44,9 +44,7 @@ function createHelpWindow(parentWindow) { applyExternalLinkHandler(window) applyContextMenu(window) - window.on('ready-to-show', () => { - window.show() - }) + showWhenWindowMarkedReady(window) return window } diff --git a/src/help/renderer/help.app.js b/src/help/renderer/help.app.js index 56a82bc9..831f0350 100644 --- a/src/help/renderer/help.app.js +++ b/src/help/renderer/help.app.js @@ -6,13 +6,13 @@ import '../../shared/assets/global.styles.css' import './help.styles.css' -import Vue, { defineAsyncComponent } from 'vue' +import Vue from 'vue' import { setupWebPage } from '../../shared/setupWebPage.js' +import { markWindowReady } from '../../shared/markWindowReady.ts' await setupWebPage() -const HelpApp = defineAsyncComponent(() => import('./HelpApp.vue')) -new Vue({ - name: 'HelpAppRoot', - render: h => h(HelpApp), -}).$mount('#app') +const { default: HelpApp } = await import('./HelpApp.vue') +new Vue(HelpApp).$mount('#app') + +markWindowReady() diff --git a/src/main.js b/src/main.js index c5d64721..c80995bb 100644 --- a/src/main.js +++ b/src/main.js @@ -20,6 +20,7 @@ const { installVueDevtools } = require('./install-vue-devtools.js') const { loadAppConfig, getAppConfig, setAppConfig } = require('./app/AppConfig.ts') const { triggerDownloadUrl } = require('./app/downloads.ts') const { applyTheme } = require('./app/theme.config.ts') +const { showWhenWindowMarkedReady, waitWindowMarkedReady } = require('./app/utils.ts') /** * Parse command line arguments @@ -146,7 +147,7 @@ app.whenReady().then(async () => { // There is no window (possible on macOS) - create if (!mainWindow || mainWindow.isDestroyed()) { mainWindow = createMainWindow() - mainWindow.once('ready-to-show', () => mainWindow.show()) + showWhenWindowMarkedReady(mainWindow) return } @@ -235,7 +236,6 @@ app.whenReady().then(async () => { mainWindow = createWelcomeWindow() createMainWindow = createWelcomeWindow - mainWindow.once('ready-to-show', () => mainWindow.show()) ipcMain.once('appData:receive', async (event, appData) => { const welcomeWindow = mainWindow @@ -256,14 +256,15 @@ app.whenReady().then(async () => { createMainWindow = createAuthenticationWindow } - mainWindow.once('ready-to-show', () => { - // Do not show the main window if it is the Talk Window opened in the background - const isTalkWindow = createMainWindow === createTalkWindow - if (!isTalkWindow || !ARGUMENTS.openInBackground) { - mainWindow.show() - } - welcomeWindow.close() - }) + await waitWindowMarkedReady(mainWindow) + + welcomeWindow.close() + + // Do not show the main window if it is the Talk Window opened in the background + const isTalkWindow = createMainWindow === createTalkWindow + if (!isTalkWindow || !ARGUMENTS.openInBackground) { + mainWindow.show() + } }) let macDockBounceId @@ -289,19 +290,18 @@ app.whenReady().then(async () => { ipcMain.handle('authentication:openLoginWebView', async (event, serverUrl) => openLoginWebView(mainWindow, serverUrl)) - ipcMain.handle('authentication:login', async () => { + ipcMain.handle('authentication:login', () => { mainWindow.close() mainWindow = createTalkWindow() createMainWindow = createTalkWindow - mainWindow.once('ready-to-show', () => mainWindow.show()) + showWhenWindowMarkedReady(mainWindow) }) - ipcMain.handle('authentication:logout', async (event) => { + ipcMain.handle('authentication:logout', async () => { if (createMainWindow === createTalkWindow) { await mainWindow.webContents.session.clearStorageData() const authenticationWindow = createAuthenticationWindow() createMainWindow = createAuthenticationWindow - authenticationWindow.once('ready-to-show', () => authenticationWindow.show()) mainWindow.destroy() mainWindow = authenticationWindow @@ -324,7 +324,7 @@ app.whenReady().then(async () => { isInWindowRelaunch = true mainWindow.destroy() mainWindow = createMainWindow() - mainWindow.once('ready-to-show', () => mainWindow.show()) + showWhenWindowMarkedReady(mainWindow) isInWindowRelaunch = false }) @@ -340,7 +340,7 @@ app.whenReady().then(async () => { // dock icon is clicked and there are no other windows open. // See window-all-closed event handler. mainWindow = createMainWindow() - mainWindow.once('ready-to-show', () => mainWindow.show()) + showWhenWindowMarkedReady(mainWindow) } }) }) diff --git a/src/talk/renderer/talk.main.js b/src/talk/renderer/talk.main.js index 26aead64..dc409143 100644 --- a/src/talk/renderer/talk.main.js +++ b/src/talk/renderer/talk.main.js @@ -11,6 +11,7 @@ import './assets/overrides.css' import 'regenerator-runtime' // TODO: Why isn't it added on bundling import { initTalkHashIntegration } from './init.js' import { setupWebPage } from '../../shared/setupWebPage.js' +import { markWindowReady } from '../../shared/markWindowReady.ts' import { createViewer } from './Viewer/Viewer.js' import { createDesktopApp } from './desktop.app.js' import { registerTalkDesktopSettingsSection } from './Settings/index.ts' @@ -35,3 +36,5 @@ window.OCA.Talk.Desktop.talkRouter.value = window.OCA.Talk.instance.$router registerTalkDesktopSettingsSection() await import('./notifications/notifications.store.js') + +markWindowReady() diff --git a/src/upgrade/renderer/upgrade.app.js b/src/upgrade/renderer/upgrade.app.js index 6d09c4fb..611731d1 100644 --- a/src/upgrade/renderer/upgrade.app.js +++ b/src/upgrade/renderer/upgrade.app.js @@ -8,7 +8,10 @@ import '../../shared/assets/global.styles.css' import Vue from 'vue' import UpgradeApp from './UpgradeApp.vue' import { setupWebPage } from '../../shared/setupWebPage.js' +import { markWindowReady } from '../../shared/markWindowReady.ts' await setupWebPage() new Vue(UpgradeApp).$mount('#app') + +markWindowReady() diff --git a/src/upgrade/upgrade.window.js b/src/upgrade/upgrade.window.js index 25679beb..5a4bc5d1 100644 --- a/src/upgrade/upgrade.window.js +++ b/src/upgrade/upgrade.window.js @@ -7,7 +7,7 @@ const { BASE_TITLE } = require('../constants.js') const { BrowserWindow } = require('electron') const { applyExternalLinkHandler } = require('../app/externalLinkHandlers.js') const { getBrowserWindowIcon } = require('../shared/icons.utils.js') -const { getScaledWindowSize } = require('../app/utils.ts') +const { getScaledWindowSize, showWhenWindowMarkedReady } = require('../app/utils.ts') /** * @@ -38,9 +38,7 @@ function createUpgradeWindow() { applyExternalLinkHandler(window) - window.on('ready-to-show', () => { - window.show() - }) + showWhenWindowMarkedReady(window) return window } diff --git a/src/welcome/welcome.js b/src/welcome/welcome.js index 2ce26812..e5ff7929 100644 --- a/src/welcome/welcome.js +++ b/src/welcome/welcome.js @@ -9,6 +9,7 @@ import { appData } from '../app/AppData.js' import { refetchAppDataIfDirty } from '../app/appData.service.js' import { initGlobals } from '../shared/globals/globals.js' import { applyAxiosInterceptors } from '../shared/setupWebPage.js' +import { markWindowReady } from '../shared/markWindowReady.ts' const quitButton = document.querySelector('.quit') quitButton.addEventListener('click', () => window.TALK_DESKTOP.quit()) @@ -20,6 +21,8 @@ window.TALK_DESKTOP.getSystemInfo().then(os => { } }) +markWindowReady() + appData.restore() initGlobals() diff --git a/src/welcome/welcome.window.js b/src/welcome/welcome.window.js index 75bbdf8e..bee0a685 100644 --- a/src/welcome/welcome.window.js +++ b/src/welcome/welcome.window.js @@ -6,7 +6,7 @@ const { BrowserWindow } = require('electron') const { getBrowserWindowIcon } = require('../shared/icons.utils.js') const { isMac } = require('../app/system.utils.ts') -const { getScaledWindowSize } = require('../app/utils.ts') +const { getScaledWindowSize, showWhenWindowMarkedReady } = require('../app/utils.ts') /** * @return {import('electron').BrowserWindow} @@ -37,6 +37,8 @@ function createWelcomeWindow() { window.loadURL(WELCOME_WINDOW_WEBPACK_ENTRY) + showWhenWindowMarkedReady(window) + return window }