tor-browser

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

Target.ts (8185B)


      1 /**
      2 * @license
      3 * Copyright 2019 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import type {Protocol} from 'devtools-protocol';
      8 
      9 import type {Browser} from '../api/Browser.js';
     10 import type {BrowserContext} from '../api/BrowserContext.js';
     11 import {PageEvent, type Page} from '../api/Page.js';
     12 import {Target, TargetType} from '../api/Target.js';
     13 import {debugError} from '../common/util.js';
     14 import type {Viewport} from '../common/Viewport.js';
     15 import {Deferred} from '../util/Deferred.js';
     16 
     17 import type {CdpCDPSession} from './CdpSession.js';
     18 import {CdpPage} from './Page.js';
     19 import type {TargetManager} from './TargetManager.js';
     20 import {CdpWebWorker} from './WebWorker.js';
     21 
     22 /**
     23 * @internal
     24 */
     25 export enum InitializationStatus {
     26  SUCCESS = 'success',
     27  ABORTED = 'aborted',
     28 }
     29 
     30 /**
     31 * @internal
     32 */
     33 export class CdpTarget extends Target {
     34  #browserContext?: BrowserContext;
     35  #session?: CdpCDPSession;
     36  #targetInfo: Protocol.Target.TargetInfo;
     37  #targetManager?: TargetManager;
     38  #sessionFactory:
     39    | ((isAutoAttachEmulated: boolean) => Promise<CdpCDPSession>)
     40    | undefined;
     41  #childTargets = new Set<CdpTarget>();
     42  _initializedDeferred = Deferred.create<InitializationStatus>();
     43  _isClosedDeferred = Deferred.create<void>();
     44  _targetId: string;
     45 
     46  /**
     47   * To initialize the target for use, call initialize.
     48   *
     49   * @internal
     50   */
     51  constructor(
     52    targetInfo: Protocol.Target.TargetInfo,
     53    session: CdpCDPSession | undefined,
     54    browserContext: BrowserContext | undefined,
     55    targetManager: TargetManager | undefined,
     56    sessionFactory:
     57      | ((isAutoAttachEmulated: boolean) => Promise<CdpCDPSession>)
     58      | undefined,
     59  ) {
     60    super();
     61    this.#session = session;
     62    this.#targetManager = targetManager;
     63    this.#targetInfo = targetInfo;
     64    this.#browserContext = browserContext;
     65    this._targetId = targetInfo.targetId;
     66    this.#sessionFactory = sessionFactory;
     67    if (this.#session) {
     68      this.#session.setTarget(this);
     69    }
     70  }
     71 
     72  override async asPage(): Promise<Page> {
     73    const session = this._session();
     74    if (!session) {
     75      return await this.createCDPSession().then(client => {
     76        return CdpPage._create(client, this, null);
     77      });
     78    }
     79    return await CdpPage._create(session, this, null);
     80  }
     81 
     82  _subtype(): string | undefined {
     83    return this.#targetInfo.subtype;
     84  }
     85 
     86  _session(): CdpCDPSession | undefined {
     87    return this.#session;
     88  }
     89 
     90  _addChildTarget(target: CdpTarget): void {
     91    this.#childTargets.add(target);
     92  }
     93 
     94  _removeChildTarget(target: CdpTarget): void {
     95    this.#childTargets.delete(target);
     96  }
     97 
     98  _childTargets(): ReadonlySet<CdpTarget> {
     99    return this.#childTargets;
    100  }
    101 
    102  protected _sessionFactory(): (
    103    isAutoAttachEmulated: boolean,
    104  ) => Promise<CdpCDPSession> {
    105    if (!this.#sessionFactory) {
    106      throw new Error('sessionFactory is not initialized');
    107    }
    108    return this.#sessionFactory;
    109  }
    110 
    111  override createCDPSession(): Promise<CdpCDPSession> {
    112    if (!this.#sessionFactory) {
    113      throw new Error('sessionFactory is not initialized');
    114    }
    115    return this.#sessionFactory(false).then(session => {
    116      session.setTarget(this);
    117      return session;
    118    });
    119  }
    120 
    121  override url(): string {
    122    return this.#targetInfo.url;
    123  }
    124 
    125  override type(): TargetType {
    126    const type = this.#targetInfo.type;
    127    switch (type) {
    128      case 'page':
    129        return TargetType.PAGE;
    130      case 'background_page':
    131        return TargetType.BACKGROUND_PAGE;
    132      case 'service_worker':
    133        return TargetType.SERVICE_WORKER;
    134      case 'shared_worker':
    135        return TargetType.SHARED_WORKER;
    136      case 'browser':
    137        return TargetType.BROWSER;
    138      case 'webview':
    139        return TargetType.WEBVIEW;
    140      case 'tab':
    141        return TargetType.TAB;
    142      default:
    143        return TargetType.OTHER;
    144    }
    145  }
    146 
    147  _targetManager(): TargetManager {
    148    if (!this.#targetManager) {
    149      throw new Error('targetManager is not initialized');
    150    }
    151    return this.#targetManager;
    152  }
    153 
    154  _getTargetInfo(): Protocol.Target.TargetInfo {
    155    return this.#targetInfo;
    156  }
    157 
    158  override browser(): Browser {
    159    if (!this.#browserContext) {
    160      throw new Error('browserContext is not initialized');
    161    }
    162    return this.#browserContext.browser();
    163  }
    164 
    165  override browserContext(): BrowserContext {
    166    if (!this.#browserContext) {
    167      throw new Error('browserContext is not initialized');
    168    }
    169    return this.#browserContext;
    170  }
    171 
    172  override opener(): Target | undefined {
    173    const {openerId} = this.#targetInfo;
    174    if (!openerId) {
    175      return;
    176    }
    177    return this.browser()
    178      .targets()
    179      .find(target => {
    180        return (target as CdpTarget)._targetId === openerId;
    181      });
    182  }
    183 
    184  _targetInfoChanged(targetInfo: Protocol.Target.TargetInfo): void {
    185    this.#targetInfo = targetInfo;
    186    this._checkIfInitialized();
    187  }
    188 
    189  _initialize(): void {
    190    this._initializedDeferred.resolve(InitializationStatus.SUCCESS);
    191  }
    192 
    193  _isTargetExposed(): boolean {
    194    return this.type() !== TargetType.TAB && !this._subtype();
    195  }
    196 
    197  protected _checkIfInitialized(): void {
    198    if (!this._initializedDeferred.resolved()) {
    199      this._initializedDeferred.resolve(InitializationStatus.SUCCESS);
    200    }
    201  }
    202 }
    203 
    204 /**
    205 * @internal
    206 */
    207 export class PageTarget extends CdpTarget {
    208  #defaultViewport?: Viewport;
    209  protected pagePromise?: Promise<Page>;
    210 
    211  constructor(
    212    targetInfo: Protocol.Target.TargetInfo,
    213    session: CdpCDPSession | undefined,
    214    browserContext: BrowserContext,
    215    targetManager: TargetManager,
    216    sessionFactory: (isAutoAttachEmulated: boolean) => Promise<CdpCDPSession>,
    217    defaultViewport: Viewport | null,
    218  ) {
    219    super(targetInfo, session, browserContext, targetManager, sessionFactory);
    220    this.#defaultViewport = defaultViewport ?? undefined;
    221  }
    222 
    223  override _initialize(): void {
    224    this._initializedDeferred
    225      .valueOrThrow()
    226      .then(async result => {
    227        if (result === InitializationStatus.ABORTED) {
    228          return;
    229        }
    230        const opener = this.opener();
    231        if (!(opener instanceof PageTarget)) {
    232          return;
    233        }
    234        if (!opener || !opener.pagePromise || this.type() !== 'page') {
    235          return true;
    236        }
    237        const openerPage = await opener.pagePromise;
    238        if (!openerPage.listenerCount(PageEvent.Popup)) {
    239          return true;
    240        }
    241        const popupPage = await this.page();
    242        openerPage.emit(PageEvent.Popup, popupPage);
    243        return true;
    244      })
    245      .catch(debugError);
    246    this._checkIfInitialized();
    247  }
    248 
    249  override async page(): Promise<Page | null> {
    250    if (!this.pagePromise) {
    251      const session = this._session();
    252      this.pagePromise = (
    253        session
    254          ? Promise.resolve(session)
    255          : this._sessionFactory()(/* isAutoAttachEmulated=*/ false)
    256      ).then(client => {
    257        return CdpPage._create(client, this, this.#defaultViewport ?? null);
    258      });
    259    }
    260    return (await this.pagePromise) ?? null;
    261  }
    262 
    263  override _checkIfInitialized(): void {
    264    if (this._initializedDeferred.resolved()) {
    265      return;
    266    }
    267    if (this._getTargetInfo().url !== '') {
    268      this._initializedDeferred.resolve(InitializationStatus.SUCCESS);
    269    }
    270  }
    271 }
    272 
    273 /**
    274 * @internal
    275 */
    276 export class DevToolsTarget extends PageTarget {}
    277 
    278 /**
    279 * @internal
    280 */
    281 export class WorkerTarget extends CdpTarget {
    282  #workerPromise?: Promise<CdpWebWorker>;
    283 
    284  override async worker(): Promise<CdpWebWorker | null> {
    285    if (!this.#workerPromise) {
    286      const session = this._session();
    287      // TODO(einbinder): Make workers send their console logs.
    288      this.#workerPromise = (
    289        session
    290          ? Promise.resolve(session)
    291          : this._sessionFactory()(/* isAutoAttachEmulated=*/ false)
    292      ).then(client => {
    293        return new CdpWebWorker(
    294          client,
    295          this._getTargetInfo().url,
    296          this._targetId,
    297          this.type(),
    298          () => {} /* consoleAPICalled */,
    299          () => {} /* exceptionThrown */,
    300          undefined /* networkManager */,
    301        );
    302      });
    303    }
    304    return await this.#workerPromise;
    305  }
    306 }
    307 
    308 /**
    309 * @internal
    310 */
    311 export class OtherTarget extends CdpTarget {}