Skip to content

Commit 6b6a332

Browse files
Add nativeEnabled setting and isSecureContext gate to webNotifications
- Add isSecureContext check (crypto.randomUUID requires secure context) - Add nativeEnabled setting (defaults true, when false returns denied and skips native calls) - Add integration tests for nativeEnabled: false behavior
1 parent 5aab99c commit 6b6a332

File tree

2 files changed

+79
-14
lines changed

2 files changed

+79
-14
lines changed

injected/integration-test/web-compat.spec.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,56 @@ test.describe('webNotifications', () => {
264264
});
265265
});
266266

267+
test.describe('webNotifications with nativeEnabled: false', () => {
268+
/**
269+
* @param {import("@playwright/test").Page} page
270+
*/
271+
async function beforeWebNotificationsDisabled(page) {
272+
await gotoAndWait(page, '/blank.html', {
273+
site: { enabledFeatures: ['webCompat'] },
274+
featureSettings: { webCompat: { webNotifications: { state: 'enabled', nativeEnabled: false } } },
275+
});
276+
}
277+
278+
test('should return denied for permission when nativeEnabled is false', async ({ page }) => {
279+
await beforeWebNotificationsDisabled(page);
280+
const permission = await page.evaluate(() => window.Notification.permission);
281+
expect(permission).toEqual('denied');
282+
});
283+
284+
test('should not send showNotification when nativeEnabled is false', async ({ page }) => {
285+
await beforeWebNotificationsDisabled(page);
286+
await page.evaluate(() => {
287+
globalThis.notifyCalls = [];
288+
globalThis.cssMessaging.impl.notify = (msg) => {
289+
globalThis.notifyCalls.push(msg);
290+
};
291+
});
292+
293+
await page.evaluate(() => new window.Notification('Test Title'));
294+
295+
const calls = await page.evaluate(() => globalThis.notifyCalls);
296+
expect(calls.length).toEqual(0);
297+
});
298+
299+
test('should return denied from requestPermission without calling native', async ({ page }) => {
300+
await beforeWebNotificationsDisabled(page);
301+
await page.evaluate(() => {
302+
globalThis.requestCalls = [];
303+
globalThis.cssMessaging.impl.request = (msg) => {
304+
globalThis.requestCalls.push(msg);
305+
return Promise.resolve({ permission: 'granted' });
306+
};
307+
});
308+
309+
const permission = await page.evaluate(() => window.Notification.requestPermission());
310+
const calls = await page.evaluate(() => globalThis.requestCalls);
311+
312+
expect(permission).toEqual('denied');
313+
expect(calls.length).toEqual(0);
314+
});
315+
});
316+
267317
test.describe('Permissions API', () => {
268318
// Fake the Permission API not existing in this browser
269319
const removePermissionsScript = `

injected/src/features/web-compat.js

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,27 @@ export class WebCompat extends ContentFeature {
279279
* management and notification display.
280280
*/
281281
webNotificationsFix() {
282+
// crypto.randomUUID() requires secure context
283+
if (!globalThis.isSecureContext) {
284+
return;
285+
}
286+
282287
console.log('[webNotificationsFix] Starting - will override Notification API');
283288
console.log('[webNotificationsFix] Current Notification exists:', !!globalThis.Notification);
284289
// eslint-disable-next-line @typescript-eslint/no-this-alias
285290
const feature = this;
286291

292+
// Check nativeEnabled setting - when false, install polyfill but skip native calls and return 'denied'
293+
const settings = this.getFeatureSetting('webNotifications') || {};
294+
const nativeEnabled = settings.nativeEnabled !== false;
295+
296+
// Wrap native calls - no-op when nativeEnabled is false
297+
const nativeNotify = nativeEnabled ? (name, data) => feature.notify(name, data) : () => {};
298+
const nativeRequest = nativeEnabled ? (name, data) => feature.request(name, data) : () => Promise.resolve({ permission: 'denied' });
299+
const nativeSubscribe = nativeEnabled ? (name, cb) => feature.subscribe(name, cb) : () => () => {};
300+
/** @type {NotificationPermission} */
301+
const permission = nativeEnabled ? 'granted' : 'denied';
302+
287303
/**
288304
* NotificationPolyfill - replaces the native Notification API
289305
*/
@@ -315,8 +331,8 @@ export class WebCompat extends ContentFeature {
315331
* @returns {'default' | 'denied' | 'granted'}
316332
*/
317333
static get permission() {
318-
console.log('[webNotificationsFix] permission getter called, returning granted');
319-
return 'granted';
334+
console.log('[webNotificationsFix] permission getter called, returning', permission);
335+
return permission;
320336
}
321337

322338
/**
@@ -326,21 +342,20 @@ export class WebCompat extends ContentFeature {
326342
static async requestPermission(deprecatedCallback) {
327343
console.log('[webNotificationsFix] requestPermission called');
328344
try {
329-
const result = await feature.request('requestPermission', {});
345+
const result = await nativeRequest('requestPermission', {});
330346
console.log('[webNotificationsFix] requestPermission result from native:', result);
331-
const permission = result?.permission || 'granted';
332-
console.log('[webNotificationsFix] requestPermission returning:', permission);
347+
const resultPermission = result?.permission || permission;
348+
console.log('[webNotificationsFix] requestPermission returning:', resultPermission);
333349
if (deprecatedCallback) {
334-
deprecatedCallback(permission);
350+
deprecatedCallback(resultPermission);
335351
}
336-
return permission;
352+
return resultPermission;
337353
} catch (e) {
338354
console.log('[webNotificationsFix] requestPermission error:', e);
339-
const fallback = 'granted';
340355
if (deprecatedCallback) {
341-
deprecatedCallback(fallback);
356+
deprecatedCallback(permission);
342357
}
343-
return fallback;
358+
return permission;
344359
}
345360
}
346361

@@ -365,7 +380,7 @@ export class WebCompat extends ContentFeature {
365380

366381
feature.#webNotifications.set(this.#id, this);
367382

368-
feature.notify('showNotification', {
383+
nativeNotify('showNotification', {
369384
id: this.#id,
370385
title: this.title,
371386
body: this.body,
@@ -375,7 +390,7 @@ export class WebCompat extends ContentFeature {
375390
}
376391

377392
close() {
378-
feature.notify('closeNotification', { id: this.#id });
393+
nativeNotify('closeNotification', { id: this.#id });
379394
feature.#webNotifications.delete(this.#id);
380395
}
381396
}
@@ -391,7 +406,7 @@ export class WebCompat extends ContentFeature {
391406
);
392407

393408
// Subscribe to notification events from native
394-
this.subscribe('notificationEvent', (data) => {
409+
nativeSubscribe('notificationEvent', (data) => {
395410
const notification = this.#webNotifications.get(data.id);
396411
if (!notification) return;
397412

@@ -420,7 +435,7 @@ export class WebCompat extends ContentFeature {
420435

421436
// Define permission getter
422437
this.defineProperty(globalThis.Notification, 'permission', {
423-
get: () => 'granted',
438+
get: () => permission,
424439
configurable: true,
425440
enumerable: true,
426441
});

0 commit comments

Comments
 (0)