@@ -277,6 +277,144 @@ test.describe('Permissions API', () => {
277277 } ) ;
278278} ) ;
279279
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+
280418test . describe ( 'ScreenOrientation API' , ( ) => {
281419 test . describe ( 'disabled feature' , ( ) => {
282420 /**
0 commit comments