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