diff --git a/e2e/specs/deep_linking/deeplink.test.js b/e2e/specs/deep_linking/deeplink.test.js index 20155a39..059ae820 100644 --- a/e2e/specs/deep_linking/deeplink.test.js +++ b/e2e/specs/deep_linking/deeplink.test.js @@ -43,7 +43,7 @@ describe('application', function desc() { const browserWindow = await this.app.browserWindow(mainWindow); const webContentsId = this.serverMap[`${config.teams[1].name}___TAB_MESSAGING`].webContentsId; const isActive = await browserWindow.evaluate((window, id) => { - return window.getBrowserViews().find((view) => view.webContents.id === id).webContents.getURL(); + return window.contentView.children.find((view) => view.webContents.id === id).webContents.getURL(); }, webContentsId); isActive.should.equal('https://github.com/test/url/'); const dropdownButtonText = await mainWindow.innerText('.ServerDropdownButton'); diff --git a/e2e/specs/menu_bar/dropdown.test.js b/e2e/specs/menu_bar/dropdown.test.js index 57ac5672..69822e3d 100644 --- a/e2e/specs/menu_bar/dropdown.test.js +++ b/e2e/specs/menu_bar/dropdown.test.js @@ -54,17 +54,17 @@ describe('menu_bar/dropdown', function desc() { after(afterFunc); it('MM-T4406_1 should show the dropdown', async () => { - let dropdownHeight = await browserWindow.evaluate((window) => window.getBrowserViews().find((view) => view.webContents.getURL().includes('dropdown')).getBounds().height); + let dropdownHeight = await browserWindow.evaluate((window) => window.contentView.children.find((view) => view.webContents.getURL().includes('dropdown')).getBounds().height); dropdownHeight.should.equal(0); await mainWindow.click('.ServerDropdownButton'); - dropdownHeight = await browserWindow.evaluate((window) => window.getBrowserViews().find((view) => view.webContents.getURL().includes('dropdown')).getBounds().height); + dropdownHeight = await browserWindow.evaluate((window) => window.contentView.children.find((view) => view.webContents.getURL().includes('dropdown')).getBounds().height); dropdownHeight.should.be.greaterThan(0); }); it('MM-T4406_2 should hide the dropdown', async () => { await mainWindow.click('.TabBar'); - const dropdownHeight = await browserWindow.evaluate((window) => window.getBrowserViews().find((view) => view.webContents.getURL().includes('dropdown')).getBounds().height); + const dropdownHeight = await browserWindow.evaluate((window) => window.contentView.children.find((view) => view.webContents.getURL().includes('dropdown')).getBounds().height); dropdownHeight.should.equal(0); }); }); @@ -100,9 +100,9 @@ describe('menu_bar/dropdown', function desc() { after(afterFunc); it('MM-T4408_1 should show the first view', async () => { - const firstViewIsAttached = await browserWindow.evaluate((window, url) => Boolean(window.getBrowserViews().find((view) => view.webContents.getURL() === url)), env.exampleURL); + const firstViewIsAttached = await browserWindow.evaluate((window, url) => Boolean(window.contentView.children.find((view) => view.webContents.getURL() === url)), env.exampleURL); firstViewIsAttached.should.be.true; - const secondViewIsAttached = await browserWindow.evaluate((window) => Boolean(window.getBrowserViews().find((view) => view.webContents.getURL() === 'https://github.com/'))); + const secondViewIsAttached = await browserWindow.evaluate((window) => Boolean(window.contentView.children.find((view) => view.webContents.getURL() === 'https://github.com/'))); secondViewIsAttached.should.be.false; }); @@ -110,9 +110,9 @@ describe('menu_bar/dropdown', function desc() { await mainWindow.click('.ServerDropdownButton'); await dropdownView.click('.ServerDropdown button.ServerDropdown__button:nth-child(2)'); - const firstViewIsAttached = await browserWindow.evaluate((window, url) => Boolean(window.getBrowserViews().find((view) => view.webContents.getURL() === url)), env.exampleURL); + const firstViewIsAttached = await browserWindow.evaluate((window, url) => Boolean(window.contentView.children.find((view) => view.webContents.getURL() === url)), env.exampleURL); firstViewIsAttached.should.be.false; - const secondViewIsAttached = await browserWindow.evaluate((window) => Boolean(window.getBrowserViews().find((view) => view.webContents.getURL() === 'https://github.com/'))); + const secondViewIsAttached = await browserWindow.evaluate((window) => Boolean(window.contentView.children.find((view) => view.webContents.getURL() === 'https://github.com/'))); secondViewIsAttached.should.be.true; }); }); diff --git a/e2e/specs/menu_bar/view_menu.test.js b/e2e/specs/menu_bar/view_menu.test.js index 5afebf47..10f0681c 100644 --- a/e2e/specs/menu_bar/view_menu.test.js +++ b/e2e/specs/menu_bar/view_menu.test.js @@ -11,7 +11,7 @@ const {asyncSleep} = require('../../modules/utils'); async function setupPromise(window, id) { const promise = new Promise((resolve) => { - const browserView = window.getBrowserViews().find((view) => view.webContents.id === id); + const browserView = window.contentView.children.find((view) => view.webContents.id === id); browserView.webContents.on('did-finish-load', () => { resolve(); }); @@ -22,13 +22,13 @@ async function setupPromise(window, id) { function getZoomFactorOfServer(browserWindow, serverId) { return browserWindow.evaluate( - (window, id) => window.getBrowserViews().find((view) => view.webContents.id === id).webContents.getZoomFactor(), + (window, id) => window.contentView.children.find((view) => view.webContents.id === id).webContents.getZoomFactor(), serverId, ); } function setZoomFactorOfServer(browserWindow, serverId, zoomFactor) { return browserWindow.evaluate( - (window, {id, zoom}) => window.getBrowserViews().find((view) => view.webContents.id === id).webContents.setZoomFactor(zoom), + (window, {id, zoom}) => window.contentView.children.find((view) => view.webContents.id === id).webContents.setZoomFactor(zoom), {id: serverId, zoom: zoomFactor}, ); } @@ -82,12 +82,12 @@ describe('menu/view', function desc() { robot.keyTap('=', [env.cmdOrCtrl]); await asyncSleep(1000); - let zoomLevel = await browserWindow.evaluate((window, id) => window.getBrowserViews().find((view) => view.webContents.id === id).webContents.getZoomFactor(), firstServerId); + let zoomLevel = await browserWindow.evaluate((window, id) => window.contentView.children.find((view) => view.webContents.id === id).webContents.getZoomFactor(), firstServerId); zoomLevel.should.be.greaterThan(1); robot.keyTap('0', [env.cmdOrCtrl]); await asyncSleep(1000); - zoomLevel = await browserWindow.evaluate((window, id) => window.getBrowserViews().find((view) => view.webContents.id === id).webContents.getZoomFactor(), firstServerId); + zoomLevel = await browserWindow.evaluate((window, id) => window.contentView.children.find((view) => view.webContents.id === id).webContents.getZoomFactor(), firstServerId); zoomLevel.should.be.equal(1); }); @@ -104,7 +104,7 @@ describe('menu/view', function desc() { robot.keyTap('=', [env.cmdOrCtrl]); await asyncSleep(1000); - const zoomLevel = await browserWindow.evaluate((window, id) => window.getBrowserViews().find((view) => view.webContents.id === id).webContents.getZoomFactor(), firstServerId); + const zoomLevel = await browserWindow.evaluate((window, id) => window.contentView.children.find((view) => view.webContents.id === id).webContents.getZoomFactor(), firstServerId); zoomLevel.should.be.greaterThan(1); }); @@ -144,7 +144,7 @@ describe('menu/view', function desc() { robot.keyTap('-', [env.cmdOrCtrl]); await asyncSleep(1000); - const zoomLevel = await browserWindow.evaluate((window, id) => window.getBrowserViews().find((view) => view.webContents.id === id).webContents.getZoomFactor(), firstServerId); + const zoomLevel = await browserWindow.evaluate((window, id) => window.contentView.children.find((view) => view.webContents.id === id).webContents.getZoomFactor(), firstServerId); zoomLevel.should.be.lessThan(1); }); diff --git a/i18n/en.json b/i18n/en.json index 2172facd..cf4a77d1 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -159,9 +159,9 @@ "renderer.components.errorView.cannotConnectToAppName": "Cannot connect to {appName}", "renderer.components.errorView.havingTroubleConnecting": "We're having trouble connecting to {appName}. We'll continue to try and establish a connection.", "renderer.components.errorView.refreshThenVerify": "If refreshing this page (Ctrl+R or Command+R) does not work please verify that:", - "renderer.components.errorView.troubleshooting.browserView.canReachFromBrowserWindow": "You can reach {url} from a browser window.", "renderer.components.errorView.troubleshooting.computerIsConnected": "Your computer is connected to the internet.", "renderer.components.errorView.troubleshooting.urlIsCorrect.appNameIsCorrect": "The {appName} URL {url} is correct", + "renderer.components.errorView.troubleshooting.webContentsView.canReachFromBrowserWindow": "You can reach {url} from a browser window.", "renderer.components.input.required": "This field is required", "renderer.components.mainPage.contextMenu.ariaLabel": "Context menu", "renderer.components.mainPage.titleBar": "{appName}", diff --git a/package-lock.json b/package-lock.json index b7c4cdd1..21af7837 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "bootstrap": "4.6.1", "bootstrap-dark": "1.0.3", "classnames": "2.5.1", - "electron-context-menu": "3.6.1", + "electron-context-menu": "4.0.4", "electron-extension-installer": "1.2.0", "electron-is-dev": "2.0.0", "electron-log": "5.2.0", @@ -5281,6 +5281,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "optional": true, "engines": { "node": ">=8" } @@ -6226,6 +6228,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "optional": true, "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" @@ -7443,26 +7447,136 @@ "dev": true }, "node_modules/electron-context-menu": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-3.6.1.tgz", - "integrity": "sha512-lcpO6tzzKUROeirhzBjdBWNqayEThmdW+2I2s6H6QMrwqTVyT3EK47jW3Nxm60KTxl5/bWfEoIruoUNn57/QkQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-4.0.4.tgz", + "integrity": "sha512-XPGj35npL8+MG9Lx5ukmK/h8KLmjYJ3e1GvwWKrNZvf2ocv746WXIyltoV1yWtkEPT7g2kQ8hFmu0ZupK5KieA==", "dependencies": { - "cli-truncate": "^2.1.0", - "electron-dl": "^3.2.1", - "electron-is-dev": "^2.0.0" + "cli-truncate": "^4.0.0", + "electron-dl": "^4.0.0", + "electron-is-dev": "^3.0.1" + }, + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/electron-context-menu/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/electron-context-menu/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/electron-context-menu/node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron-context-menu/node_modules/electron-is-dev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-3.0.1.tgz", + "integrity": "sha512-8TjjAh8Ec51hUi3o4TaU0mD3GMTOESi866oRNavj9A3IQJ7pmv+MJVmdZBFGw4GFT36X7bkqnuDNYvkQgvyI8Q==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron-context-menu/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron-context-menu/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/electron-context-menu/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron-context-menu/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/electron-dl": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.5.0.tgz", - "integrity": "sha512-Oj+VSuScVx8hEKM2HEvTQswTX6G3MLh7UoAz/oZuvKyNDfudNi1zY6PK/UnFoK1nCl9DF6k+3PFwElKbtZlDig==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-4.0.0.tgz", + "integrity": "sha512-USiB9816d2JzKv0LiSbreRfTg5lDk3lWh0vlx/gugCO92ZIJkHVH0UM18EHvKeadErP6Xn4yiTphWzYfbA2Ong==", "dependencies": { "ext-name": "^5.0.0", - "pupa": "^2.0.1", - "unused-filename": "^2.1.0" + "pupa": "^3.1.0", + "unused-filename": "^4.0.1" + }, + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7655,8 +7769,7 @@ "node_modules/emoji-regex": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", - "dev": true + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" }, "node_modules/emojis-list": { "version": "3.0.0", @@ -7914,11 +8027,14 @@ } }, "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/escape-string-regexp": { @@ -9288,6 +9404,17 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -13296,14 +13423,6 @@ "node": ">=12" } }, - "node_modules/modify-filename": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz", - "integrity": "sha512-EickqnKq3kVVaZisYuCxhtKbZjInCuwgwZWyAmRIp1NTMhri7r3380/uqwrUHfaDiPzLVTuoNy4whX66bxPVog==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -14008,6 +14127,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "engines": { "node": ">=8" } @@ -14535,14 +14655,17 @@ } }, "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", + "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", "dependencies": { - "escape-goat": "^2.0.0" + "escape-goat": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/pure-rand": { @@ -15644,6 +15767,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -15657,6 +15782,8 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15671,6 +15798,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15681,7 +15810,9 @@ "node_modules/slice-ansi/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true }, "node_modules/smart-buffer": { "version": "4.2.0", @@ -16806,15 +16937,37 @@ } }, "node_modules/unused-filename": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unused-filename/-/unused-filename-2.1.0.tgz", - "integrity": "sha512-BMiNwJbuWmqCpAM1FqxCTD7lXF97AvfQC8Kr/DIeA6VtvhJaMDupZ82+inbjl5yVP44PcxOuCSxye1QMS0wZyg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/unused-filename/-/unused-filename-4.0.1.tgz", + "integrity": "sha512-ZX6U1J04K1FoSUeoX1OicAhw4d0aro2qo+L8RhJkiGTNtBNkd/Fi1Wxoc9HzcVu6HfOzm0si/N15JjxFmD1z6A==", "dependencies": { - "modify-filename": "^1.1.0", - "path-exists": "^4.0.0" + "escape-string-regexp": "^5.0.0", + "path-exists": "^5.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unused-filename/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unused-filename/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, "node_modules/update-browserslist-db": { diff --git a/package.json b/package.json index eb925f69..3b22dac6 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,7 @@ "bootstrap": "4.6.1", "bootstrap-dark": "1.0.3", "classnames": "2.5.1", - "electron-context-menu": "3.6.1", + "electron-context-menu": "4.0.4", "electron-extension-installer": "1.2.0", "electron-is-dev": "2.0.0", "electron-log": "5.2.0", diff --git a/src/common/communication.ts b/src/common/communication.ts index cd73b89d..5d41cc72 100644 --- a/src/common/communication.ts +++ b/src/common/communication.ts @@ -108,8 +108,6 @@ export const PING_DOMAIN = 'ping-domain'; export const GET_LANGUAGE_INFORMATION = 'get-language-information'; export const GET_AVAILABLE_LANGUAGES = 'get-available-languages'; -export const VIEW_FINISHED_RESIZING = 'view-finished-resizing'; - // Calls export const GET_DESKTOP_SOURCES = 'get-desktop-sources'; export const DESKTOP_SOURCES_MODAL_REQUEST = 'desktop-sources-modal-request'; diff --git a/src/common/utils/constants.ts b/src/common/utils/constants.ts index 6b347784..ee710483 100644 --- a/src/common/utils/constants.ts +++ b/src/common/utils/constants.ts @@ -36,7 +36,7 @@ export const DOWNLOADS_DROPDOWN_MENU_HEIGHT = 160; export const DOWNLOADS_DROPDOWN_MENU_WIDTH = 154; export const DOWNLOADS_DROPDOWN_MENU_PADDING = 12; -// In order to display the box-shadow & radius on the left + right, use this WIDTH in the browserView for downloadsDropdown +// In order to display the box-shadow & radius on the left + right, use this WIDTH in the webContentsView for downloadsDropdown export const DOWNLOADS_DROPDOWN_FULL_WIDTH = DOWNLOADS_DROPDOWN_PADDING + DOWNLOADS_DROPDOWN_WIDTH + TAB_BAR_PADDING; export const DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH = (DOWNLOADS_DROPDOWN_MENU_PADDING * 2) + DOWNLOADS_DROPDOWN_MENU_WIDTH; export const DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT = DOWNLOADS_DROPDOWN_MENU_HEIGHT + TAB_BAR_PADDING; // only bottom padding included for better positioning diff --git a/src/main/app/app.test.js b/src/main/app/app.test.js index 6122dfdf..6682ed44 100644 --- a/src/main/app/app.test.js +++ b/src/main/app/app.test.js @@ -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); diff --git a/src/main/contextMenu.test.js b/src/main/contextMenu.test.js index 15d73d66..c2a4317d 100644 --- a/src/main/contextMenu.test.js +++ b/src/main/contextMenu.test.js @@ -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(); diff --git a/src/main/contextMenu.ts b/src/main/contextMenu.ts index 1429cf52..31a663ac 100644 --- a/src/main/contextMenu.ts +++ b/src/main/contextMenu.ts @@ -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); }; } diff --git a/src/main/diagnostics/steps/internal/utils.test.js b/src/main/diagnostics/steps/internal/utils.test.js index 1796625d..ad90219f 100644 --- a/src/main/diagnostics/steps/internal/utils.test.js +++ b/src/main/diagnostics/steps/internal/utils.test.js @@ -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); }); }); diff --git a/src/main/diagnostics/steps/internal/utils.ts b/src/main/diagnostics/steps/internal/utils.ts index afb2ccb6..ef2f1322 100644 --- a/src/main/diagnostics/steps/internal/utils.ts +++ b/src/main/diagnostics/steps/internal/utils.ts @@ -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; diff --git a/src/main/downloadsManager.test.js b/src/main/downloadsManager.test.js index 8b9cfe1a..4cb57307 100644 --- a/src/main/downloadsManager.test.js +++ b/src/main/downloadsManager.test.js @@ -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(), diff --git a/src/main/preload/externalAPI.ts b/src/main/preload/externalAPI.ts index f8016bd8..6e2f9843 100644 --- a/src/main/preload/externalAPI.ts +++ b/src/main/preload/externalAPI.ts @@ -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) => { diff --git a/src/main/preload/internalAPI.js b/src/main/preload/internalAPI.js index d5bb4ad3..caf1fe21 100644 --- a/src/main/preload/internalAPI.js +++ b/src/main/preload/internalAPI.js @@ -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), diff --git a/src/main/views/MattermostBrowserView.test.js b/src/main/views/MattermostWebContentsView.test.js similarity index 72% rename from src/main/views/MattermostBrowserView.test.js rename to src/main/views/MattermostWebContentsView.test.js index ec01e09f..b0e78971 100644 --- a/src/main/views/MattermostBrowserView.test.js +++ b/src/main/views/MattermostWebContentsView.test.js @@ -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 { 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 { 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 { 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); diff --git a/src/main/views/MattermostBrowserView.ts b/src/main/views/MattermostWebContentsView.ts similarity index 79% rename from src/main/views/MattermostBrowserView.ts rename to src/main/views/MattermostWebContentsView.ts index 1a5599cd..27a71824 100644 --- a/src/main/views/MattermostBrowserView.ts +++ b/src/main/views/MattermostWebContentsView.ts @@ -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); diff --git a/src/main/views/downloadsDropdownMenuView.test.js b/src/main/views/downloadsDropdownMenuView.test.js index ddbe8202..9112c674 100644 --- a/src/main/views/downloadsDropdownMenuView.test.js +++ b/src/main/views/downloadsDropdownMenuView.test.js @@ -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); }); diff --git a/src/main/views/downloadsDropdownMenuView.ts b/src/main/views/downloadsDropdownMenuView.ts index 6036e8d6..ce48a082 100644 --- a/src/main/views/downloadsDropdownMenuView.ts +++ b/src/main/views/downloadsDropdownMenuView.ts @@ -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(); }; diff --git a/src/main/views/downloadsDropdownView.test.js b/src/main/views/downloadsDropdownView.test.js index 74b65ac9..0f71b4c2 100644 --- a/src/main/views/downloadsDropdownView.test.js +++ b/src/main/views/downloadsDropdownView.test.js @@ -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', () => { diff --git a/src/main/views/downloadsDropdownView.ts b/src/main/views/downloadsDropdownView.ts index dc3194f0..453a67d6 100644 --- a/src/main/views/downloadsDropdownView.ts +++ b/src/main/views/downloadsDropdownView.ts @@ -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); diff --git a/src/main/views/loadingScreen.test.js b/src/main/views/loadingScreen.test.js index de34b959..0f888969 100644 --- a/src/main/views/loadingScreen.test.js +++ b/src/main/views/loadingScreen.test.js @@ -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(); }); }); }); diff --git a/src/main/views/loadingScreen.ts b/src/main/views/loadingScreen.ts index ef08b5a9..1c152fe2 100644 --- a/src/main/views/loadingScreen.ts +++ b/src/main/views/loadingScreen.ts @@ -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') { diff --git a/src/main/views/modalView.test.js b/src/main/views/modalView.test.js index 8b0aa909..af455a9f 100644 --- a/src/main/views/modalView.test.js +++ b/src/main/views/modalView.test.js @@ -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', () => { diff --git a/src/main/views/modalView.ts b/src/main/views/modalView.ts index 0fa591f0..f5825ca8 100644 --- a/src/main/views/modalView.ts +++ b/src/main/views/modalView.ts @@ -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 { 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 { 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 { 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 { 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; diff --git a/src/main/views/serverDropdownView.test.js b/src/main/views/serverDropdownView.test.js index 9b4621d4..e27cc889 100644 --- a/src/main/views/serverDropdownView.test.js +++ b/src/main/views/serverDropdownView.test.js @@ -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(), diff --git a/src/main/views/serverDropdownView.ts b/src/main/views/serverDropdownView.ts index 059cf7e1..1ca86a32 100644 --- a/src/main/views/serverDropdownView.ts +++ b/src/main/views/serverDropdownView.ts @@ -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; diff --git a/src/main/views/viewManager.test.js b/src/main/views/viewManager.test.js index 025669aa..00750419 100644 --- a/src/main/views/viewManager.test.js +++ b/src/main/views/viewManager.test.js @@ -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', diff --git a/src/main/views/viewManager.ts b/src/main/views/viewManager.ts index 14357687..1a9e1790 100644 --- a/src/main/views/viewManager.ts +++ b/src/main/views/viewManager.ts @@ -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; - private views: Map; + private views: Map; 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 = new Map(); + const current: Map = new Map(); for (const view of this.views.values()) { current.set(view.view.id, view); } - const views: Map = new Map(); + const views: Map = new Map(); const closed: Map = 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); diff --git a/src/main/windows/callsWidgetWindow.ts b/src/main/windows/callsWidgetWindow.ts index 7afaea53..471fceb0 100644 --- a/src/main/windows/callsWidgetWindow.ts +++ b/src/main/windows/callsWidgetWindow.ts @@ -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, diff --git a/src/main/windows/mainWindow.test.js b/src/main/windows/mainWindow.test.js index 6c6673d0..7bc8c9e4 100644 --- a/src/main/windows/mainWindow.test.js +++ b/src/main/windows/mainWindow.test.js @@ -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(), diff --git a/src/main/windows/mainWindow.ts b/src/main/windows/mainWindow.ts index f2f22c73..7c863470 100644 --- a/src/main/windows/mainWindow.ts +++ b/src/main/windows/mainWindow.ts @@ -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; 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 = () => { diff --git a/src/renderer/components/ErrorView.tsx b/src/renderer/components/ErrorView.tsx index 716cc9cc..e372d039 100644 --- a/src/renderer/components/ErrorView.tsx +++ b/src/renderer/components/ErrorView.tsx @@ -94,7 +94,7 @@ export default function ErrorView(props: Props) {
  • { setShowContent(true); - - // 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', () => { - window.desktop.viewFinishedResizing(); - }); }, []); const slides = useMemo(() => [ diff --git a/src/types/window.ts b/src/types/window.ts index 8b7f2e67..a0616e03 100644 --- a/src/types/window.ts +++ b/src/types/window.ts @@ -95,7 +95,6 @@ declare global { openWindowsCameraPreferences: () => void; openWindowsMicrophonePreferences: () => void; getMediaAccessStatus: (mediaType: 'microphone' | 'camera' | 'screen') => Promise<'not-determined' | 'granted' | 'denied' | 'restricted' | 'unknown'>; - viewFinishedResizing: () => void; modals: { cancelModal: (data?: T) => void;