Bug 967793 - Download notification disappears after a download link opens in a new tab (Aurora roll-up). r=mbrubeck, a=sledru
authorSam Foster <sfoster@mozilla.com>
Thu, 06 Mar 2014 09:45:15 -0500
changeset 183179 f081a9d022d66ad09716899b08d3818e5878bbf9
parent 183178 e6474e309d6e1c2b92d27383326f89cc9c3ea8c7
child 183180 b059913adf9c5f6ec3188afb907a897108df0a2b
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck, sledru
bugs967793
milestone29.0a2
Bug 967793 - Download notification disappears after a download link opens in a new tab (Aurora roll-up). r=mbrubeck, a=sledru
browser/metro/base/content/ContentAreaObserver.js
browser/metro/base/content/ContextUI.js
browser/metro/base/content/bindings.css
browser/metro/base/content/bindings/notification.xml
browser/metro/base/content/browser-ui.js
browser/metro/base/content/browser.js
browser/metro/base/content/browser.xul
browser/metro/base/content/downloads.js
browser/metro/base/tests/mochitest/browser_downloads.js
browser/metro/base/tests/mochitest/browser_notifications.js
browser/metro/base/tests/mochitest/head.js
browser/metro/base/tests/mochitest/metro.ini
browser/metro/theme/browser.css
--- a/browser/metro/base/content/ContentAreaObserver.js
+++ b/browser/metro/base/content/ContentAreaObserver.js
@@ -112,53 +112,41 @@ var ContentAreaObserver = {
 
   update: function cao_update (width, height) {
     let oldWidth = parseInt(this.styles["window-width"].width);
     let oldHeight = parseInt(this.styles["window-height"].height);
 
     let newWidth = width || this.width;
     let newHeight = height || this.height;
 
-    if (newHeight == oldHeight && newWidth == oldWidth)
+    if (newHeight == oldHeight && newWidth == oldWidth) {
       return;
+    }
 
     this.styles["window-width"].width = newWidth + "px";
     this.styles["window-width"].maxWidth = newWidth + "px";
     this.styles["window-height"].height = newHeight + "px";
     this.styles["window-height"].maxHeight = newHeight + "px";
 
     this._updateViewState();
 
     this.updateContentArea(newWidth, this._getContentHeightForWindow(newHeight));
     this._dispatchBrowserEvent("SizeChanged");
   },
 
   updateContentArea: function cao_updateContentArea (width, height) {
-    if (Browser.selectedBrowser) {
-      let notificationBox = Browser.getNotificationBox();
-
-      // If a notification and navbar are visible together,
-      // make the notification appear above the navbar.
-      if (ContextUI.navbarVisible && !notificationBox.notificationsHidden &&
-          notificationBox.allNotifications.length != 0) {
-        let navbarHeight = Elements.navbar.getBoundingClientRect().height;
-        notificationBox.style.paddingBottom = navbarHeight + "px";
-      } else {
-        notificationBox.style.paddingBottom = "";
-      }
-    }
-
     let oldHeight = parseInt(this.styles["content-height"].height);
     let oldWidth = parseInt(this.styles["content-width"].width);
 
     let newWidth = width || this.width;
     let newHeight = height || this.contentHeight;
 
-    if (newHeight == oldHeight && newWidth == oldWidth)
+    if (newHeight == oldHeight && newWidth == oldWidth) {
       return;
+    }
 
     this.styles["content-height"].height = newHeight + "px";
     this.styles["content-height"].maxHeight = newHeight + "px";
     this.styles["content-width"].width = newWidth + "px";
     this.styles["content-width"].maxWidth = newWidth + "px";
 
     this.updateViewableArea(newWidth, this._getViewableHeightForContent(newHeight));
     this._dispatchBrowserEvent("ContentSizeChanged");
@@ -166,18 +154,19 @@ var ContentAreaObserver = {
 
   updateViewableArea: function cao_updateViewableArea (width, height) {
     let oldHeight = parseInt(this.styles["viewable-height"].height);
     let oldWidth = parseInt(this.styles["viewable-width"].width);
 
     let newWidth = width || this.width;
     let newHeight = height || this.viewableHeight;
 
-    if (newHeight == oldHeight && newWidth == oldWidth)
+    if (newHeight == oldHeight && newWidth == oldWidth) {
       return;
+    }
 
     this.styles["viewable-height"].height = newHeight + "px";
     this.styles["viewable-height"].maxHeight = newHeight + "px";
     this.styles["viewable-width"].width = newWidth + "px";
     this.styles["viewable-width"].maxWidth = newWidth + "px";
 
     this.updateAppBarPosition();
 
--- a/browser/metro/base/content/ContextUI.js
+++ b/browser/metro/base/content/ContextUI.js
@@ -17,16 +17,17 @@ var ContextUI = {
   /*******************************************
    * init
    */
 
   init: function init() {
     Elements.browsers.addEventListener('URLChanged', this, true);
     Elements.browsers.addEventListener("AlertActive", this, true);
     Elements.browsers.addEventListener("AlertClose", this, true);
+    Elements.tabList.addEventListener('TabSelect', this, true);
     Elements.panelUI.addEventListener('ToolPanelShown', this, false);
     Elements.panelUI.addEventListener('ToolPanelHidden', this, false);
 
     Elements.tray.addEventListener("mousemove", this, false);
     Elements.tray.addEventListener("mouseleave", this, false);
 
     window.addEventListener("touchstart", this, true);
     window.addEventListener("mousedown", this, true);
@@ -170,30 +171,32 @@ var ContextUI = {
     this._hidingId = setTimeout(function () {
         ContextUI.dismissTabs();
       }, aDelay);
   },
 
   // Display the nav bar
   displayNavbar: function () {
     Elements.navbar.show();
+    Elements.chromeState.setAttribute("navbar", "visible");
     ContentAreaObserver.updateContentArea();
   },
 
   // Display the tab tray
   displayTabs: function () {
     this._clearDelayedTimeout();
     this._setIsExpanded(true);
   },
 
   // Dismiss the navbar if visible.
   dismissNavbar: function dismissNavbar() {
     if (!BrowserUI.isStartTabVisible) {
       Elements.autocomplete.closePopup();
       Elements.navbar.dismiss();
+      Elements.chromeState.removeAttribute("navbar");
       ContentAreaObserver.updateContentArea();
     }
   },
 
   // Dismiss the tabstray if visible.
   dismissTabs: function dimissTabs() {
     this._clearDelayedTimeout();
     this._setIsExpanded(false);
@@ -360,16 +363,17 @@ var ContextUI = {
         this.onDownInput(aEvent);
         break;
       case "ToolPanelShown":
       case "ToolPanelHidden":
         this.dismiss();
         break;
       case "AlertActive":
       case "AlertClose":
+      case "TabSelect":
         ContentAreaObserver.updateContentArea();
         break;
       case "MozFlyoutPanelShowing":
         if (BrowserUI.isStartTabVisible) {
           this.dismissTabs();
           this.dismissContextAppbar();
         } else {
           this.dismiss();
--- a/browser/metro/base/content/bindings.css
+++ b/browser/metro/base/content/bindings.css
@@ -47,16 +47,19 @@ setting {
 }
 autoscroller {
   -moz-binding: url('chrome://browser/content/bindings/popup.xml#element-popup');
 }
 
 notificationbox {
   -moz-binding: url('chrome://browser/content/bindings/notification.xml#notificationbox');
 }
+notification {
+  -moz-binding: url('chrome://browser/content/bindings/notification.xml#notification');
+}
 
 circularprogressindicator {
   -moz-binding: url('chrome://browser/content/bindings/circularprogress.xml#circular-progress-indicator');
 }
 
 setting[type="bool"] {
   display: -moz-box;
   -moz-binding: url("chrome://browser/content/bindings/toggleswitch.xml#setting-fulltoggle-bool");
--- a/browser/metro/base/content/bindings/notification.xml
+++ b/browser/metro/base/content/bindings/notification.xml
@@ -36,16 +36,56 @@
         ]]>
       </constructor>
       <destructor>
         <![CDATA[
           this.removeEventListener("AlertActive", this.handleEvent, true);
           this.removeEventListener("AlertClose", this.handleEvent, true);
         ]]>
       </destructor>
+      <method name="adoptNotification">
+        <parameter name="aItem"/>
+        <body>
+          <![CDATA[
+            // insert an existing notification element
+            // XXX: borrows code from appendNotification in toolkit/content/widgets/notification.xml
+            // if this sticks around, we'll want to refactor both to eliminate duplication
+
+            let priority = aItem.priority;
+            // check for where the notification should be inserted according to
+            // priority. If two are equal, the existing one appears on top.
+            let notifications = this.allNotifications;
+            let insertPos = null;
+            for (let n = notifications.length - 1; n >= 0; n--) {
+              if (notifications[n].priority < priority)
+                break;
+              insertPos = notifications[n];
+            }
+            if (!insertPos) {
+              aItem.style.position = "fixed";
+              aItem.style.top = "100%";
+              aItem.style.marginTop = "-15px";
+              aItem.style.opacity = "0";
+            }
+            let label = aItem.label;
+            this.insertBefore(aItem, insertPos);
+            aItem.label = label;
+
+            if (!insertPos)
+              this._showNotification(aItem, true, true);
+
+            // Fire event for accessibility APIs
+            var event = document.createEvent("Events");
+            event.initEvent("AlertActive", true, true);
+            aItem.dispatchEvent(event);
+
+            return aItem;
+          ]]>
+        </body>
+      </method>
       <method name="removeNotification">
         <parameter name="aItem"/>
         <parameter name="aSkipAnimation"/>
         <body>
           <![CDATA[
             if (aItem == this.currentNotification)
               this.removeCurrentNotification(aSkipAnimation);
             else if (aItem != this._closedNotification)
@@ -71,10 +111,48 @@
                 break;
             }
           ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
-
+  <binding id="notification" role="xul:alert" extends="chrome://global/content/bindings/notification.xml#notification">
+    <implementation>
+      <property name="_messageContainer" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'messageText');"/>
+      <property name="label">
+        <getter><![CDATA[
+          if (this._messageContainer.childElementCount) {
+            // return a document fragment when our label is a complex value containing elements
+            // by cloning childNodes into a document fragment, the returned value
+            // is *not* live and will survive unbinding of this notification
+            let frag = this.ownerDocument.createDocumentFragment();
+            let containerNode = this._messageContainer;
+            for(let cnode of containerNode.childNodes) {
+              frag.appendChild(cnode.cloneNode(true));
+            }
+            return frag;
+          } else {
+            return String.trim(this._messageContainer.textContent) ||
+                   this.getAttribute("label");
+          }
+        ]]></getter>
+        <setter><![CDATA[
+            // accept a string or node (e.g. document fragment, element or text node) as label value
+            if (val && "object" == typeof val && ('nodeType' in val)) {
+              let containerNode = this._messageContainer;
+              let cnode;
+              while((cnode = containerNode.firstChild)) {
+                cnode.remove();
+              }
+              if (val.ownerDocument !== this.ownerDocument) {
+                val = this.ownerDocument.importNode(val, true);
+              }
+              return containerNode.appendChild(val);
+            } else {
+              return (this._messageContainer.textContent = val);
+            }
+        ]]></setter>
+      </property>
+    </implementation>
+  </binding>
 </bindings>
--- a/browser/metro/base/content/browser-ui.js
+++ b/browser/metro/base/content/browser-ui.js
@@ -27,16 +27,17 @@ const kChangeTabAnimationDelay = 500;
  */
 
 let Elements = {};
 [
   ["contentShowing",     "bcast_contentShowing"],
   ["urlbarState",        "bcast_urlbarState"],
   ["loadingState",       "bcast_loadingState"],
   ["windowState",        "bcast_windowState"],
+  ["chromeState",        "bcast_chromeState"],
   ["mainKeyset",         "mainKeyset"],
   ["stack",              "stack"],
   ["tabList",            "tabs"],
   ["tabs",               "tabs-container"],
   ["controls",           "browser-controls"],
   ["panelUI",            "panel-container"],
   ["tray",               "tray"],
   ["toolbar",            "toolbar"],
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -1362,16 +1362,22 @@ Tab.prototype = {
       case "DOMWindowCreated":
       case "StartUIChange":
         this.updateViewport();
         break;
       case "SizeChanged":
         this.updateViewport();
         this._delayUpdateThumbnail();
         break;
+      case "AlertClose": {
+        if (this == Browser.selectedTab) {
+          this.updateViewport();
+        }
+        break;
+      }
     }
   },
 
   receiveMessage: function(aMessage) {
     switch (aMessage.name) {
       case "Content:StateChange":
         // update the thumbnail now...
         this.updateThumbnail();
@@ -1468,16 +1474,17 @@ Tab.prototype = {
     let stack = document.createElementNS(XUL_NS, "stack");
     stack.className = "browserStack";
     stack.appendChild(browser);
     stack.setAttribute("flex", "1");
     notification.appendChild(stack);
     Elements.browsers.insertBefore(notification, aInsertBefore);
 
     notification.dir = "reverse";
+    notification.addEventListener("AlertClose", this);
 
      // let the content area manager know about this browser.
     ContentAreaObserver.onBrowserCreated(browser);
 
     if (this.isPrivate) {
       let ctx = browser.docShell.QueryInterface(Ci.nsILoadContext);
       ctx.usePrivateBrowsing = true;
     }
@@ -1489,16 +1496,17 @@ Tab.prototype = {
     fl.renderMode = Ci.nsIFrameLoader.RENDER_MODE_ASYNC_SCROLL;
 
     return browser;
   },
 
   _destroyBrowser: function _destroyBrowser() {
     if (this._browser) {
       let notification = this._notification;
+      notification.removeEventListener("AlertClose", this);
       let browser = this._browser;
       browser.active = false;
 
       this._notification = null;
       this._browser = null;
       this._loading = false;
 
       Elements.browsers.removeChild(notification);
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -53,16 +53,18 @@
   <script type="application/javascript" src="chrome://browser/content/input.js"/>
   <script type="application/javascript" src="chrome://browser/content/appbar.js"/>
   <broadcasterset id="broadcasterset">
     <broadcaster id="bcast_contentShowing" disabled="false"/>
     <broadcaster id="bcast_urlbarState" mode="editing"/>
     <broadcaster id="bcast_preciseInput" input="precise"/>
     <broadcaster id="bcast_windowState" viewstate=""/>
     <broadcaster id="bcast_loadingState" loading="false"/>
+    <broadcaster id="bcast_chromeState"
+                 navbar="visible"/>
   </broadcasterset>
 
   <observerset id="observerset">
     <observes id="observe_contentShowing" element="bcast_contentShowing" attribute="disabled" onbroadcast="BrowserUI.updateUIFocus();"/>
   </observerset>
 
   <commandset id="mainCommandSet">
     <!-- basic navigation -->
@@ -199,16 +201,18 @@ Desktop browser's sync prefs.
             <toolbarbutton id="newtab-button" command="cmd_newTab" label="&newtab.label;"/>
           </vbox>
         </hbox>
       </vbox> <!-- end tray -->
 
       <!-- Content viewport -->
       <stack id="content-viewport">
         <observes element="bcast_windowState" attribute="startpage"/>
+        <observes element="bcast_chromeState"
+                  attribute="navbar"/>
         <deck id="browsers" flex="1" observes="bcast_preciseInput"/>
         <box id="vertical-scroller" class="scroller" orient="vertical" end="0" top="0"/>
         <box id="horizontal-scroller" class="scroller" orient="horizontal" left="0" bottom="0"/>
 
         <!-- Content touch selection overlay -->
         <box class="selection-overlay-hidden" id="content-selection-overlay"/>
 
         <!-- Overlay to dim screen when autocomplete shows up -->
--- a/browser/metro/base/content/downloads.js
+++ b/browser/metro/base/content/downloads.js
@@ -540,22 +540,37 @@ var MetroDownloadsView = {
         break;
     }
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
       case 'TabClose': {
         let browser = aEvent.originalTarget.linkedBrowser;
-        dump("DownloadNotificationsView handleEvent, got TabClose event for browser: "+browser+"\n");
-        let notn = this._getNotificationWithValue("download-progress");
-        if (notn && notn.defaultView == browser.contentWindow) {
-          let nextTab = Browser.getNextTab(aEvent.originalTarget);
-          let box = Browser.getNotificationBox(nextTab.linkedBrowser);
-          box.insertBefore(notn, box.firstChild);
+        let tab = Browser.getTabForBrowser(browser);
+        let notificationBox = Browser.getNotificationBox(browser);
+
+        // move any download-related notification before the tab and its notificationBox goes away
+        // The 3 possible values should be mutually exclusive
+        for(let name of ["download-progress",
+                        "save-download",
+                        "download-complete"]) {
+          let notn = notificationBox.getNotificationWithValue(name);
+          if (!notn) {
+            continue;
+          }
+
+          let nextTab = Browser.getNextTab(tab);
+          let nextBox = nextTab && Browser.getNotificationBox(nextTab.browser);
+          if (nextBox) {
+            // move notification to the next tab
+            nextBox.adoptNotification(notn);
+          } else {
+            // Alas, no browser to move the notifications to.
+          }
         }
         break;
       }
     }
   },
 
   QueryInterface: function (aIID) {
     if (!aIID.equals(Ci.nsIObserver) &&
--- a/browser/metro/base/tests/mochitest/browser_downloads.js
+++ b/browser/metro/base/tests/mochitest/browser_downloads.js
@@ -336,9 +336,54 @@ gTests.push({
  */
 gTests.push({
   desc: "Cancel/Abort Downloads",
   run: function(){
     todo(false, "Ensure that a cancelled/aborted download is in the correct state \
       including correct values for state variables (e.g. _downloadCount, _downloadsInProgress) \
       and the existence of the downloaded file.");
   }
+});
+
+/**
+ * Make sure download notifications are moved when we close tabs.
+ */
+gTests.push({
+  desc: "Download notifications in closed tabs",
+  setUp: function() {
+    // put up a couple notifications on the initial tab
+    let notificationBox = Browser.getNotificationBox();
+    notificationBox.appendNotification("not important", "low-priority-thing", "", notificationBox.PRIORITY_INFO_LOW, []);
+    notificationBox.appendNotification("so important", "high-priority-thing", "", notificationBox.PRIORITY_CRITICAL_HIGH, []);
+
+    // open a new tab where we'll conduct the test
+    yield addTab("about:mozilla");
+  },
+  run: function(){
+    let notificationBox = Browser.getNotificationBox();
+    let notn = MetroDownloadsView.showNotification("download-progress", "test message", [],
+           notificationBox.PRIORITY_WARNING_LOW);
+    Browser.closeTab(Browser.selectedTab);
+
+    yield waitForEvent(Elements.tabList, "TabRemove");
+
+    // expected behavior when a tab is closed while a download notification is showing:
+    // * the notification remains visible as long as a next tab/browser exists
+    // * normal rules about priority apply
+    // * notifications - including any pre-existing ones - display in expected order
+    let nextBox = Browser.getNotificationBox();
+    let currentNotification;
+
+    ok(nextBox.getNotificationWithValue("download-progress"), "notification was moved to next tab");
+
+    currentNotification = nextBox.currentNotification;
+    is(currentNotification.value, "high-priority-thing", "high priority notification is current");
+    currentNotification.close();
+
+    currentNotification = nextBox.currentNotification;
+    is(currentNotification.value, "download-progress", "download notification is next");
+    currentNotification.close();
+
+    currentNotification = nextBox.currentNotification;
+    is(currentNotification.value, "low-priority-thing", "low priority notification is next");
+    currentNotification.close();
+  }
 });
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/browser_notifications.js
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function test() {
+  runTests();
+}
+
+function cleanup() {
+  let notificationBox = Browser.getNotificationBox();
+  notificationBox && notificationBox.removeAllNotifications(true);
+}
+
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+
+function createTestNotification(aLabel, aID) {
+    let notificationBox = Browser.getNotificationBox();
+    let notn = notificationBox.appendNotification(aLabel || "some label", aID || "test-notification",
+                                                  "", notificationBox.PRIORITY_INFO_LOW, []);
+    return notn;
+}
+
+gTests.push({
+  desc: "Verify notification bindings are correct",
+  run: function () {
+    let notificationBox = Browser.getNotificationBox();
+    let notn = createTestNotification();
+
+    let binding = notn && getComputedStyle(notn).MozBinding;
+    is(binding,
+       "url(\"chrome://browser/content/bindings/notification.xml#notification\")",
+       "notification has expected binding");
+
+    is(getComputedStyle(notificationBox).MozBinding,
+       "url(\"chrome://browser/content/bindings/notification.xml#notificationbox\")",
+       "notificationbox has expected binding");
+  },
+  tearDown: cleanup
+});
+
+gTests.push({
+  desc: "Check label property handling",
+  run: function () {
+    let notn = createTestNotification("the label");
+    is(notn.label, "the label");
+
+    let doc = notn.ownerDocument;
+    let fragment = doc.createDocumentFragment();
+    try {
+      let boldLabel = doc.createElementNS(XHTML_NS, "b");
+      boldLabel.innerHTML = 'The <span class="foo">label</span>';
+      fragment.appendChild(boldLabel);
+      notn.label = fragment;
+    } catch (ex) {
+      ok(!ex, "Exception creating notification label with markup: "+ex.message);
+    }
+
+    // expect to get a documentFragment back when the label has markup
+    let labelNode = notn.label;
+    is(labelNode.nodeType,
+       Components.interfaces.nsIDOMNode.DOCUMENT_FRAGMENT_NODE,
+       "notification label getter returns documentFragment nodeType "+Components.interfaces.nsIDOMNode.DOCUMENT_FRAGMENT_NODE+", when value contains markup");
+    ok(labelNode !== fragment,
+       "label fragment is a newly created fragment, not the one we assigned in the setter");
+    ok(labelNode.querySelector("b > .foo"),
+       "label fragment has the expected elements in it");
+  },
+  tearDown: cleanup
+});
+
+gTests.push({
+  desc: "Check adoptNotification does what we expect",
+  setUp: function() {
+    yield addTab("about:start");
+    yield addTab("about:mozilla");
+  },
+  run: function () {
+    let browser = getBrowser();
+    let notn = createTestNotification("label", "adopt-notification");
+    let firstBox = Browser.getNotificationBox();
+    let nextTab = Browser.getNextTab(Browser.getTabForBrowser(browser));
+    let nextBox = Browser.getNotificationBox(nextTab.browser);
+
+    ok(firstBox.getNotificationWithValue("adopt-notification"), "notificationbox has our notification intially");
+    nextBox.adoptNotification(notn);
+
+    ok(!firstBox.getNotificationWithValue("adopt-notification"), "after adoptNotification, original notificationbox no longer has our notification");
+    ok(nextBox.getNotificationWithValue("adopt-notification"), "next notificationbox has our notification");
+  },
+  // leave browser in clean state for next tests
+  tearDown: cleanUpOpenedTabs
+});
+
--- a/browser/metro/base/tests/mochitest/head.js
+++ b/browser/metro/base/tests/mochitest/head.js
@@ -305,20 +305,26 @@ function addTab(aUrl) {
 
 /**
  * Cleans up tabs left open by addTab().
  * This is being called at runTests() after the test loop.
  */
 function cleanUpOpenedTabs() {
   let tab;
   while(tab = gOpenedTabs.shift()) {
+    cleanupNotificationsForBrowser(tab.browser);
     Browser.closeTab(Browser.getTabFromChrome(tab.chromeTab), { forceClose: true })
   }
 }
 
+function cleanupNotificationsForBrowser(aBrowser) {
+  let notificationBox = Browser.getNotificationBox(aBrowser);
+  notificationBox && notificationBox.removeAllNotifications(true);
+}
+
 /**
  * Waits a specified number of miliseconds for a specified event to be
  * fired on a specified element.
  *
  * Usage:
  *    let receivedEvent = waitForEvent(element, "eventName");
  *    // Do some processing here that will cause the event to be fired
  *    // ...
--- a/browser/metro/base/tests/mochitest/metro.ini
+++ b/browser/metro/base/tests/mochitest/metro.ini
@@ -49,16 +49,17 @@ support-files =
 [browser_findbar.js]
 [browser_form_auto_complete.js]
 [browser_form_selects.js]
 [browser_history.js]
 [browser_inputsource.js]
 [browser_link_click.js]
 [browser_menu_hoverstate.js]
 [browser_mouse_events.js]
+[browser_notifications.js]
 [browser_onscreen_keyboard.js]
 [browser_prefs_ui.js]
 [browser_private_browsing.js]
 [browser_prompt.js]
 [browser_remotetabs.js]
 [browser_sessionstore.js]
 [browser_snappedState.js]
 [browser_tabs.js]
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -246,20 +246,16 @@ documenttab[selected] .documenttab-selec
 
   /* Add some extra padding for a larger target */
   padding: 18px 20px 30px 20px;
   width: @newtab_button_width@;
 }
 
 /* Start UI ----------------------------------------------------------------- */
 
-#content-viewport[startpage] .active-tab-notificationbox {
-  padding-bottom: @toolbar_height@;
-}
-
 #urlbar-autocomplete[viewstate="snapped"],
 #urlbar-autocomplete[viewstate="portrait"]{
   -moz-box-orient: vertical;
 }
 
 #autocomplete-overlay {
   display: none;
   background-color: black;
@@ -500,16 +496,21 @@ documenttab[selected] .documenttab-selec
  * toolbar portion of the navbar. */
 #navbar {
   visibility: visible;
 }
 #navbar:not([hiding]):not([visible]) > #toolbar-overlay {
   visibility: hidden;
 }
 
+#content-viewport[navbar="visible"] .active-tab-notificationbox:not([count="0"]):not([notificationsVisible="false"]) {
+  padding-bottom: @toolbar_height@;
+}
+
+
 /* Progress meter ---------------------------------------------------------- */
 
 #progress-container {
   display: block;
   position: absolute;
   top: -@progress_height@;
   height: @progress_height@;
   width: 100%;