electron#BrowserView TypeScript Examples
The following examples show how to use
electron#BrowserView.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: getFromRenderer.ts From TidGi-Desktop with Mozilla Public License 2.0 | 6 votes |
/**
* Get data from a BrowserView
* @param channel
* @param viewToGetData
*/
export default async function getFromRenderer<T>(channel: Channels, viewToGetData: BrowserView | 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 = uuid();
viewToGetData.webContents.send(channel, { ipcToken });
return await new Promise((resolve) => {
ipcMain.once(`${channel}-${ipcToken}`, (_event, data: T) => resolve(data));
});
}
Example #2
Source File: index.ts From TidGi-Desktop with Mozilla Public License 2.0 | 6 votes |
public forEachView(functionToRun: (view: BrowserView, workspaceID: string, windowName: WindowNames) => unknown): void {
Object.keys(this.views).forEach((id) => {
const workspaceOwnedViews = this.views[id];
if (workspaceOwnedViews !== undefined) {
(Object.keys(workspaceOwnedViews) as WindowNames[]).forEach((name) => {
const view = this.getView(id, name);
if (view !== undefined) {
functionToRun(view, id, name);
}
});
}
});
}
Example #3
Source File: index.ts From TidGi-Desktop with Mozilla Public License 2.0 | 6 votes |
public async getActiveBrowserView(): Promise<BrowserView | undefined> {
const workspace = await this.workspaceService.getActiveWorkspace();
const isMenubarOpen = await this.windowService.isMenubarOpen();
if (workspace !== undefined) {
if (isMenubarOpen) {
return this.getView(workspace.id, WindowNames.menuBar);
} else {
return this.getView(workspace.id, WindowNames.main);
}
}
}
Example #4
Source File: index.ts From TidGi-Desktop with Mozilla Public License 2.0 | 6 votes |
public async setWikiLanguage(view: BrowserView, workspaceID: string, tiddlywikiLanguageName: string): Promise<void> {
const twLanguageUpdateTimeout = 15_000;
const retryTime = 2000;
return await new Promise<void>((resolve, reject) => {
const onRetryOrDo = (): void => {
view.webContents.send(WikiChannel.setTiddlerText, '$:/language', tiddlywikiLanguageName, workspaceID);
};
const intervalHandle = setInterval(onRetryOrDo, retryTime);
const onTimeout = (): void => {
ipcMain.removeListener(WikiChannel.setTiddlerTextDone + workspaceID, onDone);
clearInterval(intervalHandle);
const errorMessage = `setWikiLanguage("${tiddlywikiLanguageName}"), language "${tiddlywikiLanguageName}" in workspaceID ${workspaceID} is too slow to update after ${twLanguageUpdateTimeout}ms.`;
logger.error(errorMessage);
reject(new Error(errorMessage));
};
const timeoutHandle = setTimeout(onTimeout, twLanguageUpdateTimeout);
const onDone = (): void => {
clearTimeout(timeoutHandle);
clearInterval(intervalHandle);
resolve();
};
ipcMain.once(WikiChannel.setTiddlerTextDone + workspaceID, onDone);
onRetryOrDo();
});
}
Example #5
Source File: index.ts From TidGi-Desktop with Mozilla Public License 2.0 | 5 votes |
public async getActiveBrowserViews(): Promise<Array<BrowserView | undefined>> {
const workspace = await this.workspaceService.getActiveWorkspace();
if (workspace !== undefined) {
return [this.getView(workspace.id, WindowNames.main), this.getView(workspace.id, WindowNames.menuBar)];
}
return [];
}
Example #6
Source File: index.ts From TidGi-Desktop with Mozilla Public License 2.0 | 5 votes |
/**
* Record<workspaceID, Record<windowName, BrowserView>>
*
* Each workspace can have several windows to render its view (main window and menu bar)
*/
private views: Record<string, Record<WindowNames, BrowserView> | undefined> = {};
Example #7
Source File: index.ts From TidGi-Desktop with Mozilla Public License 2.0 | 5 votes |
public getView = (workspaceID: string, windowName: WindowNames): BrowserView | undefined => this.views[workspaceID]?.[windowName];
Example #8
Source File: index.ts From TidGi-Desktop with Mozilla Public License 2.0 | 5 votes |
public getAllViewOfWorkspace = (workspaceID: string): BrowserView[] => Object.values(this.views[workspaceID] ?? {});
Example #9
Source File: index.ts From TidGi-Desktop with Mozilla Public License 2.0 | 5 votes |
public setView = (workspaceID: string, windowName: WindowNames, newView: BrowserView): void => {
const workspaceOwnedViews = this.views[workspaceID];
if (workspaceOwnedViews === undefined) {
this.views[workspaceID] = { [windowName]: newView } as Record<WindowNames, BrowserView>;
} else {
workspaceOwnedViews[windowName] = newView;
}
};
Example #10
Source File: chrome-browserAction-spec.ts From electron-browser-shell with GNU General Public License v3.0 | 4 votes |
describe('chrome.browserAction', () => {
const server = useServer()
const defaultAnchorRect = {
x: 0,
y: 0,
width: 16,
height: 16,
}
const activateExtension = async (
partition: string,
webContents: WebContents,
extension: Extension,
tabId: number = -1
) => {
const details = {
eventType: 'click',
extensionId: extension.id,
tabId,
anchorRect: defaultAnchorRect,
}
const js = `browserAction.activate('${partition}', ${JSON.stringify(details)})`
await webContents.executeJavaScript(js)
}
describe('messaging', () => {
const browser = useExtensionBrowser({
url: server.getUrl,
extensionName: 'chrome-browserAction-click',
})
it('supports cross-session communication', async () => {
const otherSession = session.fromPartition(`persist:crx-${uuid()}`)
otherSession.setPreloads(browser.session.getPreloads())
const view = new BrowserView({
webPreferences: { session: otherSession, nodeIntegration: false, contextIsolation: true },
})
await view.webContents.loadURL(server.getUrl())
browser.window.addBrowserView(view)
await activateExtension(browser.partition, view.webContents, browser.extension)
})
it('can request action for specific tab', async () => {
const tab = browser.window.webContents
await activateExtension(browser.partition, tab, browser.extension, tab.id)
})
it('throws for unknown tab', async () => {
const tab = browser.window.webContents
const unknownTabId = 99999
let caught = false
try {
await activateExtension(browser.partition, tab, browser.extension, unknownTabId)
} catch {
caught = true
}
expect(caught).to.be.true
})
})
describe('onClicked', () => {
const browser = useExtensionBrowser({
url: server.getUrl,
extensionName: 'chrome-browserAction-click',
})
it('fires listeners when activated', async () => {
const tabPromise = emittedOnce(ipcMain, 'success')
await activateExtension(browser.partition, browser.window.webContents, browser.extension)
const [_, tabDetails] = await tabPromise
expect(tabDetails).to.be.an('object')
expect(tabDetails.id).to.equal(browser.window.webContents.id)
})
})
describe('popup', () => {
const browser = useExtensionBrowser({
url: server.getUrl,
extensionName: 'chrome-browserAction-popup',
})
it('opens when the browser action is clicked', async () => {
const popupPromise = emittedOnce(browser.extensions, 'browser-action-popup-created')
await activateExtension(browser.partition, browser.window.webContents, browser.extension)
const [popup] = await popupPromise
expect(popup.extensionId).to.equal(browser.extension.id)
})
it('opens when BrowserView is the active tab', async () => {
const view = new BrowserView({
webPreferences: {
session: browser.session,
nodeIntegration: false,
contextIsolation: true,
},
})
await view.webContents.loadURL(server.getUrl())
browser.window.addBrowserView(view)
browser.extensions.addTab(view.webContents, browser.window)
browser.extensions.selectTab(view.webContents)
const popupPromise = emittedOnce(browser.extensions, 'browser-action-popup-created')
await activateExtension(browser.partition, browser.window.webContents, browser.extension)
const [popup] = await popupPromise
expect(popup.extensionId).to.equal(browser.extension.id)
})
})
describe('details', () => {
const browser = useExtensionBrowser({
url: server.getUrl,
extensionName: 'rpc',
})
const props = [
{ method: 'BadgeBackgroundColor', detail: 'color', value: '#cacaca' },
{ method: 'BadgeText', detail: 'text' },
{ method: 'Popup', detail: 'popup' },
{ method: 'Title', detail: 'title' },
]
for (const { method, detail, value } of props) {
it(`sets and gets '${detail}'`, async () => {
const newValue = value || uuid()
await browser.crx.exec(`browserAction.set${method}`, { [detail]: newValue })
const result = await browser.crx.exec(`browserAction.get${method}`)
expect(result).to.equal(newValue)
})
it(`restores initial values for '${detail}'`, async () => {
const newValue = value || uuid()
const initial = await browser.crx.exec(`browserAction.get${method}`)
await browser.crx.exec(`browserAction.set${method}`, { [detail]: newValue })
await browser.crx.exec(`browserAction.set${method}`, { [detail]: null })
const result = await browser.crx.exec(`browserAction.get${method}`)
expect(result).to.equal(initial)
})
}
it('uses custom popup when opening browser action', async () => {
const popupUuid = uuid()
const popupPath = `popup.html?${popupUuid}`
await browser.crx.exec('browserAction.setPopup', { popup: popupPath })
const popupPromise = emittedOnce(browser.extensions, 'browser-action-popup-created')
await activateExtension(browser.partition, browser.window.webContents, browser.extension)
const [popup] = await popupPromise
await popup.whenReady()
expect(popup.browserWindow.webContents.getURL()).to.equal(
`chrome-extension://${browser.extension.id}/${popupPath}`
)
})
})
describe('<browser-action-list> element', () => {
const basePath = path.join(__dirname, 'fixtures/browser-action-list')
const browser = useExtensionBrowser({
extensionName: 'chrome-browserAction-popup',
})
it('lists actions', async () => {
await browser.webContents.loadFile(path.join(basePath, 'default.html'))
const extensionIds = await browser.webContents.executeJavaScript(
`(${() => {
const list = document.querySelector('browser-action-list')!
const actions = list.shadowRoot!.querySelectorAll('.action')
const ids = Array.from(actions).map((elem) => elem.id)
return ids
}})();`
)
expect(extensionIds).to.deep.equal([browser.extension.id])
})
it('lists actions in remote partition', async () => {
const remoteWindow = createCrxRemoteWindow()
const remoteTab = remoteWindow.webContents
await remoteTab.loadURL(server.getUrl())
// Add <browser-action-list> for remote partition.
await remoteTab.executeJavaScript(
`(${(partition: string) => {
const list = document.createElement('browser-action-list')
list.setAttribute('partition', partition)
document.body.appendChild(list)
}})('${browser.partition}');`
)
const extensionIds = await remoteTab.executeJavaScript(
`(${() => {
const list = document.querySelector('browser-action-list')!
const actions = list.shadowRoot!.querySelectorAll('.action')
const ids = Array.from(actions).map((elem) => elem.id)
return ids
}})();`
)
expect(extensionIds).to.deep.equal([browser.extension.id])
})
})
})
Example #11
Source File: index.ts From TidGi-Desktop with Mozilla Public License 2.0 | 4 votes |
public async addView(workspace: IWorkspace, windowName: WindowNames): Promise<void> {
// we assume each window will only have one view, so get view by window name + workspace
const existedView = this.getView(workspace.id, windowName);
const browserWindow = this.windowService.get(windowName);
if (existedView !== undefined) {
logger.warn(`BrowserViewService.addView: ${workspace.id} 's view already exists`);
return;
}
if (browserWindow === undefined) {
logger.warn(`BrowserViewService.addView: ${workspace.id} 's browser window is not ready`);
return;
}
// create a new BrowserView
const { rememberLastPageVisited, shareWorkspaceBrowsingData, spellcheck, spellcheckLanguages } = await this.preferenceService.getPreferences();
// configure session, proxy & ad blocker
const partitionId = shareWorkspaceBrowsingData ? 'persist:shared' : `persist:${workspace.id}`;
// prepare configs for start a BrowserView that loads wiki's web content
// session
const sessionOfView = session.fromPartition(partitionId);
// spellchecker
if (spellcheck && process.platform !== 'darwin') {
sessionOfView.setSpellCheckerLanguages(spellcheckLanguages);
}
const browserViewMetaData: IBrowserViewMetaData = { workspaceID: workspace.id };
const sharedWebPreferences: WebPreferences = {
devTools: true,
spellcheck,
nodeIntegration: false,
contextIsolation: true,
// allow loading pictures from the localhost network, you may want to setup img host services in your local network, set this to true will cause CORS
// TODO: make this a setting in security preference
webSecurity: false,
allowRunningInsecureContent: true,
session: sessionOfView,
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
additionalArguments: [
`${MetaDataChannel.browserViewMetaData}${WindowNames.view}`,
`${MetaDataChannel.browserViewMetaData}${encodeURIComponent(JSON.stringify(browserViewMetaData))}`,
],
};
const view = new BrowserView({
webPreferences: sharedWebPreferences,
});
// background needs to explicitly set
// if not, by default, the background of BrowserView is transparent
// which would break the CSS of certain websites
// even with dark mode, all major browsers
// always use #FFF as default page background
// https://github.com/atomery/webcatalog/issues/723
// https://github.com/electron/electron/issues/16212
view.setBackgroundColor('#fafafa');
// Handle audio & notification preferences
if (this.shouldMuteAudio !== undefined) {
view.webContents.audioMuted = this.shouldMuteAudio;
}
this.setView(workspace.id, windowName, view);
if (workspace.active) {
browserWindow.setBrowserView(view);
const contentSize = browserWindow.getContentSize();
view.setBounds(await getViewBounds(contentSize as [number, number]));
view.setAutoResize({
width: true,
height: true,
});
}
// fix some case that local ip can't be load
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const urlToReplace = (rememberLastPageVisited && workspace.lastUrl) || workspace.homeUrl;
const portReplacedUrl = replaceUrlPortWithSettingPort(urlToReplace, workspace.port);
const hostReplacedUrl = await getLocalHostUrlWithActualIP(portReplacedUrl);
logger.debug(`Load initialUrl: ${hostReplacedUrl} for windowName ${windowName} for workspace ${workspace.name}`, {
urlToReplace,
replacedUrl: portReplacedUrl,
});
/**
* Try catch loadUrl, other wise it will throw unhandled promise rejection Error: ERR_CONNECTION_REFUSED (-102) loading 'http://localhost:5212/
* We will set `didFailLoadErrorMessage`, it will set didFailLoadErrorMessage, and we throw actuarial error after that
*/
const loadInitialUrlWithCatch = async (): Promise<void> => {
try {
logger.debug(
`loadInitialUrlWithCatch(): view.webContents: ${String(view.webContents)} ${hostReplacedUrl} for windowName ${windowName} for workspace ${
workspace.name
}`,
{ stack: new Error('debug error, not a real error').stack },
);
if (await this.workspaceService.workspaceDidFailLoad(workspace.id)) {
return;
}
// will set again in view.webContents.on('did-start-loading'), but that one sometimes is too late to block services that wait for `isLoading`
await this.workspaceService.updateMetaData(workspace.id, {
// eslint-disable-next-line unicorn/no-null
didFailLoadErrorMessage: null,
isLoading: true,
});
await view.webContents.loadURL(hostReplacedUrl);
logger.debug('loadInitialUrlWithCatch() await loadURL() done');
const unregisterContextMenu = await this.menuService.initContextMenuForWindowWebContents(view.webContents);
view.webContents.on('destroyed', () => {
unregisterContextMenu();
});
} catch (error) {
logger.warn(new ViewLoadUrlError(hostReplacedUrl, `${(error as Error).message} ${(error as Error).stack ?? ''}`));
}
};
setupViewEventHandlers(view, browserWindow, {
shouldPauseNotifications: this.shouldPauseNotifications,
workspace,
sharedWebPreferences,
loadInitialUrlWithCatch,
});
await loadInitialUrlWithCatch();
}
Example #12
Source File: setupViewEventHandlers.ts From TidGi-Desktop with Mozilla Public License 2.0 | 4 votes |
/**
* Bind workspace related event handler to view.webContent
*/
export default function setupViewEventHandlers(
view: BrowserView,
browserWindow: BrowserWindow,
{ workspace, sharedWebPreferences, loadInitialUrlWithCatch }: IViewContext,
): void {
// metadata and state about current BrowserView
const viewMeta: IViewMeta = {
forceNewWindow: false,
};
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
const workspaceViewService = container.get<IWorkspaceViewService>(serviceIdentifier.WorkspaceView);
const windowService = container.get<IWindowService>(serviceIdentifier.Window);
const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference);
view.webContents.on('did-start-loading', async () => {
const workspaceObject = await workspaceService.get(workspace.id);
// this event might be triggered
// even after the workspace obj and BrowserView
// are destroyed. See https://github.com/atomery/webcatalog/issues/836
if (workspaceObject === undefined) {
return;
}
if (workspaceObject.active && (await workspaceService.workspaceDidFailLoad(workspace.id)) && browserWindow !== undefined && !browserWindow.isDestroyed()) {
// fix https://github.com/webcatalog/singlebox-legacy/issues/228
const contentSize = browserWindow.getContentSize();
view.setBounds(await getViewBounds(contentSize as [number, number]));
}
await workspaceService.updateMetaData(workspace.id, {
// eslint-disable-next-line unicorn/no-null
didFailLoadErrorMessage: null,
isLoading: true,
});
});
view.webContents.on('did-navigate-in-page', async () => {
await workspaceViewService.updateLastUrl(workspace.id, view);
});
const throttledDidFinishedLoad = throttle(async () => {
// if have error, don't realignActiveWorkspace, which will hide the error message
if (await workspaceService.workspaceDidFailLoad(workspace.id)) {
return;
}
logger.debug(`throttledDidFinishedLoad() workspace.id: ${workspace.id}, now workspaceViewService.realignActiveWorkspace() then set isLoading to false`);
// focus on initial load
// https://github.com/atomery/webcatalog/issues/398
if (workspace.active && !browserWindow.isDestroyed() && browserWindow.isFocused() && !view.webContents.isFocused()) {
view.webContents.focus();
}
// fix https://github.com/atomery/webcatalog/issues/870
await workspaceViewService.realignActiveWorkspace();
// update isLoading to false when load succeed
await workspaceService.updateMetaData(workspace.id, {
isLoading: false,
});
}, 2000);
view.webContents.on('did-finish-load', () => {
logger.debug('did-finish-load called');
void throttledDidFinishedLoad();
});
view.webContents.on('did-stop-loading', () => {
logger.debug('did-stop-loading called');
void throttledDidFinishedLoad();
});
view.webContents.on('dom-ready', () => {
logger.debug('dom-ready called');
void throttledDidFinishedLoad();
});
// https://electronjs.org/docs/api/web-contents#event-did-fail-load
// https://github.com/webcatalog/neutron/blob/3d9e65c255792672c8bc6da025513a5404d98730/main-src/libs/views.js#L397
view.webContents.on('did-fail-load', async (_event, errorCode, errorDesc, _validateUrl, isMainFrame) => {
const [workspaceObject, workspaceDidFailLoad] = await Promise.all([
workspaceService.get(workspace.id),
workspaceService.workspaceDidFailLoad(workspace.id),
]);
// this event might be triggered
// even after the workspace obj and BrowserView
// are destroyed. See https://github.com/atomery/webcatalog/issues/836
if (workspaceObject === undefined) {
return;
}
if (workspaceDidFailLoad) {
return;
}
if (isMainFrame && errorCode < 0 && errorCode !== -3) {
// Fix nodejs wiki start slow on system startup, which cause `-102 ERR_CONNECTION_REFUSED` even if wiki said it is booted, we have to retry several times
if (errorCode === -102 && view.webContents.getURL().length > 0 && workspaceObject.homeUrl.startsWith('http')) {
setTimeout(async () => {
await loadInitialUrlWithCatch();
}, 1000);
return;
}
await workspaceService.updateMetaData(workspace.id, {
isLoading: false,
didFailLoadErrorMessage: `${errorCode} ${errorDesc}`,
});
if (workspaceObject.active && browserWindow !== undefined && !browserWindow.isDestroyed()) {
// fix https://github.com/atomery/singlebox/issues/228
const contentSize = browserWindow.getContentSize();
view.setBounds(await getViewBounds(contentSize as [number, number], false, 0, 0)); // hide browserView to show error message
}
}
// edge case to handle failed auth, use setTimeout to prevent infinite loop
if (errorCode === -300 && view.webContents.getURL().length === 0 && workspaceObject.homeUrl.startsWith('http')) {
setTimeout(async () => {
await loadInitialUrlWithCatch();
}, 1000);
}
});
view.webContents.on('did-navigate', async (_event, url) => {
const workspaceObject = await workspaceService.get(workspace.id);
// this event might be triggered
// even after the workspace obj and BrowserView
// are destroyed. See https://github.com/atomery/webcatalog/issues/836
if (workspaceObject === undefined) {
return;
}
if (workspaceObject.active) {
await windowService.sendToAllWindows(WindowChannel.updateCanGoBack, view.webContents.canGoBack());
await windowService.sendToAllWindows(WindowChannel.updateCanGoForward, view.webContents.canGoForward());
}
});
view.webContents.on('did-navigate-in-page', async (_event, url) => {
const workspaceObject = await workspaceService.get(workspace.id);
// this event might be triggered
// even after the workspace obj and BrowserView
// are destroyed. See https://github.com/atomery/webcatalog/issues/836
if (workspaceObject === undefined) {
return;
}
if (workspaceObject.active) {
await windowService.sendToAllWindows(WindowChannel.updateCanGoBack, view.webContents.canGoBack());
await windowService.sendToAllWindows(WindowChannel.updateCanGoForward, view.webContents.canGoForward());
}
});
view.webContents.on('page-title-updated', async (_event, title) => {
const workspaceObject = await workspaceService.get(workspace.id);
// this event might be triggered
// even after the workspace obj and BrowserView
// are destroyed. See https://github.com/atomery/webcatalog/issues/836
if (workspaceObject === undefined) {
return;
}
if (workspaceObject.active) {
browserWindow.setTitle(title);
}
});
view.webContents.setWindowOpenHandler((details: Electron.HandlerDetails) =>
handleNewWindow(
details.url,
{
workspace,
sharedWebPreferences,
view,
meta: viewMeta,
},
details.disposition,
view.webContents,
),
);
// Handle downloads
// https://electronjs.org/docs/api/download-item
view.webContents.session.on('will-download', async (_event, item) => {
const { askForDownloadPath, downloadPath } = await preferenceService.getPreferences();
// Set the save path, making Electron not to prompt a save dialog.
if (!askForDownloadPath) {
const finalFilePath = path.join(downloadPath, item.getFilename());
if (!fsExtra.existsSync(finalFilePath)) {
// eslint-disable-next-line no-param-reassign
item.savePath = finalFilePath;
}
} else {
// set preferred path for save dialog
const options = {
...item.getSaveDialogOptions(),
defaultPath: path.join(downloadPath, item.getFilename()),
};
item.setSaveDialogOptions(options);
}
});
// Unread count badge
void preferenceService.get('unreadCountBadge').then((unreadCountBadge) => {
if (unreadCountBadge) {
view.webContents.on('page-title-updated', async (_event, title) => {
const itemCountRegex = /[([{](\d*?)[)\]}]/;
const match = itemCountRegex.exec(title);
const incString = match !== null ? match[1] : '';
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const inc = Number.parseInt(incString, 10) || 0;
await workspaceService.updateMetaData(workspace.id, {
badgeCount: inc,
});
let count = 0;
const workspaceMetaData = await workspaceService.getAllMetaData();
Object.values(workspaceMetaData).forEach((metaData) => {
if (typeof metaData?.badgeCount === 'number') {
count += metaData.badgeCount;
}
});
app.badgeCount = count;
if (process.platform === 'win32') {
if (count > 0) {
const icon = nativeImage.createFromPath(path.resolve(buildResourcePath, 'overlay-icon.png'));
browserWindow.setOverlayIcon(icon, `You have ${count} new messages.`);
} else {
// eslint-disable-next-line unicorn/no-null
browserWindow.setOverlayIcon(null, '');
}
}
});
}
});
// Find In Page
view.webContents.on('found-in-page', async (_event, result) => {
await windowService.sendToAllWindows(ViewChannel.updateFindInPageMatches, result.activeMatchOrdinal, result.matches);
});
// Link preview
view.webContents.on('update-target-url', (_event, url) => {
try {
view.webContents.send('update-target-url', url);
} catch (error) {
logger.warn(error); // eslint-disable-line no-console
}
});
}