tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PuppeteerNode.ts (11080B)


      1 /**
      2 * @license
      3 * Copyright 2020 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import {
      8  Browser as browsers_SupportedBrowser,
      9  resolveBuildId,
     10  detectBrowserPlatform,
     11  getInstalledBrowsers,
     12  uninstall,
     13 } from '@puppeteer/browsers';
     14 
     15 import type {Browser} from '../api/Browser.js';
     16 import type {Configuration} from '../common/Configuration.js';
     17 import type {ConnectOptions} from '../common/ConnectOptions.js';
     18 import {type CommonPuppeteerSettings, Puppeteer} from '../common/Puppeteer.js';
     19 import type {SupportedBrowser} from '../common/SupportedBrowser.js';
     20 import {PUPPETEER_REVISIONS} from '../revisions.js';
     21 
     22 import type {BrowserLauncher} from './BrowserLauncher.js';
     23 import {ChromeLauncher} from './ChromeLauncher.js';
     24 import {FirefoxLauncher} from './FirefoxLauncher.js';
     25 import type {ChromeReleaseChannel, LaunchOptions} from './LaunchOptions.js';
     26 
     27 /**
     28 * Extends the main {@link Puppeteer} class with Node specific behaviour for
     29 * fetching and downloading browsers.
     30 *
     31 * If you're using Puppeteer in a Node environment, this is the class you'll get
     32 * when you run `require('puppeteer')` (or the equivalent ES `import`).
     33 *
     34 * @remarks
     35 * The most common method to use is {@link PuppeteerNode.launch | launch}, which
     36 * is used to launch and connect to a new browser instance.
     37 *
     38 * See {@link Puppeteer | the main Puppeteer class} for methods common to all
     39 * environments, such as {@link Puppeteer.connect}.
     40 *
     41 * @example
     42 * The following is a typical example of using Puppeteer to drive automation:
     43 *
     44 * ```ts
     45 * import puppeteer from 'puppeteer';
     46 *
     47 * (async () => {
     48 *   const browser = await puppeteer.launch();
     49 *   const page = await browser.newPage();
     50 *   await page.goto('https://www.google.com');
     51 *   // other actions...
     52 *   await browser.close();
     53 * })();
     54 * ```
     55 *
     56 * Once you have created a `page` you have access to a large API to interact
     57 * with the page, navigate, or find certain elements in that page.
     58 * The {@link Page | `page` documentation} lists all the available methods.
     59 *
     60 * @public
     61 */
     62 export class PuppeteerNode extends Puppeteer {
     63  #launcher?: BrowserLauncher;
     64  #lastLaunchedBrowser?: SupportedBrowser;
     65 
     66  /**
     67   * @internal
     68   */
     69  defaultBrowserRevision: string;
     70 
     71  /**
     72   * @internal
     73   */
     74  configuration: Configuration = {};
     75 
     76  /**
     77   * @internal
     78   */
     79  constructor(
     80    settings: {
     81      configuration?: Configuration;
     82    } & CommonPuppeteerSettings,
     83  ) {
     84    const {configuration, ...commonSettings} = settings;
     85    super(commonSettings);
     86    if (configuration) {
     87      this.configuration = configuration;
     88    }
     89    switch (this.configuration.defaultBrowser) {
     90      case 'firefox':
     91        this.defaultBrowserRevision = PUPPETEER_REVISIONS.firefox;
     92        break;
     93      default:
     94        this.configuration.defaultBrowser = 'chrome';
     95        this.defaultBrowserRevision = PUPPETEER_REVISIONS.chrome;
     96        break;
     97    }
     98 
     99    this.connect = this.connect.bind(this);
    100    this.launch = this.launch.bind(this);
    101    this.executablePath = this.executablePath.bind(this);
    102    this.defaultArgs = this.defaultArgs.bind(this);
    103    this.trimCache = this.trimCache.bind(this);
    104  }
    105 
    106  /**
    107   * This method attaches Puppeteer to an existing browser instance.
    108   *
    109   * @param options - Set of configurable options to set on the browser.
    110   * @returns Promise which resolves to browser instance.
    111   */
    112  override connect(options: ConnectOptions): Promise<Browser> {
    113    return super.connect(options);
    114  }
    115 
    116  /**
    117   * Launches a browser instance with given arguments and options when
    118   * specified.
    119   *
    120   * When using with `puppeteer-core`,
    121   * {@link LaunchOptions.executablePath | options.executablePath} or
    122   * {@link LaunchOptions.channel | options.channel} must be provided.
    123   *
    124   * @example
    125   * You can use {@link LaunchOptions.ignoreDefaultArgs | options.ignoreDefaultArgs}
    126   * to filter out `--mute-audio` from default arguments:
    127   *
    128   * ```ts
    129   * const browser = await puppeteer.launch({
    130   *   ignoreDefaultArgs: ['--mute-audio'],
    131   * });
    132   * ```
    133   *
    134   * @remarks
    135   * Puppeteer can also be used to control the Chrome browser, but it works best
    136   * with the version of Chrome for Testing downloaded by default.
    137   * There is no guarantee it will work with any other version. If Google Chrome
    138   * (rather than Chrome for Testing) is preferred, a
    139   * {@link https://www.google.com/chrome/browser/canary.html | Chrome Canary}
    140   * or
    141   * {@link https://www.chromium.org/getting-involved/dev-channel | Dev Channel}
    142   * build is suggested. See
    143   * {@link https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/ | this article}
    144   * for a description of the differences between Chromium and Chrome.
    145   * {@link https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md | This article}
    146   * describes some differences for Linux users. See
    147   * {@link https://developer.chrome.com/blog/chrome-for-testing/ | this doc} for the description
    148   * of Chrome for Testing.
    149   *
    150   * @param options - Options to configure launching behavior.
    151   */
    152  launch(options: LaunchOptions = {}): Promise<Browser> {
    153    const {browser = this.defaultBrowser} = options;
    154    this.#lastLaunchedBrowser = browser;
    155    switch (browser) {
    156      case 'chrome':
    157        this.defaultBrowserRevision = PUPPETEER_REVISIONS.chrome;
    158        break;
    159      case 'firefox':
    160        this.defaultBrowserRevision = PUPPETEER_REVISIONS.firefox;
    161        break;
    162      default:
    163        throw new Error(`Unknown product: ${browser}`);
    164    }
    165    this.#launcher = this.#getLauncher(browser);
    166    return this.#launcher.launch(options);
    167  }
    168 
    169  /**
    170   * @internal
    171   */
    172  #getLauncher(browser: SupportedBrowser): BrowserLauncher {
    173    if (this.#launcher && this.#launcher.browser === browser) {
    174      return this.#launcher;
    175    }
    176    switch (browser) {
    177      case 'chrome':
    178        return new ChromeLauncher(this);
    179      case 'firefox':
    180        return new FirefoxLauncher(this);
    181      default:
    182        throw new Error(`Unknown product: ${browser}`);
    183    }
    184  }
    185 
    186  /**
    187   * The default executable path for a given ChromeReleaseChannel.
    188   */
    189  executablePath(channel: ChromeReleaseChannel): string;
    190  /**
    191   * The default executable path given LaunchOptions.
    192   */
    193  executablePath(options: LaunchOptions): string;
    194  /**
    195   * The default executable path.
    196   */
    197  executablePath(): string;
    198  executablePath(optsOrChannel?: ChromeReleaseChannel | LaunchOptions): string {
    199    if (optsOrChannel === undefined) {
    200      return this.#getLauncher(this.lastLaunchedBrowser).executablePath(
    201        undefined,
    202        /* validatePath= */ false,
    203      );
    204    }
    205    if (typeof optsOrChannel === 'string') {
    206      return this.#getLauncher('chrome').executablePath(
    207        optsOrChannel,
    208        /* validatePath= */ false,
    209      );
    210    }
    211    return this.#getLauncher(
    212      optsOrChannel.browser ?? this.lastLaunchedBrowser,
    213    ).resolveExecutablePath(optsOrChannel.headless, /* validatePath= */ false);
    214  }
    215 
    216  /**
    217   * @internal
    218   */
    219  get browserVersion(): string {
    220    return (
    221      this.configuration?.[this.lastLaunchedBrowser]?.version ??
    222      this.defaultBrowserRevision!
    223    );
    224  }
    225 
    226  /**
    227   * The default download path for puppeteer. For puppeteer-core, this
    228   * code should never be called as it is never defined.
    229   *
    230   * @internal
    231   */
    232  get defaultDownloadPath(): string | undefined {
    233    return this.configuration.cacheDirectory;
    234  }
    235 
    236  /**
    237   * The name of the browser that was last launched.
    238   */
    239  get lastLaunchedBrowser(): SupportedBrowser {
    240    return this.#lastLaunchedBrowser ?? this.defaultBrowser;
    241  }
    242 
    243  /**
    244   * The name of the browser that will be launched by default. For
    245   * `puppeteer`, this is influenced by your configuration. Otherwise, it's
    246   * `chrome`.
    247   */
    248  get defaultBrowser(): SupportedBrowser {
    249    return this.configuration.defaultBrowser ?? 'chrome';
    250  }
    251 
    252  /**
    253   * @deprecated Do not use as this field as it does not take into account
    254   * multiple browsers of different types. Use
    255   * {@link PuppeteerNode.defaultBrowser | defaultBrowser} or
    256   * {@link PuppeteerNode.lastLaunchedBrowser | lastLaunchedBrowser}.
    257   *
    258   * @returns The name of the browser that is under automation.
    259   */
    260  get product(): string {
    261    return this.lastLaunchedBrowser;
    262  }
    263 
    264  /**
    265   * @param options - Set of configurable options to set on the browser.
    266   *
    267   * @returns The default arguments that the browser will be launched with.
    268   */
    269  defaultArgs(options: LaunchOptions = {}): string[] {
    270    return this.#getLauncher(
    271      options.browser ?? this.lastLaunchedBrowser,
    272    ).defaultArgs(options);
    273  }
    274 
    275  /**
    276   * Removes all non-current Firefox and Chrome binaries in the cache directory
    277   * identified by the provided Puppeteer configuration. The current browser
    278   * version is determined by resolving PUPPETEER_REVISIONS from Puppeteer
    279   * unless `configuration.browserRevision` is provided.
    280   *
    281   * @remarks
    282   *
    283   * Note that the method does not check if any other Puppeteer versions
    284   * installed on the host that use the same cache directory require the
    285   * non-current binaries.
    286   *
    287   * @public
    288   */
    289  async trimCache(): Promise<void> {
    290    const platform = detectBrowserPlatform();
    291    if (!platform) {
    292      throw new Error('The current platform is not supported.');
    293    }
    294 
    295    const cacheDir = this.configuration.cacheDirectory!;
    296    const installedBrowsers = await getInstalledBrowsers({
    297      cacheDir,
    298    });
    299 
    300    const puppeteerBrowsers: Array<{
    301      product: SupportedBrowser;
    302      browser: browsers_SupportedBrowser;
    303      currentBuildId: string;
    304    }> = [
    305      {
    306        product: 'chrome',
    307        browser: browsers_SupportedBrowser.CHROME,
    308        currentBuildId: '',
    309      },
    310      {
    311        product: 'firefox',
    312        browser: browsers_SupportedBrowser.FIREFOX,
    313        currentBuildId: '',
    314      },
    315    ];
    316 
    317    // Resolve current buildIds.
    318    for (const item of puppeteerBrowsers) {
    319      const tag =
    320        this.configuration?.[item.product]?.version ??
    321        PUPPETEER_REVISIONS[item.product];
    322 
    323      item.currentBuildId = await resolveBuildId(item.browser, platform, tag);
    324    }
    325 
    326    const currentBrowserBuilds = new Set(
    327      puppeteerBrowsers.map(browser => {
    328        return `${browser.browser}_${browser.currentBuildId}`;
    329      }),
    330    );
    331 
    332    const currentBrowsers = new Set(
    333      puppeteerBrowsers.map(browser => {
    334        return browser.browser;
    335      }),
    336    );
    337 
    338    for (const installedBrowser of installedBrowsers) {
    339      // Don't uninstall browsers that are not managed by Puppeteer yet.
    340      if (!currentBrowsers.has(installedBrowser.browser)) {
    341        continue;
    342      }
    343      // Keep the browser build used by the current Puppeteer installation.
    344      if (
    345        currentBrowserBuilds.has(
    346          `${installedBrowser.browser}_${installedBrowser.buildId}`,
    347        )
    348      ) {
    349        continue;
    350      }
    351 
    352      await uninstall({
    353        browser: installedBrowser.browser,
    354        platform,
    355        cacheDir,
    356        buildId: installedBrowser.buildId,
    357      });
    358    }
    359  }
    360 }