i have a electron app that i am working on and it uses this layout, first the main browserapp.ts which is the main webcontentsview, then the toolbar.ts which is the second webcontentsview that looks like a toolbar with your avid functions like back, forward, reload and the address bar
seems that the current setup that I have does not work, here's the output
[2] (node:344) UnhandledPromiseRejectionWarning: Error: An object could not be cloned.
[2] at IpcRendererInternal.send (node:electron/js2c/sandbox_bundle:2:121939)
[2] at IpcRendererInternal.<anonymous> (node:electron/js2c/sandbox_bundle:2:121517)
[2] (Use `electron --trace-warnings ...` to show where the warning was created)
[2] (node:344) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see .html#cli_unhandled_rejections_mode). (rejection id: 1)
i havent tryed any changes yet can someone help me out
heres the browserapp.ts code
import { BrowserWindow, WebContentsView } from 'electron';
import { createToolbar } from '../toolbar/toolbar'; // Import the toolbar creation function
interface BrowserTabView {
id: number;
webContentsView: WebContentsView;
url: string;
}
const tabViews: BrowserTabView[] = [];
let activeTabId: number | null = null; // Track the active tab's ID
// Constants for heights
const TITLEBAR_HEIGHT = 30;
const TOOLBAR_HEIGHT = 37;
const TOOLBAR_OFFSET = 3.8;
export function createWebContentsView(mainWindow: BrowserWindow, id: number, url = '/'): WebContentsView {
const windowBounds = mainWindow.getBounds();
// Create the toolbar
const toolbarView = createToolbar(mainWindow);
// Calculate the y-position for the toolbar (directly below the titlebar with a small offset)
const toolbarY = TITLEBAR_HEIGHT + TOOLBAR_OFFSET; // Toolbar is directly below the titlebar with a small offset
// Position the toolbar
toolbarView.setBounds({
x: 0,
y: toolbarY, // Position directly below the titlebar with a small offset
width: windowBounds.width,
height: TOOLBAR_HEIGHT,
});
// Calculate the y-position for the main WebContentsView (below the toolbar)
const webContentsY = toolbarY + TOOLBAR_HEIGHT;
// Create and position the main WebContentsView
const webContentsView = new WebContentsView();
mainWindow.contentView.addChildView(webContentsView);
webContentsView.setBounds({
x: 0,
y: webContentsY, // Position below the toolbar
width: windowBounds.width,
height: windowBounds.height - webContentsY, // Adjust height to account for titlebar and toolbar
});
// Load the initial URL
webContentsView.webContents.loadURL(url);
// Add the new tab view to the list
tabViews.push({ id, webContentsView, url });
// If this is the first tab, select it
if (tabViews.length === 1) {
selectWebContentsView(mainWindow, id);
}
// Handle navigation events
webContentsView.webContents.on('did-navigate', (_, updatedURL) => {
const tab = tabViews.find(view => view.id === id);
if (tab) {
tab.url = updatedURL;
}
updateAddressBar(updatedURL);
});
// Handle window resize events
mainWindow.on('resize', () => {
const newBounds = mainWindow.getBounds();
// Resize the toolbar
toolbarView.setBounds({
x: 0,
y: toolbarY, // Maintain position directly below the titlebar with a small offset
width: newBounds.width,
height: TOOLBAR_HEIGHT,
});
// Resize the active tab's WebContentsView
const activeTab = tabViews.find(view => view.id === id);
if (activeTab) {
activeTab.webContentsView.setBounds({
x: 0,
y: webContentsY, // Maintain position below the toolbar
width: newBounds.width,
height: newBounds.height - webContentsY, // Adjust height dynamically
});
}
});
return webContentsView;
}
export function selectWebContentsView(mainWindow: BrowserWindow, id: number) {
const activeTab = tabViews.find(view => view.id === id);
if (activeTab) {
// Hide all views first
tabViews.forEach(view => {
view.webContentsView.setBounds({ x: 0, y: 0, width: 0, height: 0 });
});
// Show the selected view
const windowBounds = mainWindow.getBounds();
activeTab.webContentsView.setBounds({
x: 0,
y: TITLEBAR_HEIGHT + TOOLBAR_OFFSET + TOOLBAR_HEIGHT, // Position below titlebar and toolbar with offset
width: windowBounds.width,
height: windowBounds.height - (TITLEBAR_HEIGHT + TOOLBAR_OFFSET + TOOLBAR_HEIGHT), // Adjust height dynamically
});
// Update the active tab ID
activeTabId = id;
updateAddressBar(activeTab.url);
}
}
export function closeWebContentsView(mainWindow: BrowserWindow, id: number) {
const index = tabViews.findIndex(view => view.id === id);
if (index !== -1) {
const view = tabViews[index];
view.webContentsView.webContents.close(); // Close the webContents
mainWindow.contentView.removeChildView(view.webContentsView); // Remove the view from the window
tabViews.splice(index, 1);
// If there are remaining tabs, select the first one
if (tabViews.length > 0) {
selectWebContentsView(mainWindow, tabViews[0].id);
} else {
activeTabId = null; // No active tab
}
}
}
export function updateWebContentsURL(id: number, url: string) {
const view = tabViews.find(v => v.id === id);
if (view) {
view.webContentsView.webContents.loadURL(url);
view.url = url;
updateAddressBar(url);
}
}
function updateAddressBar(url: string) {
// Assuming there's a way to communicate with the renderer process to update the address bar
// This can be done using Electron's IPC
// Example: mainWindow.webContents.send('update-address-bar', url);
}
// Navigation functions
export function navigateBack() {
if (activeTabId !== null) {
const activeTab = tabViews.find(view => view.id === activeTabId);
if (activeTab) {
activeTab.webContentsView.webContents.goBack();
}
}
}
export function navigateForward() {
if (activeTabId !== null) {
const activeTab = tabViews.find(view => view.id === activeTabId);
if (activeTab) {
activeTab.webContentsView.webContents.goForward();
}
}
}
export function navigateRefresh() {
if (activeTabId !== null) {
const activeTab = tabViews.find(view => view.id === activeTabId);
if (activeTab) {
activeTab.webContentsView.webContents.reload();
}
}
}
export function navigateTo(url: string) {
if (activeTabId !== null) {
const activeTab = tabViews.find(view => view.id === activeTabId);
if (activeTab) {
activeTab.webContentsView.webContents.loadURL(url);
}
}
}
and heres the toolbar.ts
import { BrowserWindow, WebContentsView } from 'electron';
import { getToolbarStyles } from './toolbarstyle';
import * as path from 'path';
import * as fs from 'fs';
import { navigateBack, navigateForward, navigateRefresh, navigateTo } from '../browserapp/browserapp'; // Import navigation functions
export function createToolbar(mainWindow: BrowserWindow): WebContentsView {
const toolbarHeight = 37; // Height of the toolbar
const windowBounds = mainWindow.getBounds();
const toolbarView = new WebContentsView();
mainWindow.contentView.addChildView(toolbarView);
toolbarView.setBounds({
x: 0,
y: 0,
width: windowBounds.width,
height: toolbarHeight,
});
// Read SVG files as base64 (Fixes file:// loading issues)
function getBase64Icon(iconPath: string): string {
try {
const iconData = fs.readFileSync(iconPath, 'base64');
return `data:image/svg+xml;base64,${iconData}`;
} catch (error) {
console.error(`Failed to load icon: ${iconPath}`, error);
return '';
}
}
// Convert SVGs to base64
const backIconData = getBase64Icon(path.resolve(__dirname, 'toolbar-icons', 'back.svg'));
const forwardIconData = getBase64Icon(path.resolve(__dirname, 'toolbar-icons', 'forward.svg'));
const refreshIconData = getBase64Icon(path.resolve(__dirname, 'toolbar-icons', 'refresh.svg'));
// Inject CSS to style the toolbar
toolbarView.webContents.on('dom-ready', () => {
const css = getToolbarStyles();
toolbarView.webContents.insertCSS(css);
// Pass icon data safely using JSON.stringify
toolbarView.webContents.executeJavaScript(`
(() => {
document.body.innerHTML = '';
const toolbar = document.createElement('div');
toolbar.className = 'toolbar';
const backButton = document.createElement('button');
backButton.className = 'toolbar-button';
backButton.innerHTML = '<img src="${backIconData}" alt="Back">';
backButton.addEventListener('click', () => {
window.toolbarCallbacks.navigateBack();
});
const forwardButton = document.createElement('button');
forwardButton.className = 'toolbar-button';
forwardButton.innerHTML = '<img src="${forwardIconData}" alt="Forward">';
forwardButton.addEventListener('click', () => {
window.toolbarCallbacks.navigateForward();
});
const refreshButton = document.createElement('button');
refreshButton.className = 'toolbar-button';
refreshButton.innerHTML = '<img src="${refreshIconData}" alt="Refresh">';
refreshButton.addEventListener('click', () => {
window.toolbarCallbacks.navigateRefresh();
});
const addressBar = document.createElement('input');
addressBar.className = 'address-bar';
addressBar.type = 'text';
addressBar.placeholder = 'Enter address or search';
addressBar.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
window.toolbarCallbacks.navigateTo(event.target.value);
}
});
toolbar.appendChild(backButton);
toolbar.appendChild(forwardButton);
toolbar.appendChild(refreshButton);
toolbar.appendChild(addressBar);
document.body.appendChild(toolbar);
})();
`);
// Expose the callbacks to the toolbar's WebContentsView
toolbarView.webContents.executeJavaScript(`
window.toolbarCallbacks = {
navigateBack: ${navigateBack.toString()},
navigateForward: ${navigateForward.toString()},
navigateRefresh: ${navigateRefresh.toString()},
navigateTo: ${navigateTo.toString()},
};
`);
});
// Load a blank page to apply the CSS
toolbarView.webContents.loadURL('about:blank');
return toolbarView;
}
i have a electron app that i am working on and it uses this layout, first the main browserapp.ts which is the main webcontentsview, then the toolbar.ts which is the second webcontentsview that looks like a toolbar with your avid functions like back, forward, reload and the address bar
seems that the current setup that I have does not work, here's the output
[2] (node:344) UnhandledPromiseRejectionWarning: Error: An object could not be cloned.
[2] at IpcRendererInternal.send (node:electron/js2c/sandbox_bundle:2:121939)
[2] at IpcRendererInternal.<anonymous> (node:electron/js2c/sandbox_bundle:2:121517)
[2] (Use `electron --trace-warnings ...` to show where the warning was created)
[2] (node:344) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs./api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
i havent tryed any changes yet can someone help me out
heres the browserapp.ts code
import { BrowserWindow, WebContentsView } from 'electron';
import { createToolbar } from '../toolbar/toolbar'; // Import the toolbar creation function
interface BrowserTabView {
id: number;
webContentsView: WebContentsView;
url: string;
}
const tabViews: BrowserTabView[] = [];
let activeTabId: number | null = null; // Track the active tab's ID
// Constants for heights
const TITLEBAR_HEIGHT = 30;
const TOOLBAR_HEIGHT = 37;
const TOOLBAR_OFFSET = 3.8;
export function createWebContentsView(mainWindow: BrowserWindow, id: number, url = 'https://google/'): WebContentsView {
const windowBounds = mainWindow.getBounds();
// Create the toolbar
const toolbarView = createToolbar(mainWindow);
// Calculate the y-position for the toolbar (directly below the titlebar with a small offset)
const toolbarY = TITLEBAR_HEIGHT + TOOLBAR_OFFSET; // Toolbar is directly below the titlebar with a small offset
// Position the toolbar
toolbarView.setBounds({
x: 0,
y: toolbarY, // Position directly below the titlebar with a small offset
width: windowBounds.width,
height: TOOLBAR_HEIGHT,
});
// Calculate the y-position for the main WebContentsView (below the toolbar)
const webContentsY = toolbarY + TOOLBAR_HEIGHT;
// Create and position the main WebContentsView
const webContentsView = new WebContentsView();
mainWindow.contentView.addChildView(webContentsView);
webContentsView.setBounds({
x: 0,
y: webContentsY, // Position below the toolbar
width: windowBounds.width,
height: windowBounds.height - webContentsY, // Adjust height to account for titlebar and toolbar
});
// Load the initial URL
webContentsView.webContents.loadURL(url);
// Add the new tab view to the list
tabViews.push({ id, webContentsView, url });
// If this is the first tab, select it
if (tabViews.length === 1) {
selectWebContentsView(mainWindow, id);
}
// Handle navigation events
webContentsView.webContents.on('did-navigate', (_, updatedURL) => {
const tab = tabViews.find(view => view.id === id);
if (tab) {
tab.url = updatedURL;
}
updateAddressBar(updatedURL);
});
// Handle window resize events
mainWindow.on('resize', () => {
const newBounds = mainWindow.getBounds();
// Resize the toolbar
toolbarView.setBounds({
x: 0,
y: toolbarY, // Maintain position directly below the titlebar with a small offset
width: newBounds.width,
height: TOOLBAR_HEIGHT,
});
// Resize the active tab's WebContentsView
const activeTab = tabViews.find(view => view.id === id);
if (activeTab) {
activeTab.webContentsView.setBounds({
x: 0,
y: webContentsY, // Maintain position below the toolbar
width: newBounds.width,
height: newBounds.height - webContentsY, // Adjust height dynamically
});
}
});
return webContentsView;
}
export function selectWebContentsView(mainWindow: BrowserWindow, id: number) {
const activeTab = tabViews.find(view => view.id === id);
if (activeTab) {
// Hide all views first
tabViews.forEach(view => {
view.webContentsView.setBounds({ x: 0, y: 0, width: 0, height: 0 });
});
// Show the selected view
const windowBounds = mainWindow.getBounds();
activeTab.webContentsView.setBounds({
x: 0,
y: TITLEBAR_HEIGHT + TOOLBAR_OFFSET + TOOLBAR_HEIGHT, // Position below titlebar and toolbar with offset
width: windowBounds.width,
height: windowBounds.height - (TITLEBAR_HEIGHT + TOOLBAR_OFFSET + TOOLBAR_HEIGHT), // Adjust height dynamically
});
// Update the active tab ID
activeTabId = id;
updateAddressBar(activeTab.url);
}
}
export function closeWebContentsView(mainWindow: BrowserWindow, id: number) {
const index = tabViews.findIndex(view => view.id === id);
if (index !== -1) {
const view = tabViews[index];
view.webContentsView.webContents.close(); // Close the webContents
mainWindow.contentView.removeChildView(view.webContentsView); // Remove the view from the window
tabViews.splice(index, 1);
// If there are remaining tabs, select the first one
if (tabViews.length > 0) {
selectWebContentsView(mainWindow, tabViews[0].id);
} else {
activeTabId = null; // No active tab
}
}
}
export function updateWebContentsURL(id: number, url: string) {
const view = tabViews.find(v => v.id === id);
if (view) {
view.webContentsView.webContents.loadURL(url);
view.url = url;
updateAddressBar(url);
}
}
function updateAddressBar(url: string) {
// Assuming there's a way to communicate with the renderer process to update the address bar
// This can be done using Electron's IPC
// Example: mainWindow.webContents.send('update-address-bar', url);
}
// Navigation functions
export function navigateBack() {
if (activeTabId !== null) {
const activeTab = tabViews.find(view => view.id === activeTabId);
if (activeTab) {
activeTab.webContentsView.webContents.goBack();
}
}
}
export function navigateForward() {
if (activeTabId !== null) {
const activeTab = tabViews.find(view => view.id === activeTabId);
if (activeTab) {
activeTab.webContentsView.webContents.goForward();
}
}
}
export function navigateRefresh() {
if (activeTabId !== null) {
const activeTab = tabViews.find(view => view.id === activeTabId);
if (activeTab) {
activeTab.webContentsView.webContents.reload();
}
}
}
export function navigateTo(url: string) {
if (activeTabId !== null) {
const activeTab = tabViews.find(view => view.id === activeTabId);
if (activeTab) {
activeTab.webContentsView.webContents.loadURL(url);
}
}
}
and heres the toolbar.ts
import { BrowserWindow, WebContentsView } from 'electron';
import { getToolbarStyles } from './toolbarstyle';
import * as path from 'path';
import * as fs from 'fs';
import { navigateBack, navigateForward, navigateRefresh, navigateTo } from '../browserapp/browserapp'; // Import navigation functions
export function createToolbar(mainWindow: BrowserWindow): WebContentsView {
const toolbarHeight = 37; // Height of the toolbar
const windowBounds = mainWindow.getBounds();
const toolbarView = new WebContentsView();
mainWindow.contentView.addChildView(toolbarView);
toolbarView.setBounds({
x: 0,
y: 0,
width: windowBounds.width,
height: toolbarHeight,
});
// Read SVG files as base64 (Fixes file:// loading issues)
function getBase64Icon(iconPath: string): string {
try {
const iconData = fs.readFileSync(iconPath, 'base64');
return `data:image/svg+xml;base64,${iconData}`;
} catch (error) {
console.error(`Failed to load icon: ${iconPath}`, error);
return '';
}
}
// Convert SVGs to base64
const backIconData = getBase64Icon(path.resolve(__dirname, 'toolbar-icons', 'back.svg'));
const forwardIconData = getBase64Icon(path.resolve(__dirname, 'toolbar-icons', 'forward.svg'));
const refreshIconData = getBase64Icon(path.resolve(__dirname, 'toolbar-icons', 'refresh.svg'));
// Inject CSS to style the toolbar
toolbarView.webContents.on('dom-ready', () => {
const css = getToolbarStyles();
toolbarView.webContents.insertCSS(css);
// Pass icon data safely using JSON.stringify
toolbarView.webContents.executeJavaScript(`
(() => {
document.body.innerHTML = '';
const toolbar = document.createElement('div');
toolbar.className = 'toolbar';
const backButton = document.createElement('button');
backButton.className = 'toolbar-button';
backButton.innerHTML = '<img src="${backIconData}" alt="Back">';
backButton.addEventListener('click', () => {
window.toolbarCallbacks.navigateBack();
});
const forwardButton = document.createElement('button');
forwardButton.className = 'toolbar-button';
forwardButton.innerHTML = '<img src="${forwardIconData}" alt="Forward">';
forwardButton.addEventListener('click', () => {
window.toolbarCallbacks.navigateForward();
});
const refreshButton = document.createElement('button');
refreshButton.className = 'toolbar-button';
refreshButton.innerHTML = '<img src="${refreshIconData}" alt="Refresh">';
refreshButton.addEventListener('click', () => {
window.toolbarCallbacks.navigateRefresh();
});
const addressBar = document.createElement('input');
addressBar.className = 'address-bar';
addressBar.type = 'text';
addressBar.placeholder = 'Enter address or search';
addressBar.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
window.toolbarCallbacks.navigateTo(event.target.value);
}
});
toolbar.appendChild(backButton);
toolbar.appendChild(forwardButton);
toolbar.appendChild(refreshButton);
toolbar.appendChild(addressBar);
document.body.appendChild(toolbar);
})();
`);
// Expose the callbacks to the toolbar's WebContentsView
toolbarView.webContents.executeJavaScript(`
window.toolbarCallbacks = {
navigateBack: ${navigateBack.toString()},
navigateForward: ${navigateForward.toString()},
navigateRefresh: ${navigateRefresh.toString()},
navigateTo: ${navigateTo.toString()},
};
`);
});
// Load a blank page to apply the CSS
toolbarView.webContents.loadURL('about:blank');
return toolbarView;
}
Share
Improve this question
asked Mar 13 at 17:29
MrWhoSBOSSMrWhoSBOSS
119 bronze badges
1 Answer
Reset to default -1i fixed the issue by adding the needed events into a pareload.