[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 browserWindow = await this.app.browserWindow(mainWindow);
|
||||||
const webContentsId = this.serverMap[`${config.teams[1].name}___TAB_MESSAGING`].webContentsId;
|
const webContentsId = this.serverMap[`${config.teams[1].name}___TAB_MESSAGING`].webContentsId;
|
||||||
const isActive = await browserWindow.evaluate((window, id) => {
|
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);
|
}, webContentsId);
|
||||||
isActive.should.equal('https://github.com/test/url/');
|
isActive.should.equal('https://github.com/test/url/');
|
||||||
const dropdownButtonText = await mainWindow.innerText('.ServerDropdownButton');
|
const dropdownButtonText = await mainWindow.innerText('.ServerDropdownButton');
|
||||||
|
@@ -54,17 +54,17 @@ describe('menu_bar/dropdown', function desc() {
|
|||||||
after(afterFunc);
|
after(afterFunc);
|
||||||
|
|
||||||
it('MM-T4406_1 should show the dropdown', async () => {
|
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);
|
dropdownHeight.should.equal(0);
|
||||||
|
|
||||||
await mainWindow.click('.ServerDropdownButton');
|
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);
|
dropdownHeight.should.be.greaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MM-T4406_2 should hide the dropdown', async () => {
|
it('MM-T4406_2 should hide the dropdown', async () => {
|
||||||
await mainWindow.click('.TabBar');
|
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);
|
dropdownHeight.should.equal(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -100,9 +100,9 @@ describe('menu_bar/dropdown', function desc() {
|
|||||||
after(afterFunc);
|
after(afterFunc);
|
||||||
|
|
||||||
it('MM-T4408_1 should show the first view', async () => {
|
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;
|
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;
|
secondViewIsAttached.should.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -110,9 +110,9 @@ describe('menu_bar/dropdown', function desc() {
|
|||||||
await mainWindow.click('.ServerDropdownButton');
|
await mainWindow.click('.ServerDropdownButton');
|
||||||
await dropdownView.click('.ServerDropdown button.ServerDropdown__button:nth-child(2)');
|
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;
|
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;
|
secondViewIsAttached.should.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -11,7 +11,7 @@ const {asyncSleep} = require('../../modules/utils');
|
|||||||
|
|
||||||
async function setupPromise(window, id) {
|
async function setupPromise(window, id) {
|
||||||
const promise = new Promise((resolve) => {
|
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', () => {
|
browserView.webContents.on('did-finish-load', () => {
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
@@ -22,13 +22,13 @@ async function setupPromise(window, id) {
|
|||||||
|
|
||||||
function getZoomFactorOfServer(browserWindow, serverId) {
|
function getZoomFactorOfServer(browserWindow, serverId) {
|
||||||
return browserWindow.evaluate(
|
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,
|
serverId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function setZoomFactorOfServer(browserWindow, serverId, zoomFactor) {
|
function setZoomFactorOfServer(browserWindow, serverId, zoomFactor) {
|
||||||
return browserWindow.evaluate(
|
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},
|
{id: serverId, zoom: zoomFactor},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -82,12 +82,12 @@ describe('menu/view', function desc() {
|
|||||||
|
|
||||||
robot.keyTap('=', [env.cmdOrCtrl]);
|
robot.keyTap('=', [env.cmdOrCtrl]);
|
||||||
await asyncSleep(1000);
|
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);
|
zoomLevel.should.be.greaterThan(1);
|
||||||
|
|
||||||
robot.keyTap('0', [env.cmdOrCtrl]);
|
robot.keyTap('0', [env.cmdOrCtrl]);
|
||||||
await asyncSleep(1000);
|
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);
|
zoomLevel.should.be.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ describe('menu/view', function desc() {
|
|||||||
|
|
||||||
robot.keyTap('=', [env.cmdOrCtrl]);
|
robot.keyTap('=', [env.cmdOrCtrl]);
|
||||||
await asyncSleep(1000);
|
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);
|
zoomLevel.should.be.greaterThan(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ describe('menu/view', function desc() {
|
|||||||
|
|
||||||
robot.keyTap('-', [env.cmdOrCtrl]);
|
robot.keyTap('-', [env.cmdOrCtrl]);
|
||||||
await asyncSleep(1000);
|
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);
|
zoomLevel.should.be.lessThan(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -159,9 +159,9 @@
|
|||||||
"renderer.components.errorView.cannotConnectToAppName": "Cannot connect to {appName}",
|
"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.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.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.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.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.input.required": "This field is required",
|
||||||
"renderer.components.mainPage.contextMenu.ariaLabel": "Context menu",
|
"renderer.components.mainPage.contextMenu.ariaLabel": "Context menu",
|
||||||
"renderer.components.mainPage.titleBar": "{appName}",
|
"renderer.components.mainPage.titleBar": "{appName}",
|
||||||
|
229
package-lock.json
generated
229
package-lock.json
generated
@@ -16,7 +16,7 @@
|
|||||||
"bootstrap": "4.6.1",
|
"bootstrap": "4.6.1",
|
||||||
"bootstrap-dark": "1.0.3",
|
"bootstrap-dark": "1.0.3",
|
||||||
"classnames": "2.5.1",
|
"classnames": "2.5.1",
|
||||||
"electron-context-menu": "3.6.1",
|
"electron-context-menu": "4.0.4",
|
||||||
"electron-extension-installer": "1.2.0",
|
"electron-extension-installer": "1.2.0",
|
||||||
"electron-is-dev": "2.0.0",
|
"electron-is-dev": "2.0.0",
|
||||||
"electron-log": "5.2.0",
|
"electron-log": "5.2.0",
|
||||||
@@ -5281,6 +5281,8 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
|
||||||
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
|
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -6226,6 +6228,8 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
|
||||||
"integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
|
"integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"slice-ansi": "^3.0.0",
|
"slice-ansi": "^3.0.0",
|
||||||
"string-width": "^4.2.0"
|
"string-width": "^4.2.0"
|
||||||
@@ -7443,26 +7447,136 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/electron-context-menu": {
|
"node_modules/electron-context-menu": {
|
||||||
"version": "3.6.1",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-3.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-4.0.4.tgz",
|
||||||
"integrity": "sha512-lcpO6tzzKUROeirhzBjdBWNqayEThmdW+2I2s6H6QMrwqTVyT3EK47jW3Nxm60KTxl5/bWfEoIruoUNn57/QkQ==",
|
"integrity": "sha512-XPGj35npL8+MG9Lx5ukmK/h8KLmjYJ3e1GvwWKrNZvf2ocv746WXIyltoV1yWtkEPT7g2kQ8hFmu0ZupK5KieA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cli-truncate": "^2.1.0",
|
"cli-truncate": "^4.0.0",
|
||||||
"electron-dl": "^3.2.1",
|
"electron-dl": "^4.0.0",
|
||||||
"electron-is-dev": "^2.0.0"
|
"electron-is-dev": "^3.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/electron-dl": {
|
||||||
"version": "3.5.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-4.0.0.tgz",
|
||||||
"integrity": "sha512-Oj+VSuScVx8hEKM2HEvTQswTX6G3MLh7UoAz/oZuvKyNDfudNi1zY6PK/UnFoK1nCl9DF6k+3PFwElKbtZlDig==",
|
"integrity": "sha512-USiB9816d2JzKv0LiSbreRfTg5lDk3lWh0vlx/gugCO92ZIJkHVH0UM18EHvKeadErP6Xn4yiTphWzYfbA2Ong==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ext-name": "^5.0.0",
|
"ext-name": "^5.0.0",
|
||||||
"pupa": "^2.0.1",
|
"pupa": "^3.1.0",
|
||||||
"unused-filename": "^2.1.0"
|
"unused-filename": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
@@ -7655,8 +7769,7 @@
|
|||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "10.3.0",
|
"version": "10.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
|
||||||
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==",
|
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/emojis-list": {
|
"node_modules/emojis-list": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@@ -7914,11 +8027,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escape-goat": {
|
"node_modules/escape-goat": {
|
||||||
"version": "2.1.1",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz",
|
||||||
"integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==",
|
"integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escape-string-regexp": {
|
"node_modules/escape-string-regexp": {
|
||||||
@@ -9288,6 +9404,17 @@
|
|||||||
"node": "6.* || 8.* || >= 10.*"
|
"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": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||||
@@ -13296,14 +13423,6 @@
|
|||||||
"node": ">=12"
|
"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": {
|
"node_modules/moment": {
|
||||||
"version": "2.30.1",
|
"version": "2.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||||
@@ -14008,6 +14127,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -14535,14 +14655,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pupa": {
|
"node_modules/pupa": {
|
||||||
"version": "2.1.1",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz",
|
||||||
"integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==",
|
"integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"escape-goat": "^2.0.0"
|
"escape-goat": "^4.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=12.20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pure-rand": {
|
"node_modules/pure-rand": {
|
||||||
@@ -15644,6 +15767,8 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
|
||||||
"integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
|
"integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
"astral-regex": "^2.0.0",
|
"astral-regex": "^2.0.0",
|
||||||
@@ -15657,6 +15782,8 @@
|
|||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
},
|
},
|
||||||
@@ -15671,6 +15798,8 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
},
|
},
|
||||||
@@ -15681,7 +15810,9 @@
|
|||||||
"node_modules/slice-ansi/node_modules/color-name": {
|
"node_modules/slice-ansi/node_modules/color-name": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"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": {
|
"node_modules/smart-buffer": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
@@ -16806,15 +16937,37 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unused-filename": {
|
"node_modules/unused-filename": {
|
||||||
"version": "2.1.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/unused-filename/-/unused-filename-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/unused-filename/-/unused-filename-4.0.1.tgz",
|
||||||
"integrity": "sha512-BMiNwJbuWmqCpAM1FqxCTD7lXF97AvfQC8Kr/DIeA6VtvhJaMDupZ82+inbjl5yVP44PcxOuCSxye1QMS0wZyg==",
|
"integrity": "sha512-ZX6U1J04K1FoSUeoX1OicAhw4d0aro2qo+L8RhJkiGTNtBNkd/Fi1Wxoc9HzcVu6HfOzm0si/N15JjxFmD1z6A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"modify-filename": "^1.1.0",
|
"escape-string-regexp": "^5.0.0",
|
||||||
"path-exists": "^4.0.0"
|
"path-exists": "^5.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"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": {
|
"node_modules/update-browserslist-db": {
|
||||||
|
@@ -165,7 +165,7 @@
|
|||||||
"bootstrap": "4.6.1",
|
"bootstrap": "4.6.1",
|
||||||
"bootstrap-dark": "1.0.3",
|
"bootstrap-dark": "1.0.3",
|
||||||
"classnames": "2.5.1",
|
"classnames": "2.5.1",
|
||||||
"electron-context-menu": "3.6.1",
|
"electron-context-menu": "4.0.4",
|
||||||
"electron-extension-installer": "1.2.0",
|
"electron-extension-installer": "1.2.0",
|
||||||
"electron-is-dev": "2.0.0",
|
"electron-is-dev": "2.0.0",
|
||||||
"electron-log": "5.2.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_LANGUAGE_INFORMATION = 'get-language-information';
|
||||||
export const GET_AVAILABLE_LANGUAGES = 'get-available-languages';
|
export const GET_AVAILABLE_LANGUAGES = 'get-available-languages';
|
||||||
|
|
||||||
export const VIEW_FINISHED_RESIZING = 'view-finished-resizing';
|
|
||||||
|
|
||||||
// Calls
|
// Calls
|
||||||
export const GET_DESKTOP_SOURCES = 'get-desktop-sources';
|
export const GET_DESKTOP_SOURCES = 'get-desktop-sources';
|
||||||
export const DESKTOP_SOURCES_MODAL_REQUEST = 'desktop-sources-modal-request';
|
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_WIDTH = 154;
|
||||||
export const DOWNLOADS_DROPDOWN_MENU_PADDING = 12;
|
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_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_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
|
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();
|
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});
|
dialog.showMessageBox.mockResolvedValue({response: 0});
|
||||||
await handleAppCertificateError(event, webContents, testURL, 'error-1', certificate, callback);
|
await handleAppCertificateError(event, webContents, testURL, 'error-1', certificate, callback);
|
||||||
expect(callback).toHaveBeenCalledWith(true);
|
expect(callback).toHaveBeenCalledWith(true);
|
||||||
|
@@ -10,7 +10,7 @@ jest.mock('electron-context-menu', () => {
|
|||||||
|
|
||||||
describe('main/contextMenu', () => {
|
describe('main/contextMenu', () => {
|
||||||
describe('shouldShowMenu', () => {
|
describe('shouldShowMenu', () => {
|
||||||
const contextMenu = new ContextMenu();
|
const contextMenu = new ContextMenu({}, {webContents: {}});
|
||||||
|
|
||||||
it('should not show menu on internal link', () => {
|
it('should not show menu on internal link', () => {
|
||||||
expect(contextMenu.menuOptions.shouldShowMenu(null, {
|
expect(contextMenu.menuOptions.shouldShowMenu(null, {
|
||||||
@@ -73,7 +73,7 @@ describe('main/contextMenu', () => {
|
|||||||
|
|
||||||
describe('reload', () => {
|
describe('reload', () => {
|
||||||
it('should call dispose on reload', () => {
|
it('should call dispose on reload', () => {
|
||||||
const contextMenu = new ContextMenu();
|
const contextMenu = new ContextMenu({}, {webContents: {}});
|
||||||
const fn = contextMenu.menuDispose;
|
const fn = contextMenu.menuDispose;
|
||||||
contextMenu.reload();
|
contextMenu.reload();
|
||||||
expect(fn).toHaveBeenCalled();
|
expect(fn).toHaveBeenCalled();
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// 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 type {Options} from 'electron-context-menu';
|
||||||
import electronContextMenu from 'electron-context-menu';
|
import electronContextMenu from 'electron-context-menu';
|
||||||
|
|
||||||
@@ -29,11 +29,11 @@ const defaultMenuOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class ContextMenu {
|
export default class ContextMenu {
|
||||||
view: BrowserWindow | BrowserView;
|
view: BrowserWindow | WebContentsView;
|
||||||
menuOptions: Options;
|
menuOptions: Options;
|
||||||
menuDispose?: () => void;
|
menuDispose?: () => void;
|
||||||
|
|
||||||
constructor(options: Options, view: BrowserWindow | BrowserView) {
|
constructor(options: Options, view: BrowserWindow | WebContentsView) {
|
||||||
const providedOptions: Options = options || {};
|
const providedOptions: Options = options || {};
|
||||||
|
|
||||||
this.menuOptions = Object.assign({}, defaultMenuOptions, providedOptions);
|
this.menuOptions = Object.assign({}, defaultMenuOptions, providedOptions);
|
||||||
@@ -52,7 +52,7 @@ export default class ContextMenu {
|
|||||||
reload = () => {
|
reload = () => {
|
||||||
this.dispose();
|
this.dispose();
|
||||||
|
|
||||||
const options = {window: this.view, ...this.menuOptions};
|
const options = {window: this.view.webContents, ...this.menuOptions};
|
||||||
this.menuDispose = electronContextMenu(options);
|
this.menuDispose = electronContextMenu(options);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -91,14 +91,16 @@ describe('main/diagnostics/utils', () => {
|
|||||||
isDestroyed: () => false,
|
isDestroyed: () => false,
|
||||||
isVisible: () => true,
|
isVisible: () => true,
|
||||||
isEnabled: () => true,
|
isEnabled: () => true,
|
||||||
getBrowserViews: () => [{
|
contentView: {
|
||||||
getBounds: () => ({
|
children: [{
|
||||||
x: 0,
|
getBounds: () => ({
|
||||||
y: 0,
|
x: 0,
|
||||||
width: 800,
|
y: 0,
|
||||||
height: 500,
|
width: 800,
|
||||||
}),
|
height: 500,
|
||||||
}],
|
}),
|
||||||
|
}],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
it('should return true if window ok', () => {
|
it('should return true if window ok', () => {
|
||||||
expect(browserWindowVisibilityStatus('testWindow', bWindow).every((check) => check.ok)).toBe(true);
|
expect(browserWindowVisibilityStatus('testWindow', bWindow).every((check) => check.ok)).toBe(true);
|
||||||
@@ -118,17 +120,19 @@ describe('main/diagnostics/utils', () => {
|
|||||||
it('should return false if window is not enabled', () => {
|
it('should return false if window is not enabled', () => {
|
||||||
expect(browserWindowVisibilityStatus('testWindow', {...bWindow, isEnabled: () => false}).every((check) => check.ok)).toBe(false);
|
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', {
|
expect(browserWindowVisibilityStatus('testWindow', {
|
||||||
...bWindow,
|
...bWindow,
|
||||||
getBrowserViews: () => [{
|
contentView: {
|
||||||
getBounds: () => ({
|
children: [{
|
||||||
x: -1,
|
getBounds: () => ({
|
||||||
y: -4000,
|
x: -1,
|
||||||
width: 800,
|
y: -4000,
|
||||||
height: 500,
|
width: 800,
|
||||||
}),
|
height: 500,
|
||||||
}],
|
}),
|
||||||
|
}],
|
||||||
|
},
|
||||||
}).every((check) => check.ok)).toBe(false);
|
}).every((check) => check.ok)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -109,7 +109,7 @@ export function browserWindowVisibilityStatus(name: string, bWindow?: BrowserWin
|
|||||||
const destroyed = bWindow.isDestroyed();
|
const destroyed = bWindow.isDestroyed();
|
||||||
const visible = bWindow.isVisible();
|
const visible = bWindow.isVisible();
|
||||||
const enabled = bWindow.isEnabled();
|
const enabled = bWindow.isEnabled();
|
||||||
const browserViewsBounds = bWindow.getBrowserViews()?.map((view) => view.getBounds());
|
const webContentsViewsBounds = bWindow.contentView.children.map((view) => view.getBounds());
|
||||||
|
|
||||||
status.push({
|
status.push({
|
||||||
name: 'windowExists',
|
name: 'windowExists',
|
||||||
@@ -141,9 +141,9 @@ export function browserWindowVisibilityStatus(name: string, bWindow?: BrowserWin
|
|||||||
ok: enabled,
|
ok: enabled,
|
||||||
});
|
});
|
||||||
status.push({
|
status.push({
|
||||||
name: 'browserViewsBounds',
|
name: 'webContentsViewsBounds',
|
||||||
ok: browserViewsBounds.every((bounds) => boundsOk(bounds)),
|
ok: webContentsViewsBounds.every((bounds) => boundsOk(bounds)),
|
||||||
data: browserViewsBounds,
|
data: webContentsViewsBounds,
|
||||||
});
|
});
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
|
@@ -30,7 +30,7 @@ jest.mock('electron', () => {
|
|||||||
getAppPath: jest.fn(),
|
getAppPath: jest.fn(),
|
||||||
getPath: jest.fn(() => '/valid/downloads/path'),
|
getPath: jest.fn(() => '/valid/downloads/path'),
|
||||||
},
|
},
|
||||||
BrowserView: jest.fn().mockImplementation(() => ({
|
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||||
webContents: {
|
webContents: {
|
||||||
loadURL: jest.fn(),
|
loadURL: jest.fn(),
|
||||||
focus: jest.fn(),
|
focus: jest.fn(),
|
||||||
|
@@ -13,7 +13,6 @@ import {
|
|||||||
USER_ACTIVITY_UPDATE,
|
USER_ACTIVITY_UPDATE,
|
||||||
BROWSER_HISTORY_PUSH,
|
BROWSER_HISTORY_PUSH,
|
||||||
GET_VIEW_INFO_FOR_TEST,
|
GET_VIEW_INFO_FOR_TEST,
|
||||||
VIEW_FINISHED_RESIZING,
|
|
||||||
CALLS_JOIN_CALL,
|
CALLS_JOIN_CALL,
|
||||||
CALLS_JOINED_CALL,
|
CALLS_JOINED_CALL,
|
||||||
CALLS_LEAVE_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
|
// Enable secure input on macOS clients when the user is on a password input
|
||||||
let isPasswordBox = false;
|
let isPasswordBox = false;
|
||||||
const shouldSecureInput = (element: {tagName?: string; type?: string} | null, force = false) => {
|
const shouldSecureInput = (element: {tagName?: string; type?: string} | null, force = false) => {
|
||||||
|
@@ -89,7 +89,6 @@ import {
|
|||||||
OPEN_WINDOWS_CAMERA_PREFERENCES,
|
OPEN_WINDOWS_CAMERA_PREFERENCES,
|
||||||
OPEN_WINDOWS_MICROPHONE_PREFERENCES,
|
OPEN_WINDOWS_MICROPHONE_PREFERENCES,
|
||||||
GET_MEDIA_ACCESS_STATUS,
|
GET_MEDIA_ACCESS_STATUS,
|
||||||
VIEW_FINISHED_RESIZING,
|
|
||||||
GET_NONCE,
|
GET_NONCE,
|
||||||
IS_DEVELOPER_MODE_ENABLED,
|
IS_DEVELOPER_MODE_ENABLED,
|
||||||
METRICS_REQUEST,
|
METRICS_REQUEST,
|
||||||
@@ -177,7 +176,6 @@ contextBridge.exposeInMainWorld('desktop', {
|
|||||||
openWindowsCameraPreferences: () => ipcRenderer.send(OPEN_WINDOWS_CAMERA_PREFERENCES),
|
openWindowsCameraPreferences: () => ipcRenderer.send(OPEN_WINDOWS_CAMERA_PREFERENCES),
|
||||||
openWindowsMicrophonePreferences: () => ipcRenderer.send(OPEN_WINDOWS_MICROPHONE_PREFERENCES),
|
openWindowsMicrophonePreferences: () => ipcRenderer.send(OPEN_WINDOWS_MICROPHONE_PREFERENCES),
|
||||||
getMediaAccessStatus: (mediaType) => ipcRenderer.invoke(GET_MEDIA_ACCESS_STATUS, mediaType),
|
getMediaAccessStatus: (mediaType) => ipcRenderer.invoke(GET_MEDIA_ACCESS_STATUS, mediaType),
|
||||||
viewFinishedResizing: () => ipcRenderer.send(VIEW_FINISHED_RESIZING),
|
|
||||||
|
|
||||||
downloadsDropdown: {
|
downloadsDropdown: {
|
||||||
toggleDownloadsDropdownMenu: (payload) => ipcRenderer.send(TOGGLE_DOWNLOADS_DROPDOWN_MENU, payload),
|
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 {MattermostServer} from 'common/servers/MattermostServer';
|
||||||
import MessagingView from 'common/views/MessagingView';
|
import MessagingView from 'common/views/MessagingView';
|
||||||
|
|
||||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
import {MattermostWebContentsView} from './MattermostWebContentsView';
|
||||||
|
|
||||||
import ContextMenu from '../contextMenu';
|
import ContextMenu from '../contextMenu';
|
||||||
import MainWindow from '../windows/mainWindow';
|
import MainWindow from '../windows/mainWindow';
|
||||||
@@ -18,7 +18,7 @@ jest.mock('electron', () => ({
|
|||||||
getVersion: () => '5.0.0',
|
getVersion: () => '5.0.0',
|
||||||
getPath: jest.fn(() => '/valid/downloads/path'),
|
getPath: jest.fn(() => '/valid/downloads/path'),
|
||||||
},
|
},
|
||||||
BrowserView: jest.fn().mockImplementation(() => ({
|
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||||
webContents: {
|
webContents: {
|
||||||
loadURL: jest.fn(),
|
loadURL: jest.fn(),
|
||||||
on: 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 server = new MattermostServer({name: 'server_name', url: 'http://server-1.com'});
|
||||||
const view = new MessagingView(server, true);
|
const view = new MessagingView(server, true);
|
||||||
|
|
||||||
describe('main/views/MattermostBrowserView', () => {
|
describe('main/views/MattermostWebContentsView', () => {
|
||||||
describe('load', () => {
|
describe('load', () => {
|
||||||
const window = {on: jest.fn()};
|
const window = {on: jest.fn()};
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
MainWindow.get.mockReturnValue(window);
|
MainWindow.get.mockReturnValue(window);
|
||||||
@@ -85,38 +85,38 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
|
|
||||||
it('should load provided URL when provided', async () => {
|
it('should load provided URL when provided', async () => {
|
||||||
const promise = Promise.resolve();
|
const promise = Promise.resolve();
|
||||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||||
mattermostView.load('http://server-2.com');
|
mattermostView.load('http://server-2.com');
|
||||||
await promise;
|
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/');
|
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-2.com/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load server URL when not provided', async () => {
|
it('should load server URL when not provided', async () => {
|
||||||
const promise = Promise.resolve();
|
const promise = Promise.resolve();
|
||||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||||
mattermostView.load();
|
mattermostView.load();
|
||||||
await promise;
|
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/');
|
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load server URL when bad url provided', async () => {
|
it('should load server URL when bad url provided', async () => {
|
||||||
const promise = Promise.resolve();
|
const promise = Promise.resolve();
|
||||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||||
mattermostView.load('a-bad<url');
|
mattermostView.load('a-bad<url');
|
||||||
await promise;
|
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/');
|
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call retry when failing to load', async () => {
|
it('should call retry when failing to load', async () => {
|
||||||
const error = new Error('test');
|
const error = new Error('test');
|
||||||
const promise = Promise.reject(error);
|
const promise = Promise.reject(error);
|
||||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||||
mattermostView.load('a-bad<url');
|
mattermostView.load('a-bad<url');
|
||||||
await expect(promise).rejects.toThrow(error);
|
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);
|
expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com/', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -124,23 +124,23 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
const error = new Error('test');
|
const error = new Error('test');
|
||||||
error.code = 'ERR_CERT_ERROR';
|
error.code = 'ERR_CERT_ERROR';
|
||||||
const promise = Promise.reject(error);
|
const promise = Promise.reject(error);
|
||||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||||
mattermostView.load('a-bad<url');
|
mattermostView.load('a-bad<url');
|
||||||
await expect(promise).rejects.toThrow(error);
|
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(mattermostView.loadRetry).not.toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('retry', () => {
|
describe('retry', () => {
|
||||||
const window = {on: jest.fn()};
|
const window = {on: jest.fn()};
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
const retryInBackgroundFn = jest.fn();
|
const retryInBackgroundFn = jest.fn();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
MainWindow.get.mockReturnValue(window);
|
MainWindow.get.mockReturnValue(window);
|
||||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => Promise.resolve());
|
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => Promise.resolve());
|
||||||
mattermostView.loadSuccess = jest.fn();
|
mattermostView.loadSuccess = jest.fn();
|
||||||
mattermostView.loadRetry = jest.fn();
|
mattermostView.loadRetry = jest.fn();
|
||||||
mattermostView.emit = jest.fn();
|
mattermostView.emit = jest.fn();
|
||||||
@@ -154,16 +154,16 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should do nothing when webcontents are destroyed', () => {
|
it('should do nothing when webcontents are destroyed', () => {
|
||||||
const webContents = mattermostView.browserView.webContents;
|
const webContents = mattermostView.webContentsView.webContents;
|
||||||
mattermostView.browserView.webContents = null;
|
mattermostView.webContentsView.webContents = null;
|
||||||
mattermostView.retry('http://server-1.com')();
|
mattermostView.retry('http://server-1.com')();
|
||||||
expect(mattermostView.loadSuccess).not.toBeCalled();
|
expect(mattermostView.loadSuccess).not.toBeCalled();
|
||||||
mattermostView.browserView.webContents = webContents;
|
mattermostView.webContentsView.webContents = webContents;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call loadSuccess on successful load', async () => {
|
it('should call loadSuccess on successful load', async () => {
|
||||||
const promise = Promise.resolve();
|
const promise = Promise.resolve();
|
||||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||||
mattermostView.retry('http://server-1.com')();
|
mattermostView.retry('http://server-1.com')();
|
||||||
await promise;
|
await promise;
|
||||||
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com');
|
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com');
|
||||||
@@ -173,10 +173,10 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
mattermostView.maxRetries = 10;
|
mattermostView.maxRetries = 10;
|
||||||
const error = new Error('test');
|
const error = new Error('test');
|
||||||
const promise = Promise.reject(error);
|
const promise = Promise.reject(error);
|
||||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||||
mattermostView.retry('http://server-1.com')();
|
mattermostView.retry('http://server-1.com')();
|
||||||
await expect(promise).rejects.toThrow(error);
|
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);
|
expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -184,10 +184,10 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
mattermostView.maxRetries = 0;
|
mattermostView.maxRetries = 0;
|
||||||
const error = new Error('test');
|
const error = new Error('test');
|
||||||
const promise = Promise.reject(error);
|
const promise = Promise.reject(error);
|
||||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
mattermostView.webContentsView.webContents.loadURL.mockImplementation(() => promise);
|
||||||
mattermostView.retry('http://server-1.com')();
|
mattermostView.retry('http://server-1.com')();
|
||||||
await expect(promise).rejects.toThrow(error);
|
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(mattermostView.loadRetry).not.toBeCalled();
|
||||||
expect(MainWindow.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.view.id, expect.any(String), expect.any(String));
|
expect(MainWindow.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.view.id, expect.any(String), expect.any(String));
|
||||||
expect(mattermostView.status).toBe(-1);
|
expect(mattermostView.status).toBe(-1);
|
||||||
@@ -198,7 +198,7 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
|
|
||||||
describe('goToOffset', () => {
|
describe('goToOffset', () => {
|
||||||
const window = {on: jest.fn()};
|
const window = {on: jest.fn()};
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
mattermostView.reload = jest.fn();
|
mattermostView.reload = jest.fn();
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -207,18 +207,18 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should only go to offset if it can', () => {
|
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);
|
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);
|
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', () => {
|
it('should call reload if an error occurs', () => {
|
||||||
mattermostView.browserView.webContents.navigationHistory.canGoToOffset.mockReturnValue(true);
|
mattermostView.webContentsView.webContents.navigationHistory.canGoToOffset.mockReturnValue(true);
|
||||||
mattermostView.browserView.webContents.navigationHistory.goToOffset.mockImplementation(() => {
|
mattermostView.webContentsView.webContents.navigationHistory.goToOffset.mockImplementation(() => {
|
||||||
throw new Error('hi');
|
throw new Error('hi');
|
||||||
});
|
});
|
||||||
mattermostView.goToOffset(1);
|
mattermostView.goToOffset(1);
|
||||||
@@ -228,8 +228,8 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
|
|
||||||
describe('onLogin', () => {
|
describe('onLogin', () => {
|
||||||
const window = {on: jest.fn()};
|
const window = {on: jest.fn()};
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
mattermostView.browserView.webContents.getURL = jest.fn();
|
mattermostView.webContentsView.webContents.getURL = jest.fn();
|
||||||
mattermostView.reload = jest.fn();
|
mattermostView.reload = jest.fn();
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -238,19 +238,19 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should reload view when URL is not on subpath of original server URL', () => {
|
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);
|
mattermostView.onLogin(true);
|
||||||
expect(mattermostView.reload).toHaveBeenCalled();
|
expect(mattermostView.reload).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not reload if URLs are matching', () => {
|
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);
|
mattermostView.onLogin(true);
|
||||||
expect(mattermostView.reload).not.toHaveBeenCalled();
|
expect(mattermostView.reload).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not reload if URL is subpath of server URL', () => {
|
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);
|
mattermostView.onLogin(true);
|
||||||
expect(mattermostView.reload).not.toHaveBeenCalled();
|
expect(mattermostView.reload).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -258,7 +258,7 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
|
|
||||||
describe('loadSuccess', () => {
|
describe('loadSuccess', () => {
|
||||||
const window = {on: jest.fn()};
|
const window = {on: jest.fn()};
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
@@ -285,8 +285,14 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('show', () => {
|
describe('show', () => {
|
||||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn(), setTopBrowserView: jest.fn()};
|
const window = {
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
contentView: {
|
||||||
|
addChildView: jest.fn(),
|
||||||
|
removeChildView: jest.fn(),
|
||||||
|
},
|
||||||
|
on: jest.fn(),
|
||||||
|
};
|
||||||
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
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', () => {
|
it('should add browser view to window and set bounds when request is true and view not currently visible', () => {
|
||||||
mattermostView.isVisible = false;
|
mattermostView.isVisible = false;
|
||||||
mattermostView.show();
|
mattermostView.show();
|
||||||
expect(window.addBrowserView).toBeCalledWith(mattermostView.browserView);
|
expect(window.contentView.addChildView).toBeCalledWith(mattermostView.webContentsView);
|
||||||
expect(mattermostView.setBounds).toBeCalled();
|
expect(mattermostView.setBounds).toBeCalled();
|
||||||
expect(mattermostView.isVisible).toBe(true);
|
expect(mattermostView.isVisible).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -312,7 +318,7 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
it('should do nothing when not toggling', () => {
|
it('should do nothing when not toggling', () => {
|
||||||
mattermostView.isVisible = true;
|
mattermostView.isVisible = true;
|
||||||
mattermostView.show();
|
mattermostView.show();
|
||||||
expect(window.addBrowserView).not.toBeCalled();
|
expect(window.contentView.addChildView).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should focus view if view is ready', () => {
|
it('should focus view if view is ready', () => {
|
||||||
@@ -324,8 +330,14 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('hide', () => {
|
describe('hide', () => {
|
||||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn(), setTopBrowserView: jest.fn()};
|
const window = {
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
contentView: {
|
||||||
|
addChildView: jest.fn(),
|
||||||
|
removeChildView: jest.fn(),
|
||||||
|
},
|
||||||
|
on: jest.fn(),
|
||||||
|
};
|
||||||
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
MainWindow.get.mockReturnValue(window);
|
MainWindow.get.mockReturnValue(window);
|
||||||
@@ -334,20 +346,20 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
it('should remove browser view', () => {
|
it('should remove browser view', () => {
|
||||||
mattermostView.isVisible = true;
|
mattermostView.isVisible = true;
|
||||||
mattermostView.hide();
|
mattermostView.hide();
|
||||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.browserView);
|
expect(window.contentView.removeChildView).toBeCalledWith(mattermostView.webContentsView);
|
||||||
expect(mattermostView.isVisible).toBe(false);
|
expect(mattermostView.isVisible).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should do nothing when not toggling', () => {
|
it('should do nothing when not toggling', () => {
|
||||||
mattermostView.isVisible = false;
|
mattermostView.isVisible = false;
|
||||||
mattermostView.hide();
|
mattermostView.hide();
|
||||||
expect(window.removeBrowserView).not.toBeCalled();
|
expect(window.contentView.removeChildView).not.toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updateHistoryButton', () => {
|
describe('updateHistoryButton', () => {
|
||||||
const window = {on: jest.fn()};
|
const window = {on: jest.fn()};
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
MainWindow.get.mockReturnValue(window);
|
MainWindow.get.mockReturnValue(window);
|
||||||
@@ -356,13 +368,13 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
it('should erase history and set isAtRoot when navigating to root URL', () => {
|
it('should erase history and set isAtRoot when navigating to root URL', () => {
|
||||||
mattermostView.atRoot = false;
|
mattermostView.atRoot = false;
|
||||||
mattermostView.updateHistoryButton();
|
mattermostView.updateHistoryButton();
|
||||||
expect(mattermostView.browserView.webContents.navigationHistory.clear).toHaveBeenCalled();
|
expect(mattermostView.webContentsView.webContents.navigationHistory.clear).toHaveBeenCalled();
|
||||||
expect(mattermostView.isAtRoot).toBe(true);
|
expect(mattermostView.isAtRoot).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('destroy', () => {
|
describe('destroy', () => {
|
||||||
const window = {removeBrowserView: jest.fn(), on: jest.fn()};
|
const window = {contentView: {removeChildView: jest.fn()}, on: jest.fn()};
|
||||||
const contextMenu = {
|
const contextMenu = {
|
||||||
dispose: jest.fn(),
|
dispose: jest.fn(),
|
||||||
};
|
};
|
||||||
@@ -373,22 +385,22 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should remove browser view from window', () => {
|
it('should remove browser view from window', () => {
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
mattermostView.browserView.webContents.close = jest.fn();
|
mattermostView.webContentsView.webContents.close = jest.fn();
|
||||||
mattermostView.destroy();
|
mattermostView.destroy();
|
||||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.browserView);
|
expect(window.contentView.removeChildView).toBeCalledWith(mattermostView.webContentsView);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear mentions', () => {
|
it('should clear mentions', () => {
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
mattermostView.browserView.webContents.close = jest.fn();
|
mattermostView.webContentsView.webContents.close = jest.fn();
|
||||||
mattermostView.destroy();
|
mattermostView.destroy();
|
||||||
expect(AppState.clear).toBeCalledWith(mattermostView.view.id);
|
expect(AppState.clear).toBeCalledWith(mattermostView.view.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear outstanding timeouts', () => {
|
it('should clear outstanding timeouts', () => {
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
mattermostView.browserView.webContents.close = jest.fn();
|
mattermostView.webContentsView.webContents.close = jest.fn();
|
||||||
const spy = jest.spyOn(global, 'clearTimeout');
|
const spy = jest.spyOn(global, 'clearTimeout');
|
||||||
mattermostView.retryLoad = 999;
|
mattermostView.retryLoad = 999;
|
||||||
mattermostView.removeLoading = 1000;
|
mattermostView.removeLoading = 1000;
|
||||||
@@ -399,7 +411,7 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
|
|
||||||
describe('handleInputEvents', () => {
|
describe('handleInputEvents', () => {
|
||||||
const window = {on: jest.fn()};
|
const window = {on: jest.fn()};
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
|
|
||||||
it('should open three dot menu on pressing Alt', () => {
|
it('should open three dot menu on pressing Alt', () => {
|
||||||
MainWindow.get.mockReturnValue(window);
|
MainWindow.get.mockReturnValue(window);
|
||||||
@@ -424,7 +436,7 @@ describe('main/views/MattermostBrowserView', () => {
|
|||||||
|
|
||||||
describe('handleUpdateTarget', () => {
|
describe('handleUpdateTarget', () => {
|
||||||
const window = {on: jest.fn()};
|
const window = {on: jest.fn()};
|
||||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
const mattermostView = new MattermostWebContentsView(view, {}, {});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
MainWindow.get.mockReturnValue(window);
|
MainWindow.get.mockReturnValue(window);
|
@@ -1,8 +1,8 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {BrowserView, app, ipcMain} from 'electron';
|
import {WebContentsView, app, ipcMain} from 'electron';
|
||||||
import type {BrowserViewConstructorOptions, Event, Input} from 'electron/main';
|
import type {WebContentsViewConstructorOptions, Event, Input} from 'electron/main';
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
|
|
||||||
import AppState from 'common/appState';
|
import AppState from 'common/appState';
|
||||||
@@ -38,16 +38,15 @@ enum Status {
|
|||||||
WAITING_MM,
|
WAITING_MM,
|
||||||
ERROR = -1,
|
ERROR = -1,
|
||||||
}
|
}
|
||||||
|
export class MattermostWebContentsView extends EventEmitter {
|
||||||
export class MattermostBrowserView extends EventEmitter {
|
|
||||||
view: MattermostView;
|
view: MattermostView;
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
|
|
||||||
private log: Logger;
|
private log: Logger;
|
||||||
private browserView: BrowserView;
|
private webContentsView: WebContentsView;
|
||||||
private loggedIn: boolean;
|
private loggedIn: boolean;
|
||||||
private atRoot: boolean;
|
private atRoot: boolean;
|
||||||
private options: BrowserViewConstructorOptions;
|
private options: WebContentsViewConstructorOptions;
|
||||||
private removeLoading?: NodeJS.Timeout;
|
private removeLoading?: NodeJS.Timeout;
|
||||||
private contextMenu?: ContextMenu;
|
private contextMenu?: ContextMenu;
|
||||||
private status?: Status;
|
private status?: Status;
|
||||||
@@ -55,7 +54,7 @@ export class MattermostBrowserView extends EventEmitter {
|
|||||||
private maxRetries: number;
|
private maxRetries: number;
|
||||||
private altPressStatus: boolean;
|
private altPressStatus: boolean;
|
||||||
|
|
||||||
constructor(view: MattermostView, options: BrowserViewConstructorOptions) {
|
constructor(view: MattermostView, options: WebContentsViewConstructorOptions) {
|
||||||
super();
|
super();
|
||||||
this.view = view;
|
this.view = view;
|
||||||
|
|
||||||
@@ -72,29 +71,28 @@ export class MattermostBrowserView extends EventEmitter {
|
|||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
this.loggedIn = false;
|
this.loggedIn = false;
|
||||||
this.atRoot = true;
|
this.atRoot = true;
|
||||||
this.browserView = new BrowserView(this.options);
|
this.webContentsView = new WebContentsView(this.options);
|
||||||
this.resetLoadingStatus();
|
this.resetLoadingStatus();
|
||||||
|
|
||||||
this.log = ServerManager.getViewLog(this.id, 'MattermostBrowserView');
|
this.log = ServerManager.getViewLog(this.id, 'MattermostWebContentsView');
|
||||||
this.log.verbose('View created');
|
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') {
|
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') {
|
if (inputEvent.type === 'mouseDown') {
|
||||||
ipcMain.emit(CLOSE_SERVERS_DROPDOWN);
|
ipcMain.emit(CLOSE_SERVERS_DROPDOWN);
|
||||||
ipcMain.emit(CLOSE_DOWNLOADS_DROPDOWN);
|
ipcMain.emit(CLOSE_DOWNLOADS_DROPDOWN);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
WebContentsEventManager.addWebContentsEventListeners(this.browserView.webContents);
|
WebContentsEventManager.addWebContentsEventListeners(this.webContentsView.webContents);
|
||||||
|
|
||||||
if (!DeveloperMode.get('disableContextMenu')) {
|
if (!DeveloperMode.get('disableContextMenu')) {
|
||||||
this.contextMenu = new ContextMenu({}, this.browserView);
|
this.contextMenu = new ContextMenu({}, this.webContentsView);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.maxRetries = MAX_SERVER_RETRIES;
|
this.maxRetries = MAX_SERVER_RETRIES;
|
||||||
|
|
||||||
this.altPressStatus = false;
|
this.altPressStatus = false;
|
||||||
@@ -116,10 +114,10 @@ export class MattermostBrowserView extends EventEmitter {
|
|||||||
return this.loggedIn;
|
return this.loggedIn;
|
||||||
}
|
}
|
||||||
get currentURL() {
|
get currentURL() {
|
||||||
return parseURL(this.browserView.webContents.getURL());
|
return parseURL(this.webContentsView.webContents.getURL());
|
||||||
}
|
}
|
||||||
get webContentsId() {
|
get webContentsId() {
|
||||||
return this.browserView.webContents.id;
|
return this.webContentsView.webContents.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
onLogin = (loggedIn: boolean) => {
|
onLogin = (loggedIn: boolean) => {
|
||||||
@@ -139,9 +137,9 @@ export class MattermostBrowserView extends EventEmitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
goToOffset = (offset: number) => {
|
goToOffset = (offset: number) => {
|
||||||
if (this.browserView.webContents.navigationHistory.canGoToOffset(offset)) {
|
if (this.webContentsView.webContents.navigationHistory.canGoToOffset(offset)) {
|
||||||
try {
|
try {
|
||||||
this.browserView.webContents.navigationHistory.goToOffset(offset);
|
this.webContentsView.webContents.navigationHistory.goToOffset(offset);
|
||||||
this.updateHistoryButton();
|
this.updateHistoryButton();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log.error(error);
|
this.log.error(error);
|
||||||
@@ -152,25 +150,25 @@ export class MattermostBrowserView extends EventEmitter {
|
|||||||
|
|
||||||
getBrowserHistoryStatus = () => {
|
getBrowserHistoryStatus = () => {
|
||||||
if (this.currentURL?.toString() === this.view.url.toString()) {
|
if (this.currentURL?.toString() === this.view.url.toString()) {
|
||||||
this.browserView.webContents.navigationHistory.clear();
|
this.webContentsView.webContents.navigationHistory.clear();
|
||||||
this.atRoot = true;
|
this.atRoot = true;
|
||||||
} else {
|
} else {
|
||||||
this.atRoot = false;
|
this.atRoot = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canGoBack: this.browserView.webContents.navigationHistory.canGoBack(),
|
canGoBack: this.webContentsView.webContents.navigationHistory.canGoBack(),
|
||||||
canGoForward: this.browserView.webContents.navigationHistory.canGoForward(),
|
canGoForward: this.webContentsView.webContents.navigationHistory.canGoForward(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
updateHistoryButton = () => {
|
updateHistoryButton = () => {
|
||||||
const {canGoBack, canGoForward} = this.getBrowserHistoryStatus();
|
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) => {
|
load = (someURL?: URL | string) => {
|
||||||
if (!this.browserView) {
|
if (!this.webContentsView) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,11 +186,11 @@ export class MattermostBrowserView extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.log.verbose(`Loading ${loadURL}`);
|
this.log.verbose(`Loading ${loadURL}`);
|
||||||
if (this.view.type === TAB_MESSAGING) {
|
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 {
|
} 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) => {
|
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||||
if (err.code && err.code.startsWith('ERR_CERT')) {
|
if (err.code && err.code.startsWith('ERR_CERT')) {
|
||||||
MainWindow.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
MainWindow.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
||||||
@@ -221,8 +219,7 @@ export class MattermostBrowserView extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isVisible = true;
|
this.isVisible = true;
|
||||||
mainWindow.addBrowserView(this.browserView);
|
mainWindow.contentView.addChildView(this.webContentsView);
|
||||||
mainWindow.setTopBrowserView(this.browserView);
|
|
||||||
this.setBounds(getWindowBoundaries(mainWindow));
|
this.setBounds(getWindowBoundaries(mainWindow));
|
||||||
if (this.status === Status.READY) {
|
if (this.status === Status.READY) {
|
||||||
this.focus();
|
this.focus();
|
||||||
@@ -232,7 +229,7 @@ export class MattermostBrowserView extends EventEmitter {
|
|||||||
hide = () => {
|
hide = () => {
|
||||||
if (this.isVisible) {
|
if (this.isVisible) {
|
||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
MainWindow.get()?.removeBrowserView(this.browserView);
|
MainWindow.get()?.contentView.removeChildView(this.webContentsView);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -243,23 +240,23 @@ export class MattermostBrowserView extends EventEmitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getBounds = () => {
|
getBounds = () => {
|
||||||
return this.browserView.getBounds();
|
return this.webContentsView.getBounds();
|
||||||
};
|
};
|
||||||
|
|
||||||
openFind = () => {
|
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) => {
|
setBounds = (boundaries: Electron.Rectangle) => {
|
||||||
this.browserView.setBounds(boundaries);
|
this.webContentsView.setBounds(boundaries);
|
||||||
};
|
};
|
||||||
|
|
||||||
destroy = () => {
|
destroy = () => {
|
||||||
WebContentsEventManager.removeWebContentsListeners(this.webContentsId);
|
WebContentsEventManager.removeWebContentsListeners(this.webContentsId);
|
||||||
AppState.clear(this.id);
|
AppState.clear(this.id);
|
||||||
MainWindow.get()?.removeBrowserView(this.browserView);
|
performanceMonitor.unregisterView(this.webContentsView.webContents.id);
|
||||||
performanceMonitor.unregisterView(this.browserView.webContents.id);
|
MainWindow.get()?.contentView.removeChildView(this.webContentsView);
|
||||||
this.browserView.webContents.close();
|
this.webContentsView.webContents.close();
|
||||||
|
|
||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
if (this.retryLoad) {
|
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
|
// So what we do here is check to see if it's opened correctly and if not we reset it
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
if (this.browserView.webContents.isDevToolsOpened()) {
|
if (this.webContentsView.webContents.isDevToolsOpened()) {
|
||||||
this.browserView.webContents.closeDevTools();
|
this.webContentsView.webContents.closeDevTools();
|
||||||
this.browserView.webContents.openDevTools({mode: 'detach'});
|
this.webContentsView.webContents.openDevTools({mode: 'detach'});
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
this.browserView.webContents.on('devtools-opened', () => {
|
this.webContentsView.webContents.on('devtools-opened', () => {
|
||||||
clearTimeout(timeout);
|
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[]) => {
|
sendToRenderer = (channel: string, ...args: any[]) => {
|
||||||
this.browserView.webContents.send(channel, ...args);
|
this.webContentsView.webContents.send(channel, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
isDestroyed = () => {
|
isDestroyed = () => {
|
||||||
return this.browserView.webContents.isDestroyed();
|
return this.webContentsView.webContents.isDestroyed();
|
||||||
};
|
};
|
||||||
|
|
||||||
focus = () => {
|
focus = () => {
|
||||||
if (this.browserView.webContents) {
|
if (this.webContentsView.webContents) {
|
||||||
this.browserView.webContents.focus();
|
this.webContentsView.webContents.focus();
|
||||||
} else {
|
} else {
|
||||||
this.log.warn('trying to focus the browserview, but it doesn\'t yet have webcontents.');
|
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) => {
|
private retry = (loadURL: string) => {
|
||||||
return () => {
|
return () => {
|
||||||
// window was closed while retrying
|
// window was closed while retrying
|
||||||
if (!this.browserView || !this.browserView.webContents) {
|
if (!this.webContentsView || !this.webContentsView.webContents) {
|
||||||
return;
|
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) => {
|
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||||
if (this.maxRetries-- > 0) {
|
if (this.maxRetries-- > 0) {
|
||||||
this.loadRetry(loadURL, err);
|
this.loadRetry(loadURL, err);
|
||||||
@@ -402,7 +399,7 @@ export class MattermostBrowserView extends EventEmitter {
|
|||||||
private retryInBackground = (loadURL: string) => {
|
private retryInBackground = (loadURL: string) => {
|
||||||
return () => {
|
return () => {
|
||||||
// window was closed while retrying
|
// window was closed while retrying
|
||||||
if (!this.browserView || !this.browserView.webContents) {
|
if (!this.webContentsView || !this.webContentsView.webContents) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parsedURL = parseURL(loadURL);
|
const parsedURL = parseURL(loadURL);
|
@@ -30,7 +30,8 @@ jest.mock('electron', () => {
|
|||||||
getAppPath: () => '',
|
getAppPath: () => '',
|
||||||
getPath: jest.fn(() => '/valid/downloads/path'),
|
getPath: jest.fn(() => '/valid/downloads/path'),
|
||||||
},
|
},
|
||||||
BrowserView: jest.fn().mockImplementation(() => ({
|
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||||
|
setBackgroundColor: jest.fn(),
|
||||||
webContents: {
|
webContents: {
|
||||||
loadURL: jest.fn(),
|
loadURL: jest.fn(),
|
||||||
focus: jest.fn(),
|
focus: jest.fn(),
|
||||||
@@ -69,7 +70,7 @@ jest.mock('fs', () => ({
|
|||||||
|
|
||||||
describe('main/views/DownloadsDropdownMenuView', () => {
|
describe('main/views/DownloadsDropdownMenuView', () => {
|
||||||
beforeEach(() => {
|
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});
|
MainWindow.getBounds.mockReturnValue({width: 800, height: 600, x: 0, y: 0});
|
||||||
getDarwinDoNotDisturb.mockReturnValue(false);
|
getDarwinDoNotDisturb.mockReturnValue(false);
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import type {IpcMainEvent} from 'electron';
|
import type {IpcMainEvent} from 'electron';
|
||||||
import {BrowserView, ipcMain} from 'electron';
|
import {WebContentsView, ipcMain} from 'electron';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CLOSE_DOWNLOADS_DROPDOWN_MENU,
|
CLOSE_DOWNLOADS_DROPDOWN_MENU,
|
||||||
@@ -37,7 +37,7 @@ const log = new Logger('DownloadsDropdownMenuView');
|
|||||||
|
|
||||||
export class DownloadsDropdownMenuView {
|
export class DownloadsDropdownMenuView {
|
||||||
private open: boolean;
|
private open: boolean;
|
||||||
private view?: BrowserView;
|
private view?: WebContentsView;
|
||||||
private bounds?: Electron.Rectangle;
|
private bounds?: Electron.Rectangle;
|
||||||
private item?: DownloadedItem;
|
private item?: DownloadedItem;
|
||||||
private coordinates?: CoordinatesToJsonType;
|
private coordinates?: CoordinatesToJsonType;
|
||||||
@@ -66,19 +66,11 @@ export class DownloadsDropdownMenuView {
|
|||||||
throw new Error('Cannot initialize downloadsDropdownMenuView, missing MainWindow');
|
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);
|
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
|
||||||
|
this.view = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
|
||||||
const preload = getLocalPreload('internalAPI.js');
|
this.view.setBackgroundColor('#00000000');
|
||||||
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,
|
|
||||||
}});
|
|
||||||
performanceMonitor.registerView('DownloadsDropdownMenuView', this.view.webContents);
|
performanceMonitor.registerView('DownloadsDropdownMenuView', this.view.webContents);
|
||||||
this.view.webContents.loadURL('mattermost-desktop://renderer/downloadsDropdownMenu.html');
|
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.item = item;
|
||||||
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
|
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
|
||||||
this.view.setBounds(this.bounds);
|
this.view.setBounds(this.bounds);
|
||||||
MainWindow.get()?.setTopBrowserView(this.view);
|
MainWindow.get()?.contentView.addChildView(this.view);
|
||||||
this.view.webContents.focus();
|
this.view.webContents.focus();
|
||||||
this.updateDownloadsDropdownMenu();
|
this.updateDownloadsDropdownMenu();
|
||||||
};
|
};
|
||||||
|
@@ -38,7 +38,8 @@ jest.mock('electron', () => {
|
|||||||
getAppPath: () => '',
|
getAppPath: () => '',
|
||||||
getPath: jest.fn(() => '/valid/downloads/path'),
|
getPath: jest.fn(() => '/valid/downloads/path'),
|
||||||
},
|
},
|
||||||
BrowserView: jest.fn().mockImplementation(() => ({
|
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||||
|
setBackgroundColor: jest.fn(),
|
||||||
webContents: {
|
webContents: {
|
||||||
loadURL: jest.fn(),
|
loadURL: jest.fn(),
|
||||||
focus: jest.fn(),
|
focus: jest.fn(),
|
||||||
@@ -77,7 +78,7 @@ jest.mock('main/windows/mainWindow', () => ({
|
|||||||
|
|
||||||
describe('main/views/DownloadsDropdownView', () => {
|
describe('main/views/DownloadsDropdownView', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
MainWindow.get.mockReturnValue({addBrowserView: jest.fn(), setTopBrowserView: jest.fn()});
|
MainWindow.get.mockReturnValue({contentView: {addChildView: jest.fn()}});
|
||||||
getDarwinDoNotDisturb.mockReturnValue(false);
|
getDarwinDoNotDisturb.mockReturnValue(false);
|
||||||
});
|
});
|
||||||
describe('getBounds', () => {
|
describe('getBounds', () => {
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import type {IpcMainEvent} from 'electron';
|
import type {IpcMainEvent} from 'electron';
|
||||||
import {BrowserView, ipcMain} from 'electron';
|
import {WebContentsView, ipcMain} from 'electron';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CLOSE_DOWNLOADS_DROPDOWN,
|
CLOSE_DOWNLOADS_DROPDOWN,
|
||||||
@@ -33,7 +33,7 @@ export class DownloadsDropdownView {
|
|||||||
private bounds?: Electron.Rectangle;
|
private bounds?: Electron.Rectangle;
|
||||||
private windowBounds?: Electron.Rectangle;
|
private windowBounds?: Electron.Rectangle;
|
||||||
private item?: DownloadedItem;
|
private item?: DownloadedItem;
|
||||||
private view?: BrowserView;
|
private view?: WebContentsView;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
|
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
|
||||||
@@ -55,20 +55,11 @@ export class DownloadsDropdownView {
|
|||||||
throw new Error('Cannot initialize, no main window');
|
throw new Error('Cannot initialize, no main window');
|
||||||
}
|
}
|
||||||
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_FULL_WIDTH, DOWNLOADS_DROPDOWN_HEIGHT);
|
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_FULL_WIDTH, DOWNLOADS_DROPDOWN_HEIGHT);
|
||||||
|
this.view = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
|
||||||
const preload = getLocalPreload('internalAPI.js');
|
this.view.setBackgroundColor('#00000000');
|
||||||
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,
|
|
||||||
}});
|
|
||||||
|
|
||||||
performanceMonitor.registerView('DownloadsDropdownView', this.view.webContents);
|
performanceMonitor.registerView('DownloadsDropdownView', this.view.webContents);
|
||||||
this.view.webContents.loadURL('mattermost-desktop://renderer/downloadsDropdown.html');
|
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);
|
this.view.setBounds(this.bounds);
|
||||||
MainWindow.get()?.setTopBrowserView(this.view);
|
MainWindow.get()?.contentView.addChildView(this.view);
|
||||||
this.view.webContents.focus();
|
this.view.webContents.focus();
|
||||||
downloadsManager.onOpen();
|
downloadsManager.onOpen();
|
||||||
MainWindow.sendToRenderer(OPEN_DOWNLOADS_DROPDOWN);
|
MainWindow.sendToRenderer(OPEN_DOWNLOADS_DROPDOWN);
|
||||||
|
@@ -21,9 +21,10 @@ jest.mock('main/windows/mainWindow', () => ({
|
|||||||
describe('main/views/loadingScreen', () => {
|
describe('main/views/loadingScreen', () => {
|
||||||
describe('show', () => {
|
describe('show', () => {
|
||||||
const mainWindow = {
|
const mainWindow = {
|
||||||
getBrowserViews: jest.fn(),
|
contentView: {
|
||||||
setTopBrowserView: jest.fn(),
|
addChildView: jest.fn(),
|
||||||
addBrowserView: jest.fn(),
|
children: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const loadingScreen = new LoadingScreen();
|
const loadingScreen = new LoadingScreen();
|
||||||
loadingScreen.create = jest.fn();
|
loadingScreen.create = jest.fn();
|
||||||
@@ -31,7 +32,7 @@ describe('main/views/loadingScreen', () => {
|
|||||||
const view = {webContents: {send: jest.fn(), isLoading: () => false}};
|
const view = {webContents: {send: jest.fn(), isLoading: () => false}};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mainWindow.getBrowserViews.mockImplementation(() => []);
|
mainWindow.contentView.children = [];
|
||||||
MainWindow.get.mockReturnValue(mainWindow);
|
MainWindow.get.mockReturnValue(mainWindow);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -46,14 +47,14 @@ describe('main/views/loadingScreen', () => {
|
|||||||
});
|
});
|
||||||
loadingScreen.show();
|
loadingScreen.show();
|
||||||
expect(loadingScreen.create).toHaveBeenCalled();
|
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', () => {
|
it('should set the browser view as top if already exists and needs to be shown', () => {
|
||||||
loadingScreen.view = view;
|
loadingScreen.view = view;
|
||||||
mainWindow.getBrowserViews.mockImplementation(() => [view]);
|
mainWindow.contentView.children = [view];
|
||||||
loadingScreen.show();
|
loadingScreen.show();
|
||||||
expect(mainWindow.setTopBrowserView).toHaveBeenCalled();
|
expect(mainWindow.contentView.addChildView).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// 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 {DARK_MODE_CHANGE, LOADING_SCREEN_ANIMATION_FINISHED, MAIN_WINDOW_RESIZED, TOGGLE_LOADING_SCREEN_VISIBILITY} from 'common/communication';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
@@ -18,7 +18,7 @@ enum LoadingScreenState {
|
|||||||
const log = new Logger('LoadingScreen');
|
const log = new Logger('LoadingScreen');
|
||||||
|
|
||||||
export class LoadingScreen {
|
export class LoadingScreen {
|
||||||
private view?: BrowserView;
|
private view?: WebContentsView;
|
||||||
private state: LoadingScreenState;
|
private state: LoadingScreenState;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -55,15 +55,11 @@ export class LoadingScreen {
|
|||||||
if (this.view?.webContents.isLoading()) {
|
if (this.view?.webContents.isLoading()) {
|
||||||
this.view.webContents.once('did-finish-load', () => {
|
this.view.webContents.once('did-finish-load', () => {
|
||||||
this.view!.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true);
|
this.view!.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true);
|
||||||
|
mainWindow.contentView.addChildView(this.view!);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.view!.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true);
|
this.view!.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true);
|
||||||
}
|
mainWindow.contentView.addChildView(this.view!);
|
||||||
|
|
||||||
if (mainWindow.getBrowserViews().includes(this.view!)) {
|
|
||||||
mainWindow.setTopBrowserView(this.view!);
|
|
||||||
} else {
|
|
||||||
mainWindow.addBrowserView(this.view!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setBounds();
|
this.setBounds();
|
||||||
@@ -77,19 +73,9 @@ export class LoadingScreen {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private create = () => {
|
private create = () => {
|
||||||
const preload = getLocalPreload('internalAPI.js');
|
this.view = new WebContentsView({webPreferences: {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';
|
|
||||||
|
|
||||||
performanceMonitor.registerView('LoadingScreen', this.view.webContents);
|
performanceMonitor.registerView('LoadingScreen', this.view.webContents);
|
||||||
this.view.webContents.loadURL(localURL);
|
this.view.webContents.loadURL('mattermost-desktop://renderer/loadingScreen.html');
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleAnimationFinished = () => {
|
private handleAnimationFinished = () => {
|
||||||
@@ -97,7 +83,9 @@ export class LoadingScreen {
|
|||||||
|
|
||||||
if (this.view && this.state !== LoadingScreenState.HIDDEN) {
|
if (this.view && this.state !== LoadingScreenState.HIDDEN) {
|
||||||
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') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
@@ -6,7 +6,8 @@
|
|||||||
import {ModalView} from './modalView';
|
import {ModalView} from './modalView';
|
||||||
|
|
||||||
jest.mock('electron', () => ({
|
jest.mock('electron', () => ({
|
||||||
BrowserView: jest.fn().mockImplementation(() => ({
|
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||||
|
setBackgroundColor: jest.fn(),
|
||||||
webContents: {
|
webContents: {
|
||||||
loadURL: jest.fn(),
|
loadURL: jest.fn(),
|
||||||
once: jest.fn(),
|
once: jest.fn(),
|
||||||
@@ -34,7 +35,12 @@ jest.mock('main/performanceMonitor', () => ({
|
|||||||
|
|
||||||
describe('main/views/modalView', () => {
|
describe('main/views/modalView', () => {
|
||||||
describe('show', () => {
|
describe('show', () => {
|
||||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn()};
|
const window = {
|
||||||
|
contentView: {
|
||||||
|
addChildView: jest.fn(),
|
||||||
|
removeChildView: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
const onResolve = jest.fn();
|
const onResolve = jest.fn();
|
||||||
const onReject = jest.fn();
|
const onReject = jest.fn();
|
||||||
let modalView;
|
let modalView;
|
||||||
@@ -56,15 +62,15 @@ describe('main/views/modalView', () => {
|
|||||||
|
|
||||||
it('should add to window', () => {
|
it('should add to window', () => {
|
||||||
modalView.show();
|
modalView.show();
|
||||||
expect(window.addBrowserView).toBeCalledWith(modalView.view);
|
expect(window.contentView.addChildView).toBeCalledWith(modalView.view);
|
||||||
expect(modalView.status).toBe(1);
|
expect(modalView.status).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reattach if already attached', () => {
|
it('should reattach if already attached', () => {
|
||||||
modalView.windowAttached = window;
|
modalView.windowAttached = window;
|
||||||
modalView.show();
|
modalView.show();
|
||||||
expect(window.removeBrowserView).toBeCalledWith(modalView.view);
|
expect(window.contentView.removeChildView).toBeCalledWith(modalView.view);
|
||||||
expect(window.addBrowserView).toBeCalledWith(modalView.view);
|
expect(window.contentView.addChildView).toBeCalledWith(modalView.view);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delay call to focus when the modal is loading', () => {
|
it('should delay call to focus when the modal is loading', () => {
|
||||||
@@ -87,7 +93,12 @@ describe('main/views/modalView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('hide', () => {
|
describe('hide', () => {
|
||||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn()};
|
const window = {
|
||||||
|
contentView: {
|
||||||
|
addChildView: jest.fn(),
|
||||||
|
removeChildView: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
const onResolve = jest.fn();
|
const onResolve = jest.fn();
|
||||||
const onReject = jest.fn();
|
const onReject = jest.fn();
|
||||||
let modalView;
|
let modalView;
|
||||||
@@ -111,7 +122,7 @@ describe('main/views/modalView', () => {
|
|||||||
it('should remove browser view and destroy web contents on hide', () => {
|
it('should remove browser view and destroy web contents on hide', () => {
|
||||||
modalView.hide();
|
modalView.hide();
|
||||||
expect(modalView.view.webContents.close).toBeCalled();
|
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', () => {
|
it('should close dev tools when open', () => {
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import type {BrowserWindow} from 'electron';
|
import type {BrowserWindow} from 'electron';
|
||||||
import {BrowserView} from 'electron';
|
import {WebContentsView} from 'electron';
|
||||||
|
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
import performanceMonitor from 'main/performanceMonitor';
|
import performanceMonitor from 'main/performanceMonitor';
|
||||||
@@ -20,7 +20,7 @@ export class ModalView<T, T2> {
|
|||||||
key: string;
|
key: string;
|
||||||
html: string;
|
html: string;
|
||||||
data: T;
|
data: T;
|
||||||
view: BrowserView;
|
view: WebContentsView;
|
||||||
onReject: (value: T2) => void;
|
onReject: (value: T2) => void;
|
||||||
onResolve: (value: T2) => void;
|
onResolve: (value: T2) => void;
|
||||||
window: BrowserWindow;
|
window: BrowserWindow;
|
||||||
@@ -36,14 +36,8 @@ export class ModalView<T, T2> {
|
|||||||
this.data = data;
|
this.data = data;
|
||||||
this.log = new Logger('ModalView', key);
|
this.log = new Logger('ModalView', key);
|
||||||
this.log.info(`preloading with ${preload}`);
|
this.log.info(`preloading with ${preload}`);
|
||||||
this.view = new BrowserView({webPreferences: {
|
this.view = new WebContentsView({webPreferences: {preload}});
|
||||||
preload,
|
this.view.setBackgroundColor('#00000000');
|
||||||
|
|
||||||
// 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.onReject = onReject;
|
this.onReject = onReject;
|
||||||
this.onResolve = onResolve;
|
this.onResolve = onResolve;
|
||||||
this.window = currentWindow;
|
this.window = currentWindow;
|
||||||
@@ -64,11 +58,11 @@ export class ModalView<T, T2> {
|
|||||||
show = (win?: BrowserWindow, withDevTools?: boolean) => {
|
show = (win?: BrowserWindow, withDevTools?: boolean) => {
|
||||||
if (this.windowAttached) {
|
if (this.windowAttached) {
|
||||||
// we'll reatach
|
// we'll reatach
|
||||||
this.windowAttached.removeBrowserView(this.view);
|
this.windowAttached.contentView.removeChildView(this.view);
|
||||||
}
|
}
|
||||||
this.windowAttached = win || this.window;
|
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
|
// Linux sometimes doesn't have the bound initialized correctly initially, so we wait to set them
|
||||||
const setBoundsFunction = () => {
|
const setBoundsFunction = () => {
|
||||||
@@ -100,8 +94,8 @@ export class ModalView<T, T2> {
|
|||||||
if (this.view.webContents.isDevToolsOpened()) {
|
if (this.view.webContents.isDevToolsOpened()) {
|
||||||
this.view.webContents.closeDevTools();
|
this.view.webContents.closeDevTools();
|
||||||
}
|
}
|
||||||
this.windowAttached.removeBrowserView(this.view);
|
|
||||||
performanceMonitor.unregisterView(this.view.webContents.id);
|
performanceMonitor.unregisterView(this.view.webContents.id);
|
||||||
|
this.windowAttached.contentView.removeChildView(this.view);
|
||||||
this.view.webContents.close();
|
this.view.webContents.close();
|
||||||
|
|
||||||
delete this.windowAttached;
|
delete this.windowAttached;
|
||||||
|
@@ -15,7 +15,7 @@ jest.mock('main/utils', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('electron', () => ({
|
jest.mock('electron', () => ({
|
||||||
BrowserView: jest.fn().mockImplementation(() => ({
|
WebContentsView: jest.fn().mockImplementation(() => ({
|
||||||
webContents: {
|
webContents: {
|
||||||
loadURL: jest.fn(),
|
loadURL: jest.fn(),
|
||||||
focus: jest.fn(),
|
focus: jest.fn(),
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import type {IpcMainEvent} from 'electron';
|
import type {IpcMainEvent} from 'electron';
|
||||||
import {BrowserView, ipcMain} from 'electron';
|
import {WebContentsView, ipcMain} from 'electron';
|
||||||
|
|
||||||
import ServerViewState from 'app/serverViewState';
|
import ServerViewState from 'app/serverViewState';
|
||||||
import AppState from 'common/appState';
|
import AppState from 'common/appState';
|
||||||
@@ -32,7 +32,7 @@ import MainWindow from '../windows/mainWindow';
|
|||||||
const log = new Logger('ServerDropdownView');
|
const log = new Logger('ServerDropdownView');
|
||||||
|
|
||||||
export class ServerDropdownView {
|
export class ServerDropdownView {
|
||||||
private view?: BrowserView;
|
private view?: WebContentsView;
|
||||||
private servers: UniqueServer[];
|
private servers: UniqueServer[];
|
||||||
private hasGPOServers: boolean;
|
private hasGPOServers: boolean;
|
||||||
private isOpen: boolean;
|
private isOpen: boolean;
|
||||||
@@ -75,22 +75,15 @@ export class ServerDropdownView {
|
|||||||
|
|
||||||
private init = () => {
|
private init = () => {
|
||||||
log.info('init');
|
log.info('init');
|
||||||
const preload = getLocalPreload('internalAPI.js');
|
this.view = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
|
||||||
this.view = new BrowserView({webPreferences: {
|
this.view.setBackgroundColor('#00000000');
|
||||||
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,
|
|
||||||
}});
|
|
||||||
performanceMonitor.registerView('ServerDropdownView', this.view.webContents);
|
performanceMonitor.registerView('ServerDropdownView', this.view.webContents);
|
||||||
this.view.webContents.loadURL('mattermost-desktop://renderer/dropdown.html');
|
this.view.webContents.loadURL('mattermost-desktop://renderer/dropdown.html');
|
||||||
|
|
||||||
this.setOrderedServers();
|
this.setOrderedServers();
|
||||||
this.windowBounds = MainWindow.getBounds();
|
this.windowBounds = MainWindow.getBounds();
|
||||||
this.updateDropdown();
|
this.updateDropdown();
|
||||||
MainWindow.get()?.addBrowserView(this.view);
|
MainWindow.get()?.contentView.addChildView(this.view);
|
||||||
};
|
};
|
||||||
|
|
||||||
private updateDropdown = () => {
|
private updateDropdown = () => {
|
||||||
@@ -138,7 +131,7 @@ export class ServerDropdownView {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.view.setBounds(this.bounds);
|
this.view.setBounds(this.bounds);
|
||||||
MainWindow.get()?.setTopBrowserView(this.view);
|
MainWindow.get()?.contentView.addChildView(this.view);
|
||||||
this.view.webContents.focus();
|
this.view.webContents.focus();
|
||||||
MainWindow.sendToRenderer(OPEN_SERVERS_DROPDOWN);
|
MainWindow.sendToRenderer(OPEN_SERVERS_DROPDOWN);
|
||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
|
@@ -12,7 +12,7 @@ import PermissionsManager from 'main/permissionsManager';
|
|||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
|
||||||
import LoadingScreen from './loadingScreen';
|
import LoadingScreen from './loadingScreen';
|
||||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
import {MattermostWebContentsView} from './MattermostWebContentsView';
|
||||||
import {ViewManager} from './viewManager';
|
import {ViewManager} from './viewManager';
|
||||||
|
|
||||||
jest.mock('electron', () => ({
|
jest.mock('electron', () => ({
|
||||||
@@ -112,8 +112,8 @@ jest.mock('common/servers/serverManager', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('./MattermostBrowserView', () => ({
|
jest.mock('./MattermostWebContentsView', () => ({
|
||||||
MattermostBrowserView: jest.fn(),
|
MattermostWebContentsView: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('./modalManager', () => ({
|
jest.mock('./modalManager', () => ({
|
||||||
@@ -133,7 +133,7 @@ describe('main/views/viewManager', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
viewManager.showById = jest.fn();
|
viewManager.showById = jest.fn();
|
||||||
MainWindow.get.mockReturnValue({});
|
MainWindow.get.mockReturnValue({});
|
||||||
MattermostBrowserView.mockImplementation((view) => ({
|
MattermostWebContentsView.mockImplementation((view) => ({
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
load: loadFn,
|
load: loadFn,
|
||||||
once: onceFn,
|
once: onceFn,
|
||||||
@@ -181,7 +181,7 @@ describe('main/views/viewManager', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
viewManager.showById = jest.fn();
|
viewManager.showById = jest.fn();
|
||||||
MainWindow.get.mockReturnValue({});
|
MainWindow.get.mockReturnValue({});
|
||||||
MattermostBrowserView.mockImplementation((view) => ({
|
MattermostWebContentsView.mockImplementation((view) => ({
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
load: jest.fn(),
|
load: jest.fn(),
|
||||||
once: jest.fn(),
|
once: jest.fn(),
|
||||||
@@ -235,7 +235,7 @@ describe('main/views/viewManager', () => {
|
|||||||
const onceFn = jest.fn();
|
const onceFn = jest.fn();
|
||||||
const loadFn = jest.fn();
|
const loadFn = jest.fn();
|
||||||
const destroyFn = jest.fn();
|
const destroyFn = jest.fn();
|
||||||
MattermostBrowserView.mockImplementation((view) => ({
|
MattermostWebContentsView.mockImplementation((view) => ({
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
load: loadFn,
|
load: loadFn,
|
||||||
once: onceFn,
|
once: onceFn,
|
||||||
@@ -255,7 +255,7 @@ describe('main/views/viewManager', () => {
|
|||||||
|
|
||||||
it('should recycle existing views', () => {
|
it('should recycle existing views', () => {
|
||||||
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
||||||
const view = new MattermostBrowserView({
|
const view = new MattermostWebContentsView({
|
||||||
id: 'view1',
|
id: 'view1',
|
||||||
server: {
|
server: {
|
||||||
id: 'server1',
|
id: 'server1',
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import type {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
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 isDev from 'electron-is-dev';
|
||||||
|
|
||||||
import ServerViewState from 'app/serverViewState';
|
import ServerViewState from 'app/serverViewState';
|
||||||
@@ -51,7 +51,7 @@ import MainWindow from 'main/windows/mainWindow';
|
|||||||
import type {DeveloperSettings} from 'types/settings';
|
import type {DeveloperSettings} from 'types/settings';
|
||||||
|
|
||||||
import LoadingScreen from './loadingScreen';
|
import LoadingScreen from './loadingScreen';
|
||||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
import {MattermostWebContentsView} from './MattermostWebContentsView';
|
||||||
import modalManager from './modalManager';
|
import modalManager from './modalManager';
|
||||||
|
|
||||||
import {getLocalPreload, getAdjustedWindowBoundaries} from '../utils';
|
import {getLocalPreload, getAdjustedWindowBoundaries} from '../utils';
|
||||||
@@ -62,7 +62,7 @@ const URL_VIEW_HEIGHT = 20;
|
|||||||
|
|
||||||
export class ViewManager {
|
export class ViewManager {
|
||||||
private closedViews: Map<string, {srv: MattermostServer; view: MattermostView}>;
|
private closedViews: Map<string, {srv: MattermostServer; view: MattermostView}>;
|
||||||
private views: Map<string, MattermostBrowserView>;
|
private views: Map<string, MattermostWebContentsView>;
|
||||||
private currentView?: string;
|
private currentView?: string;
|
||||||
|
|
||||||
private urlViewCancel?: () => void;
|
private urlViewCancel?: () => void;
|
||||||
@@ -206,23 +206,23 @@ export class ViewManager {
|
|||||||
if (this.closedViews.has(view.id)) {
|
if (this.closedViews.has(view.id)) {
|
||||||
this.openClosedView(view.id, urlWithSchema);
|
this.openClosedView(view.id, urlWithSchema);
|
||||||
} else {
|
} else {
|
||||||
const browserView = this.views.get(view.id);
|
const webContentsView = this.views.get(view.id);
|
||||||
if (!browserView) {
|
if (!webContentsView) {
|
||||||
log.error(`Couldn't find a view matching the id ${view.id}`);
|
log.error(`Couldn't find a view matching the id ${view.id}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (browserView.isReady() && ServerManager.getRemoteInfo(browserView.view.server.id)?.serverVersion && Utils.isVersionGreaterThanOrEqualTo(ServerManager.getRemoteInfo(browserView.view.server.id)?.serverVersion ?? '', '6.0.0')) {
|
if (webContentsView.isReady() && ServerManager.getRemoteInfo(webContentsView.view.server.id)?.serverVersion && Utils.isVersionGreaterThanOrEqualTo(ServerManager.getRemoteInfo(webContentsView.view.server.id)?.serverVersion ?? '', '6.0.0')) {
|
||||||
const formattedServerURL = `${browserView.view.server.url.origin}${getFormattedPathName(browserView.view.server.url.pathname)}`;
|
const formattedServerURL = `${webContentsView.view.server.url.origin}${getFormattedPathName(webContentsView.view.server.url.pathname)}`;
|
||||||
const pathName = `/${urlWithSchema.replace(formattedServerURL, '')}`;
|
const pathName = `/${urlWithSchema.replace(formattedServerURL, '')}`;
|
||||||
browserView.sendToRenderer(BROWSER_HISTORY_PUSH, pathName);
|
webContentsView.sendToRenderer(BROWSER_HISTORY_PUSH, pathName);
|
||||||
this.deeplinkSuccess(browserView.id);
|
this.deeplinkSuccess(webContentsView.id);
|
||||||
} else {
|
} else {
|
||||||
// attempting to change parsedURL protocol results in it not being modified.
|
// attempting to change parsedURL protocol results in it not being modified.
|
||||||
browserView.resetLoadingStatus();
|
webContentsView.resetLoadingStatus();
|
||||||
browserView.load(urlWithSchema);
|
webContentsView.load(urlWithSchema);
|
||||||
browserView.once(LOAD_SUCCESS, this.deeplinkSuccess);
|
webContentsView.once(LOAD_SUCCESS, this.deeplinkSuccess);
|
||||||
browserView.once(LOAD_FAILED, this.deeplinkFailed);
|
webContentsView.once(LOAD_FAILED, this.deeplinkFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -260,26 +260,26 @@ export class ViewManager {
|
|||||||
this.closedViews.set(view.id, {srv, view});
|
this.closedViews.set(view.id, {srv, view});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const browserView = this.makeView(srv, view, url);
|
const webContentsView = this.makeView(srv, view, url);
|
||||||
this.addView(browserView);
|
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();
|
const mainWindow = MainWindow.get();
|
||||||
if (!mainWindow) {
|
if (!mainWindow) {
|
||||||
throw new Error('Cannot create view, no main window present');
|
throw new Error('Cannot create view, no main window present');
|
||||||
}
|
}
|
||||||
|
|
||||||
const browserView = new MattermostBrowserView(view, {webPreferences: {spellcheck: Config.useSpellChecker}});
|
const webContentsView = new MattermostWebContentsView(view, {webPreferences: {spellcheck: Config.useSpellChecker}});
|
||||||
browserView.once(LOAD_SUCCESS, this.activateView);
|
webContentsView.once(LOAD_SUCCESS, this.activateView);
|
||||||
browserView.on(LOADSCREEN_END, this.finishLoading);
|
webContentsView.on(LOADSCREEN_END, this.finishLoading);
|
||||||
browserView.on(LOAD_FAILED, this.failLoading);
|
webContentsView.on(LOAD_FAILED, this.failLoading);
|
||||||
browserView.on(UPDATE_TARGET_URL, this.showURLView);
|
webContentsView.on(UPDATE_TARGET_URL, this.showURLView);
|
||||||
browserView.load(url);
|
webContentsView.load(url);
|
||||||
return browserView;
|
return webContentsView;
|
||||||
};
|
};
|
||||||
|
|
||||||
private addView = (view: MattermostBrowserView): void => {
|
private addView = (view: MattermostWebContentsView): void => {
|
||||||
this.views.set(view.id, view);
|
this.views.set(view.id, view);
|
||||||
|
|
||||||
// Force a permission check for notifications
|
// Force a permission check for notifications
|
||||||
@@ -355,26 +355,18 @@ export class ViewManager {
|
|||||||
}
|
}
|
||||||
if (url && url !== '') {
|
if (url && url !== '') {
|
||||||
const urlString = typeof url === 'string' ? url : url.toString();
|
const urlString = typeof url === 'string' ? url : url.toString();
|
||||||
const preload = getLocalPreload('internalAPI.js');
|
const urlView = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
|
||||||
const urlView = new BrowserView({
|
urlView.setBackgroundColor('#00000000');
|
||||||
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/urlView.html?url=${encodeURIComponent(urlString)}`;
|
const localURL = `mattermost-desktop://renderer/urlView.html?url=${encodeURIComponent(urlString)}`;
|
||||||
performanceMonitor.registerView('URLView', urlView.webContents);
|
performanceMonitor.registerView('URLView', urlView.webContents);
|
||||||
urlView.webContents.loadURL(localURL);
|
urlView.webContents.loadURL(localURL);
|
||||||
MainWindow.get()?.addBrowserView(urlView);
|
MainWindow.get()?.contentView.addChildView(urlView);
|
||||||
const boundaries = this.views.get(this.currentView || '')?.getBounds() ?? MainWindow.getBounds();
|
const boundaries = this.views.get(this.currentView || '')?.getBounds() ?? MainWindow.getBounds();
|
||||||
|
|
||||||
const hideView = () => {
|
const hideView = () => {
|
||||||
delete this.urlViewCancel;
|
delete this.urlViewCancel;
|
||||||
try {
|
try {
|
||||||
mainWindow.removeBrowserView(urlView);
|
mainWindow.contentView.removeChildView(urlView);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('Failed to remove URL view', 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 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()) {
|
for (const view of this.views.values()) {
|
||||||
current.set(view.view.id, view);
|
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 closed: Map<string, {srv: MattermostServer; view: MattermostView}> = new Map();
|
||||||
|
|
||||||
const sortedViews = ServerManager.getAllServers().flatMap((x) => ServerManager.getOrderedTabsForServer(x.id).
|
const sortedViews = ServerManager.getAllServers().flatMap((x) => ServerManager.getOrderedTabsForServer(x.id).
|
||||||
@@ -622,10 +614,10 @@ export class ViewManager {
|
|||||||
this.closedViews.delete(view.id);
|
this.closedViews.delete(view.id);
|
||||||
}
|
}
|
||||||
this.showById(id);
|
this.showById(id);
|
||||||
const browserView = this.views.get(id)!;
|
const webContentsView = this.views.get(id)!;
|
||||||
browserView.isVisible = true;
|
webContentsView.isVisible = true;
|
||||||
browserView.on(LOAD_SUCCESS, () => {
|
webContentsView.on(LOAD_SUCCESS, () => {
|
||||||
browserView.isVisible = false;
|
webContentsView.isVisible = false;
|
||||||
this.showById(id);
|
this.showById(id);
|
||||||
});
|
});
|
||||||
ipcMain.emit(OPEN_VIEW, null, view.id);
|
ipcMain.emit(OPEN_VIEW, null, view.id);
|
||||||
|
@@ -35,7 +35,7 @@ import {
|
|||||||
openScreensharePermissionsSettingsMacOS,
|
openScreensharePermissionsSettingsMacOS,
|
||||||
resetScreensharePermissionsMacOS,
|
resetScreensharePermissionsMacOS,
|
||||||
} from 'main/utils';
|
} from 'main/utils';
|
||||||
import type {MattermostBrowserView} from 'main/views/MattermostBrowserView';
|
import type {MattermostWebContentsView} from 'main/views/MattermostWebContentsView';
|
||||||
import ViewManager from 'main/views/viewManager';
|
import ViewManager from 'main/views/viewManager';
|
||||||
import webContentsEventManager from 'main/views/webContentEvents';
|
import webContentsEventManager from 'main/views/webContentEvents';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
@@ -51,7 +51,7 @@ const log = new Logger('CallsWidgetWindow');
|
|||||||
|
|
||||||
export class CallsWidgetWindow {
|
export class CallsWidgetWindow {
|
||||||
private win?: BrowserWindow;
|
private win?: BrowserWindow;
|
||||||
private mainView?: MattermostBrowserView;
|
private mainView?: MattermostWebContentsView;
|
||||||
private options?: CallsWidgetWindowConfig;
|
private options?: CallsWidgetWindowConfig;
|
||||||
private missingScreensharePermissions?: boolean;
|
private missingScreensharePermissions?: boolean;
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ export class CallsWidgetWindow {
|
|||||||
return u.toString();
|
return u.toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
private init = (view: MattermostBrowserView, options: CallsWidgetWindowConfig) => {
|
private init = (view: MattermostWebContentsView, options: CallsWidgetWindowConfig) => {
|
||||||
this.win = new BrowserWindow({
|
this.win = new BrowserWindow({
|
||||||
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
@@ -96,6 +96,9 @@ describe('main/windows/mainWindow', () => {
|
|||||||
send: jest.fn(),
|
send: jest.fn(),
|
||||||
setWindowOpenHandler: jest.fn(),
|
setWindowOpenHandler: jest.fn(),
|
||||||
},
|
},
|
||||||
|
contentView: {
|
||||||
|
on: jest.fn(),
|
||||||
|
},
|
||||||
isMaximized: jest.fn(),
|
isMaximized: jest.fn(),
|
||||||
isFullScreen: jest.fn(),
|
isFullScreen: jest.fn(),
|
||||||
getBounds: jest.fn(),
|
getBounds: jest.fn(),
|
||||||
|
@@ -21,7 +21,6 @@ import {
|
|||||||
MAIN_WINDOW_CREATED,
|
MAIN_WINDOW_CREATED,
|
||||||
MAIN_WINDOW_RESIZED,
|
MAIN_WINDOW_RESIZED,
|
||||||
MAIN_WINDOW_FOCUSED,
|
MAIN_WINDOW_FOCUSED,
|
||||||
VIEW_FINISHED_RESIZING,
|
|
||||||
TOGGLE_SECURE_INPUT,
|
TOGGLE_SECURE_INPUT,
|
||||||
EMIT_CONFIGURATION,
|
EMIT_CONFIGURATION,
|
||||||
EXIT_FULLSCREEN,
|
EXIT_FULLSCREEN,
|
||||||
@@ -49,18 +48,14 @@ export class MainWindow extends EventEmitter {
|
|||||||
|
|
||||||
private savedWindowState?: Partial<SavedWindowState>;
|
private savedWindowState?: Partial<SavedWindowState>;
|
||||||
private ready: boolean;
|
private ready: boolean;
|
||||||
private isResizing: boolean;
|
|
||||||
private lastEmittedBounds?: Electron.Rectangle;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
this.isResizing = false;
|
|
||||||
|
|
||||||
ipcMain.handle(GET_FULL_SCREEN_STATUS, () => this.win?.isFullScreen());
|
ipcMain.handle(GET_FULL_SCREEN_STATUS, () => this.win?.isFullScreen());
|
||||||
ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing);
|
|
||||||
ipcMain.on(EMIT_CONFIGURATION, this.handleUpdateTitleBarOverlay);
|
ipcMain.on(EMIT_CONFIGURATION, this.handleUpdateTitleBarOverlay);
|
||||||
ipcMain.on(EXIT_FULLSCREEN, this.handleExitFullScreen);
|
ipcMain.on(EXIT_FULLSCREEN, this.handleExitFullScreen);
|
||||||
|
|
||||||
@@ -85,7 +80,7 @@ export class MainWindow extends EventEmitter {
|
|||||||
titleBarStyle: 'hidden' as const,
|
titleBarStyle: 'hidden' as const,
|
||||||
titleBarOverlay: this.getTitleBarOverlay(),
|
titleBarOverlay: this.getTitleBarOverlay(),
|
||||||
trafficLightPosition: {x: 12, y: 12},
|
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: {
|
webPreferences: {
|
||||||
disableBlinkFeatures: 'Auxclick',
|
disableBlinkFeatures: 'Auxclick',
|
||||||
preload: getLocalPreload('internalAPI.js'),
|
preload: getLocalPreload('internalAPI.js'),
|
||||||
@@ -131,16 +126,7 @@ export class MainWindow extends EventEmitter {
|
|||||||
this.win.on('unresponsive', this.onUnresponsive);
|
this.win.on('unresponsive', this.onUnresponsive);
|
||||||
this.win.on('enter-full-screen', this.onEnterFullScreen);
|
this.win.on('enter-full-screen', this.onEnterFullScreen);
|
||||||
this.win.on('leave-full-screen', this.onLeaveFullScreen);
|
this.win.on('leave-full-screen', this.onLeaveFullScreen);
|
||||||
this.win.on('will-resize', this.onWillResize);
|
this.win.contentView.on('bounds-changed', this.handleBoundsChanged);
|
||||||
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.webContents.on('before-input-event', this.onBeforeInputEvent);
|
this.win.webContents.on('before-input-event', this.onBeforeInputEvent);
|
||||||
|
|
||||||
// Should not allow the main window to generate a window of its own
|
// 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 = () => {
|
private onEnterFullScreen = () => {
|
||||||
this.win?.webContents.send('enter-full-screen');
|
this.win?.webContents.send('enter-full-screen');
|
||||||
this.emitBounds();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onLeaveFullScreen = () => {
|
private onLeaveFullScreen = () => {
|
||||||
this.win?.webContents.send('leave-full-screen');
|
this.win?.webContents.send('leave-full-screen');
|
||||||
this.emitBounds();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
private handleBoundsChanged = () => {
|
||||||
* Resizing code
|
this.emit(MAIN_WINDOW_RESIZED, this.win?.contentView.getBounds());
|
||||||
*/
|
|
||||||
|
|
||||||
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 handleExitFullScreen = () => {
|
private handleExitFullScreen = () => {
|
||||||
|
@@ -94,7 +94,7 @@ export default function ErrorView(props: Props) {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<FormattedMessage
|
<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.'
|
defaultMessage='You can reach <link>{url}</link> from a browser window.'
|
||||||
values={{
|
values={{
|
||||||
url: props.url,
|
url: props.url,
|
||||||
|
@@ -36,12 +36,6 @@ function WelcomeScreen({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowContent(true);
|
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(() => [
|
const slides = useMemo(() => [
|
||||||
|
@@ -95,7 +95,6 @@ declare global {
|
|||||||
openWindowsCameraPreferences: () => void;
|
openWindowsCameraPreferences: () => void;
|
||||||
openWindowsMicrophonePreferences: () => void;
|
openWindowsMicrophonePreferences: () => void;
|
||||||
getMediaAccessStatus: (mediaType: 'microphone' | 'camera' | 'screen') => Promise<'not-determined' | 'granted' | 'denied' | 'restricted' | 'unknown'>;
|
getMediaAccessStatus: (mediaType: 'microphone' | 'camera' | 'screen') => Promise<'not-determined' | 'granted' | 'denied' | 'restricted' | 'unknown'>;
|
||||||
viewFinishedResizing: () => void;
|
|
||||||
|
|
||||||
modals: {
|
modals: {
|
||||||
cancelModal: <T>(data?: T) => void;
|
cancelModal: <T>(data?: T) => void;
|
||||||
|
Reference in New Issue
Block a user