You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter WebView如何在页面加载初期注入window.ethereum

问题核心原因

在onPageFinished阶段注入window.ethereum时机过晚。Dapp的初始化脚本会在HTML文档解析阶段就同步检测window.ethereum对象是否存在,等页面完全加载完成后再挂载对象,Dapp早已走完钱包检测逻辑,不会再重新识别注入的对象。
另外你贴的代码里onPageFinished回调第一行直接写了return;,后续所有注入、favicon获取、授权弹窗的逻辑永远不会执行,先把这行调试用的代码删掉。

可行实现方案

方案1:使用官方webview_flutter的提前脚本注入能力(推荐,无需更换依赖)

webview_flutter 3.0及以上版本已经支持在页面加载的初始阶段注入JS,不需要等页面加载完成,具体实现步骤:

  1. 升级依赖到最新稳定版,确保支持UserScript相关API
  2. 不要在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 10:18:43