最近遇到了一个比较棘手的问题,目前暂时还没有解决的方法,如果有同学曾经遇到过类似的问题,欢迎将解决方案分享出来。
我先简单描述一下问题:我目前使用的 Electron 版本是 34.x 版本,使用 webview 标签渲染外部链接。若该页面中使用了 window.open() 方法在新标签页中打开链接,在浏览器上是没有问题的,但在 electron 中是没有反应的。
1. 重写 window.open 方法 #
在咨询过 AI 后,是可以通过重写 window.open() 方法来实现的。有两种重写的方式,一种是在渲染层注入,另一种是在主进程注入。
渲染层注入:
webviewRef.current.addEventListener("dom-ready", () => {
webviewRef.current.executeJavaScript(`
// 强制覆盖,即使页面已经定义
const originalOpen = window.open;
window.open = function(url, name, features) {
console.log('被拦截的 window.open:', url);
// 主进程处理
window.hivedoneApp.invoke("openUrl", { url });
return null;
};
// 冻结这个属性,防止被再次修改
Object.freeze(window.open);
`);
});
主进程注入:
app.on("web-contents-created", (_, contents) => {
contents
.executeJavaScript(
`
// 保存原生 open
// window._originalOpen = window.open; // 2
// 永久重写
window.open = function(url, target, features) {
console.log('[重写] window.open:', url, Date.now());
if (url && url.startsWith('http')) {
// 主进程处理
window.hivedoneApp.invoke("openUrl", { url });
}
// 阻止默认行为
return null;
};
console.log('✅ 全局 window.open 重写成功!', Date.now());
`,
)
.catch((err) => {});
});
这两种重写方式,都可以在 webview 中的页面生效。但若 webview 中加载的页面中还有一个 iframe,接着往下看。
2. webview 中嵌套 iframe 问题 #
但接下来就有两个问题一直没解决:
- 教程中都说
contents.setWindowOpenHandler()可以拦截window.open()触发的行为,但实际测试发现,contents.setWindowOpenHandler()并不能拦截window.open()触发的行为;目前还没找到原因;不过用重写window.open()方法倒也是可以解决。 - 若 webview 中加载页面中还有一个 iframe,iframe 中的
window.open()是不会被重写的,若 iframe 中有window.open()触发的行为,直接就是无反应,同样的,iframe 中的window.open()方法也不会被contents.setWindowOpenHandler()拦截。
目前问题就卡住了,暂时还没有找到解决方法。
3. 更换成 WebContentView #
其实倒也是可以把 webview 替换成 WebContentView。但 WebContentView 是属于主进程创建的,层级太高,会遮挡住其他元素,导致其他元素无法正常显示。若页面中有一些弹窗、提示框等元素,就会有显示问题。
为了让这些弹窗能够正常显示,我可能还得要监控弹窗的显示和隐藏事件,然后再决定是隐藏还是显示 WebContentView。处理起来,也相当麻烦。我目前的方案是,监控页面元素的变动,然后判断网页中是否有弹窗、提示框等元素,若有,就隐藏 WebContentView,若无,就显示 WebContentView。
const observer = new MutationObserver(() => {
const modals = Array.from(document.querySelectorAll(".ant-modal"));
const popovers = Array.from(document.querySelectorAll(".ant-popover"));
let isExisted =
modals.filter((modal) => {
return !modal.classList.contains("ant-modal-hidden") && !modal.classList.contains("ant-zoom-leave-active");
}).length > 0;
isExisted =
isExisted ||
popovers.filter((popover) => {
return !popover.classList.contains("ant-popover-hidden");
}).length > 0;
// 若有弹窗、提示框等元素,就隐藏 WebContentView
updateModalVisible(isExisted);
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
4. 总结 #
用 webview 标签 和 WebContentView 两种方式都各有优劣,只能说更看重哪种交互方式,进行取舍。
当然,或许有什么更好的解决方案,欢迎分享出来。