Wed, 31 Dec 2014 06:09:35 +0100
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 });
1016 }
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 });
1039 }
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();
1055 }
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();
1071 }
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();
1087 }
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();
1103 }
1104 }
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);
1115 }
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");
1132 }
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");
1138 }
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");
1147 }
1148 else if (secondaryActions.length) {
1149 is(notification.lastChild.tagName, "menuseparator", "menuseparator exists");
1150 }
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 });
1156 }
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, {});
1166 }
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") });
1193 }
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);
1206 }
1207 gBrowser.loadURI(uri);
1208 }
1210 function dismissNotification(popup) {
1211 info("[Test #" + gTestIndex + "] dismissing notification");
1212 executeSoon(function () {
1213 EventUtils.synthesizeKey("VK_ESCAPE", {});
1214 });
1215 }