diff options
Diffstat (limited to 'remote/test/puppeteer/packages/puppeteer-core/src/common/bidi/BidiOverCDP.ts')
-rw-r--r-- | remote/test/puppeteer/packages/puppeteer-core/src/common/bidi/BidiOverCDP.ts | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/bidi/BidiOverCDP.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/bidi/BidiOverCDP.ts new file mode 100644 index 0000000000..1f965c56ab --- /dev/null +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/bidi/BidiOverCDP.ts @@ -0,0 +1,190 @@ +/** + * Copyright 2023 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as BidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/bidiMapper.js'; +import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; +import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; + +import {CDPSession, Connection as CDPPPtrConnection} from '../Connection.js'; +import {Handler} from '../EventEmitter.js'; + +import {Connection as BidiPPtrConnection} from './Connection.js'; + +type CdpEvents = { + [Property in keyof ProtocolMapping.Events]: ProtocolMapping.Events[Property][0]; +}; + +/** + * @internal + */ +export async function connectBidiOverCDP( + cdp: CDPPPtrConnection +): Promise<BidiPPtrConnection> { + const transportBiDi = new NoOpTransport(); + const cdpConnectionAdapter = new CDPConnectionAdapter(cdp); + const pptrTransport = { + send(message: string): void { + // Forwards a BiDi command sent by Puppeteer to the input of the BidiServer. + transportBiDi.emitMessage(JSON.parse(message)); + }, + close(): void { + bidiServer.close(); + cdpConnectionAdapter.close(); + }, + onmessage(_message: string): void { + // The method is overridden by the Connection. + }, + }; + transportBiDi.on('bidiResponse', (message: object) => { + // Forwards a BiDi event sent by BidiServer to Puppeteer. + pptrTransport.onmessage(JSON.stringify(message)); + }); + const pptrBiDiConnection = new BidiPPtrConnection(pptrTransport); + const bidiServer = await BidiMapper.BidiServer.createAndStart( + transportBiDi, + cdpConnectionAdapter, + '' + ); + return pptrBiDiConnection; +} + +/** + * Manages CDPSessions for BidiServer. + * @internal + */ +class CDPConnectionAdapter { + #cdp: CDPPPtrConnection; + #adapters = new Map<CDPSession, CDPClientAdapter<CDPSession>>(); + #browser: CDPClientAdapter<CDPPPtrConnection>; + + constructor(cdp: CDPPPtrConnection) { + this.#cdp = cdp; + this.#browser = new CDPClientAdapter(cdp); + } + + browserClient(): CDPClientAdapter<CDPPPtrConnection> { + return this.#browser; + } + + getCdpClient(id: string) { + const session = this.#cdp.session(id); + if (!session) { + throw new Error('Unknown CDP session with id' + id); + } + if (!this.#adapters.has(session)) { + const adapter = new CDPClientAdapter(session); + this.#adapters.set(session, adapter); + return adapter; + } + return this.#adapters.get(session)!; + } + + close() { + this.#browser.close(); + for (const adapter of this.#adapters.values()) { + adapter.close(); + } + } +} + +/** + * Wrapper on top of CDPSession/CDPConnection to satisfy CDP interface that + * BidiServer needs. + * + * @internal + */ +class CDPClientAdapter<T extends Pick<CDPPPtrConnection, 'send' | 'on' | 'off'>> + extends BidiMapper.EventEmitter<CdpEvents> + implements BidiMapper.CdpClient +{ + #closed = false; + #client: T; + + constructor(client: T) { + super(); + this.#client = client; + this.#client.on('*', this.#forwardMessage as Handler<any>); + } + + #forwardMessage = <T extends keyof CdpEvents>( + method: T, + event: CdpEvents[T] + ) => { + this.emit(method, event); + }; + + async sendCommand<T extends keyof ProtocolMapping.Commands>( + method: T, + ...params: ProtocolMapping.Commands[T]['paramsType'] + ): Promise<ProtocolMapping.Commands[T]['returnType']> { + if (this.#closed) { + return; + } + try { + return await this.#client.send(method, ...params); + } catch (err) { + if (this.#closed) { + return; + } + throw err; + } + } + + close() { + this.#client.off('*', this.#forwardMessage as Handler<any>); + this.#closed = true; + } +} + +/** + * This transport is given to the BiDi server instance and allows Puppeteer + * to send and receive commands to the BiDiServer. + * @internal + */ +class NoOpTransport + extends BidiMapper.EventEmitter<any> + implements BidiMapper.BidiTransport +{ + #onMessage: ( + message: Bidi.Message.RawCommandRequest + ) => Promise<void> | void = async ( + _m: Bidi.Message.RawCommandRequest + ): Promise<void> => { + return; + }; + + emitMessage(message: Bidi.Message.RawCommandRequest) { + void this.#onMessage(message); + } + + setOnMessage( + onMessage: (message: Bidi.Message.RawCommandRequest) => Promise<void> | void + ): void { + this.#onMessage = onMessage; + } + + async sendMessage(message: Bidi.Message.OutgoingMessage): Promise<void> { + this.emit('bidiResponse', message); + } + + close() { + this.#onMessage = async ( + _m: Bidi.Message.RawCommandRequest + ): Promise<void> => { + return; + }; + } +} |