Skip to content

Commit

Permalink
refactor: BrowserView -> WebContentsView
Browse files Browse the repository at this point in the history
  • Loading branch information
linonetwo committed Jun 29, 2024
1 parent f0cb565 commit 012076c
Show file tree
Hide file tree
Showing 26 changed files with 181 additions and 179 deletions.
2 changes: 1 addition & 1 deletion src/components/TokenForm/gitTokenHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function useAuth(storageService: SupportedStorageServices): [() => Promis
const onClickLogout = useCallback(async () => {
try {
await window.service.auth.set(`${storageService}-token`, '');
await window.service.window.clearStorageData();
// await window.service.window.clearStorageData();
} catch (error) {
console.error(error);
}
Expand Down
3 changes: 1 addition & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import './helpers/singleInstance';
import './services/database/configSetting';
import { app, ipcMain, powerMonitor, protocol } from 'electron';
import unhandled from 'electron-unhandled';
import fs from 'fs-extra';
import inspector from 'node:inspector';

import { MainChannel } from '@/constants/channels';
Expand Down Expand Up @@ -101,7 +100,7 @@ const commonInit = async (): Promise<void> => {
// https://github.com/atomery/webcatalog/issues/561
// run it here not in mainWindow.createAsync()
// because if the `mainWindow` is maximized or minimized
// before the workspaces's BrowserView fully loaded
// before the workspaces's WebContentsView fully loaded
// error will occur
// see https://github.com/atomery/webcatalog/issues/637
// eslint-disable-next-line promise/always-return
Expand Down
2 changes: 1 addition & 1 deletion src/pages/AddWorkspace/useCallWikiInitialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function callWikiInitialization(
}
// start wiki on startup, or on sub-wiki creation
wikiCreationMessageSetter(t('Log.InitializeWorkspaceView'));
/** create workspace from workspaceService to store workspace configs, and create a BrowserView to actually display wiki web content from viewService */
/** create workspace from workspaceService to store workspace configs, and create a WebContentsView to actually display wiki web content from viewService */
await window.service.workspaceView.initializeWorkspaceView(newWorkspace, { isNew: true, from: configs.from });
wikiCreationMessageSetter(t('Log.InitializeWorkspaceViewDone'));
await window.service.workspaceView.setActiveWorkspaceView(newWorkspace.id);
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Main/useInitialPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useLocation } from 'wouter';

export function useInitialPage() {
const [, setLocation] = useLocation();
// when first open the TidGi and no workspace is active (so no BrowserView will be on top of the React), goto the active pages route
// when first open the TidGi and no workspace is active (so no WebContentsView will be on top of the React), goto the active pages route
const initialActivePage = usePromiseValue(async () => await window.service.pages.getActivePage(), null);
const initialActiveWorkspace = usePromiseValue(async () => await window.service.workspace.getActiveWorkspace(), null);
// only do this once, and not triggering unnecessary rerender by using ref.
Expand Down
2 changes: 1 addition & 1 deletion src/preload/common/services.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/ban-types */
/**
* Provide API from main services to GUI (for example, preference window), and tiddlywiki
* This file should be required by BrowserView's preload script to work
* This file should be required by WebContentsView's preload script to work
*/

import { createProxy } from 'electron-ipc-cat/client';
Expand Down
6 changes: 3 additions & 3 deletions src/services/libs/getFromRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Channels } from '@/constants/channels';
import { BrowserView, BrowserWindow, ipcMain } from 'electron';
import { BrowserWindow, ipcMain, WebContentsView } from 'electron';

/**
* Get data from a BrowserView
* Get data from a WebContentsView
* @param channel
* @param viewToGetData
*/
export default async function getFromRenderer<T>(channel: Channels, viewToGetData: BrowserView | BrowserWindow): Promise<T> {
export default async function getFromRenderer<T>(channel: Channels, viewToGetData: WebContentsView | BrowserWindow): Promise<T> {
// prevent several ipc happened together, and later return too early so first get the result that is for later one
const ipcToken = String(Math.random());
viewToGetData.webContents.send(channel, { ipcToken });
Expand Down
2 changes: 1 addition & 1 deletion src/services/libs/sendToMainWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function sendToMainWindowNoWait(type: WikiChannel, workspaceID: string, m
/**
* Send to main window renderer (preload script) and wait for response.
*
* Will throw error when on Windows and App is at background (BrowserView will disappear and not accessible.) https://github.com/tiddly-gittly/TidGi-Desktop/issues/398
* Will throw error when on Windows and App is at background (WebContentsView will disappear and not accessible.) https://github.com/tiddly-gittly/TidGi-Desktop/issues/398
*
* @param type The handler on renderer (preload script) side should implement `ipcRenderer.send(WikiChannel.xxx, nonceReceived, result);`, where `result` is usually `string[]` (the default type for `<T>` in function signature)
* @returns undefined if main window webContents is not found
Expand Down
4 changes: 2 additions & 2 deletions src/services/pages/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ Guide and Help are two kind of other pages, which are build-in utility pages.

## Developer point of view

When click on a Wiki workspace on sidebar, we switch to `src/pages/WikiBackground` page, and put a BrowserView on top of it (by realign this BrowserView). If BrowserView load url with error, we realign the BrowserView to hide it, and reveal the WikiBackground below of it, show error message on the WikiBackground page.
When click on a Wiki workspace on sidebar, we switch to `src/pages/WikiBackground` page, and put a WebContentsView on top of it (by realign this WebContentsView). If WebContentsView load url with error, we realign the WebContentsView to hide it, and reveal the WikiBackground below of it, show error message on the WikiBackground page.

When click on other pages like Guide page, we realign the BrowserView to hide it, and show the Guide page in the `src/pages/Main/index.tsx`.
When click on other pages like Guide page, we realign the WebContentsView to hide it, and show the Guide page in the `src/pages/Main/index.tsx`.
2 changes: 1 addition & 1 deletion src/services/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class Sync implements ISyncService {
logger.error(
`${(error as Error).message} when checking draft titles. ${
(error as Error).stack ?? ''
}\n This might because it just will throw error when on Windows and App is at background (BrowserView will disappear and not accessible.)`,
}\n This might because it just will throw error when on Windows and App is at background (WebContentsView will disappear and not accessible.)`,
);
// when app is on background, might have no draft, because user won't edit it. So just return true
return true;
Expand Down
4 changes: 2 additions & 2 deletions src/services/view/handleNewWindow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BrowserView, BrowserWindowConstructorOptions, shell } from 'electron';
import { BrowserWindowConstructorOptions, shell, WebContentsView } from 'electron';
import windowStateKeeper from 'electron-window-state';

import { SETTINGS_FOLDER } from '@/constants/appPaths';
Expand All @@ -17,7 +17,7 @@ import { handleOpenFileExternalLink } from './setupViewFileProtocol';
export interface INewWindowContext {
meta: IViewMeta;
sharedWebPreferences: BrowserWindowConstructorOptions['webPreferences'];
view: BrowserView;
view: WebContentsView;
workspace: IWorkspace;
}

Expand Down
106 changes: 52 additions & 54 deletions src/services/view/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable n/no-callback-literal */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/consistent-type-assertions */
import { BrowserView, BrowserWindow, ipcMain, WebPreferences } from 'electron';
import { BrowserWindow, ipcMain, WebContentsView, WebPreferences } from 'electron';
import { injectable } from 'inversify';

import type { IMenuService } from '@services/menu/interface';
Expand Down Expand Up @@ -100,7 +101,7 @@ export class View implements IViewService {
},
},
// same behavior as BrowserWindow with autoHideMenuBar: true
// but with addition to readjust BrowserView so it won't cover the menu bar
// but with addition to readjust WebContentsView so it won't cover the menu bar
{
label: () => i18n.t('Preference.ToggleMenuBar'),
visible: false,
Expand Down Expand Up @@ -134,9 +135,10 @@ export class View implements IViewService {
contents.zoomFactor = 1;
return;
}
// browserWindow above is for the main window's react UI
// modify browser view in the main window
const mainWindow = this.windowService.get(WindowNames.main);
mainWindow?.getBrowserView()?.webContents?.setZoomFactor(1);
const view = await this.getActiveBrowserView();
view?.webContents?.setZoomFactor?.(1);
},
enabled: hasWorkspaces,
},
Expand All @@ -154,10 +156,8 @@ export class View implements IViewService {
return;
}
// modify browser view in the main window
const mainWindow = this.windowService.get(WindowNames.main);
const webContent = mainWindow?.getBrowserView()?.webContents;
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
webContent?.setZoomFactor(webContent.getZoomFactor() + 0.05);
const view = await this.getActiveBrowserView();
view?.webContents?.setZoomFactor?.(view.webContents.getZoomFactor() + 0.05);
},
enabled: hasWorkspaces,
},
Expand All @@ -175,10 +175,8 @@ export class View implements IViewService {
return;
}
// modify browser view in the main window
const mainWindow = this.windowService.get(WindowNames.main);
const webContent = mainWindow?.getBrowserView()?.webContents;
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
webContent?.setZoomFactor(webContent.getZoomFactor() - 0.05);
const view = await this.getActiveBrowserView();
view?.webContents?.setZoomFactor?.(view.webContents.getZoomFactor() - 0.05);
},
enabled: hasWorkspaces,
},
Expand All @@ -196,8 +194,7 @@ export class View implements IViewService {
return;
}
// refresh the main window browser view's wiki content, instead of sidebar's react content
const mainWindow = this.windowService.get(WindowNames.main);
mainWindow?.getBrowserView()?.webContents?.reload();
await this.reloadActiveBrowserView();
},
enabled: hasWorkspaces,
},
Expand All @@ -206,21 +203,21 @@ export class View implements IViewService {
}

