首页
/ Puppeteer Browser.defaultBrowserContext() 深度解析:默认浏览器上下文的概念、用法与源码实现

Puppeteer Browser.defaultBrowserContext() 深度解析:默认浏览器上下文的概念、用法与源码实现

2026-09-07 16:29:37作者:俞予舒Fleming

Puppeteer 的 Browser.defaultBrowserContext()Browser 类上的一个抽象方法,用于获取浏览器的“默认浏览器上下文(Browser Context)”。浏览器启动后至少存在一个默认上下文,页面创建、Cookie 管理、权限授予等大量操作默认都发生在它之上;同时文档明确指出默认上下文无法被关闭。本文以 defaultBrowserContext API 文档 为核心,结合 puppeteer-core 源码 中 CDP 与 WebDriver BiDi 两套协议实现,讲清默认上下文的获取方式、隔离语义、与“不可关闭”约束的底层原因,以及它在实际自动化项目中的典型用法。

一、API 签名与基本语义

官方 API 文档(puppeteer.browser.defaultbrowsercontext.md)给出的方法签名如下:

class Browser {
  abstract defaultBrowserContext(): BrowserContext;
}
  • 调用方式:同步方法,无需 await,直接返回一个 BrowserContext 实例;
  • 返回值:当前浏览器对应的默认 BrowserContext 对象;
  • 关键约束(Remarks 原文):“The default browser context cannot be closed.”——默认浏览器上下文不能被关闭。

这一声明在抽象基类 Browser 的 JSDoc 中同样存在,见 Browser.ts 的 defaultBrowserContext 声明

/**
 * Gets the default {@link BrowserContext | browser context}.
 *
 * @remarks The default {@link BrowserContext | browser context} cannot be
 * closed.
 */
abstract defaultBrowserContext(): BrowserContext;

Browser 是抽象类,defaultBrowserContext() 在基类中只声明不实现,具体行为由各协议后端(CDP / BiDi)提供,这是理解下文实现细节的出发点。

二、理解前置:什么是浏览器上下文,默认上下文有何特殊

要正确使用 defaultBrowserContext(),需要先理解 BrowserContext 文档 中的几个核心设定:

  1. 上下文是用户级隔离单元:浏览器启动时至少有一个默认上下文,可通过 browser.createBrowserContext() 创建更多上下文,每个上下文拥有相互隔离的存储(cookies / localStorage 等);
  2. 非默认上下文在 Chrome 中都是 incognito(隐身)模式:文档 Remarks 明确写道,在 Chrome 中所有非默认上下文均为 incognito;而默认上下文本身“是否 incognito 取决于启动浏览器时是否传了 --incognito 参数”;
  3. 弹出窗口归属父页面的上下文:若页面通过 window.open 打开新页面,弹窗归属于父页面所在的浏览器上下文;
  4. 构造函数是内部的BrowserContext 的构造函数标记为 internal,第三方代码不应直接构造或继承它——你只能通过 browser.defaultBrowserContext() 获取默认实例,或通过 browser.createBrowserContext() 获得新实例。

创建新上下文的标准示例(摘自 BrowserContext 文档):

// Create a new browser context
const context = await browser.createBrowserContext();
// Create a new page inside context.
const page = await context.newPage();
// ... do stuff with page ...
await page.goto('https://example.com');
// Dispose context once it's no longer needed.
await context.close();

与之相对,获取默认上下文则是零参数的同步调用:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
// 获取默认上下文(同步)
const context = browser.defaultBrowserContext();

三、源码实现:CDP 后端如何维护默认上下文

puppeteer-core 的 CDP 实现在 cdp/Browser.ts 中,可以归纳出四点与默认上下文直接相关的机制。

3.1 默认上下文在 Browser 构造时就已创建

CdpBrowser 内部持有一个私有字段 #defaultContextBrowser.ts),并在构造函数中以 browserContextId = undefined 显式构造出来(构造逻辑):

this.#defaultContext = new CdpBrowserContext(
  this.#connection,
  this,
  undefined,   // browserContextId 为 undefined
  logger,
);
for (const contextId of contextIds) {
  this.#contexts.set(
    contextId,
    new CdpBrowserContext(this.#connection, this, contextId, logger),
  );
}

