@@ -7,9 +7,11 @@ import { l10n } from 'vscode';
77import { customBreakpoints , ICustomBreakpoint , IXHRBreakpoint } from '../adapter/customBreakpoints' ;
88import { Commands , CustomViews } from '../common/contributionUtils' ;
99import { EventEmitter } from '../common/events' ;
10+ import Dap from '../dap/api' ;
1011import { DebugSessionTracker } from './debugSessionTracker' ;
1112
1213const xhrBreakpointsCategory = ( ) => l10n . t ( 'XHR/Fetch URLs' ) ;
14+ const focusEmulationStorageKey = 'jsDebug.focusEmulation.enabled' ;
1315
1416class XHRBreakpoint extends vscode . TreeItem {
1517 public get checked ( ) {
@@ -68,17 +70,47 @@ class Category extends vscode.TreeItem {
6870 }
6971}
7072
71- type TreeItem = Breakpoint | Category | XHRBreakpoint ;
73+ class BreakpointsRoot extends vscode . TreeItem {
74+ public get checked ( ) {
75+ return this . checkboxState === vscode . TreeItemCheckboxState . Checked ;
76+ }
77+
78+ constructor ( ) {
79+ super ( l10n . t ( 'Browser Breakpoints' ) , vscode . TreeItemCollapsibleState . Collapsed ) ;
80+ this . checkboxState = vscode . TreeItemCheckboxState . Unchecked ;
81+ this . id = 'browserBreakpoints' ;
82+ }
83+ }
84+
85+ class FocusEmulationOption extends vscode . TreeItem {
86+ constructor ( enabled : boolean ) {
87+ super ( l10n . t ( 'Emulate a focused page' ) , vscode . TreeItemCollapsibleState . None ) ;
88+ this . checkboxState = enabled
89+ ? vscode . TreeItemCheckboxState . Checked
90+ : vscode . TreeItemCheckboxState . Unchecked ;
91+ this . tooltip = l10n . t (
92+ 'When enabled, the debugged page will behave as if it has focus' ,
93+ ) ;
94+ this . id = 'focusEmulation' ;
95+ }
96+ }
97+
98+ type BreakpointItem = BreakpointsRoot | Breakpoint | Category | XHRBreakpoint ;
99+ type BrowserOptionItem = BreakpointItem | FocusEmulationOption ;
72100
73- class BreakpointsDataProvider implements vscode . TreeDataProvider < TreeItem > {
74- private _onDidChangeTreeData = new EventEmitter < Breakpoint | undefined > ( ) ;
101+ class BrowserOptionsDataProvider implements vscode . TreeDataProvider < BrowserOptionItem > {
102+ private _onDidChangeTreeData = new EventEmitter < BrowserOptionItem | undefined > ( ) ;
75103 readonly onDidChangeTreeData = this . _onDidChangeTreeData . event ;
76104
77105 private _debugSessionTracker : DebugSessionTracker ;
78106
107+ private readonly _breakpointsRoot = new BreakpointsRoot ( ) ;
79108 private readonly categories = new Map < string , Category > ( ) ;
80109 xhrBreakpoints : XHRBreakpoint [ ] = [ ] ;
81110
111+ private readonly _emulationSessions = new Set < string > ( ) ;
112+ private _focusEmulationEnabled = false ;
113+
82114 /** Gets all breakpoint categories */
83115 public get allCategories ( ) {
84116 return this . categories . values ( ) ;
@@ -89,7 +121,9 @@ class BreakpointsDataProvider implements vscode.TreeDataProvider<TreeItem> {
89121 return [ ...this . allCategories ] . flatMap ( c => c . children ) ;
90122 }
91123
92- constructor ( debugSessionTracker : DebugSessionTracker ) {
124+ constructor ( debugSessionTracker : DebugSessionTracker , focusEmulationEnabled : boolean ) {
125+ this . _focusEmulationEnabled = focusEmulationEnabled ;
126+
93127 for ( const breakpoint of [ ...customBreakpoints ( ) . values ( ) ] ) {
94128 let category = this . categories . get ( breakpoint . group ) ;
95129 if ( ! category ) {
@@ -113,25 +147,39 @@ class BreakpointsDataProvider implements vscode.TreeDataProvider<TreeItem> {
113147 }
114148
115149 const toEnable = this . allBreakpoints . filter ( b => b . checked ) . map ( b => b . id ) ;
116- if ( toEnable . length === 0 ) {
117- return ;
150+ if ( toEnable . length > 0 ) {
151+ session . customRequest ( 'setCustomBreakpoints' , {
152+ xhr : this . xhrBreakpoints . filter ( b => b . checkboxState ) . map ( b => b . id ) ,
153+ ids : toEnable ,
154+ } ) ;
118155 }
119156
120- session . customRequest ( 'setCustomBreakpoints' , {
121- xhr : this . xhrBreakpoints . filter ( b => b . checkboxState ) . map ( b => b . id ) ,
122- ids : toEnable ,
123- } ) ;
157+ this . _checkEmulationSupport ( session ) ;
158+ } ) ;
159+
160+ debugSessionTracker . onSessionEnded ( session => {
161+ if ( this . _emulationSessions . delete ( session . id ) ) {
162+ this . _onDidChangeTreeData . fire ( undefined ) ;
163+ }
124164 } ) ;
125165 }
126166
127167 /** @inheritdoc */
128- getTreeItem ( item : TreeItem ) : vscode . TreeItem {
168+ getTreeItem ( item : BrowserOptionItem ) : vscode . TreeItem {
129169 return item ;
130170 }
131171
132172 /** @inheritdoc */
133- getChildren ( item ?: TreeItem ) : vscode . ProviderResult < TreeItem [ ] > {
173+ getChildren ( item ?: BrowserOptionItem ) : vscode . ProviderResult < BrowserOptionItem [ ] > {
134174 if ( ! item ) {
175+ const items : BrowserOptionItem [ ] = [ this . _breakpointsRoot ] ;
176+ if ( this . _emulationSessions . size > 0 ) {
177+ items . unshift ( new FocusEmulationOption ( this . _focusEmulationEnabled ) ) ;
178+ }
179+ return items ;
180+ }
181+
182+ if ( item instanceof BreakpointsRoot ) {
135183 return [ ...this . categories . values ( ) ] . sort ( ( a , b ) => a . label . localeCompare ( b . label ) ) ;
136184 }
137185
@@ -149,8 +197,10 @@ class BreakpointsDataProvider implements vscode.TreeDataProvider<TreeItem> {
149197 }
150198
151199 /** @inheritdoc */
152- getParent ( item : TreeItem ) : vscode . ProviderResult < TreeItem > {
153- if ( item instanceof Breakpoint ) {
200+ getParent ( item : BrowserOptionItem ) : vscode . ProviderResult < BrowserOptionItem > {
201+ if ( item instanceof Category ) {
202+ return this . _breakpointsRoot ;
203+ } else if ( item instanceof Breakpoint ) {
154204 return this . categories . get ( item . group ) ;
155205 } else if ( item instanceof XHRBreakpoint ) {
156206 return this . categories . get ( xhrBreakpointsCategory ( ) ) ;
@@ -159,21 +209,40 @@ class BreakpointsDataProvider implements vscode.TreeDataProvider<TreeItem> {
159209 return undefined ;
160210 }
161211
212+ public setFocusEmulation ( enabled : boolean ) : void {
213+ this . _focusEmulationEnabled = enabled ;
214+ this . _onFocusEmulationChanged ?.( enabled ) ;
215+ this . _applyFocusEmulationToAllSessions ( ) ;
216+ this . _onDidChangeTreeData . fire ( undefined ) ;
217+ }
218+
162219 /** Updates the enablement state of the breakpoints/categories */
163- public setEnabled ( breakpoints : [ TreeItem , boolean ] [ ] ) {
220+ public setBreakpointsEnabled ( breakpoints : [ BreakpointItem , boolean ] [ ] ) {
164221 for ( const [ breakpoint , enabled ] of breakpoints ) {
165222 const state = enabled
166223 ? vscode . TreeItemCheckboxState . Checked
167224 : vscode . TreeItemCheckboxState . Unchecked ;
168225
169226 breakpoint . checkboxState = state ;
170227
171- if ( breakpoint instanceof Category ) {
228+ if ( breakpoint instanceof BreakpointsRoot ) {
229+ for ( const category of this . categories . values ( ) ) {
230+ category . checkboxState = state ;
231+ for ( const child of category . children ) {
232+ child . checkboxState = state ;
233+ }
234+ }
235+ for ( const xhr of this . xhrBreakpoints ) {
236+ xhr . checkboxState = state ;
237+ }
238+ this . syncXHRCategoryState ( ) ;
239+ } else if ( breakpoint instanceof Category ) {
172240 for ( const child of this . getChildren ( breakpoint ) as XHRBreakpoint [ ] ) {
173241 if ( child . checkboxState !== state ) {
174242 child . checkboxState = state ;
175243 }
176244 }
245+ this . _syncBreakpointsRootState ( ) ;
177246 } else if ( breakpoint instanceof Breakpoint || breakpoint instanceof XHRBreakpoint ) {
178247 const parent = this . getParent ( breakpoint ) as Category ;
179248 if ( ! enabled && parent . checked ) {
@@ -186,6 +255,7 @@ class BreakpointsDataProvider implements vscode.TreeDataProvider<TreeItem> {
186255 ) {
187256 parent . checkboxState = state ;
188257 }
258+ this . _syncBreakpointsRootState ( ) ;
189259 }
190260 }
191261
@@ -236,13 +306,50 @@ class BreakpointsDataProvider implements vscode.TreeDataProvider<TreeItem> {
236306 ? vscode . TreeItemCheckboxState . Checked
237307 : vscode . TreeItemCheckboxState . Unchecked ;
238308 }
309+
310+ private _syncBreakpointsRootState ( ) : void {
311+ const allChecked = [ ...this . categories . values ( ) ] . every ( c => c . checked ) ;
312+ this . _breakpointsRoot . checkboxState = allChecked
313+ ? vscode . TreeItemCheckboxState . Checked
314+ : vscode . TreeItemCheckboxState . Unchecked ;
315+ }
316+
317+ _onFocusEmulationChanged ?: ( enabled : boolean ) => void ;
318+
319+ private async _checkEmulationSupport ( session : vscode . DebugSession ) : Promise < void > {
320+ try {
321+ const result : Dap . CanEmulateResult = await session . customRequest ( 'canEmulate' , { } ) ;
322+ if ( result . supported ) {
323+ this . _emulationSessions . add ( session . id ) ;
324+ this . _onDidChangeTreeData . fire ( undefined ) ;
325+
326+ if ( this . _focusEmulationEnabled ) {
327+ session . customRequest ( 'setFocusEmulation' , { enabled : true } ) ;
328+ }
329+ }
330+ } catch {
331+ // Session doesn't support emulation
332+ }
333+ }
334+
335+ private _applyFocusEmulationToAllSessions ( ) : void {
336+ for ( const sessionId of this . _emulationSessions ) {
337+ const session = this . _debugSessionTracker . getById ( sessionId ) ;
338+ if ( session ) {
339+ session . customRequest ( 'setFocusEmulation' , { enabled : this . _focusEmulationEnabled } ) ;
340+ }
341+ }
342+ }
239343}
240344
241345export function registerCustomBreakpointsUI (
242346 context : vscode . ExtensionContext ,
243347 debugSessionTracker : DebugSessionTracker ,
244348) {
245- const provider = new BreakpointsDataProvider ( debugSessionTracker ) ;
349+ const focusEmulationEnabled = context . workspaceState . get ( focusEmulationStorageKey , false ) ;
350+ const provider = new BrowserOptionsDataProvider ( debugSessionTracker , focusEmulationEnabled ) ;
351+ provider . _onFocusEmulationChanged = enabled =>
352+ context . workspaceState . update ( focusEmulationStorageKey , enabled ) ;
246353
247354 const view = vscode . window . createTreeView ( CustomViews . EventListenerBreakpoints , {
248355 treeDataProvider : provider ,
@@ -252,9 +359,18 @@ export function registerCustomBreakpointsUI(
252359
253360 context . subscriptions . push (
254361 view . onDidChangeCheckboxState ( async e => {
255- provider . setEnabled (
256- e . items . map ( ( [ i , state ] ) => [ i , state === vscode . TreeItemCheckboxState . Checked ] ) ,
257- ) ;
362+ const breakpointChanges : [ BreakpointItem , boolean ] [ ] = [ ] ;
363+ for ( const [ item , state ] of e . items ) {
364+ const enabled = state === vscode . TreeItemCheckboxState . Checked ;
365+ if ( item instanceof FocusEmulationOption ) {
366+ provider . setFocusEmulation ( enabled ) ;
367+ } else {
368+ breakpointChanges . push ( [ item as BreakpointItem , enabled ] ) ;
369+ }
370+ }
371+ if ( breakpointChanges . length ) {
372+ provider . setBreakpointsEnabled ( breakpointChanges ) ;
373+ }
258374 } ) ,
259375 ) ;
260376
@@ -287,13 +403,13 @@ export function registerCustomBreakpointsUI(
287403 }
288404
289405 const pickedSet = new Set ( picked . map ( i => i . id ) ) ;
290- provider . setEnabled ( provider . allBreakpoints . map ( i => [ i , pickedSet . has ( i . id ) ] ) ) ;
406+ provider . setBreakpointsEnabled ( provider . allBreakpoints . map ( i => [ i , pickedSet . has ( i . id ) ] ) ) ;
291407 } ) ,
292408 ) ;
293409
294410 context . subscriptions . push (
295411 vscode . commands . registerCommand ( Commands . RemoveAllCustomBreakpoints , ( ) => {
296- provider . setEnabled (
412+ provider . setBreakpointsEnabled (
297413 [ ...provider . allBreakpoints , ...provider . xhrBreakpoints ] . map ( bp => [ bp , false ] ) ,
298414 ) ;
299415 } ) ,
0 commit comments