hterm: Add 'desktop-notification-bell' option.

If true, terminal bells emitted while the terminal does not have focus
trigger web notifications (http://www.w3.org/TR/notifications/).

I've tried to make the behavior relatively unobtrusive: web
notifications respect the audio bell squelch, and are all cleared when
the terminal gets focus or if the user clicks any notification.

BUG=chromium:208710
Change-Id: Ic114e7e71a2ad5d82b13211bf93010b65c5131f3
Reviewed-on: https://chromium-review.googlesource.com/203003
Reviewed-by: Robert Ginda <rginda@chromium.org>
Tested-by: Robert Ginda <rginda@chromium.org>
diff --git a/hterm/js/hterm_terminal.js b/hterm/js/hterm_terminal.js
index fbfae79..1d8a01c 100644
--- a/hterm/js/hterm_terminal.js
+++ b/hterm/js/hterm_terminal.js
@@ -99,6 +99,15 @@
   this.bellAudio_ = this.document_.createElement('audio');
   this.bellAudio_.setAttribute('preload', 'auto');
 
+  // All terminal bell notifications that have been generated (not necessarily
+  // shown).
+  this.bellNotificationList_ = [];
+
+  // Whether we have permission to display notifications.
+  this.desktopNotificationBell_ = false;
+  //this.notificationPermission_ = (Notification &&
+                                  //Notification.permission === 'granted');
+
   // Cursor position and attributes saved with DECSC.
   this.savedOptions_ = {};
 
@@ -200,6 +209,21 @@
       }
     },
 
+    'desktop-notification-bell': function(v) {
+      if (v && Notification) {
+        // We cannot rely on having notification permission by default.
+        if (Notification.permission !== 'granted') {
+          Notification.requestPermission(function(permission) {
+              terminal.desktopNotificationBell_ = (permission === 'granted');
+            });
+        } else {
+          terminal.desktopNotificationBell_ = true;
+        }
+      } else {
+        terminal.desktopNotificationBell_ = false;
+      }
+    },
+
     'background-color': function(v) {
       terminal.setBackgroundColor(v);
     },
@@ -2049,18 +2073,27 @@
       self.cursorNode_.style.backgroundColor = self.prefs_.get('cursor-color');
     }, 200);
 
+  // bellSquelchTimeout_ affects both audio and notification bells.
+  if (this.bellSquelchTimeout_)
+    return;
+
   if (this.bellAudio_.getAttribute('src')) {
-    if (this.bellSquelchTimeout_)
-      return;
-
     this.bellAudio_.play();
-
     this.bellSequelchTimeout_ = setTimeout(function() {
         delete this.bellSquelchTimeout_;
       }.bind(this), 500);
   } else {
     delete this.bellSquelchTimeout_;
   }
+
+  if (this.desktopNotificationBell_ && !this.document_.hasFocus()) {
+    var n = new Notification(
+        lib.f.replaceVars(hterm.desktopNotificationTitle,
+                          {'title': this.document_.title || 'hterm'}));
+    this.bellNotificationList_.push(n);
+    // TODO: Should we try to raise the window here?
+    n.onclick = function() { self.closeBellNotifications_(); };
+  }
 };
 
 /**
@@ -2675,6 +2708,8 @@
 hterm.Terminal.prototype.onFocusChange_ = function(focused) {
   this.cursorNode_.setAttribute('focus', focused);
   this.restyleCursor_();
+  if (focused === true)
+    this.closeBellNotifications_();
 };
 
 /**
@@ -2760,3 +2795,13 @@
 hterm.Terminal.prototype.setScrollbarVisible = function(state) {
   this.scrollPort_.setScrollbarVisible(state);
 };
+
+/**
+ * Close all web notifications created by terminal bells.
+ */
+hterm.Terminal.prototype.closeBellNotifications_ = function() {
+  this.bellNotificationList_.forEach(function(n) {
+      n.close();
+    });
+  this.bellNotificationList_.length = 0;
+};