browser/base/content/test/general/browser_popupNotification.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 function test() {
     6   waitForExplicitFinish();
     8   ok(PopupNotifications, "PopupNotifications object exists");
     9   ok(PopupNotifications.panel, "PopupNotifications panel exists");
    11   // Disable transitions as they slow the test down and we want to click the
    12   // mouse buttons in a predictable location.
    13   PopupNotifications.transitionsEnabled = false;
    15   registerCleanupFunction(cleanUp);
    17   runNextTest();
    18 }
    20 function cleanUp() {
    21   for (var topic in gActiveObservers)
    22     Services.obs.removeObserver(gActiveObservers[topic], topic);
    23   for (var eventName in gActiveListeners)
    24     PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
    25   PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
    26   PopupNotifications.transitionsEnabled = true;
    27 }
    29 const PREF_SECURITY_DELAY_INITIAL = Services.prefs.getIntPref("security.notification_enable_delay");
    31 var gActiveListeners = {};
    32 var gActiveObservers = {};
    33 var gShownState = {};
    35 function goNext() {
    36   if (++gTestIndex == tests.length)
    37     executeSoon(finish);
    38   else
    39     executeSoon(runNextTest);
    40 }
    42 function runNextTest() {
    43   let nextTest = tests[gTestIndex];
    45   function addObserver(topic) {
    46     function observer() {
    47       Services.obs.removeObserver(observer, "PopupNotifications-" + topic);
    48       delete gActiveObservers["PopupNotifications-" + topic];
    50       info("[Test #" + gTestIndex + "] observer for " + topic + " called");
    51       nextTest[topic]();
    52       goNext();
    53     }
    54     Services.obs.addObserver(observer, "PopupNotifications-" + topic, false);
    55     gActiveObservers["PopupNotifications-" + topic] = observer;
    56   }
    58   if (nextTest.backgroundShow) {
    59     addObserver("backgroundShow");
    60   } else if (nextTest.updateNotShowing) {
    61     addObserver("updateNotShowing");
    62   } else if (nextTest.onShown) {
    63     doOnPopupEvent("popupshowing", function () {
    64       info("[Test #" + gTestIndex + "] popup showing");
    65     });
    66     doOnPopupEvent("popupshown", function () {
    67       gShownState[gTestIndex] = true;
    68       info("[Test #" + gTestIndex + "] popup shown");
    69       nextTest.onShown(this);
    70     });
    72     // We allow multiple onHidden functions to be defined in an array.  They're
    73     // called in the order they appear.
    74     let onHiddenArray = nextTest.onHidden instanceof Array ?
    75                         nextTest.onHidden :
    76                         [nextTest.onHidden];
    77     doOnPopupEvent("popuphidden", function () {
    78       if (!gShownState[gTestIndex]) {
    79         // This is expected to happen for test 9, so let's not treat it as a failure.
    80         info("Popup from test " + gTestIndex + " was hidden before its popupshown fired");
    81       }
    83       let onHidden = onHiddenArray.shift();
    84       info("[Test #" + gTestIndex + "] popup hidden (" + onHiddenArray.length + " hides remaining)");
    85       executeSoon(function () {
    86         onHidden.call(nextTest, this);
    87         if (!onHiddenArray.length)
    88           goNext();
    89       }.bind(this));
    90     }, onHiddenArray.length);
    91     info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen);
    92   }
    94   info("[Test #" + gTestIndex + "] running test");
    95   nextTest.run();
    96 }
    98 function doOnPopupEvent(eventName, callback, numExpected) {
    99   gActiveListeners[eventName] = function (event) {
   100     if (event.target != PopupNotifications.panel)
   101       return;
   102     if (typeof(numExpected) === "number")
   103       numExpected--;
   104     if (!numExpected) {
   105       PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
   106       delete gActiveListeners[eventName];
   107     }
   109     callback.call(PopupNotifications.panel);
   110   }
   111   PopupNotifications.panel.addEventListener(eventName, gActiveListeners[eventName], false);
   112 }
   114 var gTestIndex = 0;
   115 var gNewTab;
   117 function basicNotification() {
   118   var self = this;
   119   this.browser = gBrowser.selectedBrowser;
   120   this.id = "test-notification-" + gTestIndex;
   121   this.message = "This is popup notification " + this.id + " from test " + gTestIndex;
   122   this.anchorID = null;
   123   this.mainAction = {
   124     label: "Main Action",
   125     accessKey: "M",
   126     callback: function () {
   127       self.mainActionClicked = true;
   128     }
   129   };
   130   this.secondaryActions = [
   131     {
   132       label: "Secondary Action",
   133       accessKey: "S",
   134       callback: function () {
   135         self.secondaryActionClicked = true;
   136       }
   137     }
   138   ];
   139   this.options = {
   140     eventCallback: function (eventName) {
   141       switch (eventName) {
   142         case "dismissed":
   143           self.dismissalCallbackTriggered = true;
   144           break;
   145         case "showing":
   146           self.showingCallbackTriggered = true;
   147           break;
   148         case "shown":
   149           self.shownCallbackTriggered = true;
   150           break;
   151         case "removed":
   152           self.removedCallbackTriggered = true;
   153           break;
   154         case "swapping":
   155           self.swappingCallbackTriggered = true;
   156           break;
   157       }
   158     }
   159   };
   160 }
   162 basicNotification.prototype.addOptions = function(options) {
   163   for (let [name, value] in Iterator(options))
   164     this.options[name] = value;
   165 };
   167 function errorNotification() {
   168   var self = this;
   169   this.mainAction.callback = function () {
   170     self.mainActionClicked = true;
   171     throw new Error("Oops!");
   172   };
   173   this.secondaryActions[0].callback = function () {
   174     self.secondaryActionClicked = true;
   175     throw new Error("Oops!");
   176   };
   177 }
   179 errorNotification.prototype = new basicNotification();
   180 errorNotification.prototype.constructor = errorNotification;
   182 var wrongBrowserNotificationObject = new basicNotification();
   183 var wrongBrowserNotification;
   185 var tests = [
   186   { // Test #0
   187     run: function () {
   188       this.notifyObj = new basicNotification();
   189       showNotification(this.notifyObj);
   190     },
   191     onShown: function (popup) {
   192       checkPopup(popup, this.notifyObj);
   193       triggerMainCommand(popup);
   194     },
   195     onHidden: function (popup) {
   196       ok(this.notifyObj.mainActionClicked, "mainAction was clicked");
   197       ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
   198       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
   199     }
   200   },
   201   { // Test #1
   202     run: function () {
   203       this.notifyObj = new basicNotification();
   204       showNotification(this.notifyObj);
   205     },
   206     onShown: function (popup) {
   207       checkPopup(popup, this.notifyObj);
   208       triggerSecondaryCommand(popup, 0);
   209     },
   210     onHidden: function (popup) {
   211       ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked");
   212       ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
   213       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
   214     }
   215   },
   216   { // Test #2
   217     run: function () {
   218       this.notifyObj = new basicNotification();
   219       this.notification = showNotification(this.notifyObj);
   220     },
   221     onShown: function (popup) {
   222       checkPopup(popup, this.notifyObj);
   223       dismissNotification(popup);
   224     },
   225     onHidden: function (popup) {
   226       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
   227       this.notification.remove();
   228       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
   229     }
   230   },
   231   // test opening a notification for a background browser
   232   { // Test #3
   233     run: function () {
   234       gNewTab = gBrowser.addTab("about:blank");
   235       isnot(gBrowser.selectedTab, gNewTab, "new tab isn't selected");
   236       wrongBrowserNotificationObject.browser = gBrowser.getBrowserForTab(gNewTab);
   237       wrongBrowserNotification = showNotification(wrongBrowserNotificationObject);
   238     },
   239     backgroundShow: function () {
   240       is(PopupNotifications.isPanelOpen, false, "panel isn't open");
   241       ok(!wrongBrowserNotificationObject.mainActionClicked, "main action wasn't clicked");
   242       ok(!wrongBrowserNotificationObject.secondaryActionClicked, "secondary action wasn't clicked");
   243       ok(!wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback wasn't called");
   244     }
   245   },
   246   // now select that browser and test to see that the notification appeared
   247   { // Test #4
   248     run: function () {
   249       this.oldSelectedTab = gBrowser.selectedTab;
   250       gBrowser.selectedTab = gNewTab;
   251     },
   252     onShown: function (popup) {
   253       checkPopup(popup, wrongBrowserNotificationObject);
   254       is(PopupNotifications.isPanelOpen, true, "isPanelOpen getter doesn't lie");
   256       // switch back to the old browser
   257       gBrowser.selectedTab = this.oldSelectedTab;
   258     },
   259     onHidden: function (popup) {
   260       // actually remove the notification to prevent it from reappearing
   261       ok(wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback triggered due to tab switch");
   262       wrongBrowserNotification.remove();
   263       ok(wrongBrowserNotificationObject.removedCallbackTriggered, "removed callback triggered");
   264       wrongBrowserNotification = null;
   265     }
   266   },
   267   // test that the removed notification isn't shown on browser re-select
   268   { // Test #5
   269     run: function () {
   270       gBrowser.selectedTab = gNewTab;
   271     },
   272     updateNotShowing: function () {
   273       is(PopupNotifications.isPanelOpen, false, "panel isn't open");
   274       gBrowser.removeTab(gNewTab);
   275     }
   276   },
   277   // Test that two notifications with the same ID result in a single displayed
   278   // notification.
   279   { // Test #6
   280     run: function () {
   281       this.notifyObj = new basicNotification();
   282       // Show the same notification twice
   283       this.notification1 = showNotification(this.notifyObj);
   284       this.notification2 = showNotification(this.notifyObj);
   285     },
   286     onShown: function (popup) {
   287       checkPopup(popup, this.notifyObj);
   288       this.notification2.remove();
   289     },
   290     onHidden: function (popup) {
   291       ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
   292       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
   293     }
   294   },
   295   // Test that two notifications with different IDs are displayed
   296   { // Test #7
   297     run: function () {
   298       this.testNotif1 = new basicNotification();
   299       this.testNotif1.message += " 1";
   300       showNotification(this.testNotif1);
   301       this.testNotif2 = new basicNotification();
   302       this.testNotif2.message += " 2";
   303       this.testNotif2.id += "-2";
   304       showNotification(this.testNotif2);
   305     },
   306     onShown: function (popup) {
   307       is(popup.childNodes.length, 2, "two notifications are shown");
   308       // Trigger the main command for the first notification, and the secondary
   309       // for the second. Need to do mainCommand first since the secondaryCommand
   310       // triggering is async.
   311       triggerMainCommand(popup);
   312       is(popup.childNodes.length, 1, "only one notification left");
   313       triggerSecondaryCommand(popup, 0);
   314     },
   315     onHidden: function (popup) {
   316       ok(this.testNotif1.mainActionClicked, "main action #1 was clicked");
   317       ok(!this.testNotif1.secondaryActionClicked, "secondary action #1 wasn't clicked");
   318       ok(!this.testNotif1.dismissalCallbackTriggered, "dismissal callback #1 wasn't called");
   320       ok(!this.testNotif2.mainActionClicked, "main action #2 wasn't clicked");
   321       ok(this.testNotif2.secondaryActionClicked, "secondary action #2 was clicked");
   322       ok(!this.testNotif2.dismissalCallbackTriggered, "dismissal callback #2 wasn't called");
   323     }
   324   },
   325   // Test notification without mainAction
   326   { // Test #8
   327     run: function () {
   328       this.notifyObj = new basicNotification();
   329       this.notifyObj.mainAction = null;
   330       this.notification = showNotification(this.notifyObj);
   331     },
   332     onShown: function (popup) {
   333       checkPopup(popup, this.notifyObj);
   334       dismissNotification(popup);
   335     },
   336     onHidden: function (popup) {
   337       this.notification.remove();
   338     }
   339   },
   340   // Test two notifications with different anchors
   341   { // Test #9
   342     run: function () {
   343       this.notifyObj = new basicNotification();
   344       this.firstNotification = showNotification(this.notifyObj);
   345       this.notifyObj2 = new basicNotification();
   346       this.notifyObj2.id += "-2";
   347       this.notifyObj2.anchorID = "addons-notification-icon";
   348       // Second showNotification() overrides the first
   349       this.secondNotification = showNotification(this.notifyObj2);
   350     },
   351     onShown: function (popup) {
   352       // This also checks that only one element is shown.
   353       checkPopup(popup, this.notifyObj2);
   354       is(document.getElementById("geo-notification-icon").boxObject.width, 0,
   355          "geo anchor shouldn't be visible");
   356       dismissNotification(popup);
   357     },
   358     onHidden: [
   359       // The second showing triggers a popuphidden event that we should ignore.
   360       function (popup) {},
   361       function (popup) {
   362         // Remove the notifications
   363         this.firstNotification.remove();
   364         this.secondNotification.remove();
   365         ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
   366         ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
   367       }
   368     ]
   369   },
   370   // Test optional params
   371   { // Test #10
   372     run: function () {
   373       this.notifyObj = new basicNotification();
   374       this.notifyObj.secondaryActions = undefined;
   375       this.notification = showNotification(this.notifyObj);
   376     },
   377     onShown: function (popup) {
   378       checkPopup(popup, this.notifyObj);
   379       dismissNotification(popup);
   380     },
   381     onHidden: function (popup) {
   382       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
   383       this.notification.remove();
   384       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
   385     }
   386   },
   387   // Test that icons appear
   388   { // Test #11
   389     run: function () {
   390       this.notifyObj = new basicNotification();
   391       this.notifyObj.id = "geolocation";
   392       this.notifyObj.anchorID = "geo-notification-icon";
   393       this.notification = showNotification(this.notifyObj);
   394     },
   395     onShown: function (popup) {
   396       checkPopup(popup, this.notifyObj);
   397       isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
   398             "geo anchor should be visible");
   399       dismissNotification(popup);
   400     },
   401     onHidden: function (popup) {
   402       let icon = document.getElementById("geo-notification-icon");
   403       isnot(icon.boxObject.width, 0,
   404             "geo anchor should be visible after dismissal");
   405       this.notification.remove();
   406       is(icon.boxObject.width, 0,
   407          "geo anchor should not be visible after removal");
   408     }
   409   },
   410   // Test that persistence allows the notification to persist across reloads
   411   { // Test #12
   412     run: function () {
   413       this.oldSelectedTab = gBrowser.selectedTab;
   414       gBrowser.selectedTab = gBrowser.addTab("about:blank");
   416       let self = this;
   417       loadURI("http://example.com/", function() {
   418         self.notifyObj = new basicNotification();
   419         self.notifyObj.addOptions({
   420           persistence: 2
   421         });
   422         self.notification = showNotification(self.notifyObj);
   423       });
   424     },
   425     onShown: function (popup) {
   426       this.complete = false;
   428       let self = this;
   429       loadURI("http://example.org/", function() {
   430         loadURI("http://example.com/", function() {
   432           // Next load will remove the notification
   433           self.complete = true;
   435           loadURI("http://example.org/");
   436         });
   437       });
   438     },
   439     onHidden: function (popup) {
   440       ok(this.complete, "Should only have hidden the notification after 3 page loads");
   441       ok(this.notifyObj.removedCallbackTriggered, "removal callback triggered");
   442       gBrowser.removeTab(gBrowser.selectedTab);
   443       gBrowser.selectedTab = this.oldSelectedTab;
   444     }
   445   },
   446   // Test that a timeout allows the notification to persist across reloads
   447   { // Test #13
   448     run: function () {
   449       this.oldSelectedTab = gBrowser.selectedTab;
   450       gBrowser.selectedTab = gBrowser.addTab("about:blank");
   452       let self = this;
   453       loadURI("http://example.com/", function() {
   454         self.notifyObj = new basicNotification();
   455         // Set a timeout of 10 minutes that should never be hit
   456         self.notifyObj.addOptions({
   457           timeout: Date.now() + 600000
   458         });
   459         self.notification = showNotification(self.notifyObj);
   460       });
   461     },
   462     onShown: function (popup) {
   463       this.complete = false;
   465       let self = this;
   466       loadURI("http://example.org/", function() {
   467         loadURI("http://example.com/", function() {
   469           // Next load will hide the notification
   470           self.notification.options.timeout = Date.now() - 1;
   471           self.complete = true;
   473           loadURI("http://example.org/");
   474         });
   475       });
   476     },
   477     onHidden: function (popup) {
   478       ok(this.complete, "Should only have hidden the notification after the timeout was passed");
   479       this.notification.remove();
   480       gBrowser.removeTab(gBrowser.selectedTab);
   481       gBrowser.selectedTab = this.oldSelectedTab;
   482     }
   483   },
   484   // Test that setting persistWhileVisible allows a visible notification to
   485   // persist across location changes
   486   { // Test #14
   487     run: function () {
   488       this.oldSelectedTab = gBrowser.selectedTab;
   489       gBrowser.selectedTab = gBrowser.addTab("about:blank");
   491       let self = this;
   492       loadURI("http://example.com/", function() {
   493         self.notifyObj = new basicNotification();
   494         self.notifyObj.addOptions({
   495           persistWhileVisible: true
   496         });
   497         self.notification = showNotification(self.notifyObj);
   498       });
   499     },
   500     onShown: function (popup) {
   501       this.complete = false;
   503       let self = this;
   504       loadURI("http://example.org/", function() {
   505         loadURI("http://example.com/", function() {
   507           // Notification should persist across location changes
   508           self.complete = true;
   509           dismissNotification(popup);
   510         });
   511       });
   512     },
   513     onHidden: function (popup) {
   514       ok(this.complete, "Should only have hidden the notification after it was dismissed");
   515       this.notification.remove();
   516       gBrowser.removeTab(gBrowser.selectedTab);
   517       gBrowser.selectedTab = this.oldSelectedTab;
   518     }
   519   },
   520   // Test that nested icon nodes correctly activate popups
   521   { // Test #15
   522     run: function() {
   523       // Add a temporary box as the anchor with a button
   524       this.box = document.createElement("box");
   525       PopupNotifications.iconBox.appendChild(this.box);
   527       let button = document.createElement("button");
   528       button.setAttribute("label", "Please click me!");
   529       this.box.appendChild(button);
   531       // The notification should open up on the box
   532       this.notifyObj = new basicNotification();
   533       this.notifyObj.anchorID = this.box.id = "nested-box";
   534       this.notifyObj.addOptions({dismissed: true});
   535       this.notification = showNotification(this.notifyObj);
   537       // This test places a normal button in the notification area, which has
   538       // standard GTK styling and dimensions. Due to the clip-path, this button
   539       // gets clipped off, which makes it necessary to synthesize the mouse click
   540       // a little bit downward. To be safe, I adjusted the x-offset with the same
   541       // amount.
   542       EventUtils.synthesizeMouse(button, 4, 4, {});
   543     },
   544     onShown: function(popup) {
   545       checkPopup(popup, this.notifyObj);
   546       dismissNotification(popup);
   547     },
   548     onHidden: function(popup) {
   549       this.notification.remove();
   550       this.box.parentNode.removeChild(this.box);
   551     }
   552   },
   553   // Test that popupnotifications without popups have anchor icons shown
   554   { // Test #16
   555     run: function() {
   556       let notifyObj = new basicNotification();
   557       notifyObj.anchorID = "geo-notification-icon";
   558       notifyObj.addOptions({neverShow: true});
   559       showNotification(notifyObj);
   560     },
   561     updateNotShowing: function() {
   562       isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
   563             "geo anchor should be visible");
   564     }
   565   },
   566   // Test notification "Not Now" menu item
   567   { // Test #17
   568     run: function () {
   569       this.notifyObj = new basicNotification();
   570       this.notification = showNotification(this.notifyObj);
   571     },
   572     onShown: function (popup) {
   573       checkPopup(popup, this.notifyObj);
   574       triggerSecondaryCommand(popup, 1);
   575     },
   576     onHidden: function (popup) {
   577       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
   578       this.notification.remove();
   579       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
   580     }
   581   },
   582   // Test notification close button
   583   { // Test #18
   584     run: function () {
   585       this.notifyObj = new basicNotification();
   586       this.notification = showNotification(this.notifyObj);
   587     },
   588     onShown: function (popup) {
   589       checkPopup(popup, this.notifyObj);
   590       let notification = popup.childNodes[0];
   591       EventUtils.synthesizeMouseAtCenter(notification.closebutton, {});
   592     },
   593     onHidden: function (popup) {
   594       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
   595       this.notification.remove();
   596       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
   597     }
   598   },
   599   // Test notification when chrome is hidden
   600   { // Test #19
   601     run: function () {
   602       window.locationbar.visible = false;
   603       this.notifyObj = new basicNotification();
   604       this.notification = showNotification(this.notifyObj);
   605       window.locationbar.visible = true;
   606     },
   607     onShown: function (popup) {
   608       checkPopup(popup, this.notifyObj);
   609       is(popup.anchorNode.className, "tabbrowser-tab", "notification anchored to tab");
   610       dismissNotification(popup);
   611     },
   612     onHidden: function (popup) {
   613       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
   614       this.notification.remove();
   615       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
   616     }
   617   },
   618   // Test notification is removed when dismissed if removeOnDismissal is true
   619   { // Test #20
   620     run: function () {
   621       this.notifyObj = new basicNotification();
   622       this.notifyObj.addOptions({
   623         removeOnDismissal: true
   624       });
   625       this.notification = showNotification(this.notifyObj);
   626     },
   627     onShown: function (popup) {
   628       checkPopup(popup, this.notifyObj);
   629       dismissNotification(popup);
   630     },
   631     onHidden: function (popup) {
   632       ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
   633       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
   634     }
   635   },
   636   // Test multiple notification icons are shown
   637   { // Test #21
   638     run: function () {
   639       this.notifyObj1 = new basicNotification();
   640       this.notifyObj1.id += "_1";
   641       this.notifyObj1.anchorID = "default-notification-icon";
   642       this.notification1 = showNotification(this.notifyObj1);
   644       this.notifyObj2 = new basicNotification();
   645       this.notifyObj2.id += "_2";
   646       this.notifyObj2.anchorID = "geo-notification-icon";
   647       this.notification2 = showNotification(this.notifyObj2);
   648     },
   649     onShown: function (popup) {
   650       checkPopup(popup, this.notifyObj2);
   652       // check notifyObj1 anchor icon is showing
   653       isnot(document.getElementById("default-notification-icon").boxObject.width, 0,
   654             "default anchor should be visible");
   655       // check notifyObj2 anchor icon is showing
   656       isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
   657             "geo anchor should be visible");
   659       dismissNotification(popup);
   660     },
   661     onHidden: [
   662       function (popup) {
   663       },
   664       function (popup) {
   665         this.notification1.remove();
   666         ok(this.notifyObj1.removedCallbackTriggered, "removed callback triggered");
   668         this.notification2.remove();
   669         ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
   670       }
   671     ]
   672   },
   673   // Test that multiple notification icons are removed when switching tabs
   674   { // Test #22
   675     run: function () {
   676       // show the notification on old tab.
   677       this.notifyObjOld = new basicNotification();
   678       this.notifyObjOld.anchorID = "default-notification-icon";
   679       this.notificationOld = showNotification(this.notifyObjOld);
   681       // switch tab
   682       this.oldSelectedTab = gBrowser.selectedTab;
   683       gBrowser.selectedTab = gBrowser.addTab("about:blank");
   685       // show the notification on new tab.
   686       this.notifyObjNew = new basicNotification();
   687       this.notifyObjNew.anchorID = "geo-notification-icon";
   688       this.notificationNew = showNotification(this.notifyObjNew);
   689     },
   690     onShown: function (popup) {
   691       checkPopup(popup, this.notifyObjNew);
   693       // check notifyObjOld anchor icon is removed
   694       is(document.getElementById("default-notification-icon").boxObject.width, 0,
   695          "default anchor shouldn't be visible");
   696       // check notifyObjNew anchor icon is showing
   697       isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
   698             "geo anchor should be visible");
   700       dismissNotification(popup);
   701     },
   702     onHidden: [
   703       function (popup) {
   704       },
   705       function (popup) {
   706         this.notificationNew.remove();
   707         gBrowser.removeTab(gBrowser.selectedTab);
   709         gBrowser.selectedTab = this.oldSelectedTab;
   710         this.notificationOld.remove();
   711       }
   712     ]
   713   },
   714   { // Test #23 - test security delay - too early
   715     run: function () {
   716       // Set the security delay to 100s
   717       PopupNotifications.buttonDelay = 100000;
   719       this.notifyObj = new basicNotification();
   720       showNotification(this.notifyObj);
   721     },
   722     onShown: function (popup) {
   723       checkPopup(popup, this.notifyObj);
   724       triggerMainCommand(popup);
   726       // Wait to see if the main command worked
   727       executeSoon(function delayedDismissal() {
   728         dismissNotification(popup);
   729       });
   731     },
   732     onHidden: function (popup) {
   733       ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked because it was too soon");
   734       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
   735     }
   736   },
   737   { // Test #24  - test security delay - after delay
   738     run: function () {
   739       // Set the security delay to 10ms
   740       PopupNotifications.buttonDelay = 10;
   742       this.notifyObj = new basicNotification();
   743       showNotification(this.notifyObj);
   744     },
   745     onShown: function (popup) {
   746       checkPopup(popup, this.notifyObj);
   748       // Wait until after the delay to trigger the main action
   749       setTimeout(function delayedDismissal() {
   750         triggerMainCommand(popup);
   751       }, 500);
   753     },
   754     onHidden: function (popup) {
   755       ok(this.notifyObj.mainActionClicked, "mainAction was clicked after the delay");
   756       ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback was not triggered");
   757       PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
   758     }
   759   },
   760   { // Test #25 - reload removes notification
   761     run: function () {
   762       loadURI("http://example.com/", function() {
   763         let notifyObj = new basicNotification();
   764         notifyObj.options.eventCallback = function (eventName) {
   765           if (eventName == "removed") {
   766             ok(true, "Notification removed in background tab after reloading");
   767             executeSoon(function () {
   768               goNext();
   769             });
   770           }
   771         };
   772         showNotification(notifyObj);
   773         executeSoon(function () {
   774           gBrowser.selectedBrowser.reload();
   775         });
   776       });
   777     }
   778   },
   779   { // Test #26 - location change in background tab removes notification
   780     run: function () {
   781       let oldSelectedTab = gBrowser.selectedTab;
   782       let newTab = gBrowser.addTab("about:blank");
   783       gBrowser.selectedTab = newTab;
   785       loadURI("http://example.com/", function() {
   786         gBrowser.selectedTab = oldSelectedTab;
   787         let browser = gBrowser.getBrowserForTab(newTab);
   789         let notifyObj = new basicNotification();
   790         notifyObj.browser = browser;
   791         notifyObj.options.eventCallback = function (eventName) {
   792           if (eventName == "removed") {
   793             ok(true, "Notification removed in background tab after reloading");
   794             executeSoon(function () {
   795               gBrowser.removeTab(newTab);
   796               goNext();
   797             });
   798           }
   799         };
   800         showNotification(notifyObj);
   801         executeSoon(function () {
   802           browser.reload();
   803         });
   804       });
   805     }
   806   },
   807   { // Test #27 -  Popup notification anchor shouldn't disappear when a notification with the same ID is re-added in a background tab
   808     run: function () {
   809       loadURI("http://example.com/", function () {
   810         let originalTab = gBrowser.selectedTab;
   811         let bgTab = gBrowser.addTab("about:blank");
   812         gBrowser.selectedTab = bgTab;
   813         loadURI("http://example.com/", function () {
   814           let anchor = document.createElement("box");
   815           anchor.id = "test26-anchor";
   816           anchor.className = "notification-anchor-icon";
   817           PopupNotifications.iconBox.appendChild(anchor);
   819           gBrowser.selectedTab = originalTab;
   821           let fgNotifyObj = new basicNotification();
   822           fgNotifyObj.anchorID = anchor.id;
   823           fgNotifyObj.options.dismissed = true;
   824           let fgNotification = showNotification(fgNotifyObj);
   826           let bgNotifyObj = new basicNotification();
   827           bgNotifyObj.anchorID = anchor.id;
   828           bgNotifyObj.browser = gBrowser.getBrowserForTab(bgTab);
   829           // show the notification in the background tab ...
   830           let bgNotification = showNotification(bgNotifyObj);
   831           // ... and re-show it
   832           bgNotification = showNotification(bgNotifyObj);
   834           ok(fgNotification.id, "notification has id");
   835           is(fgNotification.id, bgNotification.id, "notification ids are the same");
   836           is(anchor.getAttribute("showing"), "true", "anchor still showing");
   838           fgNotification.remove();
   839           gBrowser.removeTab(bgTab);
   840           goNext();
   841         });
   842       });
   843     }
   844   },
   845   { // Test #28 - location change in an embedded frame should not remove a notification
   846     run: function () {
   847       loadURI("data:text/html;charset=utf8,<iframe id='iframe' src='http://example.com/'>", function () {
   848         this.notifyObj = new basicNotification();
   849         this.notifyObj.options.eventCallback = function (eventName) {
   850           if (eventName == "removed") {
   851             ok(false, "Test 28: Notification removed from browser when subframe navigated");
   852           }
   853         };
   854         showNotification(this.notifyObj);
   855       }.bind(this));
   856     },
   857     onShown: function (popup) {
   858       let self = this;
   859       let progressListener = {
   860         onLocationChange: function onLocationChange(aBrowser) {
   861           if (aBrowser != gBrowser.selectedBrowser) {
   862             return;
   863           }
   864           let notification = PopupNotifications.getNotification(self.notifyObj.id,
   865                                                                 self.notifyObj.browser);
   866           ok(notification != null, "Test 28: Notification remained when subframe navigated");
   867           self.notifyObj.options.eventCallback = undefined;
   869           notification.remove();
   870           gBrowser.removeTabsProgressListener(progressListener);
   871         },
   872       };
   874       info("Test 28: Adding progress listener and performing navigation");
   875       gBrowser.addTabsProgressListener(progressListener);
   876       content.document.getElementById("iframe")
   877                       .setAttribute("src", "http://example.org/");
   878     },
   879     onHidden: function () {}
   880   },
   881   { // Test #29 - Popup Notifications should catch exceptions from callbacks
   882     run: function () {
   883       let callbackCount = 0;
   884       this.testNotif1 = new basicNotification();
   885       this.testNotif1.message += " 1";
   886       this.notification1 = showNotification(this.testNotif1);
   887       this.testNotif1.options.eventCallback = function (eventName) {
   888         info("notifyObj1.options.eventCallback: " + eventName);
   889         if (eventName == "dismissed") {
   890           throw new Error("Oops 1!");
   891           if (++callbackCount == 2) {
   892             executeSoon(goNext);
   893           }
   894         }
   895       };
   897       this.testNotif2 = new basicNotification();
   898       this.testNotif2.message += " 2";
   899       this.testNotif2.id += "-2";
   900       this.testNotif2.options.eventCallback = function (eventName) {
   901         info("notifyObj2.options.eventCallback: " + eventName);
   902         if (eventName == "dismissed") {
   903           throw new Error("Oops 2!");
   904           if (++callbackCount == 2) {
   905             executeSoon(goNext);
   906           }
   907         }
   908       };
   909       this.notification2 = showNotification(this.testNotif2);
   910     },
   911     onShown: function (popup) {
   912       is(popup.childNodes.length, 2, "two notifications are shown");
   913       dismissNotification(popup);
   914     },
   915     onHidden: function () {
   916       this.notification1.remove();
   917       this.notification2.remove();
   918     }
   919   },
   920   { // Test #30 - Popup Notifications main actions should catch exceptions from callbacks
   921     run: function () {
   922       this.testNotif = new errorNotification();
   923       showNotification(this.testNotif);
   924     },
   925     onShown: function (popup) {
   926       checkPopup(popup, this.testNotif);
   927       triggerMainCommand(popup);
   928     },
   929     onHidden: function (popup) {
   930       ok(this.testNotif.mainActionClicked, "main action has been triggered");
   931     }
   932   },
   933   { // Test #31 - Popup Notifications secondary actions should catch exceptions from callbacks
   934     run: function () {
   935       this.testNotif = new errorNotification();
   936       showNotification(this.testNotif);
   937     },
   938     onShown: function (popup) {
   939       checkPopup(popup, this.testNotif);
   940       triggerSecondaryCommand(popup, 0);
   941     },
   942     onHidden: function (popup) {
   943       ok(this.testNotif.secondaryActionClicked, "secondary action has been triggered");
   944     }
   945   },
   946   { // Test #32 -  Existing popup notification shouldn't disappear when adding a dismissed notification
   947     run: function () {
   948       this.notifyObj1 = new basicNotification();
   949       this.notifyObj1.id += "_1";
   950       this.notifyObj1.anchorID = "default-notification-icon";
   951       this.notification1 = showNotification(this.notifyObj1);
   952     },
   953     onShown: function (popup) {
   954       // Now show a dismissed notification, and check that it doesn't clobber
   955       // the showing one.
   956       this.notifyObj2 = new basicNotification();
   957       this.notifyObj2.id += "_2";
   958       this.notifyObj2.anchorID = "geo-notification-icon";
   959       this.notifyObj2.options.dismissed = true;
   960       this.notification2 = showNotification(this.notifyObj2);
   962       checkPopup(popup, this.notifyObj1);
   964       // check that both anchor icons are showing
   965       is(document.getElementById("default-notification-icon").getAttribute("showing"), "true",
   966          "notification1 anchor should be visible");
   967       is(document.getElementById("geo-notification-icon").getAttribute("showing"), "true",
   968          "notification2 anchor should be visible");
   970       dismissNotification(popup);
   971     },
   972     onHidden: function(popup) {
   973       this.notification1.remove();
   974       this.notification2.remove();
   975     }
   976   },
   977   { // Test #33 - Showing should be able to modify the popup data
   978     run: function() {
   979       this.notifyObj = new basicNotification();
   980       var normalCallback = this.notifyObj.options.eventCallback;
   981       this.notifyObj.options.eventCallback = function (eventName) {
   982         if (eventName == "showing") {
   983           this.mainAction.label = "Alternate Label";
   984         }
   985         normalCallback.call(this, eventName);
   986       };
   987       showNotification(this.notifyObj);
   988     },
   989     onShown: function(popup) {
   990       // checkPopup checks for the matching label. Note that this assumes that
   991       // this.notifyObj.mainAction is the same as notification.mainAction,
   992       // which could be a problem if we ever decided to deep-copy.
   993       checkPopup(popup, this.notifyObj);
   994       triggerMainCommand(popup);
   995     },
   996     onHidden: function() { }
   997   },
   998   { // Test #34 - Moving a tab to a new window should remove non-swappable
   999     // notifications.
  1000     run: function() {
  1001       gBrowser.selectedTab = gBrowser.addTab("about:blank");
  1002       let notifyObj = new basicNotification();
  1003       showNotification(notifyObj);
  1004       let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
  1005       whenDelayedStartupFinished(win, function() {
  1006         let [tab] = win.gBrowser.tabs;
  1007         let anchor = win.document.getElementById("default-notification-icon");
  1008         win.PopupNotifications._reshowNotifications(anchor);
  1009         ok(win.PopupNotifications.panel.childNodes.length == 0,
  1010            "no notification displayed in new window");
  1011         ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
  1012         ok(notifyObj.removedCallbackTriggered, "the removed callback was triggered");
  1013         win.close();
  1014         goNext();
  1015       });
  1017   },
  1018   { // Test #35 - Moving a tab to a new window should preserve swappable notifications.
  1019     run: function() {
  1020       gBrowser.selectedTab = gBrowser.addTab("about:blank");
  1021       let notifyObj = new basicNotification();
  1022       let originalCallback = notifyObj.options.eventCallback;
  1023       notifyObj.options.eventCallback = function (eventName) {
  1024         originalCallback(eventName);
  1025         return eventName == "swapping";
  1026       };
  1028       showNotification(notifyObj);
  1029       let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
  1030       whenDelayedStartupFinished(win, function() {
  1031         let [tab] = win.gBrowser.tabs;
  1032         let anchor = win.document.getElementById("default-notification-icon");
  1033         win.PopupNotifications._reshowNotifications(anchor);
  1034         checkPopup(win.PopupNotifications.panel, notifyObj);
  1035         ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
  1036         win.close();
  1037         goNext();
  1038       });
  1040   },
  1041   { // Test #36 - the hideNotNow option
  1042     run: function () {
  1043       this.notifyObj = new basicNotification();
  1044       this.notifyObj.options.hideNotNow = true;
  1045       this.notifyObj.mainAction.dismiss = true;
  1046       this.notification = showNotification(this.notifyObj);
  1047     },
  1048     onShown: function (popup) {
  1049       // checkPopup verifies that the Not Now item is hidden, and that no separator is added.
  1050       checkPopup(popup, this.notifyObj);
  1051       triggerMainCommand(popup);
  1052     },
  1053     onHidden: function (popup) {
  1054       this.notification.remove();
  1056   },
  1057   { // Test #37 - the main action callback can keep the notification.
  1058     run: function () {
  1059       this.notifyObj = new basicNotification();
  1060       this.notifyObj.mainAction.dismiss = true;
  1061       this.notification = showNotification(this.notifyObj);
  1062     },
  1063     onShown: function (popup) {
  1064       checkPopup(popup, this.notifyObj);
  1065       triggerMainCommand(popup);
  1066     },
  1067     onHidden: function (popup) {
  1068       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
  1069       ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
  1070       this.notification.remove();
  1072   },
  1073   { // Test #38 - a secondary action callback can keep the notification.
  1074     run: function () {
  1075       this.notifyObj = new basicNotification();
  1076       this.notifyObj.secondaryActions[0].dismiss = true;
  1077       this.notification = showNotification(this.notifyObj);
  1078     },
  1079     onShown: function (popup) {
  1080       checkPopup(popup, this.notifyObj);
  1081       triggerSecondaryCommand(popup, 0);
  1082     },
  1083     onHidden: function (popup) {
  1084       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
  1085       ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
  1086       this.notification.remove();
  1088   },
  1089   { // Test #39 - returning true in the showing callback should dismiss the notification.
  1090     run: function() {
  1091       let notifyObj = new basicNotification();
  1092       let originalCallback = notifyObj.options.eventCallback;
  1093       notifyObj.options.eventCallback = function (eventName) {
  1094         originalCallback(eventName);
  1095         return eventName == "showing";
  1096       };
  1098       let notification = showNotification(notifyObj);
  1099       ok(notifyObj.showingCallbackTriggered, "the showing callback was triggered");
  1100       ok(!notifyObj.shownCallbackTriggered, "the shown callback wasn't triggered");
  1101       notification.remove();
  1102       goNext();
  1105 ];
  1107 function showNotification(notifyObj) {
  1108   return PopupNotifications.show(notifyObj.browser,
  1109                                  notifyObj.id,
  1110                                  notifyObj.message,
  1111                                  notifyObj.anchorID,
  1112                                  notifyObj.mainAction,
  1113                                  notifyObj.secondaryActions,
  1114                                  notifyObj.options);
  1117 function checkPopup(popup, notificationObj) {
  1118   info("[Test #" + gTestIndex + "] checking popup");
  1120   ok(notificationObj.showingCallbackTriggered, "showing callback was triggered");
  1121   ok(notificationObj.shownCallbackTriggered, "shown callback was triggered");
  1123   let notifications = popup.childNodes;
  1124   is(notifications.length, 1, "one notification displayed");
  1125   let notification = notifications[0];
  1126   if (!notification)
  1127     return;
  1128   let icon = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-icon");
  1129   if (notificationObj.id == "geolocation") {
  1130     isnot(icon.boxObject.width, 0, "icon for geo displayed");
  1131     is(popup.anchorNode.className, "notification-anchor-icon", "notification anchored to icon");
  1133   is(notification.getAttribute("label"), notificationObj.message, "message matches");
  1134   is(notification.id, notificationObj.id + "-notification", "id matches");
  1135   if (notificationObj.mainAction) {
  1136     is(notification.getAttribute("buttonlabel"), notificationObj.mainAction.label, "main action label matches");
  1137     is(notification.getAttribute("buttonaccesskey"), notificationObj.mainAction.accessKey, "main action accesskey matches");
  1139   let actualSecondaryActions = Array.filter(notification.childNodes,
  1140                                             function (child) child.nodeName == "menuitem");
  1141   let secondaryActions = notificationObj.secondaryActions || [];
  1142   let actualSecondaryActionsCount = actualSecondaryActions.length;
  1143   if (notificationObj.options.hideNotNow) {
  1144     is(notification.getAttribute("hidenotnow"), "true", "Not Now item hidden");
  1145     if (secondaryActions.length)
  1146       is(notification.lastChild.tagName, "menuitem", "no menuseparator");
  1148   else if (secondaryActions.length) {
  1149     is(notification.lastChild.tagName, "menuseparator", "menuseparator exists");
  1151   is(actualSecondaryActionsCount, secondaryActions.length, actualSecondaryActions.length + " secondary actions");
  1152   secondaryActions.forEach(function (a, i) {
  1153     is(actualSecondaryActions[i].getAttribute("label"), a.label, "label for secondary action " + i + " matches");
  1154     is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, "accessKey for secondary action " + i + " matches");
  1155   });
  1158 function triggerMainCommand(popup) {
  1159   info("[Test #" + gTestIndex + "] triggering main command");
  1160   let notifications = popup.childNodes;
  1161   ok(notifications.length > 0, "at least one notification displayed");
  1162   let notification = notifications[0];
  1164   // 20, 10 so that the inner button is hit
  1165   EventUtils.synthesizeMouse(notification.button, 20, 10, {});
  1168 function triggerSecondaryCommand(popup, index) {
  1169   info("[Test #" + gTestIndex + "] triggering secondary command");
  1170   let notifications = popup.childNodes;
  1171   ok(notifications.length > 0, "at least one notification displayed");
  1172   let notification = notifications[0];
  1174   // Cancel the arrow panel slide-in transition (bug 767133) such that
  1175   // it won't interfere with us interacting with the dropdown.
  1176   document.getAnonymousNodes(popup)[0].style.transition = "none";
  1178   notification.button.focus();
  1180   popup.addEventListener("popupshown", function () {
  1181     popup.removeEventListener("popupshown", arguments.callee, false);
  1183     // Press down until the desired command is selected
  1184     for (let i = 0; i <= index; i++)
  1185       EventUtils.synthesizeKey("VK_DOWN", {});
  1187     // Activate
  1188     EventUtils.synthesizeKey("VK_RETURN", {});
  1189   }, false);
  1191   // One down event to open the popup
  1192   EventUtils.synthesizeKey("VK_DOWN", { altKey: !navigator.platform.contains("Mac") });
  1195 function loadURI(uri, callback) {
  1196   if (callback) {
  1197     gBrowser.addEventListener("load", function() {
  1198       // Ignore the about:blank load
  1199       if (gBrowser.currentURI.spec == "about:blank")
  1200         return;
  1202       gBrowser.removeEventListener("load", arguments.callee, true);
  1204       callback();
  1205     }, true);
  1207   gBrowser.loadURI(uri);
  1210 function dismissNotification(popup) {
  1211   info("[Test #" + gTestIndex + "] dismissing notification");
  1212   executeSoon(function () {
  1213     EventUtils.synthesizeKey("VK_ESCAPE", {});
  1214   });

mercurial