Home Reference Source

lib/browser/BrowserContext.js

const util = require('util')
const EventEmitter = require('eventemitter3')
const { assert } = require('../helper')
const { exposeCDPOnTarget } = require('../__shared')

class BrowserContext extends EventEmitter {
  /**
   * @param {!Chrome|CRIConnection} connection
   * @param {!Browser} browser
   * @param {?string} contextId
   */
  constructor (connection, browser, contextId) {
    super()
    /**
     * @type {!Chrome|CRIConnection}
     * @private
     */
    this._connection = connection
    /**
     * @type {!Browser}
     * @private
     */
    this._browser = browser

    /**
     * @type {?string}
     * @private
     */
    this._id = contextId
  }

  /**
   * An array of all active targets inside the browser context
   * @return {Array<Target>} target
   */
  targets () {
    return this._browser
      .targets()
      .filter(target => target.browserContext() === this)
  }

  /**
   * This searches for a target in this specific browser context.
   * @param {function(target: Target):boolean} predicate
   * @param {{timeout?: number}} [options]
   * @return {Promise<Target>}
   * @example
   * await page.evaluate(() => window.open('https://www.example.com/'))
   * const newWindowTarget = await browserContext.waitForTarget(target => target.url() === 'https://www.example.com/')
   */
  waitForTarget (predicate, options) {
    return this._browser.waitForTarget(
      target => target.browserContext() === this && predicate(target),
      options
    )
  }

  /**
   * Returns whether BrowserContext is incognito. The default browser context is the only non-incognito browser context
   * @return {boolean}
   */
  isIncognito () {
    return !!this._id
  }

  /**
   * Creates a new page in the browser context
   * @return {Promise<Page>}
   */
  newPage () {
    return this._browser._createPageInContext(this._id)
  }

  /**
   * The browser this browser context belongs to
   * @return {!Browser}
   */
  browser () {
    return this._browser
  }

  /**
   * Closes the target specified by the targetId. If the target is a page that gets closed too.
   * @param {string} targetId
   * @return {Promise<boolean>}
   * @since chrome-remote-interface-extra
   */
  closeTarget (targetId) {
    return this._browser.closeTarget(targetId)
  }

  /**
   * Get Chrome histograms. EXPERIMENTAL
   * Optional options:
   *  - query: Requested substring in name. Only histograms which have query as a substring in their name are extracted.
   *    An empty or absent query returns all histograms.
   *  - delta: If true, retrieve delta since last call
   * @param {BrowserHistogramQuery} [options]
   * @return {Promise<Array<CDPHistogram>>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/Browser#method-getHistograms
   * @since chrome-remote-interface-extra
   */
  getHistograms (options) {
    return this._browser.getHistograms(options)
  }

  /**
   * Get a Chrome histogram by name. EXPERIMENTAL
   * @param {string} name - Requested histogram name
   * @param {boolean} [delta] - If true, retrieve delta since last call
   * @return {Promise<CDPHistogram>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/Browser#method-getHistogram
   * @since chrome-remote-interface-extra
   */
  getHistogram (name, delta) {
    return this._browser.getHistogram(name, delta)
  }

  /**
   * Get position and size of the browser window. EXPERIMENTAL
   * @param {number} windowId
   * @return {Promise<WindowBounds>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/Browser#method-getWindowBounds
   * @since chrome-remote-interface-extra
   */
  getWindowBounds (windowId) {
    return this._browser.getWindowBounds(windowId)
  }

  /**
   * Get the browser window that contains the target. EXPERIMENTAL
   * @param {string} [targetId] - Optional target id of the target to receive the window id and its bound for.
   * If called as a part of the session, associated targetId is used.
   * @return {Promise<{bounds: WindowBounds, windowId: number}>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/Browser#method-getWindowForTarget
   * @since chrome-remote-interface-extra
   */
  getWindowForTarget (targetId) {
    return this._browser.getWindowForTarget(targetId)
  }

  /**
   * Set position and/or size of the browser window. EXPERIMENTAL
   * @param {number} windowId - An browser window id
   * @param {WindowBounds} bounds - New window bounds. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'. Leaves unspecified fields unchanged.
   * @return {Promise<void>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/Browser#method-setWindowBounds
   * @since chrome-remote-interface-extra
   */
  async setWindowBounds (windowId, bounds) {
    return this._browser.setWindowBounds(windowId, bounds)
  }

  /**
   * An array of all pages inside the browser context
   * @return {Promise<Array<Page>>}
   */
  async pages () {
    const pages = await Promise.all(
      this.targets()
        .filter(target => target.type() === 'page')
        .map(target => target.page())
    )
    return pages.filter(page => !!page)
  }

  /**
   * Inject object to the target's main frame that provides a communication channel with browser target.
   *
   * Injected object will be available as window[bindingName].
   *
   * The object has the following API:
   *  * binding.send(json) - a method to send messages over the remote debugging protocol
   *  * binding.onmessage = json => handleMessage(json) - a callback that will be called for the protocol notifications and command responses.
   *
   * EXPERIMENTAL
   * @param {string} targetId
   * @param {string} [bindingName] - Binding name, 'cdp' if not specified
   * @return {Promise<void>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/Target#method-exposeDevToolsProtocol
   * @since chrome-remote-interface-extra
   */
  async exposeCDPOnTarget (targetId, bindingName) {
    await exposeCDPOnTarget(this._connection, targetId, bindingName)
  }

  /**
   * Grant specific permissions to the given origin and reject all others. EXPERIMENTAL
   * @param {string} origin - The origin these permissions will be granted for
   * @param {Array<string>} permissions - Array of permission overrides
   * @see https://chromedevtools.github.io/devtools-protocol/tot/Browser#method-grantPermissions
   * @example
   * const context = browser.defaultBrowserContext();
   * context.overridePermissions('https://example.com', ['clipboard-read']);
   * // do stuff ..
   * context.clearPermissionOverrides();
   */
  async overridePermissions (origin, permissions) {
    await this._browser.grantPermissions(
      origin,
      permissions,
      this._id || undefined
    )
  }

  /**
   * Reset all permissions overrides for this browser context
   * @return {Promise<void>}
   * @see https://chromedevtools.github.io/devtools-protocol/tot/Browser#method-resetPermissions
   */
  async clearPermissionOverrides () {
    await this._browser.resetPermissions(this._id || undefined)
  }

  /**
   * Closes the browser context. All the targets that belong to the browser context will be closed.
   * @return {Promise<void>}
   */
  async close () {
    assert(this._id, 'Non-incognito profiles cannot be closed!')
    await this._browser._disposeContext(this._id)
  }

  /**
   * @return {string}
   */
  toString () {
    return util.inspect(this, { depth: null })
  }

  /** @ignore */
  // eslint-disable-next-line space-before-function-paren
  [util.inspect.custom](depth, options) {
    if (depth < 0) {
      return options.stylize('[BrowserContext]', 'special')
    }

    const newOptions = Object.assign({}, options, {
      depth: options.depth == null ? null : options.depth - 1
    })
    const inner = util.inspect({ id: this._id }, newOptions)
    return `${options.stylize('BrowserContext', 'special')} ${inner}`
  }
}

/**
 * @type {BrowserContext}
 */
module.exports = BrowserContext