Flutter WebView如何在页面加载初期注入window.ethereum
问题核心原因
在onPageFinished阶段注入window.ethereum时机过晚。Dapp的初始化脚本会在HTML文档解析阶段就同步检测window.ethereum对象是否存在,等页面完全加载完成后再挂载对象,Dapp早已走完钱包检测逻辑,不会再重新识别注入的对象。
另外你贴的代码里onPageFinished回调第一行直接写了return;,后续所有注入、favicon获取、授权弹窗的逻辑永远不会执行,先把这行调试用的代码删掉。
可行实现方案
方案1:使用官方webview_flutter的提前脚本注入能力(推荐,无需更换依赖)
webview_flutter 3.0及以上版本已经支持在页面加载的初始阶段注入JS,不需要等页面加载完成,具体实现步骤:
- 升级依赖到最新稳定版,确保支持
UserScript相关API - 不要在
onPageStarted/onPageFinished里调用runJavascript注入provider,而是在onWebViewCreated拿到WebViewController实例后,立刻注册提前注入的脚本,指定注入时机为文档加载起始阶段,这个时机早于页面内任何JS脚本的执行,Dapp初始化时一定能读取到挂载的ethereum对象。
核心代码示例:
WebView( // 其他原有配置保留 onWebViewCreated: (controller) async { _controller = controller; // 注册JS通信通道,用于把Dapp的eth请求转发到Flutter层处理 await _controller.addJavaScriptChannel( 'EthereumChannel', onMessageReceived: (message) { // 这里处理Dapp发起的请求:连接钱包、签名、切链、转账等 final req = jsonDecode(message.message); // 处理完后把结果通过runJavascript回传给页面即可 }, ); // 核心:页面加载最开始就注入window.ethereum占位对象 await _controller.addUserScript( UserScript( injectionTime: UserScriptInjectionTime.atDocumentStart, isMainFrameOnly: true, // 仅主框架注入,避免iframe拿到provider带来安全问题 source: """ // 用defineProperty挂载对象,防止被页面脚本篡改覆盖 Object.defineProperty(window, 'ethereum', { value: { isMetaMask: true, // 适配大部分Dapp的MetaMask检测逻辑 chainId: '0x1', // 默认链ID,后续可动态更新 selectedAddress: null, isConnected: () => !!this.selectedAddress, request: async (args) => { // 把请求转发到Flutter层 return new Promise((resolve, reject) => { window.EthereumChannel.postMessage(JSON.stringify({ id: Date.now(), params: args })); // 这里加回调缓存,等Flutter返回结果后执行resolve/reject }); }, on: (eventName, cb) => { // 实现事件订阅逻辑,支持accountsChanged/chainChanged等标准事件 this._eventCbs = this._eventCbs || {}; this._eventCbs[eventName] = this._eventCbs[eventName] || []; this._eventCbs[eventName].push(cb); }, removeListener: function(eventName, cb) { // 实现取消订阅逻辑 if (!this._eventCbs || !this._eventCbs[eventName]) return; this._eventCbs[eventName] = this._eventCbs[eventName].filter(item => item !== cb); } }, writable: false, configurable: false, enumerable: true }); """, ), ); }, onPageFinished: (url) async { // 移除原有开头的return; // 这里只处理favicon获取、页面加载完成后的回调逻辑,不要在这里注入ethereum // 之前写的favicon获取、授权弹窗逻辑可以移到这里正常执行 // 用户授权后,可以通过runJavascript动态更新window.ethereum里的selectedAddress、chainId等属性,不需要重新挂载对象 } )
方案2:使用flutter_inappwebview插件(适配性更强,适合钱包类场景)
如果官方webview_flutter的注入能力满足不了需求,可以替换为flutter_inappwebview插件,它对脚本注入的时机控制更灵活,同样支持atDocumentStart注入,还内置了很多Web3适配常用的能力,目前绝大多数Flutter端的内置Dapp浏览器都是基于这个插件实现的,注入逻辑和上面的写法基本一致。
注意事项
- 不要等用户点击授权后再注入ethereum对象:必须在页面初始加载阶段就挂载符合EIP-1193标准的占位对象,Dapp检测到对象存在就会正常展示连接钱包按钮,等用户授权后再动态更新对象里的地址、链ID等信息即可。
- 之前尝试的手动触发
DOMContentLoaded/load事件没有效果:大部分Dapp的钱包检测逻辑是脚本加载后同步执行的,不会等DOM事件触发,后续重放事件无法让Dapp重新走初始化检测流程。 - 注入的provider必须严格遵循EIP-1193规范,否则会被Dapp识别为无效钱包对象。
内容的提问来源于stack exchange,提问作者Davy King
相关产品推荐
相关产品推荐

