Bug 864895 - Catch exceptions from PopupNotification callbacks. r=MattN
authorSankha Narayan Guria <sankha93@gmail.com>
Sat, 01 Jun 2013 08:22:16 +0530
changeset 165872 cdf9d97ed9cd196c977f12728f91c4aa780bf079
parent 165871 49627b9b1be3f0ffbf8c1b420a24fc7244a7e293
child 165873 30d9e30f0c8cdb2d3ef48029662a9dab066b1f28
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs864895
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 864895 - Catch exceptions from PopupNotification callbacks. r=MattN
browser/base/content/test/general/browser_popupNotification.js
toolkit/modules/PopupNotifications.jsm
--- a/browser/base/content/test/general/browser_popupNotification.js
+++ b/browser/base/content/test/general/browser_popupNotification.js
@@ -150,16 +150,64 @@ function basicNotification() {
     }
   };
   this.addOptions = function(options) {
     for (let [name, value] in Iterator(options))
       self.options[name] = value;
   }
 }
 
+function errorNotification() {
+  var self = this;
+  this.browser = gBrowser.selectedBrowser;
+  this.id = "test-notification-" + gTestIndex;
+  this.message = "This is popup notification " + this.id + " from test " + gTestIndex;
+  this.anchorID = null;
+  this.mainAction = {
+    label: "Main Action",
+    accessKey: "M",
+    callback: function () {
+      self.mainActionClicked = true;
+      throw new Error("Oops!");
+    }
+  };
+  this.secondaryActions = [
+    {
+      label: "Secondary Action",
+      accessKey: "S",
+      callback: function () {
+        self.secondaryActionClicked = true;
+        throw new Error("Oops!");
+      }
+    }
+  ];
+  this.options = {
+    eventCallback: function (eventName) {
+      switch (eventName) {
+        case "dismissed":
+          self.dismissalCallbackTriggered = true;
+          break;
+        case "showing":
+          self.showingCallbackTriggered = true;
+          break;
+        case "shown":
+          self.shownCallbackTriggered = true;
+          break;
+        case "removed":
+          self.removedCallbackTriggered = true;
+          break;
+      }
+    }
+  };
+  this.addOptions = function(options) {
+    for (let [name, value] in Iterator(options))
+      self.options[name] = value;
+  }
+}
+
 var wrongBrowserNotificationObject = new basicNotification();
 var wrongBrowserNotification;
 
 var tests = [
   { // Test #0
     run: function () {
       this.notifyObj = new basicNotification();
       showNotification(this.notifyObj);
@@ -826,17 +874,79 @@ var tests = [
         showNotification(notifyObj);
         executeSoon(function () {
           content.document.getElementById("iframe")
                           .setAttribute("src", "http://example.org/");
         });
       });
     }
   },
