@@ -4,6 +4,7 @@ import { createSerializer } from 'enzyme-to-json';
44import { configure as mobxConfigure } from 'mobx' ;
55
66import { ElectronFiddleMock } from './mocks/mocks' ;
7+ import { getOrCreateMapValue } from './utils' ;
78
89enzymeConfigure ( { adapter : new Adapter ( ) } ) ;
910
@@ -75,47 +76,159 @@ delete (window as any).localStorage;
7576window . navigator = window . navigator ?? { } ;
7677( window . navigator . clipboard as any ) = { } ;
7778
79+ type MockLock = Lock & {
80+ abortController : AbortController ;
81+ } ;
82+
7883class FakeNavigatorLocks implements LockManager {
7984 locks = {
80- held : new Set < Lock > ( ) ,
81- pending : new Set < Lock > ( ) ,
85+ held : new Map < string , Map < LockMode , Set < MockLock > > > ( ) ,
8286 } ;
8387
8488 query = async ( ) => {
8589 const result = {
86- held : [ ...this . locks . held ] ,
87- pending : [ ...this . locks . pending ] ,
90+ held : [ ...this . locks . held . values ( ) ] . reduce ( ( acc , item ) => {
91+ acc . push ( ...[ ...item . get ( 'exclusive' ) ! . values ( ) ] ) ;
92+ acc . push ( ...[ ...item . get ( 'shared' ) ! . values ( ) ] ) ;
93+
94+ return acc ;
95+ } , [ ] as MockLock [ ] ) ,
8896 } ;
8997
9098 return result as LockManagerSnapshot ;
9199 } ;
92100
93- /**
94- * WIP. Right now, this is a **very** naive mock that will just happily grant a shared lock when one is requested,
95- * but I'll add some bookkeeping and expand it to cover the exclusive lock case as well.
96- *
97- * @TODO remove this comment
98- */
99101 request = ( async ( ...args : Parameters < LockManager [ 'request' ] > ) => {
100102 const [
101103 name ,
102104 options = {
105+ ifAvailable : false ,
103106 mode : 'exclusive' ,
107+ steal : false ,
104108 } ,
105109 cb ,
106110 ] = args ;
107111
108- const { mode } = options ;
112+ const { ifAvailable, mode, steal } = options ;
113+
114+ const lock = {
115+ name,
116+ mode,
117+ abortController : new AbortController ( ) ,
118+ } as MockLock ;
119+
120+ const heldLocksWithSameName = getOrCreateMapValue (
121+ this . locks . held ,
122+ name ,
123+ new Map < LockMode , Set < MockLock > > ( ) ,
124+ ) ;
125+
126+ const exclusiveLocksWithSameName = getOrCreateMapValue (
127+ heldLocksWithSameName ,
128+ 'exclusive' ,
129+ new Set < MockLock > ( ) ,
130+ ) ;
109131
110- const lock = { name, mode, cb } as Lock ;
132+ const sharedLocksWithSameName = getOrCreateMapValue (
133+ heldLocksWithSameName ,
134+ 'shared' ,
135+ new Set < MockLock > ( ) ,
136+ ) ;
111137
112138 if ( mode === 'shared' ) {
113- this . locks . held . add ( lock ) ;
139+ sharedLocksWithSameName . add ( lock ) ;
114140
115- await cb ( lock ) ;
141+ try {
142+ await cb ( lock ) ;
143+ } finally {
144+ sharedLocksWithSameName . delete ( lock ) ;
145+ }
146+
147+ return ;
148+ }
149+
150+ // exclusive lock
151+
152+ // no locks with this name -> grant an exclusive lock
153+ if (
154+ exclusiveLocksWithSameName . size === 0 &&
155+ sharedLocksWithSameName . size === 0
156+ ) {
157+ exclusiveLocksWithSameName . add ( lock ) ;
158+
159+ try {
160+ await cb ( lock ) ;
161+ } finally {
162+ exclusiveLocksWithSameName . delete ( lock ) ;
163+ }
116164
117165 return ;
118166 }
167+
168+ // steal any currently held locks
169+ if ( steal ) {
170+ for ( const lock of sharedLocksWithSameName ) {
171+ lock . abortController . abort ( ) ;
172+ }
173+
174+ for ( const lock of exclusiveLocksWithSameName ) {
175+ lock . abortController . abort ( ) ;
176+ }
177+
178+ sharedLocksWithSameName . clear ( ) ;
179+ exclusiveLocksWithSameName . clear ( ) ;
180+
181+ exclusiveLocksWithSameName . add ( lock ) ;
182+
183+ try {
184+ await cb ( lock ) ;
185+ } finally {
186+ exclusiveLocksWithSameName . delete ( lock ) ;
187+ }
188+
189+ return ;
190+ }
191+
192+ // run the callback without waiting for the lock to be released
193+ if ( ifAvailable ) {
194+ // just run the callback without waiting for it
195+ cb ( null ) ;
196+
197+ return ;
198+ }
199+
200+ // @TODO add the lock to the list of pending locks?
201+
202+ // it's an exclusive lock, so there's only one value
203+ const currentLock = exclusiveLocksWithSameName . values ( ) . next ( )
204+ . value as MockLock ;
205+
206+ const { abortController : currentLockAbortController } = currentLock ;
207+
208+ // wait for the current lock to be released
209+ await new Promise < void > ( ( resolve , reject ) => {
210+ currentLockAbortController . signal . onabort = ( ) => resolve ( ) ;
211+
212+ const { abortController : pendingLockAbortController } = lock ;
213+
214+ // this allows the locking mechanism to release this lock
215+ pendingLockAbortController . signal . onabort = ( ) => reject ( ) ;
216+ } ) ;
217+
218+ // clear the exclusive locks
219+ exclusiveLocksWithSameName . clear ( ) ;
220+
221+ // grant our lock
222+ exclusiveLocksWithSameName . add ( lock ) ;
223+
224+ try {
225+ // run the callback
226+ await cb ( lock ) ;
227+ } finally {
228+ exclusiveLocksWithSameName . delete ( lock ) ;
229+ }
230+
231+ return ;
119232 } ) as LockManager [ 'request' ] ;
120233}
121234
0 commit comments