最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - communicating between multiple WebContentsViews (electron) - Stack Overflow

programmeradmin1浏览0评论

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
Add a comment  | 

1 Answer 1

Reset to default -1

i fixed the issue by adding the needed events into a pareload.

发布评论

评论列表(0)

  1. 暂无评论