MM-62005 Change UrlView to always stay mounted (#3453)

* MM-62005 Change UrlView to always stay mounted

* Rename message for URL view and corresponding methods

* Change log level
This commit is contained in:
Harrison Healey
2025-07-04 09:37:13 -04:00
committed by GitHub
parent 8778edd238
commit 9d26b01ef5
6 changed files with 65 additions and 34 deletions

View File

@@ -100,6 +100,7 @@ export const GET_MODAL_UNCLOSEABLE = 'get-modal-uncloseable';
export const UPDATE_PATHS = 'update-paths';
export const SET_URL_FOR_URL_VIEW = 'set-url-for-url-view';
export const UPDATE_URL_VIEW_WIDTH = 'update-url-view-width';
export const OPEN_SERVER_EXTERNALLY = 'open-server-externally';

View File

@@ -100,6 +100,7 @@ import {
LOAD_INCOMPATIBLE_SERVER,
OPEN_SERVER_UPGRADE_LINK,
OPEN_CHANGELOG_LINK,
SET_URL_FOR_URL_VIEW,
} from 'common/communication';
console.log('Preload initialized');
@@ -188,6 +189,7 @@ contextBridge.exposeInMainWorld('desktop', {
onUpdateDownloadsDropdown: (listener) => ipcRenderer.on(UPDATE_DOWNLOADS_DROPDOWN, (_, downloads, darkMode, windowBounds, item) => listener(downloads, darkMode, windowBounds, item)),
onAppMenuWillClose: (listener) => ipcRenderer.on(APP_MENU_WILL_CLOSE, () => listener()),
onFocusThreeDotMenu: (listener) => ipcRenderer.on(FOCUS_THREE_DOT_MENU, () => listener()),
onSetURLForURLView: (listener) => ipcRenderer.on(SET_URL_FOR_URL_VIEW, (_, url) => listener(url)),
updateURLViewWidth: (width) => ipcRenderer.send(UPDATE_URL_VIEW_WIDTH, width),
openNotificationPreferences: () => ipcRenderer.send(OPEN_NOTIFICATION_PREFERENCES),
openWindowsCameraPreferences: () => ipcRenderer.send(OPEN_WINDOWS_CAMERA_PREFERENCES),

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
import type {IpcMainEvent, IpcMainInvokeEvent, View} from 'electron';
import {WebContentsView, ipcMain, shell} from 'electron';
import isDev from 'electron-is-dev';
@@ -33,6 +33,7 @@ import {
UNREADS_AND_MENTIONS,
TAB_LOGIN_CHANGED,
DEVELOPER_MODE_UPDATED,
SET_URL_FOR_URL_VIEW,
} from 'common/communication';
import Config from 'common/config';
import {DEFAULT_CHANGELOG_LINK} from 'common/constants';
@@ -68,6 +69,7 @@ export class ViewManager {
private views: Map<string, MattermostWebContentsView>;
private currentView?: string;
private urlView?: WebContentsView;
private urlViewCancel?: () => void;
constructor() {
@@ -110,6 +112,8 @@ export class ViewManager {
await this.initServer(currentServer);
this.showInitial();
}
this.initURLView();
};
private initServer = async (server: MattermostServer) => {
@@ -117,6 +121,24 @@ export class ViewManager {
this.loadServer(server);
};
private initURLView = () => {
const mainWindow = MainWindow.get();
if (!mainWindow) {
return;
}
const urlView = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
urlView.setBackgroundColor('#00000000');
urlView.webContents.loadURL('mattermost-desktop://renderer/urlView.html');
MainWindow.get()?.contentView.addChildView(urlView);
performanceMonitor.registerView('URLView', urlView.webContents);
this.urlView = urlView;
};
private handleDeveloperModeUpdated = (json: DeveloperSettings) => {
log.debug('handleDeveloperModeUpdated', json);
@@ -147,6 +169,17 @@ export class ViewManager {
return this.closedViews.has(viewId);
};
private isViewInFront = (view: View) => {
const mainWindow = MainWindow.get();
if (!mainWindow) {
return false;
}
const index = mainWindow.contentView.children.indexOf(view);
const front = mainWindow.contentView.children.length - 1;
return index === front;
};
showById = (viewId: string) => {
this.getViewLogger(viewId).debug('showById', viewId);
@@ -372,35 +405,24 @@ export class ViewManager {
if (this.urlViewCancel) {
this.urlViewCancel();
}
if (url && url !== '') {
const urlString = typeof url === 'string' ? url : url.toString();
const urlView = new WebContentsView({webPreferences: {preload: getLocalPreload('internalAPI.js')}});
urlView.setBackgroundColor('#00000000');
const localURL = `mattermost-desktop://renderer/urlView.html?url=${encodeURIComponent(urlString)}`;
performanceMonitor.registerView('URLView', urlView.webContents);
urlView.webContents.loadURL(localURL);
// This is a workaround for an issue where the URL view would steal focus from the main window
// See: https://github.com/electron/electron/issues/42339
// @ts-expect-error Using an undocumented event that fires last when the URL view pops up
urlView.webContents.once('ready-to-show', () => {
log.debug('URL view focus prevented');
this.getCurrentView()?.focus();
});
if (this.urlView && !this.isViewInFront(this.urlView)) {
log.silly('moving URL view to front');
MainWindow.get()?.contentView.addChildView(this.urlView);
}
this.urlView?.webContents.send(SET_URL_FOR_URL_VIEW, urlString);
this.urlView?.setVisible(true);
MainWindow.get()?.contentView.addChildView(urlView);
const boundaries = this.views.get(this.currentView || '')?.getBounds() ?? MainWindow.getBounds();
const hideView = () => {
delete this.urlViewCancel;
try {
mainWindow.contentView.removeChildView(urlView);
} catch (e) {
log.error('Failed to remove URL view', e);
}
performanceMonitor.unregisterView(urlView.webContents.id);
urlView.webContents.close();
this.urlView?.setVisible(false);
};
const adjustWidth = (event: IpcMainEvent, width: number) => {
@@ -418,7 +440,7 @@ export class ViewManager {
};
log.silly('showURLView.setBounds', boundaries, bounds);
urlView.setBounds(bounds);
this.urlView?.setBounds(bounds);
};
ipcMain.on(UPDATE_URL_VIEW_WIDTH, adjustWidth);

View File

@@ -1,22 +1,32 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect} from 'react';
import React, {useEffect, useRef, useState} from 'react';
export default function UrlDescription(props: {url: string}) {
const urlRef = React.createRef<HTMLDivElement>();
export default function UrlDescription() {
const urlRef = useRef<HTMLDivElement>(null);
const [url, setUrl] = useState<string | undefined>();
useEffect(() => {
window.desktop.updateURLViewWidth(urlRef.current?.scrollWidth);
window.desktop.onSetURLForURLView((newUrl) => {
setUrl(newUrl);
});
}, []);
if (props.url) {
useEffect(() => {
if (url) {
window.desktop.updateURLViewWidth(urlRef.current?.scrollWidth);
}
}, [url]);
if (url) {
return (
<div
ref={urlRef}
className='HoveringURL HoveringURL-left'
>
<p>{props.url}</p>
<p>{url}</p>
</div>
);
}

View File

@@ -3,9 +3,6 @@
import 'renderer/css/components/HoveringURL.css';
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
import React from 'react';
import ReactDOM from 'react-dom';
@@ -13,9 +10,7 @@ import UrlDescription from '../../components/urlDescription';
const start = async () => {
ReactDOM.render(
<UrlDescription
url={decodeURIComponent(urlParams.get('url')!)}
/>,
<UrlDescription/>,
document.getElementById('app'),
);
};

View File

@@ -98,6 +98,7 @@ declare global {
onAppMenuWillClose: (listener: () => void) => void;
onFocusThreeDotMenu: (listener: () => void) => void;
onSetURLForURLView: (listener: (link?: string) => void) => void;
updateURLViewWidth: (width?: number) => void;
openNotificationPreferences: () => void;
openWindowsCameraPreferences: () => void;