/**
* Record<workspaceID, Record<windowName, BrowserView>>
* Record<workspaceID, Record<windowName, WebContentsView>>
*
* Each workspace can have several windows to render its view (main window and menu bar)
*/
private readonly views = new Map<string, Map<WindowNames, BrowserView> | undefined>();
private readonly views = new Map<string, Map<WindowNames, WebContentsView> | undefined>();
public async getViewCount(): Promise<number> {
// eslint-disable-next-line @typescript-eslint/return-await
return await Promise.resolve(Object.keys(this.views).length);
}

public getView(workspaceID: string, windowName: WindowNames): BrowserView | undefined {
public getView(workspaceID: string, windowName: WindowNames): WebContentsView | undefined {
return this.views.get(workspaceID)?.get(windowName);
}

public setView(workspaceID: string, windowName: WindowNames, newView: BrowserView): void {
public setView(workspaceID: string, windowName: WindowNames, newView: WebContentsView): void {
const workspaceOwnedViews = this.views.get(workspaceID);
if (workspaceOwnedViews === undefined) {
this.views.set(workspaceID, new Map([[windowName, newView]]));
Expand Down Expand Up @@ -289,13 +286,13 @@ export class View implements IViewService {
} satisfies WebPreferences;
}

public async createViewAddToWindow(workspace: IWorkspace, browserWindow: BrowserWindow, sharedWebPreferences: WebPreferences, windowName: WindowNames): Promise<BrowserView> {
// create a new BrowserView
const view = new BrowserView({
public async createViewAddToWindow(workspace: IWorkspace, browserWindow: BrowserWindow, sharedWebPreferences: WebPreferences, windowName: WindowNames): Promise<WebContentsView> {
// create a new WebContentsView
const view = new WebContentsView({
webPreferences: sharedWebPreferences,
});
// background needs to explicitly set
// if not, by default, the background of BrowserView is transparent
// if not, by default, the background of WebContentsView is transparent
// which would break the CSS of certain websites
// even with dark mode, all major browsers
// always use #FFF as default page background
Expand All @@ -307,20 +304,21 @@ export class View implements IViewService {
view.webContents.audioMuted = this.shouldMuteAudio;
}
if (workspace.active || windowName === WindowNames.secondary) {
browserWindow.setBrowserView(view);
browserWindow.contentView.addChildView(view);
const contentSize = browserWindow.getContentSize();
view.setBounds(await getViewBounds(contentSize as [number, number], { windowName }));
view.setAutoResize({
width: true,
height: true,
});
const newViewBounds = await getViewBounds(contentSize as [number, number], { windowName });
view.setBounds(newViewBounds);
// view.setAutoResize({
// width: true,
// height: true,
// });
}
return view;
}

public async initializeWorkspaceViewHandlersAndLoad(
browserWindow: BrowserWindow,
view: BrowserView,
view: WebContentsView,
configs: { sharedWebPreferences: WebPreferences; uri?: string; windowName: WindowNames; workspace: IWorkspace },
) {
const { sharedWebPreferences, uri, workspace, windowName } = configs;
Expand All @@ -337,7 +335,7 @@ export class View implements IViewService {
await this.loadUrlForView(workspace, view, uri);
}

public async loadUrlForView(workspace: IWorkspace, view: BrowserView, uri?: string): Promise<void> {
public async loadUrlForView(workspace: IWorkspace, view: WebContentsView, uri?: string): Promise<void> {
const { rememberLastPageVisited } = this.preferenceService.getPreferences();
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/strict-boolean-expressions
const urlToLoad = uri || (rememberLastPageVisited ? workspace.lastUrl : workspace.homeUrl) || workspace.homeUrl || getDefaultTidGiUrl(workspace.id);
Expand Down Expand Up @@ -368,7 +366,7 @@ export class View implements IViewService {
}
}

public forEachView(functionToRun: (view: BrowserView, workspaceID: string, windowName: WindowNames) => unknown): void {
public forEachView(functionToRun: (view: WebContentsView, workspaceID: string, windowName: WindowNames) => unknown): void {
[...this.views.keys()].forEach((workspaceID) => {
const workspaceOwnedViews = this.views.get(workspaceID);
if (workspaceOwnedViews !== undefined) {
Expand Down Expand Up @@ -407,19 +405,16 @@ export class View implements IViewService {
await this.addView(workspace, windowName);
}
} else {
browserWindow.setBrowserView(view);
logger.debug(`setActiveView() setBrowserView`);
browserWindow.contentView.addChildView(view);
logger.debug(`setActiveView() contentView.addChildView`);
const contentSize = browserWindow.getContentSize();
if (workspace !== undefined && (await this.workspaceService.workspaceDidFailLoad(workspace.id))) {
view.setBounds(await getViewBounds(contentSize as [number, number], { findInPage: false, windowName }, 0, 0)); // hide browserView to show error message
} else {
logger.debug(`setActiveView() contentSize ${JSON.stringify(contentSize)}`);
view.setBounds(await getViewBounds(contentSize as [number, number], { windowName }));
const newViewBounds = await getViewBounds(contentSize as [number, number], { windowName });
logger.debug(`setActiveView() contentSize ${JSON.stringify(newViewBounds)}`);
view.setBounds(newViewBounds);
}
view.setAutoResize({
width: true,
height: true,
});
// focus on webview
// https://github.com/quanglam2807/webcatalog/issues/398
view.webContents.focus();
Expand All @@ -436,8 +431,8 @@ export class View implements IViewService {
view.webContents.stopFindInPage('clearSelection');
view.webContents.send(WindowChannel.closeFindInPage);

// don't set activate browserView to null here `browserWindow.setBrowserView(null);`, the "current browser view" may point to other workspace's view now, it will close other workspace's view when switching workspaces.
browserWindow.removeBrowserView(view);
// don't clear contentView here `browserWindow.contentView.children = [];`, the "current contentView" may point to other workspace's view now, it will close other workspace's view when switching workspaces.
browserWindow.contentView.removeChildView(view);
} else {
logger.error(`removeView() view or browserWindow is undefined for workspaceID ${workspaceID} windowName ${windowName}, not destroying view properly.`);
}
Expand Down Expand Up @@ -525,7 +520,7 @@ export class View implements IViewService {
return view.webContents.getURL();
}

public async getActiveBrowserView(): Promise<BrowserView | undefined> {
public async getActiveBrowserView(): Promise<WebContentsView | undefined> {
const workspace = await this.workspaceService.getActiveWorkspace();
if (workspace !== undefined) {
const isMenubarOpen = await this.windowService.isMenubarOpen();
Expand All @@ -537,7 +532,7 @@ export class View implements IViewService {
}
}

public async getActiveBrowserViews(): Promise<Array<BrowserView | undefined>> {
public async getActiveBrowserViews(): Promise<Array<WebContentsView | undefined>> {
const workspace = await this.workspaceService.getActiveWorkspace();
if (workspace !== undefined) {
return [this.getView(workspace.id, WindowNames.main), this.getView(workspace.id, WindowNames.menuBar)];
Expand All @@ -560,15 +555,17 @@ export class View implements IViewService {
}

public async realignActiveView(browserWindow: BrowserWindow, activeId: string, windowName: WindowNames, isRetry?: boolean): Promise<void> {
const view = browserWindow.getBrowserView();
if (view?.webContents !== null && view?.webContents !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
const view = this.getView(activeId, windowName);
if (view?.webContents) {
const contentSize = browserWindow.getContentSize();
if (await this.workspaceService.workspaceDidFailLoad(activeId)) {
logger.warn(`realignActiveView() hide because didFailLoad`);
await this.hideView(browserWindow);
await this.hideView(browserWindow, windowName, activeId);
} else {
logger.debug(`realignActiveView() contentSize set to ${JSON.stringify(contentSize)}`);
view?.setBounds(await getViewBounds(contentSize as [number, number], { windowName }));
const newViewBounds = await getViewBounds(contentSize as [number, number], { windowName });
logger.debug(`realignActiveView() contentSize set to ${JSON.stringify(newViewBounds)}`);
view?.setBounds(newViewBounds);
}
} else if (isRetry === true) {
logger.error(
Expand All @@ -583,20 +580,21 @@ export class View implements IViewService {
}
}

public async hideView(browserWindow: BrowserWindow): Promise<void> {
const view = browserWindow.getBrowserView();
if (view !== null) {
public async hideView(browserWindow: BrowserWindow, windowName: WindowNames, idToDeactivate: string): Promise<void> {
logger.debug('Hide view', { idToDeactivate, windowName });
if (!idToDeactivate) return;
const view = this.getView(idToDeactivate, windowName);
if (view) {
const contentSize = browserWindow.getContentSize();
// disable view features
view?.setAutoResize({ horizontal: false, vertical: false });
view.webContents.stopFindInPage('clearSelection');
view.webContents.send(WindowChannel.closeFindInPage);
// make view small, hide browserView to show error message or other pages
view?.setBounds({
x: -contentSize[0],
y: -contentSize[1],
width: 0,
height: 0,
width: contentSize[0],
height: contentSize[1],
});
}
}
Expand Down
Loading

0 comments on commit 012076c

Please sign in to comment.