@@ -14,7 +14,6 @@ import Dap from '../dap/api';
1414import { acquireTrackedWebSocketServer , IPortLeaseTracker } from './portLeaseTracker' ;
1515
1616const jsDebugDomain = 'JsDebug' ;
17- const jsDebugMethodPrefix = jsDebugDomain + '.' ;
1817const eventWildcard = '*' ;
1918
2019/**
@@ -55,6 +54,64 @@ export interface ICdpProxyProvider extends IDisposable {
5554 proxy ( ) : Promise < Dap . RequestCDPProxyResult > ;
5655}
5756
57+ type ReplayMethod = { event : string ; params : Record < string , unknown > } ;
58+
59+ /**
60+ * Handles replaying events from domains. Certain events are only fired when
61+ * a domain is first enabled, so subsequent connections may not receive it.
62+ */
63+ class DomainReplays {
64+ private replays = new Map < keyof Cdp . Api , ReplayMethod [ ] > ( ) ;
65+
66+ /**
67+ * Adds a message to be replayed.
68+ */
69+ public addReplay ( domain : keyof Cdp . Api , event : string , params : unknown ) {
70+ const obj = { event : `${ domain } .${ event } ` , params : params as Record < string , unknown > } ;
71+ const arr = this . replays . get ( domain ) ;
72+ if ( arr ) {
73+ arr . push ( obj ) ;
74+ } else {
75+ this . replays . set ( domain , [ obj ] ) ;
76+ }
77+ }
78+
79+ /**
80+ * Captures replay for the event on CDP.
81+ */
82+ public capture ( cdp : Cdp . Api , domain : keyof Cdp . Api , event : string ) {
83+ ( cdp [ domain ] as {
84+ on ( event : string , fn : ( arg : Record < string , unknown > ) => void ) : void ;
85+ } ) . on ( event , evt => this . addReplay ( domain , event , evt ) ) ;
86+ }
87+
88+ /**
89+ * Filters replayed events.
90+ */
91+ public filterReply ( domain : keyof Cdp . Api , filterFn : ( r : ReplayMethod ) => boolean ) {
92+ const arr = this . replays . get ( domain ) ;
93+ if ( ! arr ) {
94+ return ;
95+ }
96+
97+ this . replays . set ( domain , arr . filter ( filterFn ) ) ;
98+ }
99+
100+ /**
101+ * Removes all replay info for a domain.
102+ */
103+ public clear ( domain : keyof Cdp . Api ) {
104+ this . replays . delete ( domain ) ;
105+ }
106+
107+ /**
108+ * Gets replay messages for the given domain.
109+ */
110+ public read ( domain : keyof Cdp . Api ) {
111+ return this . replays . get ( domain ) ?? [ ] ;
112+ }
113+ }
114+
58115export const ICdpProxyProvider = Symbol ( 'ICdpProxyProvider' ) ;
59116
60117/**
@@ -64,6 +121,7 @@ export const ICdpProxyProvider = Symbol('ICdpProxyProvider');
64121export class CdpProxyProvider implements ICdpProxyProvider {
65122 private server ?: Promise < { server : WebSocket . Server ; path : string } > ;
66123 private readonly disposables = new DisposableList ( ) ;
124+ private readonly replay = new DomainReplays ( ) ;
67125
68126 private jsDebugApi : IJsDebugDomain = {
69127 /** @inheritdoc */
@@ -90,7 +148,20 @@ export class CdpProxyProvider implements ICdpProxyProvider {
90148 @inject ( ICdpApi ) private readonly cdp : Cdp . Api ,
91149 @inject ( IPortLeaseTracker ) private readonly portTracker : IPortLeaseTracker ,
92150 @inject ( ILogger ) private readonly logger : ILogger ,
93- ) { }
151+ ) {
152+ this . replay . capture ( cdp , 'CSS' , 'fontsUpdated' ) ;
153+ this . replay . capture ( cdp , 'CSS' , 'styleSheetAdded' ) ;
154+
155+ cdp . CSS . on ( 'fontsUpdated' , evt => {
156+ if ( evt . font ) {
157+ this . replay . addReplay ( 'CSS' , 'fontsUpdated' , evt ) ;
158+ }
159+ } ) ;
160+
161+ cdp . CSS . on ( 'styleSheetRemoved' , evt =>
162+ this . replay . filterReply ( 'CSS' , s => s . params . styleSheetId !== evt . styleSheetId ) ,
163+ ) ;
164+ }
94165
95166 /**
96167 * Acquires the proxy server, and returns its address.
@@ -139,14 +210,12 @@ export class CdpProxyProvider implements ICdpProxyProvider {
139210 this . logger . verbose ( LogTag . ProxyActivity , 'received proxy message' , message ) ;
140211
141212 const { method, params, id = 0 } = message ;
213+ const [ domain , fn ] = method . split ( '.' ) ;
142214 try {
143- const result = method . startsWith ( jsDebugMethodPrefix )
144- ? await this . invokeJsDebugDomainMethod (
145- clientHandle ,
146- method . slice ( jsDebugMethodPrefix . length ) ,
147- params ,
148- )
149- : await this . cdp . session . sendOrDie ( method , params ) ;
215+ const result =
216+ domain === jsDebugDomain
217+ ? await this . invokeJsDebugDomainMethod ( clientHandle , fn , params )
218+ : await this . invokeCdpMethod ( clientHandle , domain , fn , params ) ;
150219 clientHandle . send ( { id, result } ) ;
151220 } catch ( e ) {
152221 const error =
@@ -168,11 +237,31 @@ export class CdpProxyProvider implements ICdpProxyProvider {
168237 this . server = undefined ;
169238 }
170239
240+ private invokeCdpMethod ( client : ClientHandle , domain : string , method : string , params : object ) {
241+ const promise = this . cdp . session . sendOrDie ( `${ domain } .${ method } ` , params ) ;
242+ switch ( method ) {
243+ case 'enable' :
244+ this . replay
245+ . read ( domain as keyof Cdp . Api )
246+ . forEach ( m => client . send ( { method : m . event , params : m . params } ) ) ;
247+ break ;
248+ case 'disable' :
249+ this . replay . clear ( domain as keyof Cdp . Api ) ;
250+ break ;
251+ default :
252+ // no-op
253+ }
254+
255+ // it's intentional that replay is sent before the
256+ // enabled response; this is what Chrome does.
257+ return promise ;
258+ }
259+
171260 private invokeJsDebugDomainMethod ( handle : ClientHandle , method : string , params : unknown ) {
172261 if ( ! this . jsDebugApi . hasOwnProperty ( method ) ) {
173262 throw new ProtocolError ( method ) . setCause (
174263 ProxyErrors . MethodNotFound ,
175- `${ jsDebugMethodPrefix } ${ method } not found` ,
264+ `${ jsDebugDomain } . ${ method } not found` ,
176265 ) ;
177266 }
178267
0 commit comments