在线咨询
Skip to content

辅助 WebView ​

辅助 WebView(xunlei.runtime.webview)让微应用可以打开一个受控的浏览器页面,通过代码驱动它完成页面数据提取、表单操作等任务,相当于"内置的无头浏览器自动化能力"。

典型场景 ​

  • 页面数据提取:页面内容由脚本异步渲染、直接 fetch 拿不到最终结果时,在辅助 WebView 中加载页面,等待渲染完成后从 DOM 中提取数据;
  • 表单操作:自动填表、点击、等待元素出现。

辅助 WebView 数据提取流程:微应用打开辅助 WebView、加载页面、等待渲染、提取 DOM 数据并返回微应用

权限 ​

使用辅助 WebView 需要在 manifest 中声明 webview 权限:

json
{
  "permissions": ["webview"]
}

未声明时:

  • await xunlei.runtime.webview.isAvailable() 返回 false(可用于功能探测);
  • open() 调用返回 PERMISSION_DENIED。

打开页面 ​

ts
const page = await xunlei.runtime.webview.open({
  headless: false,   // 是否隐藏窗口(true 时不显示给用户)
  title: 'Example 页面',  // 窗口标题
  width: 480,        // 窗口尺寸
  height: 640,
  userAgent: '...',  // 自定义 UA(可选)
});
选项类型说明
headlessboolean无头模式,默认 false;需要用户交互(登录)时保持 false。非本地开发环境中,事件脚本打开的网页窗口会被强制隐藏
titlestring窗口标题
width / heightnumber窗口尺寸
userAgentstring自定义 User-Agent

WebviewPage API ​

open() 返回的 page 对象提供页面驱动力:

方法说明
on(event, handler)监听页面事件,返回取消监听的函数,见事件监听
goto(url, opts?)打开地址,opts.waitUntil 支持 'load' / 'domcontentloaded',opts.timeoutMs 控制超时
execute(scriptOrFn, ...args)在页面中执行脚本(字符串或函数),返回执行结果
addInitScript(script)在后续每次页面加载前注入脚本
click(selector, opts?)点击元素
type(selector, text, opts?)向输入框输入文本
focus(selector)聚焦元素
waitForSelector(selector, opts?)等待元素出现 / 消失(visible / hidden 可分别要求可见或隐藏),返回 boolean
waitForFunction(fn, ...args)等待自定义条件成立,返回函数结果
getCookies() / setCookie(c) / deleteCookie(c) / clearCookies()Cookie 管理
url()当前页面地址
content()页面 HTML 内容
close()关闭页面

超时行为:

  • waitForSelector 超时返回 false(不抛错);
  • waitForFunction 超时返回 null(不抛错);
  • 默认超时 10 秒,轮询间隔 100ms。

事件监听 ​

page.on(event, handler) 监听页面事件,Promise 完成表示监听已就绪,并返回取消监听的函数:

ts
const unsubscribe = await page.on('url-changed', ({ url, sameDocument }) => {
  xunlei.logger.info('页面地址变化', url, sameDocument);
});

// 不再需要时取消监听
await unsubscribe();
事件触发时机回调参数
url-changed实际地址发生变化,包含同一文档内的导航;不代表页面已加载完成{ url, sameDocument }
load主页面触发 window.load,即文档及阻塞 load 的资源加载结束;刷新页面会触发,仅 hash / History API 引起的同文档导航不触发{ url }
load-error页面加载失败{ url, message }
closed页面已关闭:用户关闭窗口,或通过 close() 关闭{ reason: 'user' | 'api' }

使用要点:

  • 导航和加载事件仅针对主页面,不包含 iframe;
  • 监听不回放当前状态:需要监听导航时,在 goto() 之前完成注册;
  • 页面关闭时先通知 closed 监听器,再自动释放全部监听,之后调用 unsubscribe() 也安全;
  • 回调仅用于通知,不能阻止导航;宿主不等待异步回调完成。

示例:提取动态渲染的页面数据 ​

页面内容由脚本异步渲染时,直接 fetch HTML 拿不到最终结果。在辅助 WebView 中等渲染完成后再从 DOM 提取:

ts
const page = await xunlei.runtime.webview.open({ headless: true });
try {
  await page.goto('https://example.com/report/123');

  // 等待异步渲染完成
  await page.waitForSelector('#result .item', { timeoutMs: 10_000 });

  // 渲染完成后从 DOM 中提取结构化数据
  const items = await page.execute<{ title: string; url: string }[]>(() =>
    Array.from(document.querySelectorAll('#result .item'), (el) => ({
      title: el.querySelector('.title')?.textContent?.trim() ?? '',
      url: el.querySelector('a')?.getAttribute('href') ?? '',
    })),
  );

  if (!items.length) throw new MessageError('未提取到数据');
  return items;
} finally {
  await page.close();
}

要点:

  • headless: true 不弹窗,整个过程对用户无感;
  • 先 waitForSelector 确认渲染完成,再 execute 提取,避免读到空内容;
  • 提取逻辑在目标页面中执行,可自由使用 document 查询。

示例:表单操作 ​

自动填表、提交并读取结果:

ts
const page = await xunlei.runtime.webview.open({
  title: '提交查询',
  width: 480,
  height: 640,
});
try {
  await page.goto('https://example.com/query');
  await page.waitForSelector('#query-form input[name="keyword"]');

  await page.type('#query-form input[name="keyword"]', '迅雷');
  await page.click('#query-form button[type="submit"]');

  // 等待结果渲染
  const ok = await page.waitForSelector('#result');
  if (!ok) throw new MessageError('查询失败');

  return await page.execute<string>(
    () => document.querySelector('#result')?.textContent?.trim() ?? '',
  );
} finally {
  await page.close();
}

生命周期与限制 ​

  • 辅助 WebView 的生命周期归属于创建它的运行环境:微应用窗口关闭、事件脚本结束、微应用重载 / 停用 / 卸载时,相关页面会被自动关闭,无需(也没有)批量关闭 API——但建议用完主动 close();
  • 辅助 WebView 页面不注入 xunlei 对象,页面内无法调用平台能力;
  • 辅助 WebView 有独立的会话环境,与微应用页面、与其它微应用互不共享 Cookie;卸载微应用时,运行时会清理其页面与辅助 WebView 会话中持久化的 Cookie、存储数据和缓存。

下一步 ​