[MM-43941] Optimize app resizing on Windows/Linux by waiting for the viewport to resize (#2204)
* [MM-43941] Optimize app resizing on Windows/Linux by waiting for the viewport to resize * Some mitigations for Windows * Make the logs sillier * Fixed the no servers case Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
@@ -126,3 +126,5 @@ export const PING_DOMAIN_RESPONSE = 'ping-domain-response';
|
|||||||
export const GET_LANGUAGE_INFORMATION = 'get-language-information';
|
export const GET_LANGUAGE_INFORMATION = 'get-language-information';
|
||||||
export const RETRIEVED_LANGUAGE_INFORMATION = 'retrieved-language-information';
|
export const RETRIEVED_LANGUAGE_INFORMATION = 'retrieved-language-information';
|
||||||
export const GET_AVAILABLE_LANGUAGES = 'get-available-languages';
|
export const GET_AVAILABLE_LANGUAGES = 'get-available-languages';
|
||||||
|
|
||||||
|
export const VIEW_FINISHED_RESIZING = 'view-finished-resizing';
|
||||||
|
@@ -29,6 +29,7 @@ import {
|
|||||||
GET_VIEW_WEBCONTENTS_ID,
|
GET_VIEW_WEBCONTENTS_ID,
|
||||||
DISPATCH_GET_DESKTOP_SOURCES,
|
DISPATCH_GET_DESKTOP_SOURCES,
|
||||||
DESKTOP_SOURCES_RESULT,
|
DESKTOP_SOURCES_RESULT,
|
||||||
|
VIEW_FINISHED_RESIZING,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
|
|
||||||
const UNREAD_COUNT_INTERVAL = 1000;
|
const UNREAD_COUNT_INTERVAL = 1000;
|
||||||
@@ -292,3 +293,6 @@ ipcRenderer.on(DESKTOP_SOURCES_RESULT, (event, sources) => {
|
|||||||
|
|
||||||
/* eslint-enable no-magic-numbers */
|
/* eslint-enable no-magic-numbers */
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
ipcRenderer.send(VIEW_FINISHED_RESIZING);
|
||||||
|
});
|
||||||
|
@@ -40,7 +40,7 @@ import WebContentsEventManager from './webContentEvents';
|
|||||||
const URL_VIEW_DURATION = 10 * SECOND;
|
const URL_VIEW_DURATION = 10 * SECOND;
|
||||||
const URL_VIEW_HEIGHT = 20;
|
const URL_VIEW_HEIGHT = 20;
|
||||||
|
|
||||||
enum LoadingScreenState {
|
export enum LoadingScreenState {
|
||||||
VISIBLE = 1,
|
VISIBLE = 1,
|
||||||
FADING = 2,
|
FADING = 2,
|
||||||
HIDDEN = 3,
|
HIDDEN = 3,
|
||||||
|
@@ -58,6 +58,9 @@ jest.mock('../utils', () => ({
|
|||||||
}));
|
}));
|
||||||
jest.mock('../views/viewManager', () => ({
|
jest.mock('../views/viewManager', () => ({
|
||||||
ViewManager: jest.fn(),
|
ViewManager: jest.fn(),
|
||||||
|
LoadingScreenState: {
|
||||||
|
HIDDEN: 3,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
jest.mock('../CriticalErrorHandler', () => jest.fn());
|
jest.mock('../CriticalErrorHandler', () => jest.fn());
|
||||||
jest.mock('../views/teamDropdownView', () => jest.fn());
|
jest.mock('../views/teamDropdownView', () => jest.fn());
|
||||||
@@ -203,6 +206,7 @@ describe('main/windows/windowManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
jest.runAllTimers();
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -212,33 +216,71 @@ describe('main/windows/windowManager', () => {
|
|||||||
expect(windowManager.teamDropdown.updateWindowBounds).toHaveBeenCalled();
|
expect(windowManager.teamDropdown.updateWindowBounds).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use getContentBounds when the platform is not linux', () => {
|
|
||||||
const originalPlatform = process.platform;
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: 'win32',
|
|
||||||
});
|
|
||||||
windowManager.handleResizeMainWindow();
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: originalPlatform,
|
|
||||||
});
|
|
||||||
expect(view.setBounds).toHaveBeenCalledWith({width: 800, height: 600});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use getSize when the platform is linux', () => {
|
it('should use getSize when the platform is linux', () => {
|
||||||
const originalPlatform = process.platform;
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: 'linux',
|
|
||||||
});
|
|
||||||
windowManager.handleResizeMainWindow();
|
windowManager.handleResizeMainWindow();
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: originalPlatform,
|
|
||||||
});
|
|
||||||
expect(view.setBounds).not.toHaveBeenCalled();
|
expect(view.setBounds).not.toHaveBeenCalled();
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
expect(view.setBounds).toHaveBeenCalledWith({width: 1000, height: 900});
|
expect(view.setBounds).toHaveBeenCalledWith({width: 1000, height: 900});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('handleWillResizeMainWindow', () => {
|
||||||
|
const windowManager = new WindowManager();
|
||||||
|
const view = {
|
||||||
|
setBounds: jest.fn(),
|
||||||
|
tab: {
|
||||||
|
url: 'http://server-1.com',
|
||||||
|
},
|
||||||
|
view: {
|
||||||
|
webContents: {
|
||||||
|
getURL: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
windowManager.viewManager = {
|
||||||
|
getCurrentView: () => view,
|
||||||
|
setLoadingScreenBounds: jest.fn(),
|
||||||
|
loadingScreenState: 3,
|
||||||
|
};
|
||||||
|
windowManager.mainWindow = {
|
||||||
|
getContentBounds: () => ({width: 800, height: 600}),
|
||||||
|
getSize: () => [1000, 900],
|
||||||
|
};
|
||||||
|
windowManager.teamDropdown = {
|
||||||
|
updateWindowBounds: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getAdjustedWindowBoundaries.mockImplementation((width, height) => ({width, height}));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
windowManager.isResizing = false;
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update loading screen and team dropdown bounds', () => {
|
||||||
|
const event = {preventDefault: jest.fn()};
|
||||||
|
windowManager.handleWillResizeMainWindow(event, {width: 800, height: 600});
|
||||||
|
expect(windowManager.viewManager.setLoadingScreenBounds).toHaveBeenCalled();
|
||||||
|
expect(windowManager.teamDropdown.updateWindowBounds).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not resize if the app is already resizing', () => {
|
||||||
|
windowManager.isResizing = true;
|
||||||
|
const event = {preventDefault: jest.fn()};
|
||||||
|
windowManager.handleWillResizeMainWindow(event, {width: 800, height: 600});
|
||||||
|
expect(view.setBounds).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use provided bounds', () => {
|
||||||
|
const event = {preventDefault: jest.fn()};
|
||||||
|
windowManager.handleWillResizeMainWindow(event, {width: 800, height: 600});
|
||||||
|
expect(windowManager.isResizing).toBe(true);
|
||||||
|
expect(view.setBounds).toHaveBeenCalledWith({width: 800, height: 600});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('restoreMain', () => {
|
describe('restoreMain', () => {
|
||||||
const windowManager = new WindowManager();
|
const windowManager = new WindowManager();
|
||||||
windowManager.mainWindow = {
|
windowManager.mainWindow = {
|
||||||
|
@@ -26,15 +26,18 @@ import {
|
|||||||
DISPATCH_GET_DESKTOP_SOURCES,
|
DISPATCH_GET_DESKTOP_SOURCES,
|
||||||
DESKTOP_SOURCES_RESULT,
|
DESKTOP_SOURCES_RESULT,
|
||||||
RELOAD_CURRENT_VIEW,
|
RELOAD_CURRENT_VIEW,
|
||||||
|
VIEW_FINISHED_RESIZING,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import urlUtils from 'common/utils/url';
|
import urlUtils from 'common/utils/url';
|
||||||
import {SECOND} from 'common/utils/constants';
|
import {SECOND} from 'common/utils/constants';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import {getTabViewName, TAB_MESSAGING} from 'common/tabs/TabView';
|
import {getTabViewName, TAB_MESSAGING} from 'common/tabs/TabView';
|
||||||
|
|
||||||
|
import {MattermostView} from 'main/views/MattermostView';
|
||||||
|
|
||||||
import {getAdjustedWindowBoundaries, shouldHaveBackBar} from '../utils';
|
import {getAdjustedWindowBoundaries, shouldHaveBackBar} from '../utils';
|
||||||
|
|
||||||
import {ViewManager} from '../views/viewManager';
|
import {ViewManager, LoadingScreenState} from '../views/viewManager';
|
||||||
import CriticalErrorHandler from '../CriticalErrorHandler';
|
import CriticalErrorHandler from '../CriticalErrorHandler';
|
||||||
|
|
||||||
import TeamDropdownView from '../views/teamDropdownView';
|
import TeamDropdownView from '../views/teamDropdownView';
|
||||||
@@ -71,6 +74,7 @@ export class WindowManager {
|
|||||||
ipcMain.handle(GET_VIEW_WEBCONTENTS_ID, this.handleGetWebContentsId);
|
ipcMain.handle(GET_VIEW_WEBCONTENTS_ID, this.handleGetWebContentsId);
|
||||||
ipcMain.on(DISPATCH_GET_DESKTOP_SOURCES, this.handleGetDesktopSources);
|
ipcMain.on(DISPATCH_GET_DESKTOP_SOURCES, this.handleGetDesktopSources);
|
||||||
ipcMain.on(RELOAD_CURRENT_VIEW, this.handleReloadCurrentView);
|
ipcMain.on(RELOAD_CURRENT_VIEW, this.handleReloadCurrentView);
|
||||||
|
ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateConfig = () => {
|
handleUpdateConfig = () => {
|
||||||
@@ -134,7 +138,12 @@ export class WindowManager {
|
|||||||
});
|
});
|
||||||
this.mainWindow.on('maximize', this.handleMaximizeMainWindow);
|
this.mainWindow.on('maximize', this.handleMaximizeMainWindow);
|
||||||
this.mainWindow.on('unmaximize', this.handleUnmaximizeMainWindow);
|
this.mainWindow.on('unmaximize', this.handleUnmaximizeMainWindow);
|
||||||
this.mainWindow.on('resize', this.handleResizeMainWindow);
|
if (process.platform === 'linux') {
|
||||||
|
this.mainWindow.on('resize', this.handleResizeMainWindow);
|
||||||
|
} else {
|
||||||
|
this.mainWindow.on('will-resize', this.handleWillResizeMainWindow);
|
||||||
|
this.mainWindow.on('resized', this.handleResizedMainWindow);
|
||||||
|
}
|
||||||
this.mainWindow.on('focus', this.focusBrowserView);
|
this.mainWindow.on('focus', this.focusBrowserView);
|
||||||
this.mainWindow.on('enter-full-screen', () => this.sendToRenderer('enter-full-screen'));
|
this.mainWindow.on('enter-full-screen', () => this.sendToRenderer('enter-full-screen'));
|
||||||
this.mainWindow.on('leave-full-screen', () => this.sendToRenderer('leave-full-screen'));
|
this.mainWindow.on('leave-full-screen', () => this.sendToRenderer('leave-full-screen'));
|
||||||
@@ -173,43 +182,73 @@ export class WindowManager {
|
|||||||
this.sendToRenderer(MAXIMIZE_CHANGE, false);
|
this.sendToRenderer(MAXIMIZE_CHANGE, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResizeMainWindow = () => {
|
isResizing = false;
|
||||||
log.debug('WindowManager.handleResizeMainWindow');
|
|
||||||
|
handleWillResizeMainWindow = (event: Event, newBounds: Electron.Rectangle) => {
|
||||||
|
log.silly('WindowManager.handleWillResizeMainWindow');
|
||||||
|
|
||||||
if (!(this.viewManager && this.mainWindow)) {
|
if (!(this.viewManager && this.mainWindow)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const currentView = this.viewManager.getCurrentView();
|
|
||||||
let bounds: Partial<Electron.Rectangle>;
|
|
||||||
|
|
||||||
// Workaround for linux maximizing/minimizing, which doesn't work properly because of these bugs:
|
if (this.isResizing && this.viewManager.loadingScreenState === LoadingScreenState.HIDDEN && this.viewManager.getCurrentView()) {
|
||||||
// https://github.com/electron/electron/issues/28699
|
log.silly('prevented resize');
|
||||||
// https://github.com/electron/electron/issues/28106
|
event.preventDefault();
|
||||||
if (process.platform === 'linux') {
|
return;
|
||||||
const size = this.mainWindow.getSize();
|
|
||||||
bounds = {width: size[0], height: size[1]};
|
|
||||||
} else {
|
|
||||||
bounds = this.mainWindow.getContentBounds();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setBoundsFunction = () => {
|
this.throttledWillResize(newBounds);
|
||||||
if (currentView) {
|
this.viewManager?.setLoadingScreenBounds();
|
||||||
currentView.setBounds(getAdjustedWindowBoundaries(bounds.width!, bounds.height!, shouldHaveBackBar(currentView.tab.url, currentView.view.webContents.getURL())));
|
this.teamDropdown?.updateWindowBounds();
|
||||||
}
|
ipcMain.emit(RESIZE_MODAL, null, newBounds);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Another workaround since the window doesn't update properly under Linux for some reason
|
handleResizedMainWindow = () => {
|
||||||
|
if (this.mainWindow) {
|
||||||
|
this.throttledWillResize(this.mainWindow?.getContentBounds());
|
||||||
|
}
|
||||||
|
this.isResizing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleViewFinishedResizing = () => {
|
||||||
|
this.isResizing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private throttledWillResize = (newBounds: Electron.Rectangle) => {
|
||||||
|
this.isResizing = true;
|
||||||
|
this.setCurrentViewBounds(newBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResizeMainWindow = () => {
|
||||||
|
log.silly('WindowManager.handleResizeMainWindow');
|
||||||
|
|
||||||
|
if (!(this.viewManager && this.mainWindow)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const size = this.mainWindow.getSize();
|
||||||
|
const bounds = {width: size[0], height: size[1]};
|
||||||
|
|
||||||
|
// Another workaround since the window doesn't update p roperly under Linux for some reason
|
||||||
// See above comment
|
// See above comment
|
||||||
if (process.platform === 'linux') {
|
setTimeout(this.setCurrentViewBounds, 10, bounds);
|
||||||
setTimeout(setBoundsFunction, 10);
|
|
||||||
} else {
|
|
||||||
setBoundsFunction();
|
|
||||||
}
|
|
||||||
this.viewManager.setLoadingScreenBounds();
|
this.viewManager.setLoadingScreenBounds();
|
||||||
this.teamDropdown?.updateWindowBounds();
|
this.teamDropdown?.updateWindowBounds();
|
||||||
ipcMain.emit(RESIZE_MODAL, null, bounds);
|
ipcMain.emit(RESIZE_MODAL, null, bounds);
|
||||||
|
};
|
||||||
|
|
||||||
|
setCurrentViewBounds = (bounds: {width: number; height: number}) => {
|
||||||
|
const currentView = this.viewManager?.getCurrentView();
|
||||||
|
if (currentView) {
|
||||||
|
const adjustedBounds = getAdjustedWindowBoundaries(bounds.width, bounds.height, shouldHaveBackBar(currentView.tab.url, currentView.view.webContents.getURL()));
|
||||||
|
this.setBoundsFunction(currentView, adjustedBounds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setBoundsFunction = (currentView: MattermostView, bounds: Electron.Rectangle) => {
|
||||||
|
log.silly('setBoundsFunction', bounds.width, bounds.height);
|
||||||
|
currentView.setBounds(bounds);
|
||||||
|
};
|
||||||
|
|
||||||
// max retries allows the message to get to the renderer even if it is sent while the app is starting up.
|
// max retries allows the message to get to the renderer even if it is sent while the app is starting up.
|
||||||
sendToRendererWithRetry = (maxRetries: number, channel: string, ...args: any[]) => {
|
sendToRendererWithRetry = (maxRetries: number, channel: string, ...args: any[]) => {
|
||||||
if (!this.mainWindow || !this.mainWindowReady) {
|
if (!this.mainWindow || !this.mainWindowReady) {
|
||||||
|
Reference in New Issue
Block a user