Puppeteer BrowserContext.targets():获取并处理浏览器上下文中所有活跃 Target
【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer
BrowserContext.targets()是 Puppeteer 中浏览上下文(BrowserContext)上的一个同步方法,用于一次性拿到当前上下文内所有活跃的Target(页面、Web Worker、背景页等可调试对象)。本篇以 Puppeteer 仓库中的 API 文档为骨架,深入其 CDP 与 WebDriver BiDi 两套协议实现,讲清targets()的签名语义、返回值构成、与pages()/waitForTarget()的协作关系,帮助你在自动化脚本中正确枚举、过滤和等待上下文内的目标对象。
方法签名与返回值
根据 API 文档,该方法的定义如下:
class BrowserContext { abstract targets(): Target[]; }- 功能:获取当前浏览器上下文内所有活跃的 Target;
- 参数:无;
- 返回值:
Target[],一个包含该上下文中全部活跃 Target 实例的数组(同步返回,不产生 Promise); - 声明位置:抽象声明 位于
BrowserContext基类中,具体行为由各协议后端(CDP / BiDi)实现类覆写。
BrowserContext本身表示浏览器中相互隔离的用户环境:启动浏览器后至少存在一个默认上下文,其余可通过browser.createBrowserContext()创建(在 Chrome 中所有非默认上下文均为 incognito 模式)。每个上下文拥有独立的存储(cookies/localStorage 等),targets()正是以这个隔离边界为范围进行枚举的。
什么是 Target
理解targets()的返回值,先要看 Target 抽象类:它代表一个 CDP target,即“任何可以被调试的对象”,例如页面、frame 或 worker。Target 类型由TargetType枚举定义(定义位置):
| 枚举值 | 字面量 | 含义 |
|---|---|---|
PAGE | 'page' | 普通页面 |
BACKGROUND_PAGE | 'background_page' | 扩展/后台背景页 |
SERVICE_WORKER | 'service_worker' | Service Worker |
SHARED_WORKER | 'shared_worker' | Shared Worker |
BROWSER | 'browser' | 浏览器自身 |
WEBVIEW | 'webview' | WebView |
OTHER | 'other' | 其他类型 |
TAB | 'tab' | 内部使用的 tab 目标 |
每个 Target 实例提供了若干关键方法(见 Target 类 与 实现源码):
type():返回TargetType,是过滤targets()结果的首要依据;url():返回目标当前 URL;page():当类型为'page'、'webview'或'background_page'时返回Page,否则返回null;worker():当类型为'service_worker'或'shared_worker'时返回WebWorker,否则返回null;asPage():强制将任意类型的 target 当作 page 处理(适合处理other类型目标);opener():返回打开当前 target 的上级 target,顶层目标返回null;browserContext()/browser():返回所属上下文与浏览器,可用于反向归属判断。
典型用法是遍历并做类型分派:
const context = browser.defaultBrowserContext(); for (const target of context.targets()) { if (target.type() === 'page') { console.log(target.url()); } else if (target.type() === 'service_worker') { const worker = await target.worker(); // 处理 worker ... } }源码实现:CDP 后端的过滤链
在 CDP 实现中,CdpBrowserContext.targets()的实现只有一行过滤逻辑:
override targets(): CdpTarget[] { return this.#browser.targets().filter(target => { return target.browserContext() === this; }); }即:先从所属CdpBrowser拿到浏览器级全量 target,再按“目标所属上下文 === 当前上下文”做引用相等过滤。这就解释了Browser.targets()与BrowserContext.targets()的边界关系:前者覆盖所有上下文,后者是前者的子集。
而浏览器层的 CdpBrowser.targets() 还叠加了两道筛选条件:
override targets(): CdpTarget[] { return Array.from(this.#targetManager.getAvailableTargets().values()) .filter(target => target._isTargetExposed() && target._initializedDeferred.value() === InitializationStatus.SUCCESS ); }从源码结构看,只有同时满足“已被暴露(_isTargetExposed)”和“初始化成功(InitializationStatus.SUCCESS)”的目标才会出现在结果中。因此targets()返回的是一个稳定、已就绪的快照:正在初始化中或尚未暴露的目标不会混入,调用方无需再做初始化状态判断。
源码实现:BiDi 后端的目标集合
在 WebDriver BiDi 后端中,targets()的数据来源完全不同。BidiBrowserContext 内部维护了一张#targets映射表(结构定义):
readonly #targets = new Map< BidiPage, [ BidiPageTarget, Map<BidiFrame | BidiWebWorker, BidiFrameTarget | BidiWorkerTarget>, ] >(); override targets(): Target[] { return [...this.#targets.values()].flatMap(([target, frames]) => { return [target, ...frames.values()]; }); }即“页面 → [页面 Target, {frame/worker → frame/worker Target}]”的结构,targets()将其拍平为一维数组返回。这张表是随着页面事件动态维护的:在#createPage中,每当新浏览上下文出现时创建BidiPageTarget,帧挂载(FrameAttached)时注册BidiFrameTarget,worker 创建(WorkerCreated)时注册BidiWorkerTarget;对应地,TargetCreated、TargetChanged、TargetDestroyed事件会在创建、导航和销毁时通过trustedEmitter发出。BiDi 后端因此返回的 target 集合粒度更细——不仅包含页面目标,还包含每个 frame 与 worker 对应的 target 对象。
两种实现的差异提示了一点:targets()的具体元素构成与协议后端相关,跨后端编写脚本时尽量依赖type()、url()等抽象方法做判断,而不是假设元素数量或类型分布。
与 pages() 的关系:pages 是 targets 的投影
BrowserContext.pages()的文档说明“非可见页面(如background_page)不会出现在列表中,可用Target.page自行查找”。在 CDP 实现中可以看到pages()正是对targets()的投影(实现位置):
override async pages(includeAll = false): Promise<Page[]> { const pages = await Promise.all( this.targets() .filter(target => { return ( target.type() === 'page' || ((target.type() === 'other' || includeAll) && this.#browser._getIsPageTargetCallback()?.(target)) ); }) .map(target => target.page()), ); return pages.filter(page => !!page); }其规则是:只保留type() === 'page'的目标,外加经_getIsPageTargetCallback判定为页面性质的'other'目标(includeAll为true时放宽);随后调用target.page()并把返回null的条目剔除。因此当你需要拿到pages()遗漏的目标(例如后台页、无法直接映射为Page的对象)时,回退到targets()+asPage()是文档推荐的思路。
配合 waitForTarget() 等待新目标出现
targets()是同步快照,对于“目标稍后才出现”的场景(典型如window.open打开弹窗),应使用同类的waitForTarget():
async waitForTarget( predicate: (x: Target) => boolean | Promise<boolean>, options: WaitForTargetOptions = {}, ): Promise<Target> { const {timeout: ms = 30000} = options; return await firstValueFrom( merge( fromEmitterEvent(this, BrowserContextEvent.TargetCreated), fromEmitterEvent(this, BrowserContextEvent.TargetChanged), from(this.targets()), ).pipe(filterAsync(predicate), raceWith(timeout(ms))), ); }从实现可以看到三个关键细节:
- 默认超时 30 秒(
timeout: ms = 30000),可通过options.timeout覆盖; - 它把“
TargetCreated事件流 +TargetChanged事件流 +当前targets()快照”合并为一个数据流,也就是说已经存在且满足条件的目标会立即命中,无需等待新事件; - 支持同步或异步谓词(
filterAsync),因此可以在谓词内调用target.page()等异步方法做条件判断。
官方文档给出的示例是捕获window.open产生的新窗口 target:
await page.evaluate(() => window.open('https://www.example.com/')); const newWindowTarget = await context.waitForTarget( target => target.url() === 'https://www.example.com/', );仓库测试 test/src/target.test.ts 中有一个更完整的异步版本,用Promise.all同时发起等待与打开动作,谓词内通过target.page().then(...)比对 URL,并在{timeout: 3000}下断言新 page 与otherPage不是同一实例——这正是waitForTarget的推荐写法。而 枚举场景的用例 则验证了browser.targets()中应同时存在about:blank的 page 目标与browser类型目标,可作为targets()行为的对照基准。
使用建议与边界
- 作用域:
context.targets()只返回该上下文内的目标;跨上下文枚举请使用browser.targets()(Browser.targets 文档)。 - 快照语义:方法同步返回数组快照,不随后续页面打开/关闭而变化;实时追踪请订阅 BrowserContextEvent 中的
TargetCreated/TargetChanged/TargetDestroyed事件(BiDi 与 CDP 后端均会发出)。 - 就绪保证:在 CDP 后端中,结果已过滤掉未暴露或未初始化成功的目标(CdpBrowser.targets);BiDi 后端则随
#targets映射实时增删。 - 与页面列表的取舍:拿
Page对象用await context.pages();需要 worker、后台页或自定义过滤逻辑时,用targets()遍历后按type()分派page()/worker()/asPage()。
相关文件索引
| 内容 | 路径 |
|---|---|
| 方法 API 文档 | docs/api/puppeteer.browsercontext.targets.md |
| Target 类文档 | docs/api/puppeteer.target.md |
| 抽象声明与 waitForTarget | packages/puppeteer-core/src/api/BrowserContext.ts |
| Target 抽象类 | packages/puppeteer-core/src/api/Target.ts |
| CDP 上下文实现 | packages/puppeteer-core/src/cdp/BrowserContext.ts |
| CDP 浏览器实现 | packages/puppeteer-core/src/cdp/Browser.ts |
| BiDi 上下文实现 | packages/puppeteer-core/src/bidi/BrowserContext.ts |
| 行为测试 | test/src/target.test.ts |
【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考