diff --git a/lib/polyfill.coffee b/lib/polyfill.coffee
index 6bab699cb..44ad79c18 100644
--- a/lib/polyfill.coffee
+++ b/lib/polyfill.coffee
@@ -1,9 +1,21 @@
Polyfill =
init: ->
<% if (type === 'crx') { %>
- Polyfill.toBlob()
- Polyfill.visibility()
+ @notificationPermission()
+ @toBlob()
+ @visibility()
<% } %>
+ notificationPermission: ->
+ return if window.Notification and 'permission' of Notification
+ Object.defineProperty Notification, 'permission',
+ get: ->
+ switch webkitNotifications.checkPermission()
+ when 0
+ 'granted'
+ when 1
+ 'default'
+ when 2
+ 'denied'
toBlob: ->
HTMLCanvasElement::toBlob or= (cb) ->
data = atob @toDataURL()[22..]
diff --git a/src/General/Config.coffee b/src/General/Config.coffee
index 2270444ec..cd0aa1413 100644
--- a/src/General/Config.coffee
+++ b/src/General/Config.coffee
@@ -2,6 +2,7 @@ Config =
main:
'Miscellaneous':
'Enable 4chan\'s Extension': [false, 'Compatibility between <%= meta.name %> and 4chan\'s inline extension is NOT guaranteed.']
+ 'Desktop Notifications': [true, 'Enables desktop notifications across various <%= meta.name %> features.']
'Announcement Hiding': [true, 'Add button to hide 4chan announcements.']
'404 Redirect': [true, 'Redirect dead threads and images.']
'Keybinds': [true, 'Bind actions to keyboard shortcuts.']
diff --git a/src/General/Header.coffee b/src/General/Header.coffee
index fd81f49cb..9c395a752 100644
--- a/src/General/Header.coffee
+++ b/src/General/Header.coffee
@@ -89,6 +89,8 @@ Header =
Header.setCatalogLinks Conf['Header catalog links']
$.sync 'Header catalog links', Header.setCatalogLinks
+ @enableDesktopNotifications()
+
setBoardList: ->
nav = $.id 'boardNavDesktop'
if a = $ "a[href*='/#{g.BOARD}/']", nav
@@ -268,3 +270,31 @@ Header =
{type, content, lifetime, cb} = e.detail
notif = new Notice type, content, lifetime
cb notif if cb
+
+ areNotificationsEnabled: false
+ enableDesktopNotifications: ->
+ return unless window.Notification and Conf['Desktop Notifications']
+ switch Notification.permission
+ when 'granted'
+ Header.areNotificationsEnabled = true
+ return
+ when 'denied'
+ # requestPermission doesn't work if status is 'denied',
+ # but it'll still work if status is 'default'.
+ return
+
+ el = $.el 'span',
+ innerHTML: """
+ Desktop notification permissions are not granted:
+ or
+ """
+ [authorize, disable] = $$ 'button', el
+ $.on authorize, 'click', ->
+ Notification.requestPermission (status) ->
+ Header.areNotificationsEnabled = status is 'granted'
+ return if status is 'default'
+ notice.close()
+ $.on disable, 'click', ->
+ $.set 'Desktop Notifications', false
+ notice.close()
+ notice = new Notice 'info', el
diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee
index 29e549653..be5495b8d 100644
--- a/src/Monitoring/Unread.coffee
+++ b/src/Monitoring/Unread.coffee
@@ -96,12 +96,12 @@ Unread =
Unread.openNotification post
return
openNotification: (post) ->
- return unless d.hidden
+ return unless d.hidden and Header.areNotificationsEnabled
name = if Conf['Anonymize']
'Anonymous'
else
$('.nameBlock', post.nodes.info).textContent.trim()
- notif = new Notification "#{name} replied to you.",
+ notif = new Notification "#{name} replied to you",
body: post.info.comment
icon: Favicon.logo
notif.onclick = ->
diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee
index 794005af6..c54662a00 100644
--- a/src/Posting/QR.coffee
+++ b/src/Posting/QR.coffee
@@ -113,7 +113,7 @@ QR =
QR.captcha.nodes.input.focus()
notice = new Notice 'warning', el
QR.notifications.push notice
- return unless d.hidden
+ return unless d.hidden and Header.areNotificationsEnabled
notif = new Notification 'Quick reply warning',
body: el.textContent
icon: Favicon.logo