diff --git a/README.md b/README.md index bb1fca5ae..9cc187de6 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ at [WPT WebDriver BiDi status](https://wpt.fyi/results/webdriver/tests/bidi). **"BiDi+"** is an extension of the WebDriver BiDi protocol. In addition to [WebDriver BiDi](https://w3c.github.io/webdriver-bidi/) it has: -### Command `cdp.sendCommand` +### Command `goog:cdp.sendCommand` ```cddl CdpSendCommandCommand = { - method: "cdp.sendCommand", + method: "goog:cdp.sendCommand", params: CdpSendCommandParameters, } @@ -45,11 +45,11 @@ The command runs the described [CDP command](https://chromedevtools.github.io/devtools-protocol) and returns the result. -### Command `cdp.getSession` +### Command `goog:cdp.getSession` ```cddl CdpGetSessionCommand = { - method: "cdp.getSession", + method: "goog:cdp.getSession", params: CdpGetSessionParameters, } @@ -64,11 +64,11 @@ CdpGetSessionResult = { The command returns the default CDP session for the selected browsing context. -### Command `cdp.resolveRealm` +### Command `goog:cdp.resolveRealm` ```cddl CdpResolveRealmCommand = { - method: "cdp.resolveRealm", + method: "goog:cdp.resolveRealm", params: CdpResolveRealmParameters, } @@ -83,11 +83,11 @@ CdpResolveRealmResult = { The command returns resolves a BiDi realm to its CDP execution context ID. -### Events `cdp` +### Events `goog:cdp` ```cddl CdpEventReceivedEvent = { - method: "cdp.", + method: "goog:cdp.", params: CdpEventReceivedParameters, } diff --git a/src/bidiMapper/CommandProcessor.ts b/src/bidiMapper/CommandProcessor.ts index 58097636d..d64b907b9 100644 --- a/src/bidiMapper/CommandProcessor.ts +++ b/src/bidiMapper/CommandProcessor.ts @@ -237,6 +237,22 @@ export class CommandProcessor extends EventEmitter { // CDP module // keep-sorted start block=yes + case 'goog:cdp.getSession': + return this.#cdpProcessor.getSession( + this.#parser.parseGetSessionParams(command.params), + ); + case 'goog:cdp.resolveRealm': + return this.#cdpProcessor.resolveRealm( + this.#parser.parseResolveRealmParams(command.params), + ); + case 'goog:cdp.sendCommand': + return await this.#cdpProcessor.sendCommand( + this.#parser.parseSendCommandParams(command.params), + ); + // keep-sorted end + // CDP deprecated domain. + // https://github.com/GoogleChromeLabs/chromium-bidi/issues/2844 + // keep-sorted start block=yes case 'cdp.getSession': return this.#cdpProcessor.getSession( this.#parser.parseGetSessionParams(command.params), diff --git a/src/bidiMapper/modules/cdp/CdpTarget.ts b/src/bidiMapper/modules/cdp/CdpTarget.ts index 0e6839d58..8b8ca47b2 100644 --- a/src/bidiMapper/modules/cdp/CdpTarget.ts +++ b/src/bidiMapper/modules/cdp/CdpTarget.ts @@ -407,6 +407,20 @@ export class CdpTarget { if (typeof event !== 'string') { return; } + this.#eventManager.registerEvent( + { + type: 'event', + method: `goog:cdp.${event}`, + params: { + event, + params, + session: this.cdpSessionId, + }, + }, + this.id, + ); + // Duplicate the event to the deprecated event name. + // https://github.com/GoogleChromeLabs/chromium-bidi/issues/2844 this.#eventManager.registerEvent( { type: 'event', diff --git a/src/bidiMapper/modules/session/SubscriptionManager.ts b/src/bidiMapper/modules/session/SubscriptionManager.ts index b46bfe61f..0bf5596e7 100644 --- a/src/bidiMapper/modules/session/SubscriptionManager.ts +++ b/src/bidiMapper/modules/session/SubscriptionManager.ts @@ -24,7 +24,7 @@ import { import type {BrowsingContextStorage} from '../context/BrowsingContextStorage.js'; import type {SubscriptionItem} from './EventManager.js'; -import {isCdpEvent} from './events.js'; +import {isCdpEvent, isDeprecatedCdpEvent} from './events.js'; /** * Returns the cartesian product of the given arrays. @@ -142,7 +142,7 @@ export class SubscriptionManager { const priority = contextToEventMap.get(context)?.get(eventMethod); // For CDP we can't provide specific event name when subscribing // to the module directly. - // Because of that we need to see event `cdp` exits in the map. + // Because of that we need to see event `cdp` exists in the map. if (isCdpEvent(eventMethod)) { const cdpPriority = contextToEventMap .get(context) @@ -155,6 +155,19 @@ export class SubscriptionManager { // to only one of the two (priority ?? cdpPriority); } + // https://github.com/GoogleChromeLabs/chromium-bidi/issues/2844. + if (isDeprecatedCdpEvent(eventMethod)) { + const cdpPriority = contextToEventMap + .get(context) + ?.get(ChromiumBidi.BiDiModule.DeprecatedCdp); + // If we subscribe to the event directly and `cdp` module as well + // priority will be different we take minimal priority + return priority && cdpPriority + ? Math.min(priority, cdpPriority) + : // At this point we know that we have subscribed + // to only one of the two + (priority ?? cdpPriority); + } return priority; }) .filter((p) => p !== undefined); diff --git a/src/bidiMapper/modules/session/events.spec.ts b/src/bidiMapper/modules/session/events.spec.ts index 1c6d93d87..7bda65ef2 100644 --- a/src/bidiMapper/modules/session/events.spec.ts +++ b/src/bidiMapper/modules/session/events.spec.ts @@ -18,19 +18,56 @@ import {expect} from 'chai'; import {ChromiumBidi} from '../../../protocol/protocol.js'; -import {assertSupportedEvent, isCdpEvent} from './events.js'; +import { + assertSupportedEvent, + isCdpEvent, + isDeprecatedCdpEvent, +} from './events.js'; + +const CDP_EVENTS = [ + 'goog:cdp', + 'goog:cdp.event', + 'goog:cdp.Debugger.breakpointResolved', +]; +const DEPRECATED_CDP_EVENTS = [ + 'cdp', + 'cdp.event', + 'cdp.Debugger.breakpointResolved', +]; +const NON_CDP_EVENTS = ['log', 'log.entryAdded']; describe('event', () => { describe('isCdpEvent', () => { - it('should return true for CDP events', () => { - expect(isCdpEvent('cdp')).to.be.true; - expect(isCdpEvent('cdp.event')).to.be.true; - }); + for (const cdpEvent of CDP_EVENTS) + it(`should return true for CDP event '${cdpEvent}'`, () => { + expect(isCdpEvent(cdpEvent)).to.be.true; + }); + for (const deprecatedCdpEvent of DEPRECATED_CDP_EVENTS) + it(`should return false for deprecated CDP event '${deprecatedCdpEvent}'`, () => { + expect(isCdpEvent(deprecatedCdpEvent)).to.be.false; + }); - it('should return false for non-CDP events', () => { - expect(isCdpEvent('log')).to.be.false; - expect(isCdpEvent('log.entryAdded')).to.be.false; - }); + for (const nonCdpEvent of NON_CDP_EVENTS) + it(`should return false for non-CDP event '${nonCdpEvent}'`, () => { + expect(isCdpEvent(nonCdpEvent)).to.be.false; + }); + }); + + describe('isDeprecatedCdpEvent', () => { + for (const cdpEvent of CDP_EVENTS) + it(`should return false for CDP event '${cdpEvent}'`, () => { + expect(isDeprecatedCdpEvent(cdpEvent)).to.be.false; + }); + + for (const deprecatedCdpEvent of DEPRECATED_CDP_EVENTS) + it(`should return true for deprecated CDP event '${deprecatedCdpEvent}'`, () => { + expect(isDeprecatedCdpEvent(deprecatedCdpEvent)).to.be.true; + }); + + for (const nonCdpEvent of NON_CDP_EVENTS) + it(`should return false for non-CDP event '${nonCdpEvent}'`, () => { + expect(isDeprecatedCdpEvent(nonCdpEvent)).to.be.false; + }); }); describe('assertSupportedEvent', () => { @@ -40,11 +77,18 @@ describe('event', () => { }).to.throw('Unknown event: unknown'); }); - it('should not throw for known events', () => { + it('should not throw for known deprecated CDP events', () => { expect(() => { assertSupportedEvent('cdp.Debugger.breakpointResolved'); assertSupportedEvent(ChromiumBidi.Log.EventNames.LogEntryAdded); }).to.not.throw(); }); + + it('should not throw for known CDP events', () => { + expect(() => { + assertSupportedEvent('goog:cdp.Debugger.breakpointResolved'); + assertSupportedEvent(ChromiumBidi.Log.EventNames.LogEntryAdded); + }).to.not.throw(); + }); }); }); diff --git a/src/bidiMapper/modules/session/events.ts b/src/bidiMapper/modules/session/events.ts index d556f3668..3ce5767a9 100644 --- a/src/bidiMapper/modules/session/events.ts +++ b/src/bidiMapper/modules/session/events.ts @@ -28,6 +28,16 @@ export function isCdpEvent(name: string): boolean { name.split('.').at(0)?.startsWith(ChromiumBidi.BiDiModule.Cdp) ?? false ); } +/** + * Returns true if the given event is a deprecated CDP event. + * @see https://chromedevtools.github.io/devtools-protocol/ + */ +export function isDeprecatedCdpEvent(name: string): boolean { + return ( + name.split('.').at(0)?.startsWith(ChromiumBidi.BiDiModule.DeprecatedCdp) ?? + false + ); +} /** * Asserts that the given event is known to BiDi or BiDi+, or throws otherwise. @@ -35,7 +45,11 @@ export function isCdpEvent(name: string): boolean { export function assertSupportedEvent( name: string, ): asserts name is ChromiumBidi.EventNames { - if (!ChromiumBidi.EVENT_NAMES.has(name as never) && !isCdpEvent(name)) { + if ( + !ChromiumBidi.EVENT_NAMES.has(name as never) && + !isCdpEvent(name) && + !isDeprecatedCdpEvent(name) + ) { throw new InvalidArgumentException(`Unknown event: ${name}`); } } diff --git a/src/protocol/cdp.ts b/src/protocol/cdp.ts index 3e1168430..ead7c0659 100644 --- a/src/protocol/cdp.ts +++ b/src/protocol/cdp.ts @@ -33,7 +33,11 @@ export type Command = { export type CommandData = | SendCommandCommand | GetSessionCommand - | ResolveRealmCommand; + | ResolveRealmCommand + // https://github.com/GoogleChromeLabs/chromium-bidi/issues/2844 + | DeprecatedSendCommandCommand + | DeprecatedGetSessionCommand + | DeprecatedResolveRealmCommand; export type CommandResponse = { type: 'success'; @@ -45,11 +49,16 @@ export type ResultData = | GetSessionResult | ResolveRealmResult; -export type SendCommandCommand = { +export type DeprecatedSendCommandCommand = { method: 'cdp.sendCommand'; params: SendCommandParameters; }; +export type SendCommandCommand = { + method: 'goog:cdp.sendCommand'; + params: SendCommandParameters; +}; + export type SendCommandParameters< Command extends keyof ProtocolMapping.Commands = keyof ProtocolMapping.Commands, @@ -64,11 +73,16 @@ export type SendCommandResult = { session?: Protocol.Target.SessionID; }; -export type GetSessionCommand = { +export type DeprecatedGetSessionCommand = { method: 'cdp.getSession'; params: GetSessionParameters; }; +export type GetSessionCommand = { + method: 'goog:cdp.getSession'; + params: GetSessionParameters; +}; + export type GetSessionParameters = { context: BrowsingContext.BrowsingContext; }; @@ -77,11 +91,16 @@ export type GetSessionResult = { session?: Protocol.Target.SessionID; }; -export type ResolveRealmCommand = { +export type DeprecatedResolveRealmCommand = { method: 'cdp.resolveRealm'; params: ResolveRealmParameters; }; +export type ResolveRealmCommand = { + method: 'goog:cdp.resolveRealm'; + params: ResolveRealmParameters; +}; + export type ResolveRealmParameters = { realm: Script.Realm; }; @@ -93,13 +112,23 @@ export type ResolveRealmResult = { export type Event = { type: 'event'; } & EventData; -export type EventData = EventDataFor; -export type EventDataFor = { +export type EventData = + | EventDataFor + | DeprecatedEventDataFor; + +export type DeprecatedEventDataFor< + EventName extends keyof ProtocolMapping.Events, +> = { method: `cdp.${EventName}`; params: EventParametersFor; }; +export type EventDataFor = { + method: `goog:cdp.${EventName}`; + params: EventParametersFor; +}; + export type EventParametersFor = { event: EventName; diff --git a/src/protocol/chromium-bidi.ts b/src/protocol/chromium-bidi.ts index b8382928c..13380ae55 100644 --- a/src/protocol/chromium-bidi.ts +++ b/src/protocol/chromium-bidi.ts @@ -36,7 +36,8 @@ export enum BiDiModule { Bluetooth = 'bluetooth', Browser = 'browser', BrowsingContext = 'browsingContext', - Cdp = 'cdp', + Cdp = 'goog:cdp', + DeprecatedCdp = 'cdp', Input = 'input', Log = 'log', Network = 'network', diff --git a/tests/session/test_cdp.py b/tests/session/test_cdp.py index cd824a38f..f80068a35 100644 --- a/tests/session/test_cdp.py +++ b/tests/session/test_cdp.py @@ -23,11 +23,17 @@ wait_for_event) +# https://github.com/GoogleChromeLabs/chromium-bidi/issues/2844 +@pytest.fixture(params=["", "goog:"]) +def cdp_prefix(request): + return request.param + + @pytest.mark.asyncio -async def test_cdp_sendCommand_resultReturned(websocket): +async def test_cdp_sendCommand_resultReturned(websocket, cdp_prefix): command_result = await execute_command( websocket, { - "method": "cdp.sendCommand", + "method": f"{cdp_prefix}cdp.sendCommand", "params": { "method": "Target.getTargets", "params": {} @@ -39,14 +45,14 @@ async def test_cdp_sendCommand_resultReturned(websocket): @pytest.mark.asyncio async def test_cdp_subscribe_toSpecificEvent(websocket, context_id, - get_cdp_session_id): - await subscribe(websocket, ["cdp.Runtime.consoleAPICalled"]) + get_cdp_session_id, cdp_prefix): + await subscribe(websocket, [f"{cdp_prefix}cdp.Runtime.consoleAPICalled"]) session_id = await get_cdp_session_id(context_id) await send_JSON_command( websocket, { - "method": "cdp.sendCommand", + "method": f"{cdp_prefix}cdp.sendCommand", "params": { "method": "Runtime.evaluate", "params": { @@ -59,7 +65,7 @@ async def test_cdp_subscribe_toSpecificEvent(websocket, context_id, assert resp == AnyExtending({ "type": "event", - "method": "cdp.Runtime.consoleAPICalled", + "method": f"{cdp_prefix}cdp.Runtime.consoleAPICalled", "params": { "event": "Runtime.consoleAPICalled", "params": { @@ -80,14 +86,14 @@ async def test_cdp_subscribe_toSpecificEvent(websocket, context_id, @pytest.mark.asyncio async def test_cdp_subscribe_to_all_cdp_events(websocket, get_cdp_session_id, - context_id): - await subscribe(websocket, ["cdp"]) + context_id, cdp_prefix): + await subscribe(websocket, [f"{cdp_prefix}cdp"]) session_id = await get_cdp_session_id(context_id) await send_JSON_command( websocket, { - "method": "cdp.sendCommand", + "method": f"{cdp_prefix}cdp.sendCommand", "params": { "method": "Runtime.evaluate", "params": { @@ -97,11 +103,12 @@ async def test_cdp_subscribe_to_all_cdp_events(websocket, get_cdp_session_id, } }) - resp = await wait_for_event(websocket, "cdp.Runtime.consoleAPICalled") + resp = await wait_for_event(websocket, + f"{cdp_prefix}cdp.Runtime.consoleAPICalled") assert resp == AnyExtending({ "type": "event", - "method": "cdp.Runtime.consoleAPICalled", + "method": f"{cdp_prefix}cdp.Runtime.consoleAPICalled", "params": { "event": "Runtime.consoleAPICalled", "params": { @@ -121,14 +128,15 @@ async def test_cdp_subscribe_to_all_cdp_events(websocket, get_cdp_session_id, @pytest.mark.asyncio -async def test_cdp_wait_for_event(websocket, get_cdp_session_id, context_id): - await subscribe(websocket, ["cdp.Runtime.consoleAPICalled"]) +async def test_cdp_wait_for_event(websocket, get_cdp_session_id, context_id, + cdp_prefix): + await subscribe(websocket, [f"{cdp_prefix}cdp.Runtime.consoleAPICalled"]) session_id = await get_cdp_session_id(context_id) await send_JSON_command( websocket, { - "method": "cdp.sendCommand", + "method": f"{cdp_prefix}cdp.sendCommand", "params": { "method": "Runtime.evaluate", "params": { @@ -138,11 +146,11 @@ async def test_cdp_wait_for_event(websocket, get_cdp_session_id, context_id): } }) - event_response = await wait_for_event(websocket, - "cdp.Runtime.consoleAPICalled") + event_response = await wait_for_event( + websocket, f"{cdp_prefix}cdp.Runtime.consoleAPICalled") assert event_response == AnyExtending({ "type": "event", - "method": "cdp.Runtime.consoleAPICalled", + "method": f"{cdp_prefix}cdp.Runtime.consoleAPICalled", "params": { "event": "Runtime.consoleAPICalled", "params": { @@ -163,7 +171,7 @@ async def test_cdp_wait_for_event(websocket, get_cdp_session_id, context_id): @pytest.mark.asyncio async def test_cdp_no_extraneous_events(websocket, get_cdp_session_id, - create_context, url_base): + create_context, url_base, cdp_prefix): new_context_id = await create_context() await execute_command( websocket, { @@ -175,13 +183,13 @@ async def test_cdp_no_extraneous_events(websocket, get_cdp_session_id, } }) - await subscribe(websocket, ["cdp"], [new_context_id]) + await subscribe(websocket, [f"{cdp_prefix}cdp"], [new_context_id]) session_id = await get_cdp_session_id(new_context_id) id = await send_JSON_command( websocket, { - "method": "cdp.sendCommand", + "method": f"{cdp_prefix}cdp.sendCommand", "params": { "method": "Target.attachToTarget", "params": { @@ -206,6 +214,6 @@ async def test_cdp_no_extraneous_events(websocket, get_cdp_session_id, timeout=1.0) for event in events: - if event['method'].startswith( - 'cdp') and event['params']['session'] == session_id: + if event['method'].startswith(f"{cdp_prefix}cdp.") and event['params'][ + 'session'] == session_id: raise Exception("Unrelated CDP events detected")