[MM-59823] Migrate BrowserView
to WebContentsView
(#3177)
* Migrate to WebContentsView from BrowserView * A bit of cleanup, stop holding reference to the loading screen * Fix tests * Fix i18n --------- Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
@@ -162,7 +162,7 @@ describe('main/app/app', () => {
|
||||
expect(CertificateStore.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should load URL using MattermostBrowserView when trusting certificate', async () => {
|
||||
it('should load URL using MattermostWebContentsView when trusting certificate', async () => {
|
||||
dialog.showMessageBox.mockResolvedValue({response: 0});
|
||||
await handleAppCertificateError(event, webContents, testURL, 'error-1', certificate, callback);
|
||||
expect(callback).toHaveBeenCalledWith(true);
|
||||
|
@@ -10,7 +10,7 @@ jest.mock('electron-context-menu', () => {
|
||||
|
||||
describe('main/contextMenu', () => {
|
||||
describe('shouldShowMenu', () => {
|
||||
const contextMenu = new ContextMenu();
|
||||
const contextMenu = new ContextMenu({}, {webContents: {}});
|
||||
|
||||
it('should not show menu on internal link', () => {
|
||||
expect(contextMenu.menuOptions.shouldShowMenu(null, {
|
||||
@@ -73,7 +73,7 @@ describe('main/contextMenu', () => {
|
||||
|
||||
describe('reload', () => {
|
||||
it('should call dispose on reload', () => {
|
||||
const contextMenu = new ContextMenu();
|
||||
const contextMenu = new ContextMenu({}, {webContents: {}});
|
||||
const fn = contextMenu.menuDispose;
|
||||
contextMenu.reload();
|
||||
expect(fn).toHaveBeenCalled();
|
||||
|
@@ -2,7 +2,7 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {BrowserView, BrowserWindow, ContextMenuParams, Event} from 'electron';
|
||||
import type {WebContentsView, BrowserWindow, ContextMenuParams, Event} from 'electron';
|
||||
import type {Options} from 'electron-context-menu';
|
||||
import electronContextMenu from 'electron-context-menu';
|
||||
|
||||
@@ -29,11 +29,11 @@ const defaultMenuOptions = {
|
||||
};
|
||||
|
||||
export default class ContextMenu {
|
||||
view: BrowserWindow | BrowserView;
|
||||
view: BrowserWindow | WebContentsView;
|
||||
menuOptions: Options;
|
||||
menuDispose?: () => void;
|
||||
|
||||
constructor(options: Options, view: BrowserWindow | BrowserView) {
|
||||
constructor(options: Options, view: BrowserWindow | WebContentsView) {
|
||||
const providedOptions: Options = options || {};
|
||||
|
||||
this.menuOptions = Object.assign({}, defaultMenuOptions, providedOptions);
|
||||
@@ -52,7 +52,7 @@ export default class ContextMenu {
|
||||
reload = () => {
|
||||
this.dispose();
|
||||
|
||||
const options = {window: this.view, ...this.menuOptions};
|
||||
const options = {window: this.view.webContents, ...this.menuOptions};
|
||||
this.menuDispose = electronContextMenu(options);
|
||||
};
|
||||
}
|
||||
|
@@ -91,14 +91,16 @@ describe('main/diagnostics/utils', () => {
|
||||
isDestroyed: () => false,
|
||||
isVisible: () => true,
|
||||
isEnabled: () => true,
|
||||
getBrowserViews: () => [{
|
||||
getBounds: () => ({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 800,
|
||||
height: 500,
|
||||
}),
|
||||
}],
|
||||
contentView: {
|
||||
children: [{
|
||||
getBounds: () => ({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 800,
|
||||
height: 500,
|
||||
}),
|
||||
}],
|
||||
},
|
||||
};
|
||||
it('should return true if window ok', () => {
|
||||
expect(browserWindowVisibilityStatus('testWindow', bWindow).every((check) => check.ok)).toBe(true);
|
||||
@@ -118,17 +120,19 @@ describe('main/diagnostics/utils', () => {
|
||||
it('should return false if window is not enabled', () => {
|
||||
expect(browserWindowVisibilityStatus('testWindow', {...bWindow, isEnabled: () => false}).every((check) => check.ok)).toBe(false);
|
||||
});
|
||||
it('should return false if a child browserView has invalid bounds', () => {
|
||||
it('should return false if a child webContentsView has invalid bounds', () => {
|
||||
expect(browserWindowVisibilityStatus('testWindow', {
|
||||
...bWindow,
|
||||
getBrowserViews: () => [{
|
||||
getBounds: () => ({
|
||||
x: -1,
|
||||
y: -4000,
|
||||
width: 800,
|
||||
height: 500,
|
||||
}),
|
||||
}],
|
||||
contentView: {
|
||||
children: [{
|
||||
getBounds: () => ({
|
||||
x: -1,
|
||||
y: -4000,
|
||||
width: 800,
|
||||
height: 500,
|
||||
}),
|
||||
}],
|
||||
},
|
||||
}).every((check) => check.ok)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
@@ -109,7 +109,7 @@ export function browserWindowVisibilityStatus(name: string, bWindow?: BrowserWin
|
||||
const destroyed = bWindow.isDestroyed();
|
||||
const visible = bWindow.isVisible();
|
||||
const enabled = bWindow.isEnabled();
|
||||
const browserViewsBounds = bWindow.getBrowserViews()?.map((view) => view.getBounds());
|
||||
const webContentsViewsBounds = bWindow.contentView.children.map((view) => view.getBounds());
|
||||
|
||||
status.push({
|
||||
name: 'windowExists',
|
||||
@@ -141,9 +141,9 @@ export function browserWindowVisibilityStatus(name: string, bWindow?: BrowserWin
|
||||
ok: enabled,
|
||||
});
|
||||
status.push({
|
||||
name: 'browserViewsBounds',
|
||||
ok: browserViewsBounds.every((bounds) => boundsOk(bounds)),
|
||||
data: browserViewsBounds,
|
||||
name: 'webContentsViewsBounds',
|
||||
ok: webContentsViewsBounds.every((bounds) => boundsOk(bounds)),
|
||||
data: webContentsViewsBounds,
|
||||
});
|
||||
|
||||
return status;
|
||||
|
@@ -30,7 +30,7 @@ jest.mock('electron', () => {
|
||||
getAppPath: jest.fn(),
|
||||
getPath: jest.fn(() => '/valid/downloads/path'),
|
||||
},
|
||||
BrowserView: jest.fn().mockImplementation(() => ({
|
||||
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||
webContents: {
|
||||
loadURL: jest.fn(),
|
||||
focus: jest.fn(),
|
||||
|
@@ -13,7 +13,6 @@ import {
|
||||
USER_ACTIVITY_UPDATE,
|
||||
BROWSER_HISTORY_PUSH,
|
||||
GET_VIEW_INFO_FOR_TEST,
|
||||
VIEW_FINISHED_RESIZING,
|
||||
CALLS_JOIN_CALL,
|
||||
CALLS_JOINED_CALL,
|
||||
CALLS_LEAVE_CALL,
|
||||
@@ -140,12 +139,6 @@ if (process.env.NODE_ENV === 'test') {
|
||||
****************************************************************************
|
||||
*/
|
||||
|
||||
// Let the main process know when the window has finished resizing
|
||||
// This is to reduce the amount of white box that happens when expand the BrowserView
|
||||
window.addEventListener('resize', () => {
|
||||
ipcRenderer.send(VIEW_FINISHED_RESIZING);
|
||||
});
|
||||
|
||||
// Enable secure input on macOS clients when the user is on a password input
|
||||
let isPasswordBox = false;
|
||||
const shouldSecureInput = (element: {tagName?: string; type?: string} | null, force = false) => {
|
||||
|
@@ -89,7 +89,6 @@ import {
|
||||
OPEN_WINDOWS_CAMERA_PREFERENCES,
|
||||
OPEN_WINDOWS_MICROPHONE_PREFERENCES,
|
||||
GET_MEDIA_ACCESS_STATUS,
|
||||
VIEW_FINISHED_RESIZING,
|
||||
GET_NONCE,
|
||||
IS_DEVELOPER_MODE_ENABLED,
|
||||
METRICS_REQUEST,
|
||||
@@ -177,7 +176,6 @@ contextBridge.exposeInMainWorld('desktop', {
|
||||
openWindowsCameraPreferences: () => ipcRenderer.send(OPEN_WINDOWS_CAMERA_PREFERENCES),
|
||||
openWindowsMicrophonePreferences: () => ipcRenderer.send(OPEN_WINDOWS_MICROPHONE_PREFERENCES),
|
||||
getMediaAccessStatus: (mediaType) => ipcRenderer.invoke(GET_MEDIA_ACCESS_STATUS, mediaType),
|
||||
viewFinishedResizing: () => ipcRenderer.send(VIEW_FINISHED_RESIZING),
|
||||
|
||||
downloadsDropdown: {
|
||||
toggleDownloadsDropdownMenu: (payload) => ipcRenderer.send(TOGGLE_DOWNLOADS_DROPDOWN_MENU, payload),
|
||||
|
@@ -8,7 +8,7 @@ import {LOAD_FAILED, UPDATE_TARGET_URL} from 'common/communication';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import MessagingView from 'common/views/MessagingView';
|
||||
|
||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
||||
import {MattermostWebContentsView} from './MattermostWebContentsView';
|
||||
|
||||
import ContextMenu from '../contextMenu';
|
||||
import MainWindow from '../windows/mainWindow';
|
||||
@@ -18,7 +18,7 @@ jest.mock('electron', () => ({
|
||||
getVersion: () => '5.0.0',
|
||||
getPath: jest.fn(() => '/valid/downloads/path'),
|
||||
},
|
||||
BrowserView: jest.fn().mockImplementation(() => ({
|
||||
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||
webContents: {
|
||||
loadURL: jest.fn(),
|
||||
on: jest.fn(),
|
||||
@@ -72,10 +72,10 @@ jest.mock('main/performanceMonitor', () => ({
|
||||
const server = new MattermostServer({name: 'server_name', url: 'http://server-1.com'});
|
||||
const view = new MessagingView(server, true);
|
||||
|
||||
describe('main/views/MattermostBrowserView', () => {
|
||||
describe('main/views/MattermostWebContentsView', () => {
|
||||
describe('load', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
@@ -85,38 +85,38 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
|
||||
it('should load provided URL when provided', async () => {
|
||||
const promise = Promise.resolve();
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.load('http://server-2.com');
|
||||
await promise;
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-2.com/', expect.any(Object));
|
||||
expect(mattermostView.webContentsView.webContents.loadURL).toBeCalledWith('http://server-2.com/', expect.any(Object));
|
||||
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-2.com/');
|
||||
});
|
||||
|
||||
it('should load server URL when not provided', async () => {
|
||||
const promise = Promise.resolve();
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.load();
|
||||
await promise;
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.webContentsView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com/');
|
||||
});
|
||||
|
||||
it('should load server URL when bad url provided', async () => {
|
||||
const promise = Promise.resolve();
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.load('a-bad<url');
|
||||
await promise;
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.webContentsView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com/');
|
||||
});
|
||||
|
||||
it('should call retry when failing to load', async () => {
|
||||
const error = new Error('test');
|
||||
const promise = Promise.reject(error);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.load('a-bad<url');
|
||||
await expect(promise).rejects.toThrow(error);
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.webContentsView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com/', error);
|
||||
});
|
||||
|
||||
@@ -124,23 +124,23 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
const error = new Error('test');
|
||||
error.code = 'ERR_CERT_ERROR';
|
||||
const promise = Promise.reject(error);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.load('a-bad<url');
|
||||
await expect(promise).rejects.toThrow(error);
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.webContentsView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.loadRetry).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('retry', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
const retryInBackgroundFn = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => Promise.resolve());
|
||||
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => Promise.resolve());
|
||||
mattermostView.loadSuccess = jest.fn();
|
||||
mattermostView.loadRetry = jest.fn();
|
||||
mattermostView.emit = jest.fn();
|
||||
@@ -154,16 +154,16 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
});
|
||||
|
||||
it('should do nothing when webcontents are destroyed', () => {
|
||||
const webContents = mattermostView.browserView.webContents;
|
||||
mattermostView.browserView.webContents = null;
|
||||
const webContents = mattermostView.webContentsView.webContents;
|
||||
mattermostView.webContentsView.webContents = null;
|
||||
mattermostView.retry('http://server-1.com')();
|
||||
expect(mattermostView.loadSuccess).not.toBeCalled();
|
||||
mattermostView.browserView.webContents = webContents;
|
||||
mattermostView.webContentsView.webContents = webContents;
|
||||
});
|
||||
|
||||
it('should call loadSuccess on successful load', async () => {
|
||||
const promise = Promise.resolve();
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.retry('http://server-1.com')();
|
||||
await promise;
|
||||
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com');
|
||||
@@ -173,10 +173,10 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
mattermostView.maxRetries = 10;
|
||||
const error = new Error('test');
|
||||
const promise = Promise.reject(error);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.retry('http://server-1.com')();
|
||||
await expect(promise).rejects.toThrow(error);
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
||||
expect(mattermostView.webContentsView.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
||||
expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com', error);
|
||||
});
|
||||
|
||||
@@ -184,10 +184,10 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
mattermostView.maxRetries = 0;
|
||||
const error = new Error('test');
|
||||
const promise = Promise.reject(error);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.retry('http://server-1.com')();
|
||||
await expect(promise).rejects.toThrow(error);
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
||||
expect(mattermostView.webContentsView.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
||||
expect(mattermostView.loadRetry).not.toBeCalled();
|
||||
expect(MainWindow.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.view.id, expect.any(String), expect.any(String));
|
||||
expect(mattermostView.status).toBe(-1);
|
||||
@@ -198,7 +198,7 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
|
||||
describe('goToOffset', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
mattermostView.reload = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
@@ -207,18 +207,18 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
});
|
||||
|
||||
it('should only go to offset if it can', () => {
|
||||
mattermostView.browserView.webContents.navigationHistory.canGoToOffset.mockReturnValue(false);
|
||||
mattermostView.webContentsView.webContents.navigationHistory.canGoToOffset.mockReturnValue(false);
|
||||
mattermostView.goToOffset(1);
|
||||
expect(mattermostView.browserView.webContents.navigationHistory.goToOffset).not.toBeCalled();
|
||||
expect(mattermostView.webContentsView.webContents.navigationHistory.goToOffset).not.toBeCalled();
|
||||
|
||||
mattermostView.browserView.webContents.navigationHistory.canGoToOffset.mockReturnValue(true);
|
||||
mattermostView.webContentsView.webContents.navigationHistory.canGoToOffset.mockReturnValue(true);
|
||||
mattermostView.goToOffset(1);
|
||||
expect(mattermostView.browserView.webContents.navigationHistory.goToOffset).toBeCalled();
|
||||
expect(mattermostView.webContentsView.webContents.navigationHistory.goToOffset).toBeCalled();
|
||||
});
|
||||
|
||||
it('should call reload if an error occurs', () => {
|
||||
mattermostView.browserView.webContents.navigationHistory.canGoToOffset.mockReturnValue(true);
|
||||
mattermostView.browserView.webContents.navigationHistory.goToOffset.mockImplementation(() => {
|
||||
mattermostView.webContentsView.webContents.navigationHistory.canGoToOffset.mockReturnValue(true);
|
||||
mattermostView.webContentsView.webContents.navigationHistory.goToOffset.mockImplementation(() => {
|
||||
throw new Error('hi');
|
||||
});
|
||||
mattermostView.goToOffset(1);
|
||||
@@ -228,8 +228,8 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
|
||||
describe('onLogin', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
mattermostView.browserView.webContents.getURL = jest.fn();
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
mattermostView.webContentsView.webContents.getURL = jest.fn();
|
||||
mattermostView.reload = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
@@ -238,19 +238,19 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
});
|
||||
|
||||
it('should reload view when URL is not on subpath of original server URL', () => {
|
||||
mattermostView.browserView.webContents.getURL.mockReturnValue('http://server-2.com/subpath');
|
||||
mattermostView.webContentsView.webContents.getURL.mockReturnValue('http://server-2.com/subpath');
|
||||
mattermostView.onLogin(true);
|
||||
expect(mattermostView.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not reload if URLs are matching', () => {
|
||||
mattermostView.browserView.webContents.getURL.mockReturnValue('http://server-1.com');
|
||||
mattermostView.webContentsView.webContents.getURL.mockReturnValue('http://server-1.com');
|
||||
mattermostView.onLogin(true);
|
||||
expect(mattermostView.reload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not reload if URL is subpath of server URL', () => {
|
||||
mattermostView.browserView.webContents.getURL.mockReturnValue('http://server-1.com/subpath');
|
||||
mattermostView.webContentsView.webContents.getURL.mockReturnValue('http://server-1.com/subpath');
|
||||
mattermostView.onLogin(true);
|
||||
expect(mattermostView.reload).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -258,7 +258,7 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
|
||||
describe('loadSuccess', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
@@ -285,8 +285,14 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
});
|
||||
|
||||
describe('show', () => {
|
||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn(), setTopBrowserView: jest.fn()};
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
const window = {
|
||||
contentView: {
|
||||
addChildView: jest.fn(),
|
||||
removeChildView: jest.fn(),
|
||||
},
|
||||
on: jest.fn(),
|
||||
};
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
@@ -304,7 +310,7 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
it('should add browser view to window and set bounds when request is true and view not currently visible', () => {
|
||||
mattermostView.isVisible = false;
|
||||
mattermostView.show();
|
||||
expect(window.addBrowserView).toBeCalledWith(mattermostView.browserView);
|
||||
expect(window.contentView.addChildView).toBeCalledWith(mattermostView.webContentsView);
|
||||
expect(mattermostView.setBounds).toBeCalled();
|
||||
expect(mattermostView.isVisible).toBe(true);
|
||||
});
|
||||
@@ -312,7 +318,7 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
it('should do nothing when not toggling', () => {
|
||||
mattermostView.isVisible = true;
|
||||
mattermostView.show();
|
||||
expect(window.addBrowserView).not.toBeCalled();
|
||||
expect(window.contentView.addChildView).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should focus view if view is ready', () => {
|
||||
@@ -324,8 +330,14 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
});
|
||||
|
||||
describe('hide', () => {
|
||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn(), setTopBrowserView: jest.fn()};
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
const window = {
|
||||
contentView: {
|
||||
addChildView: jest.fn(),
|
||||
removeChildView: jest.fn(),
|
||||
},
|
||||
on: jest.fn(),
|
||||
};
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
@@ -334,20 +346,20 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
it('should remove browser view', () => {
|
||||
mattermostView.isVisible = true;
|
||||
mattermostView.hide();
|
||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.browserView);
|
||||
expect(window.contentView.removeChildView).toBeCalledWith(mattermostView.webContentsView);
|
||||
expect(mattermostView.isVisible).toBe(false);
|
||||
});
|
||||
|
||||
it('should do nothing when not toggling', () => {
|
||||
mattermostView.isVisible = false;
|
||||
mattermostView.hide();
|
||||
expect(window.removeBrowserView).not.toBeCalled();
|
||||
expect(window.contentView.removeChildView).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateHistoryButton', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
@@ -356,13 +368,13 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
it('should erase history and set isAtRoot when navigating to root URL', () => {
|
||||
mattermostView.atRoot = false;
|
||||
mattermostView.updateHistoryButton();
|
||||
expect(mattermostView.browserView.webContents.navigationHistory.clear).toHaveBeenCalled();
|
||||
expect(mattermostView.webContentsView.webContents.navigationHistory.clear).toHaveBeenCalled();
|
||||
expect(mattermostView.isAtRoot).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroy', () => {
|
||||
const window = {removeBrowserView: jest.fn(), on: jest.fn()};
|
||||
const window = {contentView: {removeChildView: jest.fn()}, on: jest.fn()};
|
||||
const contextMenu = {
|
||||
dispose: jest.fn(),
|
||||
};
|
||||
@@ -373,22 +385,22 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
});
|
||||
|
||||
it('should remove browser view from window', () => {
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
mattermostView.browserView.webContents.close = jest.fn();
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
mattermostView.webContentsView.webContents.close = jest.fn();
|
||||
mattermostView.destroy();
|
||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.browserView);
|
||||
expect(window.contentView.removeChildView).toBeCalledWith(mattermostView.webContentsView);
|
||||
});
|
||||
|
||||
it('should clear mentions', () => {
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
mattermostView.browserView.webContents.close = jest.fn();
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
mattermostView.webContentsView.webContents.close = jest.fn();
|
||||
mattermostView.destroy();
|
||||
expect(AppState.clear).toBeCalledWith(mattermostView.view.id);
|
||||
});
|
||||
|
||||
it('should clear outstanding timeouts', () => {
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
mattermostView.browserView.webContents.close = jest.fn();
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
mattermostView.webContentsView.webContents.close = jest.fn();
|
||||
const spy = jest.spyOn(global, 'clearTimeout');
|
||||
mattermostView.retryLoad = 999;
|
||||
mattermostView.removeLoading = 1000;
|
||||
@@ -399,7 +411,7 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
|
||||
describe('handleInputEvents', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
|
||||
it('should open three dot menu on pressing Alt', () => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
@@ -424,7 +436,7 @@ describe('main/views/MattermostBrowserView', () => {
|
||||
|
||||
describe('handleUpdateTarget', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserView, app, ipcMain} from 'electron';
|
||||
import type {BrowserViewConstructorOptions, Event, Input} from 'electron/main';
|
||||
import {WebContentsView, app, ipcMain} from 'electron';
|
||||
import type {WebContentsViewConstructorOptions, Event, Input} from 'electron/main';
|
||||
import {EventEmitter} from 'events';
|
||||
|
||||
import AppState from 'common/appState';
|
||||
@@ -38,16 +38,15 @@ enum Status {
|
||||
WAITING_MM,
|
||||
ERROR = -1,
|
||||
}
|
||||
|
||||
export class MattermostBrowserView extends EventEmitter {
|
||||
export class MattermostWebContentsView extends EventEmitter {
|
||||
view: MattermostView;
|
||||
isVisible: boolean;
|
||||
|
||||
private log: Logger;
|
||||
private browserView: BrowserView;
|
||||
private webContentsView: WebContentsView;
|
||||
private loggedIn: boolean;
|
||||
private atRoot: boolean;
|
||||
private options: BrowserViewConstructorOptions;
|
||||
private options: WebContentsViewConstructorOptions;
|
||||
private removeLoading?: NodeJS.Timeout;
|
||||
private contextMenu?: ContextMenu;
|
||||
private status?: Status;
|
||||
@@ -55,7 +54,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
private maxRetries: number;
|
||||
private altPressStatus: boolean;
|
||||
|
||||
constructor(view: MattermostView, options: BrowserViewConstructorOptions) {
|
||||
constructor(view: MattermostView, options: WebContentsViewConstructorOptions) {
|
||||
super();
|
||||
this.view = view;
|
||||
|
||||
@@ -72,29 +71,28 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
this.isVisible = false;
|
||||
this.loggedIn = false;
|
||||
this.atRoot = true;
|
||||
this.browserView = new BrowserView(this.options);
|
||||
this.webContentsView = new WebContentsView(this.options);
|
||||
this.resetLoadingStatus();
|
||||
|
||||
this.log = ServerManager.getViewLog(this.id, 'MattermostBrowserView');
|
||||
this.log = ServerManager.getViewLog(this.id, 'MattermostWebContentsView');
|
||||
this.log.verbose('View created');
|
||||
|
||||
this.browserView.webContents.on('update-target-url', this.handleUpdateTarget);
|
||||
this.webContentsView.webContents.on('update-target-url', this.handleUpdateTarget);
|
||||
if (process.platform !== 'darwin') {
|
||||
this.browserView.webContents.on('before-input-event', this.handleInputEvents);
|
||||
this.webContentsView.webContents.on('before-input-event', this.handleInputEvents);
|
||||
}
|
||||
this.browserView.webContents.on('input-event', (_, inputEvent) => {
|
||||
this.webContentsView.webContents.on('input-event', (_, inputEvent) => {
|
||||
if (inputEvent.type === 'mouseDown') {
|
||||
ipcMain.emit(CLOSE_SERVERS_DROPDOWN);
|
||||
ipcMain.emit(CLOSE_DOWNLOADS_DROPDOWN);
|
||||
}
|
||||
});
|
||||
|
||||
WebContentsEventManager.addWebContentsEventListeners(this.browserView.webContents);
|
||||
WebContentsEventManager.addWebContentsEventListeners(this.webContentsView.webContents);
|
||||
|
||||
if (!DeveloperMode.get('disableContextMenu')) {
|
||||
this.contextMenu = new ContextMenu({}, this.browserView);
|
||||
this.contextMenu = new ContextMenu({}, this.webContentsView);
|
||||
}
|
||||
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
|
||||
this.altPressStatus = false;
|
||||
@@ -116,10 +114,10 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
return this.loggedIn;
|
||||
}
|
||||
get currentURL() {
|
||||
return parseURL(this.browserView.webContents.getURL());
|
||||
return parseURL(this.webContentsView.webContents.getURL());
|
||||
}
|
||||
get webContentsId() {
|
||||
return this.browserView.webContents.id;
|
||||
return this.webContentsView.webContents.id;
|
||||
}
|
||||
|
||||
onLogin = (loggedIn: boolean) => {
|
||||
@@ -139,9 +137,9 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
};
|
||||
|
||||
goToOffset = (offset: number) => {
|
||||
if (this.browserView.webContents.navigationHistory.canGoToOffset(offset)) {
|
||||
if (this.webContentsView.webContents.navigationHistory.canGoToOffset(offset)) {
|
||||
try {
|
||||
this.browserView.webContents.navigationHistory.goToOffset(offset);
|
||||
this.webContentsView.webContents.navigationHistory.goToOffset(offset);
|
||||
this.updateHistoryButton();
|
||||
} catch (error) {
|
||||
this.log.error(error);
|
||||
@@ -152,25 +150,25 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
|
||||
getBrowserHistoryStatus = () => {
|
||||
if (this.currentURL?.toString() === this.view.url.toString()) {
|
||||
this.browserView.webContents.navigationHistory.clear();
|
||||
this.webContentsView.webContents.navigationHistory.clear();
|
||||
this.atRoot = true;
|
||||
} else {
|
||||
this.atRoot = false;
|
||||
}
|
||||
|
||||
return {
|
||||
canGoBack: this.browserView.webContents.navigationHistory.canGoBack(),
|
||||
canGoForward: this.browserView.webContents.navigationHistory.canGoForward(),
|
||||
canGoBack: this.webContentsView.webContents.navigationHistory.canGoBack(),
|
||||
canGoForward: this.webContentsView.webContents.navigationHistory.canGoForward(),
|
||||
};
|
||||
};
|
||||
|
||||
updateHistoryButton = () => {
|
||||
const {canGoBack, canGoForward} = this.getBrowserHistoryStatus();
|
||||
this.browserView.webContents.send(BROWSER_HISTORY_STATUS_UPDATED, canGoBack, canGoForward);
|
||||
this.webContentsView.webContents.send(BROWSER_HISTORY_STATUS_UPDATED, canGoBack, canGoForward);
|
||||
};
|
||||
|
||||
load = (someURL?: URL | string) => {
|
||||
if (!this.browserView) {
|
||||
if (!this.webContentsView) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -188,11 +186,11 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
}
|
||||
this.log.verbose(`Loading ${loadURL}`);
|
||||
if (this.view.type === TAB_MESSAGING) {
|
||||
performanceMonitor.registerServerView(`Server ${this.browserView.webContents.id}`, this.browserView.webContents, this.view.server.id);
|
||||
performanceMonitor.registerServerView(`Server ${this.webContentsView.webContents.id}`, this.webContentsView.webContents, this.view.server.id);
|
||||
} else {
|
||||
performanceMonitor.registerView(`Server ${this.browserView.webContents.id}`, this.browserView.webContents, this.view.server.id);
|
||||
performanceMonitor.registerView(`Server ${this.webContentsView.webContents.id}`, this.webContentsView.webContents, this.view.server.id);
|
||||
}
|
||||
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent(DeveloperMode.get('browserOnly'))});
|
||||
const loading = this.webContentsView.webContents.loadURL(loadURL, {userAgent: composeUserAgent(DeveloperMode.get('browserOnly'))});
|
||||
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||
if (err.code && err.code.startsWith('ERR_CERT')) {
|
||||
MainWindow.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
||||
@@ -221,8 +219,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
this.isVisible = true;
|
||||
mainWindow.addBrowserView(this.browserView);
|
||||
mainWindow.setTopBrowserView(this.browserView);
|
||||
mainWindow.contentView.addChildView(this.webContentsView);
|
||||
this.setBounds(getWindowBoundaries(mainWindow));
|
||||
if (this.status === Status.READY) {
|
||||
this.focus();
|
||||
@@ -232,7 +229,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
hide = () => {
|
||||
if (this.isVisible) {
|
||||
this.isVisible = false;
|
||||
MainWindow.get()?.removeBrowserView(this.browserView);
|
||||
MainWindow.get()?.contentView.removeChildView(this.webContentsView);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -243,23 +240,23 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
};
|
||||
|
||||
getBounds = () => {
|
||||
return this.browserView.getBounds();
|
||||
return this.webContentsView.getBounds();
|
||||
};
|
||||
|
||||
openFind = () => {
|
||||
this.browserView.webContents.sendInputEvent({type: 'keyDown', keyCode: 'F', modifiers: [process.platform === 'darwin' ? 'cmd' : 'ctrl', 'shift']});
|
||||
this.webContentsView.webContents.sendInputEvent({type: 'keyDown', keyCode: 'F', modifiers: [process.platform === 'darwin' ? 'cmd' : 'ctrl', 'shift']});
|
||||
};
|
||||
|
||||
setBounds = (boundaries: Electron.Rectangle) => {
|
||||
this.browserView.setBounds(boundaries);
|
||||
this.webContentsView.setBounds(boundaries);
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
WebContentsEventManager.removeWebContentsListeners(this.webContentsId);
|
||||
AppState.clear(this.id);
|
||||
MainWindow.get()?.removeBrowserView(this.browserView);
|
||||
performanceMonitor.unregisterView(this.browserView.webContents.id);
|
||||
this.browserView.webContents.close();
|
||||
performanceMonitor.unregisterView(this.webContentsView.webContents.id);
|
||||
MainWindow.get()?.contentView.removeChildView(this.webContentsView);
|
||||
this.webContentsView.webContents.close();
|
||||
|
||||
this.isVisible = false;
|
||||
if (this.retryLoad) {
|
||||
@@ -311,17 +308,17 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
// So what we do here is check to see if it's opened correctly and if not we reset it
|
||||
if (process.platform === 'darwin') {
|
||||
const timeout = setTimeout(() => {
|
||||
if (this.browserView.webContents.isDevToolsOpened()) {
|
||||
this.browserView.webContents.closeDevTools();
|
||||
this.browserView.webContents.openDevTools({mode: 'detach'});
|
||||
if (this.webContentsView.webContents.isDevToolsOpened()) {
|
||||
this.webContentsView.webContents.closeDevTools();
|
||||
this.webContentsView.webContents.openDevTools({mode: 'detach'});
|
||||
}
|
||||
}, 500);
|
||||
this.browserView.webContents.on('devtools-opened', () => {
|
||||
this.webContentsView.webContents.on('devtools-opened', () => {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
}
|
||||
|
||||
this.browserView.webContents.openDevTools({mode: 'detach'});
|
||||
this.webContentsView.webContents.openDevTools({mode: 'detach'});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -329,16 +326,16 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
*/
|
||||
|
||||
sendToRenderer = (channel: string, ...args: any[]) => {
|
||||
this.browserView.webContents.send(channel, ...args);
|
||||
this.webContentsView.webContents.send(channel, ...args);
|
||||
};
|
||||
|
||||
isDestroyed = () => {
|
||||
return this.browserView.webContents.isDestroyed();
|
||||
return this.webContentsView.webContents.isDestroyed();
|
||||
};
|
||||
|
||||
focus = () => {
|
||||
if (this.browserView.webContents) {
|
||||
this.browserView.webContents.focus();
|
||||
if (this.webContentsView.webContents) {
|
||||
this.webContentsView.webContents.focus();
|
||||
} else {
|
||||
this.log.warn('trying to focus the browserview, but it doesn\'t yet have webcontents.');
|
||||
}
|
||||
@@ -381,10 +378,10 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
private retry = (loadURL: string) => {
|
||||
return () => {
|
||||
// window was closed while retrying
|
||||
if (!this.browserView || !this.browserView.webContents) {
|
||||
if (!this.webContentsView || !this.webContentsView.webContents) {
|
||||
return;
|
||||
}
|
||||
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent(DeveloperMode.get('browserOnly'))});
|
||||
const loading = this.webContentsView.webContents.loadURL(loadURL, {userAgent: composeUserAgent(DeveloperMode.get('browserOnly'))});
|
||||
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||
if (this.maxRetries-- > 0) {
|
||||
this.loadRetry(loadURL, err);
|
||||
@@ -402,7 +399,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
private retryInBackground = (loadURL: string) => {
|
||||
return () => {
|
||||
// window was closed while retrying
|
||||
if (!this.browserView || !this.browserView.webContents) {
|
||||
if (!this.webContentsView || !this.webContentsView.webContents) {
|
||||
return;
|
||||
}
|
||||
const parsedURL = parseURL(loadURL);
|
@@ -30,7 +30,8 @@ jest.mock('electron', () => {
|
||||
getAppPath: () => '',
|
||||
getPath: jest.fn(() => '/valid/downloads/path'),
|
||||
},
|
||||
BrowserView: jest.fn().mockImplementation(() => ({
|
||||
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||
setBackgroundColor: jest.fn(),
|
||||
webContents: {
|
||||
loadURL: jest.fn(),
|
||||
focus: jest.fn(),
|
||||
@@ -69,7 +70,7 @@ jest.mock('fs', () => ({
|
||||
|
||||
describe('main/views/DownloadsDropdownMenuView', () => {
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue({addBrowserView: jest.fn(), setTopBrowserView: jest.fn()});
|
||||
MainWindow.get.mockReturnValue({contentView: {addChildView: jest.fn()}});
|
||||
MainWindow.getBounds.mockReturnValue({width: 800, height: 600, x: 0, y: 0});
|
||||
getDarwinDoNotDisturb.mockReturnValue(false);
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import type {IpcMainEvent} from 'electron';
|
||||
import {BrowserView, ipcMain} from 'electron';
|
||||
import {WebContentsView, ipcMain} from 'electron';
|
||||
|
||||
import {
|
||||
CLOSE_DOWNLOADS_DROPDOWN_MENU,
|
||||
@@ -37,7 +37,7 @@ const log = new Logger('DownloadsDropdownMenuView');
|
||||
|
||||
export class DownloadsDropdownMenuView {
|
||||
private open: boolean;
|
||||
private view?: BrowserView;
|
||||
private view?: WebContentsView;
|
||||
private bounds?: Electron.Rectangle;
|
||||
private item?: DownloadedItem;
|
||||
private coordinates?: CoordinatesToJsonType;
|
||||
@@ -66,19 +66,11 @@ export class DownloadsDropdownMenuView {
|
||||
throw new Error('Cannot initialize downloadsDropdownMenuView, missing MainWindow');
|
||||
}
|
||||
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
|
||||
|
||||
const preload = getLocalPreload('internalAPI.js');
|
||||
this.view = new BrowserView({webPreferences: {
|
||||
preload,
|
||||
|
||||
// Workaround for this issue: https://github.com/electron/electron/issues/30993
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
transparent: true,
|
||||
}});
|
||||
this.view = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
|
||||
this.view.setBackgroundColor('#00000000');
|
||||
performanceMonitor.registerView('DownloadsDropdownMenuView', this.view.webContents);
|
||||
this.view.webContents.loadURL('mattermost-desktop://renderer/downloadsDropdownMenu.html');
|
||||
MainWindow.get()?.addBrowserView(this.view);
|
||||
MainWindow.get()?.contentView.addChildView(this.view);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -128,7 +120,7 @@ export class DownloadsDropdownMenuView {
|
||||
this.item = item;
|
||||
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
|
||||
this.view.setBounds(this.bounds);
|
||||
MainWindow.get()?.setTopBrowserView(this.view);
|
||||
MainWindow.get()?.contentView.addChildView(this.view);
|
||||
this.view.webContents.focus();
|
||||
this.updateDownloadsDropdownMenu();
|
||||
};
|
||||
|
@@ -38,7 +38,8 @@ jest.mock('electron', () => {
|
||||
getAppPath: () => '',
|
||||
getPath: jest.fn(() => '/valid/downloads/path'),
|
||||
},
|
||||
BrowserView: jest.fn().mockImplementation(() => ({
|
||||
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||
setBackgroundColor: jest.fn(),
|
||||
webContents: {
|
||||
loadURL: jest.fn(),
|
||||
focus: jest.fn(),
|
||||
@@ -77,7 +78,7 @@ jest.mock('main/windows/mainWindow', () => ({
|
||||
|
||||
describe('main/views/DownloadsDropdownView', () => {
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue({addBrowserView: jest.fn(), setTopBrowserView: jest.fn()});
|
||||
MainWindow.get.mockReturnValue({contentView: {addChildView: jest.fn()}});
|
||||
getDarwinDoNotDisturb.mockReturnValue(false);
|
||||
});
|
||||
describe('getBounds', () => {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {IpcMainEvent} from 'electron';
|
||||
import {BrowserView, ipcMain} from 'electron';
|
||||
import {WebContentsView, ipcMain} from 'electron';
|
||||
|
||||
import {
|
||||
CLOSE_DOWNLOADS_DROPDOWN,
|
||||
@@ -33,7 +33,7 @@ export class DownloadsDropdownView {
|
||||
private bounds?: Electron.Rectangle;
|
||||
private windowBounds?: Electron.Rectangle;
|
||||
private item?: DownloadedItem;
|
||||
private view?: BrowserView;
|
||||
private view?: WebContentsView;
|
||||
|
||||
constructor() {
|
||||
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
|
||||
@@ -55,20 +55,11 @@ export class DownloadsDropdownView {
|
||||
throw new Error('Cannot initialize, no main window');
|
||||
}
|
||||
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_FULL_WIDTH, DOWNLOADS_DROPDOWN_HEIGHT);
|
||||
|
||||
const preload = getLocalPreload('internalAPI.js');
|
||||
this.view = new BrowserView({webPreferences: {
|
||||
preload,
|
||||
|
||||
// Workaround for this issue: https://github.com/electron/electron/issues/30993
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
transparent: true,
|
||||
}});
|
||||
|
||||
this.view = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
|
||||
this.view.setBackgroundColor('#00000000');
|
||||
performanceMonitor.registerView('DownloadsDropdownView', this.view.webContents);
|
||||
this.view.webContents.loadURL('mattermost-desktop://renderer/downloadsDropdown.html');
|
||||
MainWindow.get()?.addBrowserView(this.view);
|
||||
MainWindow.get()?.contentView.addChildView(this.view);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -109,7 +100,7 @@ export class DownloadsDropdownView {
|
||||
}
|
||||
|
||||
this.view.setBounds(this.bounds);
|
||||
MainWindow.get()?.setTopBrowserView(this.view);
|
||||
MainWindow.get()?.contentView.addChildView(this.view);
|
||||
this.view.webContents.focus();
|
||||
downloadsManager.onOpen();
|
||||
MainWindow.sendToRenderer(OPEN_DOWNLOADS_DROPDOWN);
|
||||
|
@@ -21,9 +21,10 @@ jest.mock('main/windows/mainWindow', () => ({
|
||||
describe('main/views/loadingScreen', () => {
|
||||
describe('show', () => {
|
||||
const mainWindow = {
|
||||
getBrowserViews: jest.fn(),
|
||||
setTopBrowserView: jest.fn(),
|
||||
addBrowserView: jest.fn(),
|
||||
contentView: {
|
||||
addChildView: jest.fn(),
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
const loadingScreen = new LoadingScreen();
|
||||
loadingScreen.create = jest.fn();
|
||||
@@ -31,7 +32,7 @@ describe('main/views/loadingScreen', () => {
|
||||
const view = {webContents: {send: jest.fn(), isLoading: () => false}};
|
||||
|
||||
beforeEach(() => {
|
||||
mainWindow.getBrowserViews.mockImplementation(() => []);
|
||||
mainWindow.contentView.children = [];
|
||||
MainWindow.get.mockReturnValue(mainWindow);
|
||||
});
|
||||
|
||||
@@ -46,14 +47,14 @@ describe('main/views/loadingScreen', () => {
|
||||
});
|
||||
loadingScreen.show();
|
||||
expect(loadingScreen.create).toHaveBeenCalled();
|
||||
expect(mainWindow.addBrowserView).toHaveBeenCalled();
|
||||
expect(mainWindow.contentView.addChildView).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set the browser view as top if already exists and needs to be shown', () => {
|
||||
loadingScreen.view = view;
|
||||
mainWindow.getBrowserViews.mockImplementation(() => [view]);
|
||||
mainWindow.contentView.children = [view];
|
||||
loadingScreen.show();
|
||||
expect(mainWindow.setTopBrowserView).toHaveBeenCalled();
|
||||
expect(mainWindow.contentView.addChildView).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserView, app, ipcMain} from 'electron';
|
||||
import {WebContentsView, app, ipcMain} from 'electron';
|
||||
|
||||
import {DARK_MODE_CHANGE, LOADING_SCREEN_ANIMATION_FINISHED, MAIN_WINDOW_RESIZED, TOGGLE_LOADING_SCREEN_VISIBILITY} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
@@ -18,7 +18,7 @@ enum LoadingScreenState {
|
||||
const log = new Logger('LoadingScreen');
|
||||
|
||||
export class LoadingScreen {
|
||||
private view?: BrowserView;
|
||||
private view?: WebContentsView;
|
||||
private state: LoadingScreenState;
|
||||
|
||||
constructor() {
|
||||
@@ -55,15 +55,11 @@ export class LoadingScreen {
|
||||
if (this.view?.webContents.isLoading()) {
|
||||
this.view.webContents.once('did-finish-load', () => {
|
||||
this.view!.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true);
|
||||
mainWindow.contentView.addChildView(this.view!);
|
||||
});
|
||||
} else {
|
||||
this.view!.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true);
|
||||
}
|
||||
|
||||
if (mainWindow.getBrowserViews().includes(this.view!)) {
|
||||
mainWindow.setTopBrowserView(this.view!);
|
||||
} else {
|
||||
mainWindow.addBrowserView(this.view!);
|
||||
mainWindow.contentView.addChildView(this.view!);
|
||||
}
|
||||
|
||||
this.setBounds();
|
||||
@@ -77,19 +73,9 @@ export class LoadingScreen {
|
||||
};
|
||||
|
||||
private create = () => {
|
||||
const preload = getLocalPreload('internalAPI.js');
|
||||
this.view = new BrowserView({webPreferences: {
|
||||
preload,
|
||||
|
||||
// Workaround for this issue: https://github.com/electron/electron/issues/30993
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
transparent: true,
|
||||
}});
|
||||
const localURL = 'mattermost-desktop://renderer/loadingScreen.html';
|
||||
|
||||
this.view = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
|
||||
performanceMonitor.registerView('LoadingScreen', this.view.webContents);
|
||||
this.view.webContents.loadURL(localURL);
|
||||
this.view.webContents.loadURL('mattermost-desktop://renderer/loadingScreen.html');
|
||||
};
|
||||
|
||||
private handleAnimationFinished = () => {
|
||||
@@ -97,7 +83,9 @@ export class LoadingScreen {
|
||||
|
||||
if (this.view && this.state !== LoadingScreenState.HIDDEN) {
|
||||
this.state = LoadingScreenState.HIDDEN;
|
||||
MainWindow.get()?.removeBrowserView(this.view);
|
||||
MainWindow.get()?.contentView.removeChildView(this.view);
|
||||
this.view.webContents.close();
|
||||
delete this.view;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
|
@@ -6,7 +6,8 @@
|
||||
import {ModalView} from './modalView';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
BrowserView: jest.fn().mockImplementation(() => ({
|
||||
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||
setBackgroundColor: jest.fn(),
|
||||
webContents: {
|
||||
loadURL: jest.fn(),
|
||||
once: jest.fn(),
|
||||
@@ -34,7 +35,12 @@ jest.mock('main/performanceMonitor', () => ({
|
||||
|
||||
describe('main/views/modalView', () => {
|
||||
describe('show', () => {
|
||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn()};
|
||||
const window = {
|
||||
contentView: {
|
||||
addChildView: jest.fn(),
|
||||
removeChildView: jest.fn(),
|
||||
},
|
||||
};
|
||||
const onResolve = jest.fn();
|
||||
const onReject = jest.fn();
|
||||
let modalView;
|
||||
@@ -56,15 +62,15 @@ describe('main/views/modalView', () => {
|
||||
|
||||
it('should add to window', () => {
|
||||
modalView.show();
|
||||
expect(window.addBrowserView).toBeCalledWith(modalView.view);
|
||||
expect(window.contentView.addChildView).toBeCalledWith(modalView.view);
|
||||
expect(modalView.status).toBe(1);
|
||||
});
|
||||
|
||||
it('should reattach if already attached', () => {
|
||||
modalView.windowAttached = window;
|
||||
modalView.show();
|
||||
expect(window.removeBrowserView).toBeCalledWith(modalView.view);
|
||||
expect(window.addBrowserView).toBeCalledWith(modalView.view);
|
||||
expect(window.contentView.removeChildView).toBeCalledWith(modalView.view);
|
||||
expect(window.contentView.addChildView).toBeCalledWith(modalView.view);
|
||||
});
|
||||
|
||||
it('should delay call to focus when the modal is loading', () => {
|
||||
@@ -87,7 +93,12 @@ describe('main/views/modalView', () => {
|
||||
});
|
||||
|
||||
describe('hide', () => {
|
||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn()};
|
||||
const window = {
|
||||
contentView: {
|
||||
addChildView: jest.fn(),
|
||||
removeChildView: jest.fn(),
|
||||
},
|
||||
};
|
||||
const onResolve = jest.fn();
|
||||
const onReject = jest.fn();
|
||||
let modalView;
|
||||
@@ -111,7 +122,7 @@ describe('main/views/modalView', () => {
|
||||
it('should remove browser view and destroy web contents on hide', () => {
|
||||
modalView.hide();
|
||||
expect(modalView.view.webContents.close).toBeCalled();
|
||||
expect(window.removeBrowserView).toBeCalledWith(modalView.view);
|
||||
expect(window.contentView.removeChildView).toBeCalledWith(modalView.view);
|
||||
});
|
||||
|
||||
it('should close dev tools when open', () => {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {BrowserWindow} from 'electron';
|
||||
import {BrowserView} from 'electron';
|
||||
import {WebContentsView} from 'electron';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
import performanceMonitor from 'main/performanceMonitor';
|
||||
@@ -20,7 +20,7 @@ export class ModalView<T, T2> {
|
||||
key: string;
|
||||
html: string;
|
||||
data: T;
|
||||
view: BrowserView;
|
||||
view: WebContentsView;
|
||||
onReject: (value: T2) => void;
|
||||
onResolve: (value: T2) => void;
|
||||
window: BrowserWindow;
|
||||
@@ -36,14 +36,8 @@ export class ModalView<T, T2> {
|
||||
this.data = data;
|
||||
this.log = new Logger('ModalView', key);
|
||||
this.log.info(`preloading with ${preload}`);
|
||||
this.view = new BrowserView({webPreferences: {
|
||||
preload,
|
||||
|
||||
// Workaround for this issue: https://github.com/electron/electron/issues/30993
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
transparent: true,
|
||||
}});
|
||||
this.view = new WebContentsView({webPreferences: {preload}});
|
||||
this.view.setBackgroundColor('#00000000');
|
||||
this.onReject = onReject;
|
||||
this.onResolve = onResolve;
|
||||
this.window = currentWindow;
|
||||
@@ -64,11 +58,11 @@ export class ModalView<T, T2> {
|
||||
show = (win?: BrowserWindow, withDevTools?: boolean) => {
|
||||
if (this.windowAttached) {
|
||||
// we'll reatach
|
||||
this.windowAttached.removeBrowserView(this.view);
|
||||
this.windowAttached.contentView.removeChildView(this.view);
|
||||
}
|
||||
this.windowAttached = win || this.window;
|
||||
|
||||
this.windowAttached.addBrowserView(this.view);
|
||||
this.windowAttached.contentView.addChildView(this.view);
|
||||
|
||||
// Linux sometimes doesn't have the bound initialized correctly initially, so we wait to set them
|
||||
const setBoundsFunction = () => {
|
||||
@@ -100,8 +94,8 @@ export class ModalView<T, T2> {
|
||||
if (this.view.webContents.isDevToolsOpened()) {
|
||||
this.view.webContents.closeDevTools();
|
||||
}
|
||||
this.windowAttached.removeBrowserView(this.view);
|
||||
performanceMonitor.unregisterView(this.view.webContents.id);
|
||||
this.windowAttached.contentView.removeChildView(this.view);
|
||||
this.view.webContents.close();
|
||||
|
||||
delete this.windowAttached;
|
||||
|
@@ -15,7 +15,7 @@ jest.mock('main/utils', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
BrowserView: jest.fn().mockImplementation(() => ({
|
||||
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||
webContents: {
|
||||
loadURL: jest.fn(),
|
||||
focus: jest.fn(),
|
||||
|
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {IpcMainEvent} from 'electron';
|
||||
import {BrowserView, ipcMain} from 'electron';
|
||||
import {WebContentsView, ipcMain} from 'electron';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
import AppState from 'common/appState';
|
||||
@@ -32,7 +32,7 @@ import MainWindow from '../windows/mainWindow';
|
||||
const log = new Logger('ServerDropdownView');
|
||||
|
||||
export class ServerDropdownView {
|
||||
private view?: BrowserView;
|
||||
private view?: WebContentsView;
|
||||
private servers: UniqueServer[];
|
||||
private hasGPOServers: boolean;
|
||||
private isOpen: boolean;
|
||||
@@ -75,22 +75,15 @@ export class ServerDropdownView {
|
||||
|
||||
private init = () => {
|
||||
log.info('init');
|
||||
const preload = getLocalPreload('internalAPI.js');
|
||||
this.view = new BrowserView({webPreferences: {
|
||||
preload,
|
||||
|
||||
// Workaround for this issue: https://github.com/electron/electron/issues/30993
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
transparent: true,
|
||||
}});
|
||||
this.view = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
|
||||
this.view.setBackgroundColor('#00000000');
|
||||
performanceMonitor.registerView('ServerDropdownView', this.view.webContents);
|
||||
this.view.webContents.loadURL('mattermost-desktop://renderer/dropdown.html');
|
||||
|
||||
this.setOrderedServers();
|
||||
this.windowBounds = MainWindow.getBounds();
|
||||
this.updateDropdown();
|
||||
MainWindow.get()?.addBrowserView(this.view);
|
||||
MainWindow.get()?.contentView.addChildView(this.view);
|
||||
};
|
||||
|
||||
private updateDropdown = () => {
|
||||
@@ -138,7 +131,7 @@ export class ServerDropdownView {
|
||||
return;
|
||||
}
|
||||
this.view.setBounds(this.bounds);
|
||||
MainWindow.get()?.setTopBrowserView(this.view);
|
||||
MainWindow.get()?.contentView.addChildView(this.view);
|
||||
this.view.webContents.focus();
|
||||
MainWindow.sendToRenderer(OPEN_SERVERS_DROPDOWN);
|
||||
this.isOpen = true;
|
||||
|
@@ -12,7 +12,7 @@ import PermissionsManager from 'main/permissionsManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import LoadingScreen from './loadingScreen';
|
||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
||||
import {MattermostWebContentsView} from './MattermostWebContentsView';
|
||||
import {ViewManager} from './viewManager';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
@@ -112,8 +112,8 @@ jest.mock('common/servers/serverManager', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('./MattermostBrowserView', () => ({
|
||||
MattermostBrowserView: jest.fn(),
|
||||
jest.mock('./MattermostWebContentsView', () => ({
|
||||
MattermostWebContentsView: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('./modalManager', () => ({
|
||||
@@ -133,7 +133,7 @@ describe('main/views/viewManager', () => {
|
||||
beforeEach(() => {
|
||||
viewManager.showById = jest.fn();
|
||||
MainWindow.get.mockReturnValue({});
|
||||
MattermostBrowserView.mockImplementation((view) => ({
|
||||
MattermostWebContentsView.mockImplementation((view) => ({
|
||||
on: jest.fn(),
|
||||
load: loadFn,
|
||||
once: onceFn,
|
||||
@@ -181,7 +181,7 @@ describe('main/views/viewManager', () => {
|
||||
beforeEach(() => {
|
||||
viewManager.showById = jest.fn();
|
||||
MainWindow.get.mockReturnValue({});
|
||||
MattermostBrowserView.mockImplementation((view) => ({
|
||||
MattermostWebContentsView.mockImplementation((view) => ({
|
||||
on: jest.fn(),
|
||||
load: jest.fn(),
|
||||
once: jest.fn(),
|
||||
@@ -235,7 +235,7 @@ describe('main/views/viewManager', () => {
|
||||
const onceFn = jest.fn();
|
||||
const loadFn = jest.fn();
|
||||
const destroyFn = jest.fn();
|
||||
MattermostBrowserView.mockImplementation((view) => ({
|
||||
MattermostWebContentsView.mockImplementation((view) => ({
|
||||
on: jest.fn(),
|
||||
load: loadFn,
|
||||
once: onceFn,
|
||||
@@ -255,7 +255,7 @@ describe('main/views/viewManager', () => {
|
||||
|
||||
it('should recycle existing views', () => {
|
||||
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
||||
const view = new MattermostBrowserView({
|
||||
const view = new MattermostWebContentsView({
|
||||
id: 'view1',
|
||||
server: {
|
||||
id: 'server1',
|
||||
|
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
import {BrowserView, dialog, ipcMain} from 'electron';
|
||||
import {WebContentsView, dialog, ipcMain} from 'electron';
|
||||
import isDev from 'electron-is-dev';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
@@ -51,7 +51,7 @@ import MainWindow from 'main/windows/mainWindow';
|
||||
import type {DeveloperSettings} from 'types/settings';
|
||||
|
||||
import LoadingScreen from './loadingScreen';
|
||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
||||
import {MattermostWebContentsView} from './MattermostWebContentsView';
|
||||
import modalManager from './modalManager';
|
||||
|
||||
import {getLocalPreload, getAdjustedWindowBoundaries} from '../utils';
|
||||
@@ -62,7 +62,7 @@ const URL_VIEW_HEIGHT = 20;
|
||||
|
||||
export class ViewManager {
|
||||
private closedViews: Map<string, {srv: MattermostServer; view: MattermostView}>;
|
||||
private views: Map<string, MattermostBrowserView>;
|
||||
private views: Map<string, MattermostWebContentsView>;
|
||||
private currentView?: string;
|
||||
|
||||
private urlViewCancel?: () => void;
|
||||
@@ -206,23 +206,23 @@ export class ViewManager {
|
||||
if (this.closedViews.has(view.id)) {
|
||||
this.openClosedView(view.id, urlWithSchema);
|
||||
} else {
|
||||
const browserView = this.views.get(view.id);
|
||||
if (!browserView) {
|
||||
const webContentsView = this.views.get(view.id);
|
||||
if (!webContentsView) {
|
||||
log.error(`Couldn't find a view matching the id ${view.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (browserView.isReady() && ServerManager.getRemoteInfo(browserView.view.server.id)?.serverVersion && Utils.isVersionGreaterThanOrEqualTo(ServerManager.getRemoteInfo(browserView.view.server.id)?.serverVersion ?? '', '6.0.0')) {
|
||||
const formattedServerURL = `${browserView.view.server.url.origin}${getFormattedPathName(browserView.view.server.url.pathname)}`;
|
||||
if (webContentsView.isReady() && ServerManager.getRemoteInfo(webContentsView.view.server.id)?.serverVersion && Utils.isVersionGreaterThanOrEqualTo(ServerManager.getRemoteInfo(webContentsView.view.server.id)?.serverVersion ?? '', '6.0.0')) {
|
||||
const formattedServerURL = `${webContentsView.view.server.url.origin}${getFormattedPathName(webContentsView.view.server.url.pathname)}`;
|
||||
const pathName = `/${urlWithSchema.replace(formattedServerURL, '')}`;
|
||||
browserView.sendToRenderer(BROWSER_HISTORY_PUSH, pathName);
|
||||
this.deeplinkSuccess(browserView.id);
|
||||
webContentsView.sendToRenderer(BROWSER_HISTORY_PUSH, pathName);
|
||||
this.deeplinkSuccess(webContentsView.id);
|
||||
} else {
|
||||
// attempting to change parsedURL protocol results in it not being modified.
|
||||
browserView.resetLoadingStatus();
|
||||
browserView.load(urlWithSchema);
|
||||
browserView.once(LOAD_SUCCESS, this.deeplinkSuccess);
|
||||
browserView.once(LOAD_FAILED, this.deeplinkFailed);
|
||||
webContentsView.resetLoadingStatus();
|
||||
webContentsView.load(urlWithSchema);
|
||||
webContentsView.once(LOAD_SUCCESS, this.deeplinkSuccess);
|
||||
webContentsView.once(LOAD_FAILED, this.deeplinkFailed);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -260,26 +260,26 @@ export class ViewManager {
|
||||
this.closedViews.set(view.id, {srv, view});
|
||||
return;
|
||||
}
|
||||
const browserView = this.makeView(srv, view, url);
|
||||
this.addView(browserView);
|
||||
const webContentsView = this.makeView(srv, view, url);
|
||||
this.addView(webContentsView);
|
||||
};
|
||||
|
||||
private makeView = (srv: MattermostServer, view: MattermostView, url?: string): MattermostBrowserView => {
|
||||
private makeView = (srv: MattermostServer, view: MattermostView, url?: string): MattermostWebContentsView => {
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
throw new Error('Cannot create view, no main window present');
|
||||
}
|
||||
|
||||
const browserView = new MattermostBrowserView(view, {webPreferences: {spellcheck: Config.useSpellChecker}});
|
||||
browserView.once(LOAD_SUCCESS, this.activateView);
|
||||
browserView.on(LOADSCREEN_END, this.finishLoading);
|
||||
browserView.on(LOAD_FAILED, this.failLoading);
|
||||
browserView.on(UPDATE_TARGET_URL, this.showURLView);
|
||||
browserView.load(url);
|
||||
return browserView;
|
||||
const webContentsView = new MattermostWebContentsView(view, {webPreferences: {spellcheck: Config.useSpellChecker}});
|
||||
webContentsView.once(LOAD_SUCCESS, this.activateView);
|
||||
webContentsView.on(LOADSCREEN_END, this.finishLoading);
|
||||
webContentsView.on(LOAD_FAILED, this.failLoading);
|
||||
webContentsView.on(UPDATE_TARGET_URL, this.showURLView);
|
||||
webContentsView.load(url);
|
||||
return webContentsView;
|
||||
};
|
||||
|
||||
private addView = (view: MattermostBrowserView): void => {
|
||||
private addView = (view: MattermostWebContentsView): void => {
|
||||
this.views.set(view.id, view);
|
||||
|
||||
// Force a permission check for notifications
|
||||
@@ -355,26 +355,18 @@ export class ViewManager {
|
||||
}
|
||||
if (url && url !== '') {
|
||||
const urlString = typeof url === 'string' ? url : url.toString();
|
||||
const preload = getLocalPreload('internalAPI.js');
|
||||
const urlView = new BrowserView({
|
||||
webPreferences: {
|
||||
preload,
|
||||
|
||||
// Workaround for this issue: https://github.com/electron/electron/issues/30993
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
transparent: true,
|
||||
}});
|
||||
const urlView = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
|
||||
urlView.setBackgroundColor('#00000000');
|
||||
const localURL = `mattermost-desktop://renderer/urlView.html?url=${encodeURIComponent(urlString)}`;
|
||||
performanceMonitor.registerView('URLView', urlView.webContents);
|
||||
urlView.webContents.loadURL(localURL);
|
||||
MainWindow.get()?.addBrowserView(urlView);
|
||||
MainWindow.get()?.contentView.addChildView(urlView);
|
||||
const boundaries = this.views.get(this.currentView || '')?.getBounds() ?? MainWindow.getBounds();
|
||||
|
||||
const hideView = () => {
|
||||
delete this.urlViewCancel;
|
||||
try {
|
||||
mainWindow.removeBrowserView(urlView);
|
||||
mainWindow.contentView.removeChildView(urlView);
|
||||
} catch (e) {
|
||||
log.error('Failed to remove URL view', e);
|
||||
}
|
||||
@@ -427,12 +419,12 @@ export class ViewManager {
|
||||
|
||||
const currentViewId: string | undefined = this.views.get(this.currentView as string)?.view.id;
|
||||
|
||||
const current: Map<string, MattermostBrowserView> = new Map();
|
||||
const current: Map<string, MattermostWebContentsView> = new Map();
|
||||
for (const view of this.views.values()) {
|
||||
current.set(view.view.id, view);
|
||||
}
|
||||
|
||||
const views: Map<string, MattermostBrowserView> = new Map();
|
||||
const views: Map<string, MattermostWebContentsView> = new Map();
|
||||
const closed: Map<string, {srv: MattermostServer; view: MattermostView}> = new Map();
|
||||
|
||||
const sortedViews = ServerManager.getAllServers().flatMap((x) => ServerManager.getOrderedTabsForServer(x.id).
|
||||
@@ -622,10 +614,10 @@ export class ViewManager {
|
||||
this.closedViews.delete(view.id);
|
||||
}
|
||||
this.showById(id);
|
||||
const browserView = this.views.get(id)!;
|
||||
browserView.isVisible = true;
|
||||
browserView.on(LOAD_SUCCESS, () => {
|
||||
browserView.isVisible = false;
|
||||
const webContentsView = this.views.get(id)!;
|
||||
webContentsView.isVisible = true;
|
||||
webContentsView.on(LOAD_SUCCESS, () => {
|
||||
webContentsView.isVisible = false;
|
||||
this.showById(id);
|
||||
});
|
||||
ipcMain.emit(OPEN_VIEW, null, view.id);
|
||||
|
@@ -35,7 +35,7 @@ import {
|
||||
openScreensharePermissionsSettingsMacOS,
|
||||
resetScreensharePermissionsMacOS,
|
||||
} from 'main/utils';
|
||||
import type {MattermostBrowserView} from 'main/views/MattermostBrowserView';
|
||||
import type {MattermostWebContentsView} from 'main/views/MattermostWebContentsView';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import webContentsEventManager from 'main/views/webContentEvents';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
@@ -51,7 +51,7 @@ const log = new Logger('CallsWidgetWindow');
|
||||
|
||||
export class CallsWidgetWindow {
|
||||
private win?: BrowserWindow;
|
||||
private mainView?: MattermostBrowserView;
|
||||
private mainView?: MattermostWebContentsView;
|
||||
private options?: CallsWidgetWindowConfig;
|
||||
private missingScreensharePermissions?: boolean;
|
||||
|
||||
@@ -135,7 +135,7 @@ export class CallsWidgetWindow {
|
||||
return u.toString();
|
||||
};
|
||||
|
||||
private init = (view: MattermostBrowserView, options: CallsWidgetWindowConfig) => {
|
||||
private init = (view: MattermostWebContentsView, options: CallsWidgetWindowConfig) => {
|
||||
this.win = new BrowserWindow({
|
||||
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||
|
@@ -96,6 +96,9 @@ describe('main/windows/mainWindow', () => {
|
||||
send: jest.fn(),
|
||||
setWindowOpenHandler: jest.fn(),
|
||||
},
|
||||
contentView: {
|
||||
on: jest.fn(),
|
||||
},
|
||||
isMaximized: jest.fn(),
|
||||
isFullScreen: jest.fn(),
|
||||
getBounds: jest.fn(),
|
||||
|
@@ -21,7 +21,6 @@ import {
|
||||
MAIN_WINDOW_CREATED,
|
||||
MAIN_WINDOW_RESIZED,
|
||||
MAIN_WINDOW_FOCUSED,
|
||||
VIEW_FINISHED_RESIZING,
|
||||
TOGGLE_SECURE_INPUT,
|
||||
EMIT_CONFIGURATION,
|
||||
EXIT_FULLSCREEN,
|
||||
@@ -49,18 +48,14 @@ export class MainWindow extends EventEmitter {
|
||||
|
||||
private savedWindowState?: Partial<SavedWindowState>;
|
||||
private ready: boolean;
|
||||
private isResizing: boolean;
|
||||
private lastEmittedBounds?: Electron.Rectangle;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Create the browser window.
|
||||
this.ready = false;
|
||||
this.isResizing = false;
|
||||
|
||||
ipcMain.handle(GET_FULL_SCREEN_STATUS, () => this.win?.isFullScreen());
|
||||
ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing);
|
||||
ipcMain.on(EMIT_CONFIGURATION, this.handleUpdateTitleBarOverlay);
|
||||
ipcMain.on(EXIT_FULLSCREEN, this.handleExitFullScreen);
|
||||
|
||||
@@ -85,7 +80,7 @@ export class MainWindow extends EventEmitter {
|
||||
titleBarStyle: 'hidden' as const,
|
||||
titleBarOverlay: this.getTitleBarOverlay(),
|
||||
trafficLightPosition: {x: 12, y: 12},
|
||||
backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||
backgroundColor: '#000', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||
webPreferences: {
|
||||
disableBlinkFeatures: 'Auxclick',
|
||||
preload: getLocalPreload('internalAPI.js'),
|
||||
@@ -131,16 +126,7 @@ export class MainWindow extends EventEmitter {
|
||||
this.win.on('unresponsive', this.onUnresponsive);
|
||||
this.win.on('enter-full-screen', this.onEnterFullScreen);
|
||||
this.win.on('leave-full-screen', this.onLeaveFullScreen);
|
||||
this.win.on('will-resize', this.onWillResize);
|
||||
this.win.on('resized', this.onResized);
|
||||
if (process.platform === 'win32') {
|
||||
// We don't want this on macOS, it's an alias of 'move'
|
||||
// This is mostly a fix for Windows 11 snapping
|
||||
this.win.on('moved', this.onResized);
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
this.win.on('resize', this.onResize);
|
||||
}
|
||||
this.win.contentView.on('bounds-changed', this.handleBoundsChanged);
|
||||
this.win.webContents.on('before-input-event', this.onBeforeInputEvent);
|
||||
|
||||
// Should not allow the main window to generate a window of its own
|
||||
@@ -457,82 +443,16 @@ export class MainWindow extends EventEmitter {
|
||||
});
|
||||
};
|
||||
|
||||
private emitBounds = (bounds?: Electron.Rectangle, force?: boolean) => {
|
||||
// Workaround since the window bounds aren't updated immediately when the window is maximized for some reason
|
||||
// We also don't want to force too many resizes so we throttle here
|
||||
setTimeout(() => {
|
||||
const newBounds = bounds ?? this.getBounds();
|
||||
if (!force && newBounds?.height === this.lastEmittedBounds?.height && newBounds?.width === this.lastEmittedBounds?.width) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For some reason on Linux I've seen the menu bar popup again
|
||||
this.win?.setMenuBarVisibility(false);
|
||||
|
||||
this.emit(MAIN_WINDOW_RESIZED, newBounds);
|
||||
this.lastEmittedBounds = newBounds;
|
||||
}, 10);
|
||||
};
|
||||
|
||||
private onEnterFullScreen = () => {
|
||||
this.win?.webContents.send('enter-full-screen');
|
||||
this.emitBounds();
|
||||
};
|
||||
|
||||
private onLeaveFullScreen = () => {
|
||||
this.win?.webContents.send('leave-full-screen');
|
||||
this.emitBounds();
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizing code
|
||||
*/
|
||||
|
||||
private onWillResize = (event: Event, newBounds: Electron.Rectangle) => {
|
||||
log.silly('onWillResize', newBounds);
|
||||
|
||||
/**
|
||||
* Fixes an issue on win11 related to Snap where the first "will-resize" event would return the same bounds
|
||||
* causing the "resize" event to not fire
|
||||
*/
|
||||
const prevBounds = this.getBounds();
|
||||
if (prevBounds?.height === newBounds.height && prevBounds?.width === newBounds.width) {
|
||||
log.debug('prevented resize');
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Workaround for macOS to stop the window from sending too many resize calls to the BrowserViews
|
||||
if (process.platform === 'darwin' && this.isResizing) {
|
||||
log.debug('prevented resize');
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isResizing = true;
|
||||
this.emitBounds(newBounds);
|
||||
};
|
||||
|
||||
private onResize = () => {
|
||||
log.silly('onResize');
|
||||
|
||||
// Workaround for Windows to stop the window from sending too many resize calls to the BrowserViews
|
||||
if (process.platform === 'win32' && this.isResizing) {
|
||||
return;
|
||||
}
|
||||
this.emitBounds();
|
||||
};
|
||||
|
||||
private onResized = () => {
|
||||
log.debug('onResized');
|
||||
|
||||
// Because this is the final window state after a resize, we force the size here
|
||||
this.emitBounds(this.getBounds(), true);
|
||||
this.isResizing = false;
|
||||
};
|
||||
|
||||
private handleViewFinishedResizing = () => {
|
||||
this.isResizing = false;
|
||||
private handleBoundsChanged = () => {
|
||||
this.emit(MAIN_WINDOW_RESIZED, this.win?.contentView.getBounds());
|
||||
};
|
||||
|
||||
private handleExitFullScreen = () => {
|
||||
|
Reference in New Issue
Block a user