@@ -277,6 +277,144 @@ test.describe('Permissions API', () => {
277
277
} ) ;
278
278
} ) ;
279
279
280
+ test . describe ( 'Permissions API - when present' , ( ) => {
281
+ function checkForPermissions ( ) {
282
+ return ! ! window . navigator . permissions ;
283
+ }
284
+
285
+ test . describe ( 'disabled feature' , ( ) => {
286
+ test ( 'should not modify existing permissions API' , async ( { page } ) => {
287
+ await gotoAndWait ( page , '/blank.html' , { site : { enabledFeatures : [ ] } } ) ;
288
+ const hasPermissions = await page . evaluate ( checkForPermissions ) ;
289
+ expect ( hasPermissions ) . toEqual ( true ) ;
290
+
291
+ // Test that the original API behavior is preserved
292
+ const originalQuery = await page . evaluate ( ( ) => {
293
+ return window . navigator . permissions . query ;
294
+ } ) ;
295
+ expect ( typeof originalQuery ) . toBe ( 'function' ) ;
296
+ } ) ;
297
+ } ) ;
298
+
299
+ test . describe ( 'enabled feature' , ( ) => {
300
+ /**
301
+ * @param {import("@playwright/test").Page } page
302
+ */
303
+ async function before ( page ) {
304
+ await gotoAndWait ( page , '/blank.html' , {
305
+ site : {
306
+ enabledFeatures : [ 'webCompat' ] ,
307
+ } ,
308
+ featureSettings : {
309
+ webCompat : {
310
+ permissionsPresent : {
311
+ state : 'enabled' ,
312
+ } ,
313
+ permissions : {
314
+ state : 'enabled' ,
315
+ supportedPermissions : {
316
+ geolocation : { } ,
317
+ push : {
318
+ name : 'notifications' ,
319
+ } ,
320
+ camera : {
321
+ name : 'video_capture' ,
322
+ native : true ,
323
+ } ,
324
+ } ,
325
+ } ,
326
+ } ,
327
+ } ,
328
+ } ) ;
329
+ }
330
+
331
+ /**
332
+ * @param {import("@playwright/test").Page } page
333
+ * @param {any } name
334
+ * @return {Promise<{result: any, message: *}> }
335
+ */
336
+ async function checkPermission ( page , name ) {
337
+ const payload = `window.navigator.permissions.query(${ JSON . stringify ( { name } ) } )` ;
338
+ const result = await page . evaluate ( payload ) . catch ( ( e ) => {
339
+ return { threw : e } ;
340
+ } ) ;
341
+ const message = await page . evaluate ( ( ) => {
342
+ return globalThis . shareReq ;
343
+ } ) ;
344
+ return { result, message } ;
345
+ }
346
+
347
+ test ( 'should preserve existing permissions API' , async ( { page } ) => {
348
+ await before ( page ) ;
349
+ const hasPermissions = await page . evaluate ( checkForPermissions ) ;
350
+ expect ( hasPermissions ) . toEqual ( true ) ;
351
+ } ) ;
352
+
353
+ test ( 'should fall through to original API for non-native permissions' , async ( { page } ) => {
354
+ await before ( page ) ;
355
+ const { result } = await checkPermission ( page , 'geolocation' ) ;
356
+ // Should use original API behavior, not our custom implementation
357
+ expect ( result ) . toBeDefined ( ) ;
358
+ // The result should be a native PermissionStatus, not our custom one
359
+ expect ( result . constructor . name ) . toBe ( 'PermissionStatus' ) ;
360
+ } ) ;
361
+
362
+ test ( 'should fall through to original API for unsupported permissions' , async ( { page } ) => {
363
+ await before ( page ) ;
364
+ const { result } = await checkPermission ( page , 'notexistent' ) ;
365
+ // Should use original API behavior for validation
366
+ expect ( result . threw ) . not . toBeUndefined ( ) ;
367
+ } ) ;
368
+
369
+ test ( 'should intercept native permissions and return custom result' , async ( { page } ) => {
370
+ await before ( page ) ;
371
+ // Fake result from native
372
+ await page . evaluate ( ( ) => {
373
+ globalThis . cssMessaging . impl . request = ( req ) => {
374
+ globalThis . shareReq = req ;
375
+ return Promise . resolve ( { state : 'granted' } ) ;
376
+ } ;
377
+ } ) ;
378
+ const { result, message } = await checkPermission ( page , 'camera' ) ;
379
+ expect ( result ) . toMatchObject ( { name : 'video_capture' , state : 'granted' } ) ;
380
+ expect ( message ) . toMatchObject ( { featureName : 'webCompat' , method : 'permissionsQuery' , params : { name : 'camera' } } ) ;
381
+ } ) ;
382
+
383
+ test ( 'should fall through to original API when native messaging fails' , async ( { page } ) => {
384
+ await before ( page ) ;
385
+ await page . evaluate ( ( ) => {
386
+ globalThis . cssMessaging . impl . request = ( message ) => {
387
+ globalThis . shareReq = message ;
388
+ return Promise . reject ( new Error ( 'something wrong' ) ) ;
389
+ } ;
390
+ } ) ;
391
+ const { result, message } = await checkPermission ( page , 'camera' ) ;
392
+ // Should fall through to original API when messaging fails
393
+ expect ( result ) . toBeDefined ( ) ;
394
+ expect ( message ) . toMatchObject ( { featureName : 'webCompat' , method : 'permissionsQuery' , params : { name : 'camera' } } ) ;
395
+ } ) ;
396
+
397
+ test ( 'should fall through to original API for invalid arguments' , async ( { page } ) => {
398
+ await before ( page ) ;
399
+ const { result } = await checkPermission ( page , null ) ;
400
+ // Should use original API validation
401
+ expect ( result . threw ) . not . toBeUndefined ( ) ;
402
+ } ) ;
403
+
404
+ test ( 'should use configured name override for native permissions' , async ( { page } ) => {
405
+ await before ( page ) ;
406
+ await page . evaluate ( ( ) => {
407
+ globalThis . cssMessaging . impl . request = ( req ) => {
408
+ globalThis . shareReq = req ;
409
+ return Promise . resolve ( { state : 'denied' } ) ;
410
+ } ;
411
+ } ) ;
412
+ const { result } = await checkPermission ( page , 'push' ) ;
413
+ expect ( result ) . toMatchObject ( { name : 'notifications' , state : 'denied' } ) ;
414
+ } ) ;
415
+ } ) ;
416
+ } ) ;
417
+
280
418
test . describe ( 'ScreenOrientation API' , ( ) => {
281
419
test . describe ( 'disabled feature' , ( ) => {
282
420
/**
0 commit comments