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