这里有个从源码结构看的关键设计:默认上下文的标识是 undefined,而不是某个具体的 browserContextId。它不进入 #contexts 这个存放“用户创建上下文”的 Map,而是作为独立字段存在。后续所有“按 id 找上下文”的查找逻辑,都会把“id 缺失或未命中”的情况兜底路由到默认上下文(见 target 路由逻辑):

const context =
  browserContextId && this.#contexts.has(browserContextId)
    ? this.#contexts.get(browserContextId)
    : this.#defaultContext;

也就是说,凡是未携带 browserContextId 的 CDP target(如初始 about:blank 页面、未指定上下文的打开请求),都会归入默认上下文管理。

3.2 browserContexts() 中默认上下文排在首位

browserContexts() 的 CDP 实现直接返回 [this.#defaultContext, ...Array.from(this.#contexts.values())]实现),随后 defaultBrowserContext() 简单地返回 this.#defaultContext

override browserContexts(): CdpBrowserContext[] {
  return [this.#defaultContext, ...Array.from(this.#contexts.values())];
}

override defaultBrowserContext(): CdpBrowserContext {
  return this.#defaultContext;
}

这与 Browser 抽象类的 browserContexts() 文档 中“新建浏览器会返回单个 BrowserContext 实例”的描述一致:新启动的浏览器恰好只有这一个默认上下文。

3.3 browser.newPage() 实际委托给默认上下文

Browser.newPage() 的 CDP 实现(newPage):

override async newPage(options?: CreatePageOptions): Promise<Page> {
  return await this.#defaultContext.newPage(options);
}

这印证了 Browser 抽象类文档newPage 的定义——“在默认浏览器上下文中创建新页面”。换言之,最常见的 const page = await browser.newPage() 写法,其页面天生就属于默认上下文,与 browser.defaultBrowserContext().newPage() 等价。

四、为什么“默认上下文不能关闭”:从 _disposeContext 看

文档中“默认浏览器上下文不能被关闭”的约束,在源码层面有清晰对应。CDP 后端通过 _disposeContext(contextId?) 销毁上下文(实现):

async _disposeContext(contextId?: string): Promise<void> {
  if (!contextId) {
    return;   // 无 id 即默认上下文:直接返回,不做任何清理
  }
  await this.#connection.send('Target.disposeBrowserContext', {
    browserContextId: contextId,
  });
  this.#contexts.delete(contextId);
}

由于默认上下文的 browserContextId 正是 undefined,这条早退分支使 Target.disposeBrowserContext 永远不会针对它发出——关闭操作对默认上下文静默无效,这正是文档 Remarks 所述行为的技术根源。同理,BrowserContext.close() 的文档 也把“默认浏览器上下文不能被关闭”写进了方法备注中(见 BrowserContext.ts 中 close 的 JSDoc)。

对使用者的实际含义:不要指望用 context.close() 来“清理”默认上下文的 Cookie 或状态;想清空状态应使用 deleteMatchingCookies()clearPermissionOverrides() 等按内容清理的 API,而清理整个浏览器会话则应 browser.close()

五、BiDi 后端的对应实现

Puppeteer 也支持 WebDriver BiDi 协议栈,其默认上下文实现位于 bidi/Browser.ts

override defaultBrowserContext(): BidiBrowserContext {
  return this.#browserContexts.get(this.#browserCore.defaultUserContext)!;
}

override newPage(options?: CreatePageOptions): Promise<Page> {
  return this.defaultBrowserContext().newPage(options);
}

从源码结构看,BiDi 后端的模型是“用户上下文(user context)”,它通过底层 browserCore.defaultUserContext 这个标识,在 #browserContexts 映射表中取出对应的 BidiBrowserContext。两套协议虽然实现路径不同(CDP 用 undefined id 兜底路由,BiDi 用 defaultUserContext 键查表),但对外的公共契约完全一致:defaultBrowserContext() 同步返回不可关闭的默认上下文,newPage() 委托到该上下文。因此面向 Browser 抽象类的代码在两种协议下行为一致。

六、Browser 上的 Cookie/权限快捷方法与默认上下文的关系

Browser 抽象类 上的一组方法被明确标注为“默认上下文的快捷方式(Shortcut for ...)”,它们全部直接转发到 this.defaultBrowserContext()

