Open browser links in workspace tabs (#1375)
* Open browser window requests as workspace tabs * Tighten browser new-tab request boundaries
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { WorkspaceLayout } from "@/stores/workspace-layout-store";
|
||||||
|
import { createDefaultLayout } from "@/stores/workspace-layout-store";
|
||||||
|
import { openTabInLayoutFocused } from "@/stores/workspace-layout-actions";
|
||||||
|
import { resolveBrowserNewTabRequest, type BrowserNewTabRequest } from ".";
|
||||||
|
|
||||||
|
function createLayoutWithBrowser(browserId: string): WorkspaceLayout {
|
||||||
|
return openTabInLayoutFocused({
|
||||||
|
layout: createDefaultLayout(),
|
||||||
|
target: { kind: "browser", browserId },
|
||||||
|
now: 1,
|
||||||
|
}).layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("browser new-tab requests", () => {
|
||||||
|
it("accepts desktop requests from browser tabs in the current workspace", () => {
|
||||||
|
const request = resolveBrowserNewTabRequest({
|
||||||
|
payload: {
|
||||||
|
sourceBrowserId: "browser-1",
|
||||||
|
url: "https://example.com/target",
|
||||||
|
},
|
||||||
|
workspaceLayout: createLayoutWithBrowser("browser-1"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(request).toEqual<BrowserNewTabRequest>({
|
||||||
|
sourceBrowserId: "browser-1",
|
||||||
|
url: "https://example.com/target",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores desktop requests from another workspace", () => {
|
||||||
|
const request = resolveBrowserNewTabRequest({
|
||||||
|
payload: {
|
||||||
|
sourceBrowserId: "browser-from-other-workspace",
|
||||||
|
url: "https://example.com/target",
|
||||||
|
},
|
||||||
|
workspaceLayout: createLayoutWithBrowser("browser-1"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(request).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects unsupported desktop request URLs", () => {
|
||||||
|
const request = resolveBrowserNewTabRequest({
|
||||||
|
payload: {
|
||||||
|
sourceBrowserId: "browser-1",
|
||||||
|
url: "file:///etc/passwd",
|
||||||
|
},
|
||||||
|
workspaceLayout: createLayoutWithBrowser("browser-1"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(request).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { getDesktopHost, type DesktopBrowserNewTabRequestEvent } from "@/desktop/host";
|
||||||
|
import { collectAllTabs, type WorkspaceLayout } from "@/stores/workspace-layout-store";
|
||||||
|
import { getIsElectron } from "@/constants/platform";
|
||||||
|
import { useStableEvent } from "@/hooks/use-stable-event";
|
||||||
|
|
||||||
|
export type BrowserNewTabRequest = DesktopBrowserNewTabRequestEvent;
|
||||||
|
|
||||||
|
function isAllowedBrowserNewTabUrl(value: string): boolean {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(value);
|
||||||
|
return (
|
||||||
|
parsed.protocol === "http:" || parsed.protocol === "https:" || parsed.href === "about:blank"
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readDesktopBrowserNewTabRequest(payload: unknown): BrowserNewTabRequest | null {
|
||||||
|
if (!payload || typeof payload !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const candidate = payload as Partial<BrowserNewTabRequest>;
|
||||||
|
if (typeof candidate.sourceBrowserId !== "string" || !candidate.sourceBrowserId.trim()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof candidate.url !== "string" || !isAllowedBrowserNewTabUrl(candidate.url)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
sourceBrowserId: candidate.sourceBrowserId,
|
||||||
|
url: candidate.url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function workspaceContainsBrowser(input: {
|
||||||
|
workspaceLayout: WorkspaceLayout | null | undefined;
|
||||||
|
browserId: string;
|
||||||
|
}): boolean {
|
||||||
|
if (!input.workspaceLayout) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return collectAllTabs(input.workspaceLayout.root).some((tab) => {
|
||||||
|
return tab.target.kind === "browser" && tab.target.browserId === input.browserId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveBrowserNewTabRequest(input: {
|
||||||
|
payload: unknown;
|
||||||
|
workspaceLayout: WorkspaceLayout | null | undefined;
|
||||||
|
}): BrowserNewTabRequest | null {
|
||||||
|
const request = readDesktopBrowserNewTabRequest(input.payload);
|
||||||
|
if (!request) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!workspaceContainsBrowser({
|
||||||
|
workspaceLayout: input.workspaceLayout,
|
||||||
|
browserId: request.sourceBrowserId,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDesktopBrowserNewTabRequests(input: {
|
||||||
|
enabled: boolean;
|
||||||
|
workspaceLayout: WorkspaceLayout | null | undefined;
|
||||||
|
openUrl: (url: string) => void;
|
||||||
|
}): void {
|
||||||
|
const handleNewTabRequest = useStableEvent((payload: unknown) => {
|
||||||
|
const request = resolveBrowserNewTabRequest({
|
||||||
|
payload,
|
||||||
|
workspaceLayout: input.workspaceLayout,
|
||||||
|
});
|
||||||
|
if (!request) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
input.openUrl(request.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!input.enabled || !getIsElectron()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const unsubscribe = getDesktopHost()?.events?.on?.(
|
||||||
|
"browser-new-tab-request",
|
||||||
|
handleNewTabRequest,
|
||||||
|
);
|
||||||
|
if (typeof unsubscribe === "function") {
|
||||||
|
return unsubscribe;
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
void unsubscribe?.then((dispose) => dispose());
|
||||||
|
};
|
||||||
|
}, [handleNewTabRequest, input.enabled]);
|
||||||
|
}
|
||||||
@@ -111,6 +111,11 @@ export interface DesktopBrowserShortcutEvent {
|
|||||||
action: "focus-url";
|
action: "focus-url";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DesktopBrowserNewTabRequestEvent {
|
||||||
|
sourceBrowserId: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DesktopBrowserBridge {
|
export interface DesktopBrowserBridge {
|
||||||
setWorkspaceActiveBrowser?: (browserId: string | null) => Promise<void>;
|
setWorkspaceActiveBrowser?: (browserId: string | null) => Promise<void>;
|
||||||
openDevTools?: (browserId: string) => Promise<unknown>;
|
openDevTools?: (browserId: string) => Promise<unknown>;
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ import {
|
|||||||
buildWorkspaceTabMenuEntries,
|
buildWorkspaceTabMenuEntries,
|
||||||
type WorkspaceTabMenuEntry,
|
type WorkspaceTabMenuEntry,
|
||||||
} from "@/screens/workspace/workspace-tab-menu";
|
} from "@/screens/workspace/workspace-tab-menu";
|
||||||
|
import { useDesktopBrowserNewTabRequests } from "@/browser/new-tab-requests";
|
||||||
import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types";
|
import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types";
|
||||||
import {
|
import {
|
||||||
resolveWorkspaceHeaderRenderState,
|
resolveWorkspaceHeaderRenderState,
|
||||||
@@ -2329,6 +2330,12 @@ function WorkspaceScreenContent({
|
|||||||
[openWorkspaceTabFocused, persistenceKey],
|
[openWorkspaceTabFocused, persistenceKey],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useDesktopBrowserNewTabRequests({
|
||||||
|
enabled: Boolean(persistenceKey),
|
||||||
|
workspaceLayout,
|
||||||
|
openUrl: handleOpenUrlInBrowserTab,
|
||||||
|
});
|
||||||
|
|
||||||
const handleSelectSwitcherTab = useCallback(
|
const handleSelectSwitcherTab = useCallback(
|
||||||
(key: string) => {
|
(key: string) => {
|
||||||
navigateToTabId(key);
|
navigateToTabId(key);
|
||||||
|
|||||||
+47
@@ -1,8 +1,34 @@
|
|||||||
import { webContents as allWebContents, type WebContents } from "electron";
|
import { webContents as allWebContents, type WebContents } from "electron";
|
||||||
|
import {
|
||||||
|
BROWSER_NEW_TAB_REQUEST_EVENT,
|
||||||
|
handleBrowserWindowOpenRequest,
|
||||||
|
isAllowedBrowserWebviewUrl,
|
||||||
|
} from "./window-open.js";
|
||||||
|
|
||||||
|
export { BROWSER_NEW_TAB_REQUEST_EVENT, handleBrowserWindowOpenRequest };
|
||||||
|
|
||||||
const browserIdsByWebContentsId = new Map<number, string>();
|
const browserIdsByWebContentsId = new Map<number, string>();
|
||||||
let workspaceActiveBrowserId: string | null = null;
|
let workspaceActiveBrowserId: string | null = null;
|
||||||
|
|
||||||
|
function getBrowserIdFromWebviewPartition(partition: string | undefined): string | null {
|
||||||
|
const prefix = "persist:paseo-browser-";
|
||||||
|
if (!partition?.startsWith(prefix)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const browserId = partition.slice(prefix.length).trim();
|
||||||
|
return browserId.length > 0 ? browserId : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readBrowserIdFromWebviewAttach(input: {
|
||||||
|
src?: string;
|
||||||
|
partition?: string;
|
||||||
|
}): string | null {
|
||||||
|
if (!isAllowedBrowserWebviewUrl(input.src)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getBrowserIdFromWebviewPartition(input.partition);
|
||||||
|
}
|
||||||
|
|
||||||
export function listRegisteredPaseoBrowserIds(): string[] {
|
export function listRegisteredPaseoBrowserIds(): string[] {
|
||||||
return Array.from(new Set(browserIdsByWebContentsId.values())).sort();
|
return Array.from(new Set(browserIdsByWebContentsId.values())).sort();
|
||||||
}
|
}
|
||||||
@@ -45,3 +71,24 @@ export function getWorkspaceActivePaseoBrowserWebContents(): WebContents | null
|
|||||||
}
|
}
|
||||||
return getPaseoBrowserWebContents(workspaceActiveBrowserId);
|
return getPaseoBrowserWebContents(workspaceActiveBrowserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function preventUnsafeBrowserWebviewNavigation(
|
||||||
|
event: { preventDefault: () => void },
|
||||||
|
url: string | undefined,
|
||||||
|
): void {
|
||||||
|
if (!isAllowedBrowserWebviewUrl(url)) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerBrowserWebviewNavigationGuards(contents: WebContents): void {
|
||||||
|
contents.on("will-navigate", (event) => {
|
||||||
|
preventUnsafeBrowserWebviewNavigation(event, event.url);
|
||||||
|
});
|
||||||
|
contents.on("will-frame-navigate", (event) => {
|
||||||
|
preventUnsafeBrowserWebviewNavigation(event, event.url);
|
||||||
|
});
|
||||||
|
contents.on("will-redirect", (event) => {
|
||||||
|
preventUnsafeBrowserWebviewNavigation(event, event.url);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { handleBrowserWindowOpenRequest } from ".";
|
||||||
|
|
||||||
|
describe("browser webview window-open requests", () => {
|
||||||
|
it("denies Electron window creation and requests a Paseo browser tab", () => {
|
||||||
|
const requestNewTab = vi.fn();
|
||||||
|
|
||||||
|
const result = handleBrowserWindowOpenRequest({
|
||||||
|
url: "https://example.com/target",
|
||||||
|
sourceBrowserId: "browser-1",
|
||||||
|
requestNewTab,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({ action: "deny" });
|
||||||
|
expect(requestNewTab).toHaveBeenCalledWith({
|
||||||
|
sourceBrowserId: "browser-1",
|
||||||
|
url: "https://example.com/target",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("denies unsupported window-open requests before asking for a Paseo browser tab", () => {
|
||||||
|
const requestNewTab = vi.fn();
|
||||||
|
|
||||||
|
const result = handleBrowserWindowOpenRequest({
|
||||||
|
url: "file:///etc/passwd",
|
||||||
|
sourceBrowserId: "browser-1",
|
||||||
|
requestNewTab,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({ action: "deny" });
|
||||||
|
expect(requestNewTab).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export const BROWSER_NEW_TAB_REQUEST_EVENT = "paseo:event:browser-new-tab-request";
|
||||||
|
|
||||||
|
export interface BrowserNewTabRequestPayload {
|
||||||
|
sourceBrowserId: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAllowedBrowserWebviewUrl(value: string | undefined): boolean {
|
||||||
|
if (!value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsed = new URL(value);
|
||||||
|
return (
|
||||||
|
parsed.protocol === "http:" || parsed.protocol === "https:" || parsed.href === "about:blank"
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleBrowserWindowOpenRequest(input: {
|
||||||
|
url: string;
|
||||||
|
sourceBrowserId: string | null;
|
||||||
|
requestNewTab: (payload: BrowserNewTabRequestPayload) => void;
|
||||||
|
}): { action: "deny" } {
|
||||||
|
if (!isAllowedBrowserWebviewUrl(input.url) || !input.sourceBrowserId) {
|
||||||
|
return { action: "deny" };
|
||||||
|
}
|
||||||
|
|
||||||
|
input.requestNewTab({
|
||||||
|
sourceBrowserId: input.sourceBrowserId,
|
||||||
|
url: input.url,
|
||||||
|
});
|
||||||
|
return { action: "deny" };
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { app, Menu, BrowserWindow, ipcMain } from "electron";
|
import { app, Menu, BrowserWindow, ipcMain } from "electron";
|
||||||
import { getWorkspaceActivePaseoBrowserWebContents } from "./browser-webviews.js";
|
import { getWorkspaceActivePaseoBrowserWebContents } from "./browser-webviews/index.js";
|
||||||
|
|
||||||
interface ShowContextMenuInput {
|
interface ShowContextMenuInput {
|
||||||
kind?: "terminal";
|
kind?: "terminal";
|
||||||
|
|||||||
@@ -46,12 +46,16 @@ import { registerOpenerHandlers } from "./features/opener.js";
|
|||||||
import { registerEditorTargetHandlers } from "./features/editor-targets.js";
|
import { registerEditorTargetHandlers } from "./features/editor-targets.js";
|
||||||
import { setupApplicationMenu } from "./features/menu.js";
|
import { setupApplicationMenu } from "./features/menu.js";
|
||||||
import {
|
import {
|
||||||
|
BROWSER_NEW_TAB_REQUEST_EVENT,
|
||||||
getPaseoBrowserIdForWebContents,
|
getPaseoBrowserIdForWebContents,
|
||||||
getPaseoBrowserWebContents,
|
getPaseoBrowserWebContents,
|
||||||
|
handleBrowserWindowOpenRequest,
|
||||||
listRegisteredPaseoBrowserIds,
|
listRegisteredPaseoBrowserIds,
|
||||||
|
readBrowserIdFromWebviewAttach,
|
||||||
|
registerBrowserWebviewNavigationGuards,
|
||||||
registerPaseoBrowserWebContents,
|
registerPaseoBrowserWebContents,
|
||||||
setWorkspaceActivePaseoBrowserId,
|
setWorkspaceActivePaseoBrowserId,
|
||||||
} from "./features/browser-webviews.js";
|
} from "./features/browser-webviews/index.js";
|
||||||
import { parseOpenProjectPathFromArgv } from "./open-project-routing.js";
|
import { parseOpenProjectPathFromArgv } from "./open-project-routing.js";
|
||||||
import { PendingOpenProjectStore } from "./pending-open-project-store.js";
|
import { PendingOpenProjectStore } from "./pending-open-project-store.js";
|
||||||
import { getDesktopSettingsStore } from "./settings/desktop-settings-electron.js";
|
import { getDesktopSettingsStore } from "./settings/desktop-settings-electron.js";
|
||||||
@@ -73,28 +77,6 @@ const PASEO_DEBUG = process.env.PASEO_DEBUG === "1";
|
|||||||
const DISABLE_SINGLE_INSTANCE_LOCK = process.env.PASEO_DISABLE_SINGLE_INSTANCE_LOCK === "1";
|
const DISABLE_SINGLE_INSTANCE_LOCK = process.env.PASEO_DISABLE_SINGLE_INSTANCE_LOCK === "1";
|
||||||
const APP_NAME = process.env.PASEO_TEST_APP_NAME?.trim() || "Paseo";
|
const APP_NAME = process.env.PASEO_TEST_APP_NAME?.trim() || "Paseo";
|
||||||
|
|
||||||
function isAllowedBrowserWebviewUrl(value: string | undefined): boolean {
|
|
||||||
if (!value) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const parsed = new URL(value);
|
|
||||||
return (
|
|
||||||
parsed.protocol === "http:" || parsed.protocol === "https:" || parsed.href === "about:blank"
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function preventUnsafeBrowserWebviewNavigation(
|
|
||||||
event: Electron.Event,
|
|
||||||
url: string | undefined,
|
|
||||||
): void {
|
|
||||||
if (!isAllowedBrowserWebviewUrl(url)) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const BROWSER_SHORTCUT_EVENT = "paseo:event:browser-shortcut";
|
const BROWSER_SHORTCUT_EVENT = "paseo:event:browser-shortcut";
|
||||||
const BROWSER_FORWARDED_KEY_EVENT = "paseo:event:browser-forwarded-key";
|
const BROWSER_FORWARDED_KEY_EVENT = "paseo:event:browser-forwarded-key";
|
||||||
|
|
||||||
@@ -127,15 +109,6 @@ const DESKTOP_SMOKE_ENV = "PASEO_DESKTOP_SMOKE";
|
|||||||
const DESKTOP_SMOKE_STOP_REQUEST = "paseo-smoke-stop";
|
const DESKTOP_SMOKE_STOP_REQUEST = "paseo-smoke-stop";
|
||||||
app.setName(APP_NAME);
|
app.setName(APP_NAME);
|
||||||
|
|
||||||
function getBrowserIdFromWebviewPartition(partition: string | undefined): string | null {
|
|
||||||
const prefix = "persist:paseo-browser-";
|
|
||||||
if (!partition?.startsWith(prefix)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const browserId = partition.slice(prefix.length).trim();
|
|
||||||
return browserId.length > 0 ? browserId : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pendingBrowserWebviewIds: string[] = [];
|
const pendingBrowserWebviewIds: string[] = [];
|
||||||
|
|
||||||
function isBrowserRefreshInput(input: Electron.Input): boolean {
|
function isBrowserRefreshInput(input: Electron.Input): boolean {
|
||||||
@@ -470,11 +443,7 @@ async function createWindow(
|
|||||||
setupDefaultContextMenu(mainWindow);
|
setupDefaultContextMenu(mainWindow);
|
||||||
setupDragDropPrevention(mainWindow);
|
setupDragDropPrevention(mainWindow);
|
||||||
mainWindow.webContents.on("will-attach-webview", (event, webPreferences, params) => {
|
mainWindow.webContents.on("will-attach-webview", (event, webPreferences, params) => {
|
||||||
if (!isAllowedBrowserWebviewUrl(params.src)) {
|
const browserId = readBrowserIdFromWebviewAttach(params);
|
||||||
event.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const browserId = getBrowserIdFromWebviewPartition(params.partition);
|
|
||||||
if (!browserId) {
|
if (!browserId) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
@@ -534,25 +503,19 @@ async function createWindow(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
contents.setWindowOpenHandler(({ url }) => {
|
contents.setWindowOpenHandler(({ url }) =>
|
||||||
if (!isAllowedBrowserWebviewUrl(url)) {
|
handleBrowserWindowOpenRequest({
|
||||||
return { action: "deny" };
|
url,
|
||||||
}
|
sourceBrowserId: getPaseoBrowserIdForWebContents(contents),
|
||||||
contents.loadURL(url).catch(() => undefined);
|
requestNewTab: (payload) => {
|
||||||
return { action: "deny" };
|
mainWindow.webContents.send(BROWSER_NEW_TAB_REQUEST_EVENT, payload);
|
||||||
});
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
contents.on("context-menu", (_contextMenuEvent, params) => {
|
contents.on("context-menu", (_contextMenuEvent, params) => {
|
||||||
showBrowserWebviewContextMenu(mainWindow, contents, params);
|
showBrowserWebviewContextMenu(mainWindow, contents, params);
|
||||||
});
|
});
|
||||||
contents.on("will-navigate", (event) => {
|
registerBrowserWebviewNavigationGuards(contents);
|
||||||
preventUnsafeBrowserWebviewNavigation(event, event.url);
|
|
||||||
});
|
|
||||||
contents.on("will-frame-navigate", (event) => {
|
|
||||||
preventUnsafeBrowserWebviewNavigation(event, event.url);
|
|
||||||
});
|
|
||||||
contents.on("will-redirect", (event) => {
|
|
||||||
preventUnsafeBrowserWebviewNavigation(event, event.url);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.once("ready-to-show", () => {
|
mainWindow.once("ready-to-show", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user