-  { // Test #29 -  Existing popup notification shouldn't disappear when adding a dismissed notification
+  { // Test #29 - Popup Notifications should catch exceptions from callbacks
+    run: function () {
+      let callbackCount = 0;
+      this.testNotif1 = new basicNotification();
+      this.testNotif1.message += " 1";
+      showNotification(this.testNotif1);
+      this.testNotif1.options.eventCallback = function (eventName) {
+        info("notifyObj1.options.eventCallback: " + eventName);
+        if (eventName == "dismissed") {
+          throw new Error("Oops 1!");
+          if (++callbackCount == 2) {
+            executeSoon(goNext);
+          }
+        }
+      };
+
+      this.testNotif2 = new basicNotification();
+      this.testNotif2.message += " 2";
+      this.testNotif2.id += "-2";
+      this.testNotif2.options.eventCallback = function (eventName) {
+        info("notifyObj2.options.eventCallback: " + eventName);
+        if (eventName == "dismissed") {
+          throw new Error("Oops 2!");
+          if (++callbackCount == 2) {
+            executeSoon(goNext);
+          }
+        }
+      };
+      showNotification(this.testNotif2);
+    },
+    onShown: function (popup) {
+      is(popup.childNodes.length, 2, "two notifications are shown");
+      dismissNotification(popup);
+    },
+    onHidden: function () {}
+  },
+  { // Test #30 - Popup Notifications main actions should catch exceptions from callbacks
+    run: function () {
+      this.testNotif = new errorNotification();
+      showNotification(this.testNotif);
+    },
+    onShown: function (popup) {
+      checkPopup(popup, this.testNotif);
+      triggerMainCommand(popup);
+    },
+    onHidden: function (popup) {
+      ok(this.testNotif.mainActionClicked, "main action has been triggered");
+    }
+  },
+  { // Test #31 - Popup Notifications secondary actions should catch exceptions from callbacks
+    run: function () {
+      this.testNotif = new errorNotification();
+      showNotification(this.testNotif);
+    },
+    onShown: function (popup) {
+      checkPopup(popup, this.testNotif);
+      triggerSecondaryCommand(popup, 0);
+    },
+    onHidden: function (popup) {
+      ok(this.testNotif.secondaryActionClicked, "secondary action has been triggered");
+    }
+  },
+  { // Test #32 -  Existing popup notification shouldn't disappear when adding a dismissed notification
     run: function () {
       this.notifyObj1 = new basicNotification();
       this.notifyObj1.id += "_1";
       this.notifyObj1.anchorID = "default-notification-icon";
       this.notification1 = showNotification(this.notifyObj1);
     },
     onShown: function (popup) {
       // Now show a dismissed notification, and check that it doesn't clobber
@@ -857,17 +967,17 @@ var tests = [
 
       dismissNotification(popup);
     },
     onHidden: function(popup) {
       this.notification1.remove();
       this.notification2.remove();
     }
   },
-  { // Test #30 - Showing should be able to modify the popup data
+  { // Test #33 - Showing should be able to modify the popup data
     run: function() {
       this.notifyObj = new basicNotification();
       var normalCallback = this.notifyObj.options.eventCallback;
       this.notifyObj.options.eventCallback = function (eventName) {
         if (eventName == "showing") {
           this.mainAction.label = "Alternate Label";
         }
         normalCallback.call(this, eventName);
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 this.EXPORTED_SYMBOLS = ["PopupNotifications"];
 
-var Cc = Components.classes, Ci = Components.interfaces;
+var Cc = Components.classes, Ci = Components.interfaces, Cu = Components.utils;
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 const NOTIFICATION_EVENT_DISMISSED = "dismissed";
 const NOTIFICATION_EVENT_REMOVED = "removed";
 const NOTIFICATION_EVENT_SHOWING = "showing";
 const NOTIFICATION_EVENT_SHOWN = "shown";
 
 const ICON_SELECTOR = ".notification-anchor-icon";
 const ICON_ATTRIBUTE_SHOWING = "showing";
@@ -743,18 +743,22 @@ PopupNotifications.prototype = {
         n.dismissed = false;
     });
 
     // ...and then show them.
     this._update(notifications, anchor);
   },
 
   _fireCallback: function PopupNotifications_fireCallback(n, event) {
-    if (n.options.eventCallback)
-      n.options.eventCallback.call(n, event);
+    try {
+      if (n.options.eventCallback)
+        n.options.eventCallback.call(n, event);
+    } catch (error) {
+      Cu.reportError(error);
+    }
   },
 
   _onPopupHidden: function PopupNotifications_onPopupHidden(event) {
     if (event.target != this.panel || this._ignoreDismissal)
       return;
 
     let browser = this.panel.firstChild &&
                   this.panel.firstChild.notification.browser;
@@ -812,29 +816,37 @@ PopupNotifications.prototype = {
     }
 
     if (timeSinceShown < this.buttonDelay) {
       Services.console.logStringMessage("PopupNotifications_onButtonCommand: " +
                                         "Button click happened before the security delay: " +
                                         timeSinceShown + "ms");
       return;
     }
-    notification.mainAction.callback.call();
+    try {
+      notification.mainAction.callback.call();
+    } catch(error) {
+      Cu.reportError(error);
+    }
 
     this._remove(notification);
     this._update();
   },
 
   _onMenuCommand: function PopupNotifications_onMenuCommand(event) {
     let target = event.originalTarget;
     if (!target.action || !target.notification)
       throw "menucommand target has no associated action/notification";
 
     event.stopPropagation();
-    target.action.callback.call();
+    try {
+      target.action.callback.call();
+    } catch(error) {
+      Cu.reportError(error);
+    }
 
     this._remove(target.notification);
     this._update();
   },
 
   _notify: function PopupNotifications_notify(topic) {
     Services.obs.notifyObservers(null, "PopupNotifications-" + topic, "");
   },