Browser 方法 等价调用
browser.cookies() browser.defaultBrowserContext().cookies()
browser.setCookie(...cookies) browser.defaultBrowserContext().setCookie(...cookies)
browser.deleteCookie(...cookies) browser.defaultBrowserContext().deleteCookie(...cookies)
browser.deleteMatchingCookies(...filters) browser.defaultBrowserContext().deleteMatchingCookies(...filters)
browser.setPermission(origin, ...) browser.defaultBrowserContext().setPermission(origin, ...)

源码示例(setPermission 快捷方法):

async setPermission(
  origin: string,
  ...permissions: Array<{
    permission: PermissionDescriptor;
    state: PermissionState;
  }>
): Promise<void> {
  return await this.defaultBrowserContext().setPermission(
    origin,
    ...permissions,
  );
}

这说明 defaultBrowserContext() 是 Puppeteer 中“未显式指定上下文时一切操作的落脚点”:浏览器级快捷 API 的默认作用域就是它。当项目里同时使用了多个上下文时,就需要注意 browser.cookies() 这类快捷方法只读写默认上下文的 Cookie,其他上下文里的 Cookie 必须通过 context.cookies() 单独获取。

七、BrowserContext 的关键属性与可调用能力

defaultBrowserContext() 返回的默认上下文与 createBrowserContext() 返回的上下文共享同一套 API(BrowserContext 类,继承自 EventEmitter<BrowserContextEvents>),可参考 BrowserContext API 文档

属性

属性 类型 说明
closed readonly boolean 该上下文是否已关闭
id readonly string | undefined 上下文标识;结合上文源码可知默认上下文在 CDP 实现中对应的 id 为 undefined

方法browser()(取回所属浏览器)、newPage(options)pages(includeAll)(列出打开的页面,非可见页面如 background_page 不在列表中)、targets()waitForTarget(predicate, options)cookies() / setCookie(cookies) / deleteCookie(cookies) / deleteMatchingCookies(filters)setPermission(origin, permissions) / clearPermissionOverrides()close(),以及 [disposeSymbol]() / [asyncDisposeSymbol]() 等资源释放支持。

结合默认上下文不可关闭的约束,一个典型的“多上下文隔离 + 复用默认上下文”的工作模式是:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();

// 1. 默认上下文:主流程,状态可跨会话持久
const defaultContext = browser.defaultBrowserContext();
const mainPage = await defaultContext.newPage();

// 2. 隔离上下文:用于不受主会话污染的一次性操作
const incognito = await browser.createBrowserContext();
const guestPage = await incognito.newPage();
await guestPage.goto('https://example.com');

// 3. 结束一次性上下文(默认上下文则不应 close)
await incognito.close();

await browser.close();

八、版本说明与适用前提

  • 本文基于当前仓库中 puppeteer-coreBrowser / BrowserContext 抽象层及其 CDP、BiDi 实现;指定关联文档为版本化 API 文档 version-25.8.0 下的 puppeteer.browser.defaultbrowsercontext.md,其内容与仓库内的 docs/api/puppeteer.browser.defaultbrowsercontext.md 同源(版本化文档由 website/materialize-docs.ts 在构建时从 docs/ 目录与发布标签物化生成),两者内容一致;
  • “默认上下文不可关闭”是跨协议的公共契约,由抽象基类文档与两个后端的实现共同保证;
  • 关于 --incognito 启动参数使默认上下文进入隐身模式的说法,来自 BrowserContext 文档 的 Remarks,适用于 Chrome;
  • BrowserContext 构造函数标记为内部实现细节,第三方代码应只通过 browser.defaultBrowserContext()browser.createBrowserContext() 获取上下文实例。

小结

Browser.defaultBrowserContext() 虽然只是一个无参同步方法,但它锚定了 Puppeteer 浏览器对象模型的中心:浏览器启动即拥有默认上下文,browser.newPage() 与浏览器级 Cookie/权限快捷方法都默认作用于它;CDP 实现以 undefinedbrowserContextId 作为默认上下文的标识并兜底路由所有未指定上下文的 target,BiDi 实现则以 defaultUserContext 键映射出对应对象;而“不可关闭”的约束在 _disposeContext 的早退分支中得到落实。理解这些,可以确保在多上下文场景下正确选择隔离策略、避免误用默认上下文的生命周期 API。

登录后查看全文
热门项目推荐
相关项目推荐