@@ -5,6 +5,7 @@ import { consume } from '@lit/context'
55import { type ComponentChildren , h , render , type VNode } from 'preact'
66import { customElement , query } from 'lit/decorators.js'
77import type { SimplifiedVNode } from '../../../../script/types'
8+ import type { CommandLog } from '@wdio/devtools-service/types'
89
910import {
1011 mutationContext ,
@@ -43,10 +44,6 @@ const COMPONENT = 'wdio-devtools-browser'
4344export class DevtoolsBrowser extends Element {
4445 #vdom = document . createDocumentFragment ( )
4546 #activeUrl?: string
46- #resizeTimer?: number
47- #boundResize = ( ) => this . #debouncedResize( )
48- #checkpoints = new Map < number , DocumentFragment > ( )
49- #checkpointStride = 50
5047
5148 @consume ( { context : metadataContext , subscribe : true } )
5249 metadata : Metadata | undefined = undefined
@@ -96,25 +93,22 @@ export class DevtoolsBrowser extends Element {
9693
9794 async connectedCallback ( ) {
9895 super . connectedCallback ( )
99- window . addEventListener ( 'resize' , this . #boundResize )
100- window . addEventListener ( 'window-drag' , this . #boundResize )
96+ window . addEventListener ( 'resize' , this . #setIframeSize . bind ( this ) )
97+ window . addEventListener ( 'window-drag' , this . #setIframeSize . bind ( this ) )
10198 window . addEventListener (
10299 'app-mutation-highlight' ,
103100 this . #highlightMutation. bind ( this )
104101 )
105102 window . addEventListener ( 'app-mutation-select' , ( ev ) =>
106103 this . #renderBrowserState( ev . detail )
107104 )
105+ window . addEventListener (
106+ 'show-command' ,
107+ this . #handleShowCommand as EventListener
108+ )
108109 await this . updateComplete
109110 }
110111
111- #debouncedResize( ) {
112- if ( this . #resizeTimer) {
113- window . clearTimeout ( this . #resizeTimer)
114- }
115- this . #resizeTimer = window . setTimeout ( ( ) => this . #setIframeSize( ) , 80 )
116- }
117-
118112 #setIframeSize( ) {
119113 const metadata = this . metadata
120114 if ( ! this . section || ! this . iframe || ! this . header || ! metadata ) {
@@ -145,6 +139,31 @@ export class DevtoolsBrowser extends Element {
145139 this . iframe . style . transform = `scale(${ scale } )`
146140 }
147141
142+ #handleShowCommand = ( event : Event ) =>
143+ this . #renderCommandScreenshot(
144+ ( event as CustomEvent < { command ?: CommandLog } > ) . detail ?. command
145+ )
146+
147+ async #renderCommandScreenshot( command ?: CommandLog ) {
148+ const screenshot = command ?. screenshot
149+ if ( ! screenshot ) {
150+ return
151+ }
152+
153+ if ( ! this . iframe ) {
154+ await this . updateComplete
155+ }
156+ if ( ! this . iframe ) {
157+ return
158+ }
159+
160+ this . iframe . srcdoc = `
161+ <body style="margin:0;background:#111;display:flex;justify-content:center;align-items:flex-start;">
162+ <img src="data:image/png;base64,${ screenshot } " style="max-width:100%;height:auto;display:block;" />
163+ </body>
164+ `
165+ }
166+
148167 async #renderNewDocument( doc : SimplifiedVNode , baseUrl : string ) {
149168 const root = transform ( doc )
150169 const baseTag = h ( 'base' , { href : baseUrl } )
@@ -182,6 +201,7 @@ export class DevtoolsBrowser extends Element {
182201 if ( ! this . iframe ) {
183202 await this . updateComplete
184203 }
204+
185205 if ( mutation . type === 'attributes' ) {
186206 return this . #handleAttributeMutation( mutation )
187207 }
@@ -203,22 +223,16 @@ export class DevtoolsBrowser extends Element {
203223 }
204224
205225 #handleAttributeMutation( mutation : TraceMutation ) {
206- if ( ! mutation . attributeName ) {
226+ if ( ! mutation . attributeName || ! mutation . attributeValue ) {
207227 return
208228 }
229+
209230 const el = this . #queryElement( mutation . target ! )
210231 if ( ! el ) {
211232 return
212233 }
213234
214- if (
215- mutation . attributeValue === undefined ||
216- mutation . attributeValue === null
217- ) {
218- el . removeAttribute ( mutation . attributeName )
219- } else {
220- el . setAttribute ( mutation . attributeName , mutation . attributeValue )
221- }
235+ el . setAttribute ( mutation . attributeName , mutation . attributeValue || '' )
222236 }
223237
224238 #handleChildListMutation( mutation : TraceMutation ) {
@@ -296,80 +310,52 @@ export class DevtoolsBrowser extends Element {
296310
297311 async #renderBrowserState( mutationEntry ?: TraceMutation ) {
298312 const mutations = this . mutations
299- if ( ! mutations ?. length ) {
300- return
301- }
302-
303- const targetIndex = mutationEntry ? mutations . indexOf ( mutationEntry ) : 0
304- if ( targetIndex < 0 ) {
313+ if ( ! mutations || ! mutations . length ) {
305314 return
306315 }
307316
308- // locate nearest checkpoint (<= targetIndex)
309- const checkpointIndices = [ ...this . #checkpoints. keys ( ) ] . sort (
310- ( a , b ) => a - b
311- )
312- const nearest = checkpointIndices . filter ( ( i ) => i <= targetIndex ) . pop ( )
313-
314- if ( nearest !== undefined ) {
315- // start from checkpoint clone
316- this . #vdom = this . #checkpoints
317- . get ( nearest ) !
318- . cloneNode ( true ) as DocumentFragment
319- } else {
320- this . #vdom = document . createDocumentFragment ( )
321- }
322-
323- // find root after checkpoint (initial full doc mutation)
324- const startIndex = nearest !== undefined ? nearest + 1 : 0
325- let rootIndex = startIndex
326- for ( let i = startIndex ; i <= targetIndex ; i ++ ) {
327- const m = mutations [ i ]
328- if ( m . addedNodes . length === 1 && Boolean ( m . url ) ) {
329- rootIndex = i
330- }
331- }
332- if ( rootIndex !== startIndex ) {
333- this . #vdom = document . createDocumentFragment ( )
334- }
317+ const mutationIndex = mutationEntry ? mutations . indexOf ( mutationEntry ) : 0
318+ this . #vdom = document . createDocumentFragment ( )
319+ const rootIndex =
320+ mutations
321+ . map (
322+ ( m , i ) =>
323+ [
324+ // is document loaded
325+ m . addedNodes . length === 1 && Boolean ( m . url ) ,
326+ // index
327+ i
328+ ] as const
329+ )
330+ . filter (
331+ ( [ isDocLoaded , docLoadedIndex ] ) =>
332+ isDocLoaded && docLoadedIndex <= mutationIndex
333+ )
334+ . map ( ( [ , i ] ) => i )
335+ . pop ( ) || 0
335336
336337 this . #activeUrl =
337338 mutations [ rootIndex ] . url || this . metadata ?. url || 'unknown'
338-
339- for ( let i = rootIndex ; i <= targetIndex ; i ++ ) {
340- try {
341- await this . #handleMutation( mutations [ i ] )
342- // create checkpoint
343- if ( i % this . #checkpointStride === 0 && ! this . #checkpoints. has ( i ) ) {
344- this . #checkpoints. set (
345- i ,
346- this . #vdom. cloneNode ( true ) as DocumentFragment
347- )
348- }
349- } catch ( err : any ) {
350- console . warn ( `Failed to render mutation ${ i } : ${ err ?. message } ` )
351- }
339+ for ( let i = rootIndex ; i <= mutationIndex ; i ++ ) {
340+ await this . #handleMutation( mutations [ i ] ) . catch ( ( err ) =>
341+ console . warn ( `Failed to render mutation: ${ err . message } ` )
342+ )
352343 }
353344
354- const mutation = mutations [ targetIndex ]
345+ /**
346+ * scroll changed element into view
347+ */
348+ const mutation = mutations [ mutationIndex ]
355349 if ( mutation . target ) {
356350 const el = this . #queryElement( mutation . target )
357- el ?. scrollIntoView ( { block : 'center' , inline : 'center' } )
351+ if ( el ) {
352+ el . scrollIntoView ( { block : 'center' , inline : 'center' } )
353+ }
358354 }
359355
360356 this . requestUpdate ( )
361357 }
362358
363- /**
364- * Public API: jump to mutation index
365- */
366- goToMutation ( index : number ) {
367- const m = this . mutations [ index ]
368- if ( m ) {
369- this . #renderBrowserState( m )
370- }
371- }
372-
373359 render ( ) {
374360 /**
375361 * render a browser state if it hasn't before
0 commit comments