@@ -99,6 +99,13 @@ export interface SnapshotOptions {
9999 * @defaultValue `true`
100100 */
101101 interestingOnly ?: boolean ;
102+ /**
103+ * If true, gets accessibility trees for each of the iframes in the frame
104+ * subtree.
105+ *
106+ * @defaultValue `false`
107+ */
108+ includeIframes ?: boolean ;
102109 /**
103110 * Root node to get the accessibility tree for
104111 * @defaultValue The root node of the entire page.
@@ -130,12 +137,14 @@ export interface SnapshotOptions {
130137 */
131138export class Accessibility {
132139 #realm: Realm ;
140+ #frameId: string ;
133141
134142 /**
135143 * @internal
136144 */
137- constructor ( realm : Realm ) {
145+ constructor ( realm : Realm , frameId = '' ) {
138146 this . #realm = realm ;
147+ this . #frameId = frameId ;
139148 }
140149
141150 /**
@@ -180,9 +189,16 @@ export class Accessibility {
180189 public async snapshot (
181190 options : SnapshotOptions = { } ,
182191 ) : Promise < SerializedAXNode | null > {
183- const { interestingOnly = true , root = null } = options ;
192+ const {
193+ interestingOnly = true ,
194+ root = null ,
195+ includeIframes = false ,
196+ } = options ;
184197 const { nodes} = await this . #realm. environment . client . send (
185198 'Accessibility.getFullAXTree' ,
199+ {
200+ frameId : this . #frameId,
201+ } ,
186202 ) ;
187203 let backendNodeId : number | undefined ;
188204 if ( root ) {
@@ -195,15 +211,44 @@ export class Accessibility {
195211 backendNodeId = node . backendNodeId ;
196212 }
197213 const defaultRoot = AXNode . createTree ( this . #realm, nodes ) ;
214+ const populateIframes = async ( root : AXNode ) : Promise < void > => {
215+ if ( root . payload . role ?. value === 'Iframe' ) {
216+ if ( ! root . payload . backendDOMNodeId ) {
217+ return ;
218+ }
219+ using handle = ( await this . #realm. adoptBackendNode (
220+ root . payload . backendDOMNodeId ,
221+ ) ) as ElementHandle < Element > ;
222+ if ( ! handle || ! ( 'contentFrame' in handle ) ) {
223+ return ;
224+ }
225+ const frame = await handle . contentFrame ( ) ;
226+ if ( ! frame ) {
227+ return ;
228+ }
229+ const iframeSnapshot = await frame . accessibility . snapshot ( options ) ;
230+ root . iframeSnapshot = iframeSnapshot ?? undefined ;
231+ }
232+ for ( const child of root . children ) {
233+ await populateIframes ( child ) ;
234+ }
235+ } ;
236+
198237 let needle : AXNode | null = defaultRoot ;
199238 if ( ! defaultRoot ) {
200239 return null ;
201240 }
241+
242+ if ( includeIframes ) {
243+ await populateIframes ( defaultRoot ) ;
244+ }
245+
202246 if ( backendNodeId ) {
203247 needle = defaultRoot . find ( node => {
204248 return node . payload . backendDOMNodeId === backendNodeId ;
205249 } ) ;
206250 }
251+
207252 if ( ! needle ) {
208253 return null ;
209254 }
@@ -237,6 +282,12 @@ export class Accessibility {
237282 if ( children . length ) {
238283 serializedNode . children = children ;
239284 }
285+ if ( node . iframeSnapshot ) {
286+ if ( ! serializedNode . children ) {
287+ serializedNode . children = [ ] ;
288+ }
289+ serializedNode . children . push ( node . iframeSnapshot ) ;
290+ }
240291 return [ serializedNode ] ;
241292 }
242293
@@ -245,7 +296,7 @@ export class Accessibility {
245296 node : AXNode ,
246297 insideControl : boolean ,
247298 ) : void {
248- if ( node . isInteresting ( insideControl ) ) {
299+ if ( node . isInteresting ( insideControl ) || node . iframeSnapshot ) {
249300 collection . add ( node ) ;
250301 }
251302 if ( node . isLeafNode ( ) ) {
@@ -261,6 +312,7 @@ export class Accessibility {
261312class AXNode {
262313 public payload : Protocol . Accessibility . AXNode ;
263314 public children : AXNode [ ] = [ ] ;
315+ public iframeSnapshot ?: SerializedAXNode ;
264316
265317 #richlyEditable = false ;
266318 #editable = false ;
0 commit comments