Merge fx-team to m-c on a CLOSED TREE. FIREFOX_AURORA_29_BASE
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 03 Feb 2014 15:38:27 -0500
changeset 166629 ba2cc1eda988a1614d8986ae145d28e1268409b9
parent 166609 067123df4f779596c2acedcf66579de1dfd5e1ee (current diff)
parent 166628 5fb066c6d76c3ef0fc6c523fc0305b31fdac4f41 (diff)
child 166630 bfbfeaf931b104c2728f4775273e9238f7733b06
child 166643 ad62bf4d62b4e3f8a652de98082fe61cfb61685f
child 166672 5d00b6b0908b408c7bd4a5dd8f042b2805942a0c
push id26143
push userryanvm@gmail.com
push dateMon, 03 Feb 2014 20:38:35 +0000
treeherdermozilla-central@ba2cc1eda988 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c on a CLOSED TREE.
--- a/browser/base/content/browser-customization.js
+++ b/browser/base/content/browser-customization.js
@@ -31,20 +31,17 @@ let CustomizationHandler = {
     // Disable the toolbar context menu items
     let menubar = document.getElementById("main-menubar");
     for (let childNode of menubar.childNodes)
       childNode.setAttribute("disabled", true);
 
     let cmd = document.getElementById("cmd_CustomizeToolbars");
     cmd.setAttribute("disabled", "true");
 
-    let splitter = document.getElementById("urlbar-search-splitter");
-    if (splitter) {
-      splitter.parentNode.removeChild(splitter);
-    }
+    UpdateUrlbarSearchSplitterState();
 
     CombinedStopReload.uninit();
     CombinedBackForward.uninit();
     PlacesToolbarHelper.customizeStart();
     BookmarkingUI.customizeStart();
     DownloadsButton.customizeStart();
 
     // The additional padding on the sides of the browser
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -975,18 +975,25 @@ let BookmarkingUI = {
     if (!this._shouldUpdateStarState()) {
       return null;
     }
     let widget = CustomizableUI.getWidget("bookmarks-menu-button")
                                .forWindow(window);
     if (widget.overflowed)
       return widget.anchor;
 
-    return document.getAnonymousElementByAttribute(this.star, "class",
-                                                   "toolbarbutton-icon");
+    let star = this.star;
+    return star ? document.getAnonymousElementByAttribute(star, "class",
+                                                          "toolbarbutton-icon")
+                : null;
+  },
+
+  get notifier() {
+    delete this.notifier;
+    return this.notifier = document.getElementById("bookmarked-notification-anchor");
   },
 
   get broadcaster() {
     delete this.broadcaster;
     let broadcaster = document.getElementById("bookmarkThisPageBroadcaster");
     return this.broadcaster = broadcaster;
   },
 
@@ -1082,17 +1089,17 @@ let BookmarkingUI = {
       insertionPoint: ".panel-subview-footer"
     });
   },
 
   /**
    * Handles star styling based on page proxy state changes.
    */
   onPageProxyStateChanged: function BUI_onPageProxyStateChanged(aState) {
-    if (!this._shouldUpdateStarState()) {
+    if (!this._shouldUpdateStarState() || !this.star) {
       return;
     }
 
     if (aState == "invalid") {
       this.star.setAttribute("disabled", "true");
       this.button.removeAttribute("starred");
       this.button.setAttribute("buttontooltiptext", "");
     }
@@ -1153,16 +1160,17 @@ let BookmarkingUI = {
   },
 
   init: function() {
     CustomizableUI.addListener(this);
     this._updateCustomizationState();
   },
 
   _hasBookmarksObserver: false,
+  _itemIds: [],
   uninit: function BUI_uninit() {
     this._updateBookmarkPageMenuItem(true);
     CustomizableUI.removeListener(this);
 
     this._uninitView();
 
     if (this._hasBookmarksObserver) {
       PlacesUtils.removeLazyBookmarkObserver(this);
@@ -1227,70 +1235,110 @@ let BookmarkingUI = {
     if (!this._shouldUpdateStarState()) {
       if (this.button.hasAttribute("starred")) {
         this.button.removeAttribute("starred");
         this.button.removeAttribute("buttontooltiptext");
       }
       return;
     }
 
-    if (this._itemIds && this._itemIds.length > 0) {
+    if (this._itemIds.length > 0) {
       this.button.setAttribute("starred", "true");
       this.button.setAttribute("buttontooltiptext", this._starredTooltip);
     }
     else {
       this.button.removeAttribute("starred");
       this.button.setAttribute("buttontooltiptext", this._unstarredTooltip);
     }
   },
 
   /**
    * forceReset is passed when we're destroyed and the label should go back
    * to the default (Bookmark This Page) for OS X.
    */
   _updateBookmarkPageMenuItem: function BUI__updateBookmarkPageMenuItem(forceReset) {
-    let isStarred = !forceReset && this._itemIds && this._itemIds.length > 0;
+    let isStarred = !forceReset && this._itemIds.length > 0;
     let label = isStarred ? "editlabel" : "bookmarklabel";
     this.broadcaster.setAttribute("label", this.broadcaster.getAttribute(label));
   },
 
   onMainMenuPopupShowing: function BUI_onMainMenuPopupShowing(event) {
     this._updateBookmarkPageMenuItem();
     PlacesCommandHook.updateBookmarkAllTabsCommand();
   },
 
+  _showBookmarkedNotification: function BUI_showBookmarkedNotification() {
+
+    if (this._notificationTimeout) {
+      clearTimeout(this._notificationTimeout);
+    }
+
+    if (this.notifier.style.transform == '') {
+      let buttonRect = this.button.getBoundingClientRect();
+      let notifierRect = this.notifier.getBoundingClientRect();
+      let topDiff = buttonRect.top - notifierRect.top;
+      let leftDiff = buttonRect.left - notifierRect.left;
+      let heightDiff = buttonRect.height - notifierRect.height;
+      let widthDiff = buttonRect.width - notifierRect.width;
+      let translateX = (leftDiff + .5 * widthDiff) + "px";
+      let translateY = (topDiff + .5 * heightDiff) + "px";
+      this.notifier.style.transform = "translate(" +  translateX + ", " + translateY + ")";
+    }
+
+    let isInBookmarksToolbar = this.button.classList.contains("bookmark-item");
+    if (isInBookmarksToolbar)
+      this.notifier.setAttribute("in-bookmarks-toolbar", true);
+
+    let isInOverflowPanel = this.button.classList.contains("overflowedItem");
+    if (!isInOverflowPanel) {
+      this.notifier.setAttribute("notification", "finish");
+      this.button.setAttribute("notification", "finish");
+    }
+
+    this._notificationTimeout = setTimeout( () => {
+      this.notifier.removeAttribute("notification");
+      this.notifier.removeAttribute("in-bookmarks-toolbar");
+      this.button.removeAttribute("notification");
+      this.notifier.style.transform = '';
+    }, 1000);
+  },
+
   onCommand: function BUI_onCommand(aEvent) {
     if (aEvent.target != aEvent.currentTarget) {
       return;
     }
 
     // Handle special case when the button is in the panel.
     let widget = CustomizableUI.getWidget("bookmarks-menu-button")
                                .forWindow(window);
+    let isBookmarked = this._itemIds.length > 0;
+
     if (this._currentAreaType == CustomizableUI.TYPE_MENU_PANEL) {
       let view = document.getElementById("PanelUI-bookmarks");
       view.addEventListener("ViewShowing", this);
       view.addEventListener("ViewHiding", this);
       widget.node.setAttribute("closemenu", "none");
       PanelUI.showSubView("PanelUI-bookmarks", widget.node,
                           CustomizableUI.AREA_PANEL);
       return;
     }
     if (widget.overflowed) {
       // Allow to close the panel if the page is already bookmarked, cause
       // we are going to open the edit bookmark panel.
-      if (this._itemIds.length > 0)
+      if (isBookmarked)
         widget.node.removeAttribute("closemenu");
       else
         widget.node.setAttribute("closemenu", "none");
     }
 
     // Ignore clicks on the star if we are updating its state.
     if (!this._pendingStmt) {
-      PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0);
+      if (!isBookmarked)
+        this._showBookmarkedNotification();
+      PlacesCommandHook.bookmarkCurrentPage(isBookmarked);
     }
   },
 
   handleEvent: function BUI_handleEvent(aEvent) {
     switch (aEvent.type) {
       case "ViewShowing":
         this.onPanelMenuViewShowing(aEvent);
         break;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2118,16 +2118,23 @@ function losslessDecodeURI(aURI) {
 }
 
 function UpdateUrlbarSearchSplitterState()
 {
   var splitter = document.getElementById("urlbar-search-splitter");
   var urlbar = document.getElementById("urlbar-container");
   var searchbar = document.getElementById("search-container");
 
+  if (document.documentElement.getAttribute("customizing") == "true") {
+    if (splitter) {
+      splitter.remove();
+    }
+    return;
+  }
+
   // If the splitter is already in the right place, we don't need to do anything:
   if (splitter &&
       ((splitter.nextSibling == searchbar && splitter.previousSibling == urlbar) ||
        (splitter.nextSibling == urlbar && splitter.previousSibling == searchbar))) {
     return;
   }
 
   var ibefore = null;
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -457,16 +457,22 @@
 #include ../../components/customizableui/content/panelUI.inc.xul
 
     <hbox id="downloads-animation-container" mousethrough="always">
       <vbox id="downloads-notification-anchor">
         <vbox id="downloads-indicator-notification"/>
       </vbox>
     </hbox>
 
+    <hbox id="bookmarked-notification-container" mousethrough="always">
+      <vbox id="bookmarked-notification-anchor">
+        <vbox id="bookmarked-notification"/>
+      </vbox>
+    </hbox>
+
     <tooltip id="dynamic-shortcut-tooltip"
              onpopupshowing="UpdateDynamicShortcutTooltipText(this.triggerNode)">
       <label id="dynamic-shortcut-tooltip-label"/>
     </tooltip>
   </popupset>
 
 #ifdef CAN_DRAW_IN_TITLEBAR
 <vbox id="titlebar">
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -32,16 +32,17 @@
           <toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
                          closemenu="none"
                          tooltiptext="&appMenuHelp.tooltip;"
                          oncommand="PanelUI.showHelpView(this.parentNode);"/>
           <toolbarseparator/>
           <toolbarbutton id="PanelUI-quit"
 #ifdef XP_WIN
                          label="&quitApplicationCmdWin.label;"
+                         tooltiptext="&quitApplicationCmdWin.tooltip;"
 #else
 #ifdef XP_MACOSX
                          label="&quitApplicationCmdMac.label;"
 #else
                          label="&quitApplicationCmd.label;"
 #endif
 #endif
                          command="cmd_quitApplication"/>
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -415,22 +415,21 @@ const PanelUI = {
     }
     items.appendChild(fragment);
   },
 
   _updateQuitTooltip: function() {
 #ifndef XP_WIN
 #ifdef XP_MACOSX
     let tooltipId = "quit-button.tooltiptext.mac";
+#else
+    let tooltipId = "quit-button.tooltiptext.linux2";
+#endif
     let brands = Services.strings.createBundle("chrome://branding/locale/brand.properties");
     let stringArgs = [brands.GetStringFromName("brandShortName")];
-#else
-    let tooltipId = "quit-button.tooltiptext.linux";
-    let stringArgs = [];
-#endif
 
     let key = document.getElementById("key_quitApplication");
     stringArgs.push(ShortcutUtils.prettifyShortcut(key));
     let tooltipString = CustomizableUI.getLocalizedProperty({x: tooltipId}, "x", stringArgs);
     let quitButton = document.getElementById("PanelUI-quit");
     quitButton.setAttribute("tooltiptext", tooltipString);
 #endif
   },
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -698,16 +698,18 @@ let CustomizableUIInternal = {
         container = areaNode.overflowable.getContainerFor(widgetNode);
       }
 
       this.notifyListeners("onWidgetBeforeDOMChange", widgetNode, null, container, true);
 
       // We remove location attributes here to make sure they're gone too when a
       // widget is removed from a toolbar to the palette. See bug 930950.
       this.removeLocationAttributes(widgetNode);
+      // We also need to remove the panel context menu if it's there:
+      this.ensureButtonContextMenu(widgetNode);
       widgetNode.removeAttribute("wrap");
       if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
         container.removeChild(widgetNode);
       } else {
         areaNode.toolbox.palette.appendChild(widgetNode);
       }
       this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true);
 
@@ -1134,18 +1136,16 @@ let CustomizableUIInternal = {
     }
     return def;
   },
 
   handleWidgetCommand: function(aWidget, aNode, aEvent) {
     LOG("handleWidgetCommand");
 
     if (aWidget.type == "button") {
-      this.maybeAutoHidePanel(aEvent);
-
       if (aWidget.onCommand) {
         try {
           aWidget.onCommand.call(null, aEvent);
         } catch (e) {
           ERROR(e);
         }
       } else {
         //XXXunf Need to think this through more, and formalize.
@@ -1157,20 +1157,16 @@ let CustomizableUIInternal = {
       let ownerWindow = aNode.ownerDocument.defaultView;
       ownerWindow.PanelUI.showSubView(aWidget.viewId, aNode,
                                       this.getPlacementOfWidget(aNode.id).area);
     }
   },
 
   handleWidgetClick: function(aWidget, aNode, aEvent) {
     LOG("handleWidgetClick");
-    if (aWidget.type == "button") {
-      this.maybeAutoHidePanel(aEvent);
-    }
-
     if (aWidget.onClick) {
       try {
         aWidget.onClick.call(null, aEvent);
       } catch(e) {
         Cu.reportError(e);
       }
     } else {
       //XXXunf Need to think this through more, and formalize.
@@ -1317,17 +1313,17 @@ let CustomizableUIInternal = {
     // We can't use event.target because we might have passed a panelview
     // anonymous content boundary as well, and so target points to the
     // panelmultiview in that case. Unfortunately, this means we get
     // anonymous child nodes instead of the real ones, so looking for the 
     // 'stoooop, don't close me' attributes is more involved.
     let target = aEvent.originalTarget;
     let closemenu = "auto";
     let widgetType = "button";
-    while (target.localName != "panel") {
+    while (target.parentNode && target.localName != "panel") {
       closemenu = target.getAttribute("closemenu");
       widgetType = target.getAttribute("widget-type");
       if (closemenu == "none" || closemenu == "single" ||
           widgetType == "view") {
         break;
       }
       target = target.parentNode;
     }
--- a/browser/components/customizableui/src/CustomizeMode.jsm
+++ b/browser/components/customizableui/src/CustomizeMode.jsm
@@ -14,32 +14,37 @@ const kPaletteId = "customization-palett
 const kAboutURI = "about:customizing";
 const kDragDataTypePrefix = "text/toolbarwrapper-id/";
 const kPlaceholderClass = "panel-customization-placeholder";
 const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
 const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
 const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
 const kMaxTransitionDurationMs = 2000;
 
+const kPanelItemContextMenu = "customizationPanelItemContextMenu";
+const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/CustomizableUI.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DragPositionManager",
                                   "resource:///modules/DragPositionManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
                                   "resource:///modules/BrowserUITelemetry.jsm");
 
 let gModuleName = "[CustomizeMode]";
 #include logging.js
 
 let gDisableAnimation = null;
 
+let gDraggingInToolbars;
+
 function CustomizeMode(aWindow) {
   if (gDisableAnimation === null) {
     gDisableAnimation = Services.prefs.getPrefType(kPrefCustomizationAnimation) == Ci.nsIPrefBranch.PREF_BOOL &&
                         Services.prefs.getBoolPref(kPrefCustomizationAnimation);
   }
   this.window = aWindow;
   this.document = aWindow.document;
   this.browser = aWindow.gBrowser;
@@ -650,19 +655,16 @@ CustomizeMode.prototype = {
     } else if (aNode.hasAttribute("label")) {
       wrapper.setAttribute("title", aNode.getAttribute("label"));
     }
 
     if (aNode.hasAttribute("flex")) {
       wrapper.setAttribute("flex", aNode.getAttribute("flex"));
     }
 
-
-    const kPanelItemContextMenu = "customizationPanelItemContextMenu";
-    const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
     let contextMenuAttrName = aNode.getAttribute("context") ? "context" :
                                 aNode.getAttribute("contextmenu") ? "contextmenu" : "";
     let currentContextMenu = aNode.getAttribute(contextMenuAttrName);
     let contextMenuForPlace = aPlace == "panel" ?
                                 kPanelItemContextMenu :
                                 kPaletteItemContextMenu;
     if (aPlace != "toolbar") {
       wrapper.setAttribute("context", contextMenuForPlace);
@@ -699,16 +701,18 @@ CustomizeMode.prototype = {
 
   unwrapToolbarItem: function(aWrapper) {
     if (aWrapper.nodeName != "toolbarpaletteitem") {
       return aWrapper;
     }
     aWrapper.removeEventListener("mousedown", this);
     aWrapper.removeEventListener("mouseup", this);
 
+    let place = aWrapper.getAttribute("place");
+
     let toolbarItem = aWrapper.firstChild;
     if (!toolbarItem) {
       ERROR("no toolbarItem child for " + aWrapper.tagName + "#" + aWrapper.id);
       aWrapper.remove();
       return null;
     }
 
     if (aWrapper.hasAttribute("itemobserves")) {
@@ -731,16 +735,18 @@ CustomizeMode.prototype = {
     }
 
     let wrappedContext = toolbarItem.getAttribute("wrapped-context");
     if (wrappedContext) {
       let contextAttrName = toolbarItem.getAttribute("wrapped-contextAttrName");
       toolbarItem.setAttribute(contextAttrName, wrappedContext);
       toolbarItem.removeAttribute("wrapped-contextAttrName");
       toolbarItem.removeAttribute("wrapped-context");
+    } else if (place == "panel") {
+      toolbarItem.setAttribute("context", kPanelItemContextMenu);
     }
 
     if (aWrapper.parentNode) {
       aWrapper.parentNode.replaceChild(toolbarItem, aWrapper);
     }
     return toolbarItem;
   },
 
@@ -1043,28 +1049,32 @@ CustomizeMode.prototype = {
     dt.effectAllowed = "move";
 
     let itemRect = draggedItem.getBoundingClientRect();
     let itemCenter = {x: itemRect.left + itemRect.width / 2,
                       y: itemRect.top + itemRect.height / 2};
     this._dragOffset = {x: aEvent.clientX - itemCenter.x,
                         y: aEvent.clientY - itemCenter.y};
 
+    gDraggingInToolbars = new Set();
+
     // Hack needed so that the dragimage will still show the
     // item as it appeared before it was hidden.
     this._initializeDragAfterMove = function() {
       // For automated tests, we sometimes start exiting customization mode
       // before this fires, which leaves us with placeholders inserted after
       // we've exited. So we need to check that we are indeed customizing.
       if (this._customizing && !this._transitioning) {
         item.hidden = true;
         this._showPanelCustomizationPlaceholders();
         DragPositionManager.start(this.window);
-        if (!isInToolbar && item.nextSibling) {
-          this._setDragActive(item.nextSibling, "before", draggedItem.id, false);
+        if (item.nextSibling) {
+          this._setDragActive(item.nextSibling, "before", draggedItem.id, isInToolbar);
+        } else if (isInToolbar && item.previousSibling) {
+          this._setDragActive(item.previousSibling, "after", draggedItem.id, isInToolbar);
         }
       }
       this._initializeDragAfterMove = null;
       this.window.clearTimeout(this._dragInitializeTimeout);
     }.bind(this);
     this._dragInitializeTimeout = this.window.setTimeout(this._initializeDragAfterMove, 0);
   },
 
@@ -1403,55 +1413,80 @@ CustomizeMode.prototype = {
            mozSourceNode.ownerDocument.defaultView != this.window;
   },
 
   _setDragActive: function(aItem, aValue, aDraggedItemId, aInToolbar) {
     if (!aItem) {
       return;
     }
 
-    if (aItem.hasAttribute("dragover") != aValue) {
+    if (aItem.getAttribute("dragover") != aValue) {
       aItem.setAttribute("dragover", aValue);
 
       let window = aItem.ownerDocument.defaultView;
       let draggedItem = window.document.getElementById(aDraggedItemId);
       if (!aInToolbar) {
         this._setGridDragActive(aItem, draggedItem, aValue);
       } else {
+        let targetArea = this._getCustomizableParent(aItem);
+        let makeSpaceImmediately = false;
+        if (!gDraggingInToolbars.has(targetArea.id)) {
+          gDraggingInToolbars.add(targetArea.id);
+          let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItemId);
+          let originArea = this._getCustomizableParent(draggedWrapper);
+          makeSpaceImmediately = originArea == targetArea;
+        }
         // Calculate width of the item when it'd be dropped in this position
         let width = this._getDragItemSize(aItem, draggedItem).width;
         let direction = window.getComputedStyle(aItem).direction;
         let prop, otherProp;
         // If we're inserting before in ltr, or after in rtl:
         if ((aValue == "before") == (direction == "ltr")) {
           prop = "borderLeftWidth";
           otherProp = "border-right-width";
         } else {
           // otherwise:
           prop = "borderRightWidth";
           otherProp = "border-left-width";
         }
+        if (makeSpaceImmediately) {
+          aItem.setAttribute("notransition", "true");
+        }
         aItem.style[prop] = width + 'px';
         aItem.style.removeProperty(otherProp);
+        if (makeSpaceImmediately) {
+          // Force a layout flush:
+          aItem.getBoundingClientRect();
+          aItem.removeAttribute("notransition");
+        }
       }
     }
   },
   _cancelDragActive: function(aItem, aNextItem, aNoTransition) {
     let currentArea = this._getCustomizableParent(aItem);
     if (!currentArea) {
       return;
     }
     let isToolbar = CustomizableUI.getAreaType(currentArea.id) == "toolbar";
     if (isToolbar) {
+      if (aNoTransition) {
+        aItem.setAttribute("notransition", "true");
+      }
       aItem.removeAttribute("dragover");
       // Remove both property values in the case that the end padding
       // had been set.
       aItem.style.removeProperty("border-left-width");
       aItem.style.removeProperty("border-right-width");
+      if (aNoTransition) {
+        // Force a layout flush:
+        aItem.getBoundingClientRect();
+        aItem.removeAttribute("notransition");
+      }
     } else  {
+      aItem.removeAttribute("dragover");
       if (aNextItem) {
         let nextArea = this._getCustomizableParent(aNextItem);
         if (nextArea == currentArea) {
           // No need to do anything if we're still dragging in this area:
           return;
         }
       }
       // Otherwise, clear everything out:
--- a/browser/components/customizableui/test/browser_880164_customization_context_menus.js
+++ b/browser/components/customizableui/test/browser_880164_customization_context_menus.js
@@ -269,16 +269,49 @@ add_task(function() {
   checkContextMenu(contextMenu, expectedEntries);
 
   hiddenPromise = contextMenuHidden(contextMenu);
   contextMenu.hidePopup();
   yield hiddenPromise;
   yield resetCustomization();
 });
 
+// Bug 947586 - After customization, panel items show wrong context menu options
+add_task(function() {
+  yield startCustomizing();
+  yield endCustomizing();
+
+  yield PanelUI.show();
+
+  let contextMenu = document.getElementById("customizationPanelItemContextMenu");
+  let shownContextPromise = contextMenuShown(contextMenu);
+  let newWindowButton = document.getElementById("new-window-button");
+  ok(newWindowButton, "new-window-button was found");
+  EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
+  yield shownContextPromise;
+
+  is(PanelUI.panel.state, "open", "The PanelUI should still be open.");
+
+  let expectedEntries = [
+    [".customize-context-moveToToolbar", true],
+    [".customize-context-removeFromPanel", true],
+    ["---"],
+    [".viewCustomizeToolbar", true]
+  ];
+  checkContextMenu(contextMenu, expectedEntries);
+
+  let hiddenContextPromise = contextMenuHidden(contextMenu);
+  contextMenu.hidePopup();
+  yield hiddenContextPromise;
+
+  let hiddenPromise = promisePanelHidden(window);
+  PanelUI.hide();
+  yield hiddenPromise;
+});
+
 function contextMenuShown(aContextMenu) {
   let deferred = Promise.defer();
   let win = aContextMenu.ownerDocument.defaultView;
   let timeoutId = win.setTimeout(() => {
     deferred.reject("Context menu (" + aContextMenu.id + ") did not show within 20 seconds.");
   }, 20000);
   function onPopupShown(e) {
     aContextMenu.removeEventListener("popupshown", onPopupShown);
--- a/browser/components/sessionstore/src/SessionFile.jsm
+++ b/browser/components/sessionstore/src/SessionFile.jsm
@@ -23,23 +23,26 @@ this.EXPORTED_SYMBOLS = ["SessionFile"];
  *   another attempts to copy that file.
  *
  * This implementation uses OS.File, which guarantees property 1.
  */
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
+const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
   "resource://gre/modules/TelemetryStopwatch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
@@ -120,20 +123,67 @@ let SessionFileInternal = {
   _latestWrite: null,
 
   /**
    * |true| once we have decided to stop receiving write instructiosn
    */
   _isClosed: false,
 
   read: function () {
-    return SessionWorker.post("read").then(msg => {
-      this._recordTelemetry(msg.telemetry);
-      return msg.ok;
+    // We must initialize the worker during startup so it will be ready to
+    // perform the final write. If shutdown happens soon after startup and
+    // the worker has not started yet we may not write.
+    // See Bug 964531.
+    SessionWorker.post("init");
+
+    return Task.spawn(function () {
+      let data = null;
+      for (let filename of [this.path, this.backupPath]) {
+        try {
+          data = yield this._readSessionFile(filename);
+          break;
+        } catch (ex if ex == Cr.NS_ERROR_FILE_NOT_FOUND) {
+          // Ignore exceptions about non-existent files.
+        }
+      }
+
+      throw new Task.Result(data || "");
+    }.bind(this));
+  },
+
+  /**
+   * Read the session file asynchronously.
+   *
+   * @param filename
+   *        string The name of the session file.
+   * @returns {promise}
+   */
+  _readSessionFile: function (path) {
+    let deferred = Promise.defer();
+    let file = FileUtils.File(path);
+    let durationMs = Date.now();
+
+    NetUtil.asyncFetch(file, function(inputStream, status) {
+      if (!Components.isSuccessCode(status)) {
+        deferred.reject(status);
+        return;
+      }
+
+      let byteLength = inputStream.available();
+      let data = NetUtil.readInputStreamToString(inputStream, byteLength,
+        { charset: "UTF-8" });
+      durationMs = Date.now() - durationMs;
+
+      deferred.resolve(data);
+
+      Telemetry.getHistogramById("FX_SESSION_RESTORE_READ_FILE_MS").add(durationMs);
+      Telemetry.getHistogramById("FX_SESSION_RESTORE_FILE_SIZE_BYTES").add(byteLength);
     });
+
+    return deferred.promise;
   },
 
   gatherTelemetry: function(aStateString) {
     return Task.spawn(function() {
       let msg = yield SessionWorker.post("gatherTelemetry", [aStateString]);
       this._recordTelemetry(msg.telemetry);
       throw new Task.Result(msg.telemetry);
     }.bind(this));
--- a/browser/components/sessionstore/src/SessionWorker.js
+++ b/browser/components/sessionstore/src/SessionWorker.js
@@ -62,37 +62,20 @@ let Agent = {
 
   // The path to sessionstore.js
   path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
 
   // The path to sessionstore.bak
   backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
 
   /**
-   * Read the session from disk.
-   * In case sessionstore.js does not exist, attempt to read sessionstore.bak.
+   * NO-OP to start the worker.
    */
-  read: function () {
-    for (let path of [this.path, this.backupPath]) {
-      try {
-        let durationMs = Date.now();
-        let bytes = File.read(path);
-        durationMs = Date.now() - durationMs;
-
-        return {
-          result: Decoder.decode(bytes),
-          telemetry: {FX_SESSION_RESTORE_READ_FILE_MS: durationMs,
-                      FX_SESSION_RESTORE_FILE_SIZE_BYTES: bytes.byteLength}
-        };
-      } catch (ex if isNoSuchFileEx(ex)) {
-        // Ignore exceptions about non-existent files.
-      }
-    }
-    // No sessionstore data files found. Return an empty string.
-    return {result: ""};
+  init: function () {
+    return {result: true};
   },
 
   /**
    * Write the session to disk.
    */
   write: function (stateString) {
     let exn;
     let telemetry = {};
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -565,16 +565,17 @@ you can use these alternative items. Oth
 <!ENTITY newTabButton.tooltip           "Open a new tab">
 <!ENTITY newWindowButton.tooltip        "Open a new window">
 <!ENTITY sidebarCloseButton.tooltip     "Close sidebar">
 
 <!ENTITY fullScreenButton.tooltip       "Display the window in full screen">
 
 <!ENTITY quitApplicationCmdWin.label       "Exit"> 
 <!ENTITY quitApplicationCmdWin.accesskey   "x">
+<!ENTITY quitApplicationCmdWin.tooltip     "Exit &brandShortName;">
 <!ENTITY goBackCmd.commandKey "[">
 <!ENTITY goForwardCmd.commandKey "]">
 <!ENTITY quitApplicationCmd.label       "Quit"> 
 <!ENTITY quitApplicationCmd.accesskey   "Q">
 <!ENTITY quitApplicationCmdMac.label    "Quit &brandShortName;">
 <!-- LOCALIZATION NOTE(quitApplicationCmdUnix.key): This keyboard shortcut is used by both Linux and OSX builds. -->
 <!ENTITY quitApplicationCmdUnix.key     "Q">
 
--- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -86,13 +86,14 @@ feed-button.tooltiptext = Subscribe to this page…
 characterencoding-button.label = Character Encoding
 characterencoding-button.tooltiptext2 = Show Character Encoding options
 
 email-link-button.label = Email Link
 # LOCALIZATION NOTE (email-link-button.tooltiptext2): Use the unicode ellipsis char,
 # \u2026, or use "..." if \u2026 doesn't suit traditions in your locale.
 email-link-button.tooltiptext2 = Email Link…
 
-# LOCALIZATION NOTE(quit-button.tooltiptext.linux): %S is the keyboard shortcut
-quit-button.tooltiptext.linux = Quit (%S)
+# LOCALIZATION NOTE(quit-button.tooltiptext.linux2): %1$S is the brand name (e.g. Firefox),
+# %2$S is the keyboard shortcut
+quit-button.tooltiptext.linux2 = Quit %1$S (%2$S)
 # LOCALIZATION NOTE(quit-button.tooltiptext.mac): %1$S is the brand name (e.g. Firefox),
 # %2$S is the keyboard shortcut
 quit-button.tooltiptext.mac = Quit %1$S (%2$S)
--- a/browser/metro/base/content/helperui/MenuUI.js
+++ b/browser/metro/base/content/helperui/MenuUI.js
@@ -162,16 +162,17 @@ var ContextMenuUI = {
     if (contentTypes.indexOf("link") != -1 &&
         (contentTypes.indexOf("image") != -1  ||
          contentTypes.indexOf("video") != -1 ||
          contentTypes.indexOf("selected-text") != -1))
       multipleMediaTypes = true;
 
     for (let command of Array.slice(this.commands.childNodes)) {
       command.hidden = true;
+      command.selected = false;
     }
 
     let optionsAvailable = false;
     for (let command of Array.slice(this.commands.childNodes)) {
       let types = command.getAttribute("type").split(",");
       let lowPriority = (command.hasAttribute("priority") &&
         command.getAttribute("priority") == "low");
       let searchTextItem = (command.id == "context-search");
--- a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
+++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
@@ -869,12 +869,49 @@ gTests.push({
       return clipboardContent == contentToCopy;
     });
     ok(clipboardContent == contentToCopy, "Rich content copied.");
 
     Browser.closeTab(Browser.selectedTab, { forceClose: true });
   }
 });
 
+gTests.push({
+  desc: "Bug 963067 - 'Cut' in the cut, copy, paste menu is always active " +
+        "after a browser launch.",
+  run: function test() {
+    info(chromeRoot + "browser_context_menu_tests_02.html");
+    yield addTab(chromeRoot + "browser_context_menu_tests_02.html");
+
+    purgeEventQueue();
+    emptyClipboard();
+
+    ContextUI.dismiss();
+    yield waitForCondition(() => !ContextUI.navbarVisible);
+
+    let tabWindow = Browser.selectedTab.browser.contentWindow;
+    let input = tabWindow.document.getElementById("text3-input");
+    let cutMenuItem = document.getElementById("context-cut");
+
+    input.select();
+
+    // Emulate RichListBox's behavior and set first item selected by default.
+    cutMenuItem.selected = true;
+
+    let promise = waitForEvent(document, "popupshown");
+    sendContextMenuClickToElement(tabWindow, input);
+    yield promise;
+
+    ok(!cutMenuItem.hidden && !cutMenuItem.selected,
+       "Cut menu item is visible and not selected.");
+
+    promise = waitForEvent(document, "popuphidden");
+    ContextMenuUI.hide();
+    yield promise;
+
+    Browser.closeTab(Browser.selectedTab, { forceClose: true });
+  }
+});
+
 function test() {
   setDevPixelEqualToPx();
   runTests();
 }
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -112,16 +112,72 @@ toolbarbutton.bookmark-item[open="true"]
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
 }
 
 toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
 #personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel.png") !important;
 }
 
+/* ----- BOOKMARK STAR ANIMATION ----- */
+
+@keyframes animation-bookmarkAdded {
+  from { transform: rotate(0deg) translateX(-20px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-20px) rotate(-180deg) scale(2.2); opacity: 1; }
+  80%  { opacity: 1; }
+  to   { transform: rotate(180deg) translateX(-20px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkAddedToBookmarksBar {
+  from { transform: rotate(0deg) translateX(-12px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-12px) rotate(-180deg) scale(2.2); opacity: 1; }
+  80%  { opacity: 1; }
+  to   { transform: rotate(180deg) translateX(-12px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkPulse {
+  from { transform: scale(1); }
+  50%  { transform: scale(1.3); }
+  to   { transform: scale(1); }
+}
+
+#bookmarked-notification-container {
+  min-height: 1px;
+  min-width: 1px;
+  height: 1px;
+  margin-bottom: -1px;
+  z-index: 5;
+  position: relative;
+}
+
+#bookmarked-notification {
+  background-size: 16px;
+  background-position: center;
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+  opacity: 0;
+}
+
+#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+  background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
+  animation: animation-bookmarkAdded 800ms;
+  animation-timing-function: ease ease ease linear;
+}
+
+#bookmarked-notification-anchor[notification="finish"][in-bookmarks-toolbar=true] > #bookmarked-notification {
+  animation: animation-bookmarkAddedToBookmarksBar 800ms;
+}
+
+#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+  animation: animation-bookmarkPulse 300ms;
+  animation-delay: 600ms;
+  animation-timing-function: ease-out;
+}
+
 /* Bookmark menus */
 menu.bookmark-item,
 menuitem.bookmark-item {
   min-width: 0;
   max-width: 32em;
 }
 
 .bookmark-item > .menu-iconic-left {
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -90,16 +90,17 @@ browser.jar:
   skin/classic/browser/feeds/audioFeedIcon.png        (feeds/feedIcon.png)
   skin/classic/browser/feeds/audioFeedIcon16.png      (feeds/feedIcon16.png)
   skin/classic/browser/feeds/subscribe.css            (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css         (feeds/subscribe-ui.css)
   skin/classic/browser/newtab/newTab.css              (newtab/newTab.css)
   skin/classic/browser/newtab/controls.png            (newtab/controls.png)
   skin/classic/browser/places/bookmarksMenu.png       (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png    (places/bookmarksToolbar.png)
+  skin/classic/browser/places/bookmarks-notification-finish.png  (places/bookmarks-notification-finish.png)
   skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
   skin/classic/browser/places/calendar.png            (places/calendar.png)
 * skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
   skin/classic/browser/places/livemark-item.png       (places/livemark-item.png)
   skin/classic/browser/places/star-icons.png          (places/star-icons.png)
   skin/classic/browser/places/starred48.png           (places/starred48.png)
   skin/classic/browser/places/unstarred48.png         (places/unstarred48.png)
   skin/classic/browser/places/places.css              (places/places.css)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2dc77f40822399d5dae035273db73b8b1d10ad61
GIT binary patch
literal 2905
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
DneU9e
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -356,16 +356,78 @@ toolbarpaletteitem[place="palette"] > #p
     list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel@2x.png") !important;
   }
 
   #bookmarks-toolbar-placeholder > .toolbarbutton-icon {
     width: 16px;
   }
 }
 
+/* ----- BOOKMARK STAR ANIMATION ----- */
+
+@keyframes animation-bookmarkAdded {
+  from { transform: rotate(0deg) translateX(-20px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-20px) rotate(-180deg) scale(2.2); opacity: 1; }
+  80%  { opacity: 1; }
+  to   { transform: rotate(180deg) translateX(-20px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkAddedToBookmarksBar {
+  from { transform: rotate(0deg) translateX(-12px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-12px) rotate(-180deg) scale(2.2); opacity: 1; }
+  80%  { opacity: 1; }
+  to   { transform: rotate(180deg) translateX(-12px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkPulse {
+  from { transform: scale(1); }
+  50%  { transform: scale(1.3); }
+  to   { transform: scale(1); }
+}
+
+#bookmarked-notification-container {
+  min-height: 1px;
+  min-width: 1px;
+  height: 1px;
+  margin-bottom: -1px;
+  z-index: 5;
+  position: relative;
+}
+
+#bookmarked-notification {
+  background-size: 16px;
+  background-position: center;
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+  opacity: 0;
+}
+
+#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+  background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
+  animation: animation-bookmarkAdded 800ms;
+  animation-timing-function: ease ease ease linear;
+}
+
+#bookmarked-notification-anchor[notification="finish"][in-bookmarks-toolbar=true] > #bookmarked-notification {
+  animation: animation-bookmarkAddedToBookmarksBar 800ms;
+}
+
+@media (min-resolution: 2dppx) {
+  #bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+    background-image: url("chrome://browser/skin/places/bookmarks-notification-finish@2x.png");
+  }
+}
+
+#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+  animation: animation-bookmarkPulse 300ms;
+  animation-delay: 600ms;
+  animation-timing-function: ease-out;
+}
+
 /* ----- BOOKMARK MENUS ----- */
 
 .bookmark-item > .menu-iconic-left > .menu-iconic-icon {
   width: 16px;
   height: 16px;
 }
 
 #bookmarksToolbarFolderMenu,
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -152,16 +152,18 @@ browser.jar:
   skin/classic/browser/places/allBookmarks.png              (places/allBookmarks.png)
 * skin/classic/browser/places/places.css                    (places/places.css)
 * skin/classic/browser/places/organizer.css                 (places/organizer.css)
   skin/classic/browser/places/query.png                     (places/query.png)
   skin/classic/browser/places/query@2x.png                  (places/query@2x.png)
   skin/classic/browser/places/bookmarksMenu.png             (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png          (places/bookmarksToolbar.png)
   skin/classic/browser/places/bookmarksToolbar@2x.png       (places/bookmarksToolbar@2x.png)
+  skin/classic/browser/places/bookmarks-notification-finish.png  (places/bookmarks-notification-finish.png)
+  skin/classic/browser/places/bookmarks-notification-finish@2x.png  (places/bookmarks-notification-finish@2x.png)
   skin/classic/browser/places/bookmarksToolbar-menuPanel.png    (places/bookmarksToolbar-menuPanel.png)
   skin/classic/browser/places/bookmarksToolbar-menuPanel@2x.png (places/bookmarksToolbar-menuPanel@2x.png)
   skin/classic/browser/places/history.png                   (places/history.png)
   skin/classic/browser/places/history@2x.png                (places/history@2x.png)
   skin/classic/browser/places/star-icons.png                (places/star-icons.png)
   skin/classic/browser/places/star-icons@2x.png             (places/star-icons@2x.png)
   skin/classic/browser/places/toolbar.png                   (places/toolbar.png)
   skin/classic/browser/places/toolbarDropMarker.png         (places/toolbarDropMarker.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2dc77f40822399d5dae035273db73b8b1d10ad61
GIT binary patch
literal 2905
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
DneU9e
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..aa00fd0e520400658331fa85bbc2898a0dfa657e
GIT binary patch
literal 7523
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
--- a/browser/themes/shared/UITour.inc.css
+++ b/browser/themes/shared/UITour.inc.css
@@ -46,16 +46,17 @@
 #UITourTooltipDescription {
   -moz-margin-start: 0;
   -moz-margin-end: 0;
   font-size: 1.15rem;
   line-height: 1.8rem;
 }
 
 #UITourTooltipClose {
+  visibility: hidden; /* XXX Temporarily disabled by bug 966913 */
   -moz-appearance: none;
   border: none;
   background-color: transparent;
   min-width: 0;
   -moz-margin-start: 4px;
   -moz-margin-end: -10px;
   margin-top: -8px;
 }
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -112,31 +112,32 @@
                     url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
                     linear-gradient(to bottom, #3e86ce, #3878ba);
   background-position: center top, left center, left top, left top, left top;
   background-repeat: no-repeat, no-repeat, repeat, repeat, no-repeat;
   background-size: auto 12px, 12px 100%, auto, auto, auto;
   background-attachment: scroll, scroll, fixed, fixed, scroll;
 }
 
-toolbarpaletteitem {
+toolbarpaletteitem[place="toolbar"] {
   transition: border-width 250ms ease-in-out;
 }
 
 toolbarpaletteitem[mousedown] {
   cursor: -moz-grabbing;
 }
 
 .panel-customization-placeholder,
 toolbarpaletteitem[place="palette"],
 toolbarpaletteitem[place="panel"] {
   transition: transform .3s ease-in-out;
 }
 
 toolbarpaletteitem[notransition].panel-customization-placeholder,
+toolbarpaletteitem[notransition][place="toolbar"],
 toolbarpaletteitem[notransition][place="palette"],
 toolbarpaletteitem[notransition][place="panel"] {
   transition: none;
 }
 
 toolbarpaletteitem > toolbarbutton > .toolbarbutton-icon,
 toolbarpaletteitem > toolbaritem.panel-wide-item,
 toolbarpaletteitem > toolbarbutton[type="menu-button"] {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -313,16 +313,61 @@ toolbarbutton.bookmark-item[open="true"]
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
 }
 
 toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
 #personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel.png") !important;
 }
 
+/* ----- BOOKMARK STAR ANIMATION ----- */
+
+@keyframes animation-bookmarkAdded {
+  from { transform: rotate(0deg) translateX(-16px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-16px) rotate(-180deg) scale(2.2); opacity: 1; }
+  80%  { opacity: 1; }
+  to   { transform: rotate(180deg) translateX(-16px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkPulse {
+  from { transform: scale(1); }
+  50%  { transform: scale(1.3); }
+  to   { transform: scale(1); }
+}
+
+#bookmarked-notification-container {
+  min-height: 1px;
+  min-width: 1px;
+  height: 1px;
+  margin-bottom: -1px;
+  z-index: 5;
+  position: relative;
+}
+
+#bookmarked-notification {
+  background-size: 16px;
+  background-position: center;
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+  opacity: 0;
+}
+
+#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+  background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
+  animation: animation-bookmarkAdded 800ms;
+  animation-timing-function: ease ease ease linear;
+}
+
+#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+  animation: animation-bookmarkPulse 300ms;
+  animation-delay: 600ms;
+  animation-timing-function: ease-out;
+}
+
 /* ::::: bookmark menus ::::: */
 
 menu.bookmark-item,
 menuitem.bookmark-item {
   min-width: 0;
   max-width: 32em;
 }
 
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -114,16 +114,17 @@ browser.jar:
         skin/classic/browser/newtab/controls.png                     (newtab/controls.png)
         skin/classic/browser/places/places.css                       (places/places.css)
 *       skin/classic/browser/places/organizer.css                    (places/organizer.css)
         skin/classic/browser/places/bookmark.png                     (places/bookmark.png)
         skin/classic/browser/places/query.png                        (places/query.png)
         skin/classic/browser/places/bookmarksMenu.png                (places/bookmarksMenu.png)
         skin/classic/browser/places/bookmarksToolbar.png             (places/bookmarksToolbar.png)
         skin/classic/browser/places/bookmarksToolbar-menuPanel.png   (places/bookmarksToolbar-menuPanel.png)
+        skin/classic/browser/places/bookmarks-notification-finish.png (places/bookmarks-notification-finish.png)
         skin/classic/browser/places/calendar.png                     (places/calendar.png)
         skin/classic/browser/places/toolbarDropMarker.png            (places/toolbarDropMarker.png)
         skin/classic/browser/places/editBookmarkOverlay.css          (places/editBookmarkOverlay.css)
         skin/classic/browser/places/libraryToolbar.png               (places/libraryToolbar.png)
         skin/classic/browser/places/starred48.png                    (places/starred48.png)
         skin/classic/browser/places/unstarred48.png                  (places/unstarred48.png)
         skin/classic/browser/places/tag.png                          (places/tag.png)
         skin/classic/browser/places/history.png                      (places/history.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2dc77f40822399d5dae035273db73b8b1d10ad61
GIT binary patch
literal 2905
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
DneU9e
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -515,25 +515,16 @@ abstract public class BrowserApp extends
                 hideBrowserSearch();
                 hideHomePager();
 
                 // Re-enable doorhanger notifications. They may trigger on the selected tab above.
                 mDoorHangerPopup.enable();
             }
         });
 
-        mBrowserToolbar.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                if (isHomePagerVisible()) {
-                    mHomePager.onToolbarFocusChange(hasFocus);
-                }
-            }
-        });
-
         // Intercept key events for gamepad shortcuts
         mBrowserToolbar.setOnKeyListener(this);
 
         if (mTabsPanel != null) {
             mTabsPanel.setTabsLayoutChangeListener(this);
             updateSideBarState();
         }
 
@@ -1653,17 +1644,17 @@ abstract public class BrowserApp extends
         // Show the toolbar before hiding about:home so the
         // onMetricsChanged callback still works.
         if (isDynamicToolbarEnabled() && mLayerView != null) {
             mLayerView.getLayerMarginsAnimator().showMargins(true);
         }
 
         if (mHomePager == null) {
             final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
-            mHomePager = (HomePager) homePagerStub.inflate().findViewById(R.id.home_pager);
+            mHomePager = (HomePager) homePagerStub.inflate();
         }
 
         mHomePager.show(getSupportLoaderManager(),
                         getSupportFragmentManager(),
                         pageId, animator);
 
         // Hide the web content so it cannot be focused by screen readers.
         hideWebContentOnPropertyAnimationEnd(animator);
--- a/mobile/android/base/home/HomeBanner.java
+++ b/mobile/android/base/home/HomeBanner.java
@@ -1,19 +1,15 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.home;
 
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.PropertyAnimator.Property;
-import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
-import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
@@ -22,229 +18,121 @@ import org.json.JSONObject;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.text.Html;
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class HomeBanner extends LinearLayout
                         implements GeckoEventListener {
     private static final String LOGTAG = "GeckoHomeBanner";
 
-    final TextView mTextView;
-    final ImageView mIconView;
-    final ImageButton mCloseButton;
-
-    // Used for tracking scroll length
-    private float mTouchY = -1;
-
-    // Used to detect for upwards scroll to push banner all the way up
-    private boolean mSnapBannerToTop;
-
-    // Used so that we don't move the banner when scrolling between pages
-    private boolean mScrollingPages = false;
-
-    // User has dismissed the banner using the close button
-    private boolean mDismissed = false;
-
     public HomeBanner(Context context) {
         this(context, null);
     }
 
     public HomeBanner(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         LayoutInflater.from(context).inflate(R.layout.home_banner, this);
-        mTextView = (TextView) findViewById(R.id.text);
-        mIconView = (ImageView) findViewById(R.id.icon);
-        mCloseButton = (ImageButton) findViewById(R.id.close);
+    }
 
-        mCloseButton.getDrawable().setAlpha(127);
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
         // Tapping on the close button will ensure that the banner is never
         // showed again on this session.
-        mCloseButton.setOnClickListener(new View.OnClickListener() {
+        final ImageButton closeButton = (ImageButton) findViewById(R.id.close);
+
+        // The drawable should have 50% opacity.
+        closeButton.getDrawable().setAlpha(127);
+
+        closeButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
-                animateDown();
-                mDismissed = true;
+                HomeBanner.this.setVisibility(View.GONE);
             }
         });
 
         setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 // Send the current message id back to JS.
-                GeckoAppShell.sendEventToGecko(
-                    GeckoEvent.createBroadcastEvent("HomeBanner:Click",(String) getTag()));
+                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Click", (String) getTag()));
             }
         });
-    }
 
-    @Override
-    public void onAttachedToWindow() {
         GeckoAppShell.getEventDispatcher().registerEventListener("HomeBanner:Data", this);
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null));
     }
 
     @Override
     public void onDetachedFromWindow() {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this);
-    }
+        super.onDetachedFromWindow();
 
-    public void show() {
-        if (!mDismissed) {
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null));
-        }
-    }
+        GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this);
+     }
 
-    public void hide() {
-        animateDown();
-    }
-
-    public void setScrollingPages(boolean scrollingPages) {
-        mScrollingPages = scrollingPages;
+    public boolean isDismissed() {
+        return (getVisibility() == View.GONE);
     }
 
     @Override
-    public void handleMessage(final String event, final JSONObject message) {
-        ThreadUtils.postToUiThread(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    // Store the current message id to pass back to JS in the view's OnClickListener.
-                    setTag(message.getString("id"));
-                    setText(message.getString("text"));
-                    setIcon(message.optString("iconURI"));
-                    animateUp();
-                } catch (JSONException e) {
-                    Log.e(LOGTAG, "Exception handling " + event + " message", e);
+    public void handleMessage(String event, JSONObject message) {
+        try {
+            // Store the current message id to pass back to JS in the view's OnClickListener.
+            setTag(message.getString("id"));
+
+            // Display styled text from an HTML string.
+            final Spanned text = Html.fromHtml(message.getString("text"));
+            final TextView textView = (TextView) findViewById(R.id.text);
+
+            // Update the banner message on the UI thread.
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    textView.setText(text);
+                    setVisibility(View.VISIBLE);
                 }
-            }
-        });
-    }
+            });
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "Exception handling " + event + " message", e);
+            return;
+        }
 
-    private void setText(String text) {
-        // Display styled text from an HTML string.
-        final Spanned html = Html.fromHtml(text);
+        final String iconURI = message.optString("iconURI");
+        final ImageView iconView = (ImageView) findViewById(R.id.icon);
 
-        // Update the banner message on the UI thread.
-        mTextView.setText(html);
-    }
-
-    private void setIcon(String iconURI) {
         if (TextUtils.isEmpty(iconURI)) {
             // Hide the image view if we don't have an icon to show.
-            mIconView.setVisibility(View.GONE);
+            iconView.setVisibility(View.GONE);
             return;
         }
 
         BitmapUtils.getDrawable(getContext(), iconURI, new BitmapUtils.BitmapLoader() {
             @Override
             public void onBitmapFound(final Drawable d) {
                 // Bail if getDrawable doesn't find anything.
                 if (d == null) {
-                    mIconView.setVisibility(View.GONE);
+                    iconView.setVisibility(View.GONE);
                     return;
                 }
 
-                // Update the banner icon
-                mIconView.setImageDrawable(d);
-            }
-        });
-    }
-
-    private void animateDown() {
-        // No need to animate if already translated.
-        if (getVisibility() == GONE && ViewHelper.getTranslationY(this) == getHeight()) {
-            return;
-        }
-
-        final PropertyAnimator animator = new PropertyAnimator(100);
-        animator.attach(this, Property.TRANSLATION_Y, getHeight());
-        animator.start();
-        animator.addPropertyAnimationListener(new PropertyAnimationListener() {
-            @Override
-            public void onPropertyAnimationStart() {}
-            public void onPropertyAnimationEnd() {
-                HomeBanner.this.setVisibility(GONE);
+                // Update the banner icon on the UI thread.
+                ThreadUtils.postToUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        iconView.setImageDrawable(d);
+                    }
+                });
             }
         });
     }
-
-    private void animateUp() {
-        // No need to animate if already translated.
-        if (getVisibility() == VISIBLE && ViewHelper.getTranslationY(this) == 0) {
-            return;
-        }
-
-        setVisibility(View.VISIBLE);
-        final PropertyAnimator animator = new PropertyAnimator(100);
-        animator.attach(this, Property.TRANSLATION_Y, 0);
-        animator.start();
-    }
-
-    /**
-     * Touches to the HomePager are forwarded here to handle the hiding / showing of the banner
-     * on scroll.
-     */
-    public void handleHomeTouch(MotionEvent event) {
-        if (mDismissed || mScrollingPages) {
-            return;
-        }
-
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN: {
-                mTouchY = event.getRawY();
-                break;
-            }
-
-            case MotionEvent.ACTION_MOVE: {
-                // There is a chance that we won't receive ACTION_DOWN, if the touch event
-                // actually started on the Grid instead of the List. Treat this as first event.
-                if (mTouchY == -1) {
-                    mTouchY = event.getRawY();
-                    return;
-                }
-
-                final float curY = event.getRawY();
-                final float delta = mTouchY - curY;
-                mSnapBannerToTop = delta <= 0.0f;
-
-                final float height = getHeight();
-                float newTranslationY = ViewHelper.getTranslationY(this) + delta;
-
-                // Clamp the values to be between 0 and height.
-                if (newTranslationY < 0.0f) {
-                    newTranslationY = 0.0f;
-                } else if (newTranslationY > height) {
-                    newTranslationY = height;
-                }
-
-                ViewHelper.setTranslationY(this, newTranslationY);
-                mTouchY = curY;
-                break;
-            }
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL: {
-                mTouchY = -1;
-                final float y = ViewHelper.getTranslationY(this);
-                final float height = getHeight();
-                if (y > 0.0f && y < height) {
-                    if (mSnapBannerToTop) {
-                        animateUp();
-                    } else {
-                        animateDown();
-                    }
-                }
-                break;
-            }
-        }
-    }
 }
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -19,44 +19,39 @@ import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
 import android.support.v4.view.ViewPager;
 import android.view.ViewGroup.LayoutParams;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewGroup;
-import android.view.ViewParent;
 import android.view.View;
 
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
 public class HomePager extends ViewPager {
-    private static final String LOGTAG = "GeckoHomePager";
 
     private static final int LOADER_ID_CONFIG = 0;
 
     private final Context mContext;
     private volatile boolean mLoaded;
     private Decor mDecor;
     private View mTabStrip;
-    private HomeBanner mHomeBanner;
 
     private final OnAddPanelListener mAddPanelListener;
 
     private final HomeConfig mConfig;
     private ConfigLoaderCallbacks mConfigLoaderCallbacks;
 
     private String mInitialPanelId;
-    private int mDefaultPanelIndex;
 
     // Whether or not we need to restart the loader when we show the HomePager.
     private boolean mRestartLoader;
 
     // This is mostly used by UI tests to easily fetch
     // specific list views at runtime.
     static final String LIST_TAG_HISTORY = "history";
     static final String LIST_TAG_BOOKMARKS = "bookmarks";
@@ -239,40 +234,27 @@ public class HomePager extends ViewPager
             });
 
             ViewHelper.setAlpha(this, 0.0f);
 
             animator.attach(this,
                             PropertyAnimator.Property.ALPHA,
                             1.0f);
         }
-
-        // Setup banner and decor listeners
-        mHomeBanner = (HomeBanner) ((ViewGroup) getParent()).findViewById(R.id.home_banner);
-        setOnPageChangeListener(new HomePagerOnPageChangeListener());
     }
 
     /**
      * Hides the pager and removes all child fragments.
      */
     public void hide() {
         mLoaded = false;
         setVisibility(GONE);
         setAdapter(null);
     }
 
-    @Override
-    public void setVisibility(int visibility) {
-        // Ensure that no decorations are overlaying the mainlayout
-        if (mHomeBanner != null) {
-            mHomeBanner.setVisibility(visibility);
-        }
-        super.setVisibility(visibility);
-    }
-
     /**
      * Determines whether the pager is visible.
      *
      * Unlike getVisibility(), this method does not need to be called on the UI
      * thread.
      *
      * @return Whether the pager and its fragments are being displayed
      */
@@ -282,53 +264,28 @@ public class HomePager extends ViewPager
 
     @Override
     public void setCurrentItem(int item, boolean smoothScroll) {
         super.setCurrentItem(item, smoothScroll);
 
         if (mDecor != null) {
             mDecor.onPageSelected(item);
         }
-        if (mHomeBanner != null) {
-            if (item == mDefaultPanelIndex) {
-                mHomeBanner.show();
-            } else {
-                mHomeBanner.hide();
-            }
-        }
     }
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
             // Drop the soft keyboard by stealing focus from the URL bar.
             requestFocus();
         }
 
         return super.onInterceptTouchEvent(event);
     }
 
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent event) {
-        // Get touches to pages, pass to banner, and forward to pages.
-        if (mHomeBanner != null) {
-            mHomeBanner.handleHomeTouch(event);
-        }
-
-        return super.dispatchTouchEvent(event);
-    }
-
-    public void onToolbarFocusChange(boolean hasFocus) {
-        if (hasFocus) {
-            mHomeBanner.hide();
-        } else if (mDefaultPanelIndex == getCurrentItem() || getAdapter().getCount() == 0) {
-            mHomeBanner.show();
-        }
-    }
-
     private void updateUiFromPanelConfigs(List<PanelConfig> panelConfigs) {
         // We only care about the adapter if HomePager is currently
         // loaded, which means it's visible in the activity.
         if (!mLoaded) {
             return;
         }
 
         if (mDecor != null) {
@@ -342,19 +299,16 @@ public class HomePager extends ViewPager
         setAdapter(null);
 
         // Only keep enabled panels.
         final List<PanelConfig> enabledPanels = new ArrayList<PanelConfig>();
 
         for (PanelConfig panelConfig : panelConfigs) {
             if (!panelConfig.isDisabled()) {
                 enabledPanels.add(panelConfig);
-                if (panelConfig.isDefault()) {
-                    mDefaultPanelIndex = enabledPanels.size() - 1;
-                }
             }
         }
 
         // Update the adapter with the new panel configs
         adapter.update(enabledPanels);
 
         // Hide the tab strip if the new configuration contains no panels.
         final int count = enabledPanels.size();
@@ -390,40 +344,9 @@ public class HomePager extends ViewPager
         public void onLoadFinished(Loader<List<PanelConfig>> loader, List<PanelConfig> panelConfigs) {
             updateUiFromPanelConfigs(panelConfigs);
         }
 
         @Override
         public void onLoaderReset(Loader<List<PanelConfig>> loader) {
         }
     }
-
-    private class HomePagerOnPageChangeListener implements ViewPager.OnPageChangeListener {
-        @Override
-        public void onPageSelected(int position) {
-            if (mDecor != null) {
-                mDecor.onPageSelected(position);
-            }
-
-            if (mHomeBanner != null) {
-                if (position == mDefaultPanelIndex) {
-                    mHomeBanner.show();
-                } else {
-                    mHomeBanner.hide();
-                }
-            }
-        }
-
-        @Override
-        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-            if (mDecor != null) {
-                mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels);
-            }
-
-            if (mHomeBanner != null) {
-                mHomeBanner.setScrollingPages(positionOffsetPixels > 0);
-            }
-        }
-
-        @Override
-        public void onPageScrollStateChanged(int state) { }
-    }
 }
--- a/mobile/android/base/home/TopSitesPanel.java
+++ b/mobile/android/base/home/TopSitesPanel.java
@@ -81,16 +81,25 @@ public class TopSitesPanel extends HomeF
     private TopSitesGridAdapter mGridAdapter;
 
     // List of top sites
     private ListView mList;
 
     // Grid of top sites
     private TopSitesGridView mGrid;
 
+    // Banner to show snippets.
+    private HomeBanner mBanner;
+
+    // Raw Y value of the last event that happened on the list view.
+    private float mListTouchY = -1;
+
+    // Scrolling direction of the banner.
+    private boolean mSnapBannerToTop;
+
     // Callbacks used for the search and favicon cursor loaders
     private CursorLoaderCallbacks mCursorLoaderCallbacks;
 
     // Callback for thumbnail loader
     private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks;
 
     // Listener for editing pinned sites.
     private EditPinnedSiteListener mEditPinnedSiteListener;
@@ -192,16 +201,25 @@ public class TopSitesPanel extends HomeF
             }
         });
 
         mGrid.setOnUrlOpenListener(mUrlOpenListener);
         mGrid.setOnEditPinnedSiteListener(mEditPinnedSiteListener);
 
         registerForContextMenu(mList);
         registerForContextMenu(mGrid);
+
+        mBanner = (HomeBanner) view.findViewById(R.id.home_banner);
+        mList.setOnTouchListener(new OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                TopSitesPanel.this.handleListTouchEvent(event);
+                return false;
+            }
+        });
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
 
         // Discard any additional item clicks on the list
         // as the panel is getting destroyed (see bug 930160).
@@ -430,16 +448,70 @@ public class TopSitesPanel extends HomeF
                 @Override
                 public void run() {
                     BrowserDB.pinSite(context.getContentResolver(), url, title, position);
                 }
             });
         }
     }
 
+    private void handleListTouchEvent(MotionEvent event) {
+        // Ignore the event if the banner is hidden for this session.
+        if (mBanner.isDismissed()) {
+            return;
+        }
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN: {
+                mListTouchY = event.getRawY();
+                break;
+            }
+
+            case MotionEvent.ACTION_MOVE: {
+                // There is a chance that we won't receive ACTION_DOWN, if the touch event
+                // actually started on the Grid instead of the List. Treat this as first event.
+                if (mListTouchY == -1) {
+                    mListTouchY = event.getRawY();
+                    return;
+                }
+
+                final float curY = event.getRawY();
+                final float delta = mListTouchY - curY;
+                mSnapBannerToTop = (delta > 0.0f) ? false : true;
+
+                final float height = mBanner.getHeight();
+                float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta;
+
+                // Clamp the values to be between 0 and height.
+                if (newTranslationY < 0.0f) {
+                    newTranslationY = 0.0f;
+                } else if (newTranslationY > height) {
+                    newTranslationY = height;
+                }
+
+                ViewHelper.setTranslationY(mBanner, newTranslationY);
+                mListTouchY = curY;
+                break;
+            }
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL: {
+                mListTouchY = -1;
+                final float y = ViewHelper.getTranslationY(mBanner);
+                final float height = mBanner.getHeight();
+                if (y > 0.0f && y < height) {
+                    final PropertyAnimator animator = new PropertyAnimator(100);
+                    animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height);
+                    animator.start();
+                }
+                break;
+            }
+        }
+    }
+
     private void updateUiFromCursor(Cursor c) {
         mList.setHeaderDividersEnabled(c != null && c.getCount() > mMaxGridEntries);
     }
 
     private static class TopSitesLoader extends SimpleCursorLoader {
         // Max number of search results
         private static final int SEARCH_LIMIT = 30;
         private int mMaxGridEntries;
--- a/mobile/android/base/resources/layout-large-v11/home_pager.xml
+++ b/mobile/android/base/resources/layout-large-v11/home_pager.xml
@@ -9,16 +9,15 @@
 <org.mozilla.gecko.home.HomePager xmlns:android="http://schemas.android.com/apk/res/android"
                                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                                   android:id="@+id/home_pager"
                                   android:layout_width="fill_parent"
                                   android:layout_height="fill_parent"
                                   android:background="@android:color/white"
                                   android:visibility="gone">
 
-    <org.mozilla.gecko.home.TabMenuStrip android:id="@+id/tablet_menu_strip"
-                                         android:layout_width="fill_parent"
+    <org.mozilla.gecko.home.TabMenuStrip android:layout_width="fill_parent"
                                          android:layout_height="32dip"
                                          android:background="@color/background_light"
                                          android:layout_gravity="top"
                                          gecko:strip="@drawable/home_tab_menu_strip"/>
 
 </org.mozilla.gecko.home.HomePager>
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ b/mobile/android/base/resources/layout/gecko_app.xml
@@ -31,26 +31,16 @@
                          android:layout_width="fill_parent"
                          android:layout_height="fill_parent">
 
                 <ViewStub android:id="@+id/home_pager_stub"
                           android:layout="@layout/home_pager"
                           android:layout_width="fill_parent"
                           android:layout_height="fill_parent"/>
 
-                <org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
-                                                   style="@style/Widget.HomeBanner"
-                                                   android:layout_width="fill_parent"
-                                                   android:layout_height="@dimen/home_banner_height"
-                                                   android:background="@drawable/home_banner"
-                                                   android:layout_gravity="bottom"
-                                                   android:gravity="center_vertical"
-                                                   android:visibility="gone"
-                                                   android:clickable="true"
-                                                   android:focusable="true"/>
 
             </FrameLayout>
 
         </RelativeLayout>
 
         <org.mozilla.gecko.FindInPageBar android:id="@+id/find_in_page"
                                          android:layout_width="fill_parent"
                                          android:layout_height="wrap_content"
--- a/mobile/android/base/resources/layout/home_pager.xml
+++ b/mobile/android/base/resources/layout/home_pager.xml
@@ -9,18 +9,17 @@
 <org.mozilla.gecko.home.HomePager xmlns:android="http://schemas.android.com/apk/res/android"
                                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                                   android:id="@+id/home_pager"
                                   android:layout_width="fill_parent"
                                   android:layout_height="fill_parent"
                                   android:background="@android:color/white"
                                   android:visibility="gone">
 
-    <org.mozilla.gecko.home.HomePagerTabStrip android:id="@+id/phone_menu_strip"
-                                              android:layout_width="fill_parent"
+    <org.mozilla.gecko.home.HomePagerTabStrip android:layout_width="fill_parent"
                                               android:layout_height="32dip"
                                               android:layout_gravity="top"
                                               android:gravity="bottom"
                                               android:background="@color/background_light"
                                               gecko:tabIndicatorColor="@color/text_color_highlight"
                                               android:textAppearance="@style/TextAppearance.Widget.HomePagerTabStrip"/>
 
 </org.mozilla.gecko.home.HomePager>
--- a/mobile/android/base/resources/layout/home_top_sites_panel.xml
+++ b/mobile/android/base/resources/layout/home_top_sites_panel.xml
@@ -1,10 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
-<org.mozilla.gecko.home.HomeListView xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/list"
-        style="@style/Widget.TopSitesListView"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent" />
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="fill_parent"
+             android:layout_height="fill_parent"
+             android:orientation="vertical">
+
+    <org.mozilla.gecko.home.HomeListView
+            android:id="@+id/list"
+            style="@style/Widget.TopSitesListView"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"/>
+
+    <org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
+                                       style="@style/Widget.HomeBanner"
+                                       android:layout_width="fill_parent"
+                                       android:layout_height="@dimen/home_banner_height"
+                                       android:background="@drawable/home_banner"
+                                       android:layout_gravity="bottom"
+                                       android:gravity="center_vertical"
+                                       android:visibility="gone"
+                                       android:clickable="true"
+                                       android:focusable="true"/>
+
+</FrameLayout>
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -137,17 +137,16 @@ public class BrowserToolbar extends Geck
     private List<View> mFocusOrder;
 
     private OnActivateListener mActivateListener;
     private OnCommitListener mCommitListener;
     private OnDismissListener mDismissListener;
     private OnFilterListener mFilterListener;
     private OnStartEditingListener mStartEditingListener;
     private OnStopEditingListener mStopEditingListener;
-    private OnFocusChangeListener mFocusChangeListener;
 
     final private BrowserApp mActivity;
     private boolean mHasSoftMenuButton;
 
     private UIMode mUIMode;
     private boolean mAnimatingEntry;
 
     private int mUrlBarViewOffset;
@@ -311,19 +310,16 @@ public class BrowserToolbar extends Geck
                 setContentDescription(contentDescription);
             }
         });
 
         mUrlEditLayout.setOnFocusChangeListener(new View.OnFocusChangeListener() {
             @Override
             public void onFocusChange(View v, boolean hasFocus) {
                 setSelected(hasFocus);
-                if (mFocusChangeListener != null) {
-                    mFocusChangeListener.onFocusChange(v, hasFocus);
-                }
             }
         });
 
         mTabs.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
                 toggleTabs();
             }
@@ -792,20 +788,16 @@ public class BrowserToolbar extends Geck
     public void setOnStartEditingListener(OnStartEditingListener listener) {
         mStartEditingListener = listener;
     }
 
     public void setOnStopEditingListener(OnStopEditingListener listener) {
         mStopEditingListener = listener;
     }
 
-    public void setOnFocusChangeListener(OnFocusChangeListener listener) {
-        mFocusChangeListener = listener;
-    }
-
     private void showUrlEditLayout() {
         setUrlEditLayoutVisibility(true, null);
     }
 
     private void showUrlEditLayout(PropertyAnimator animator) {
         setUrlEditLayoutVisibility(true, animator);
     }
 
--- a/toolkit/components/crashmonitor/CrashMonitor.jsm
+++ b/toolkit/components/crashmonitor/CrashMonitor.jsm
@@ -29,22 +29,25 @@
  * checkpoint file tells us that the corresponding stage was reached
  * during the last run, the absence of a notification after a crash
  * does not necessarily tell us that the checkpoint wasn't reached.
  */
 
 this.EXPORTED_SYMBOLS = [ "CrashMonitor" ];
 
 const Cu = Components.utils;
+const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
 
 const NOTIFICATIONS = [
   "final-ui-startup",
   "sessionstore-windows-restored",
   "quit-application-granted",
   "quit-application",
   "profile-change-net-teardown",
   "profile-change-teardown",
@@ -88,35 +91,52 @@ let CrashMonitorInternal = {
   path: OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"),
 
   /**
    * Load checkpoints from previous session asynchronously.
    *
    * @return {Promise} A promise that resolves/rejects once loading is complete
    */
   loadPreviousCheckpoints: function () {
-    let promise = Task.spawn(function () {
-      let notifications;
+    let deferred = Promise.defer();
+    CrashMonitorInternal.previousCheckpoints = deferred.promise;
+
+    let file = FileUtils.File(CrashMonitorInternal.path);
+    NetUtil.asyncFetch(file, function(inputStream, status) {
+      if (!Components.isSuccessCode(status)) {
+        if (status != Cr.NS_ERROR_FILE_NOT_FOUND) {
+          Cu.reportError("Error while loading crash monitor data: " + status);
+        }
+
+        deferred.resolve(null);
+        return;
+      }
+
+      let data = NetUtil.readInputStreamToString(inputStream,
+        inputStream.available(), { charset: "UTF-8" });
+
+      let notifications = null;
       try {
-        let decoder = new TextDecoder();
-        let data = yield OS.File.read(CrashMonitorInternal.path);
-        let contents = decoder.decode(data);
-        notifications = JSON.parse(contents);
-      } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
-        // If checkpoint file cannot be read
-        throw new Task.Result(null);
+        notifications = JSON.parse(data);
       } catch (ex) {
-        Cu.reportError("Error while loading crash monitor data: " + ex);
-        throw new Task.Result(null);
+        Cu.reportError("Error while parsing crash monitor data: " + ex);
+        deferred.resolve(null);
       }
-      throw new Task.Result(Object.freeze(notifications));
+
+      try {
+        deferred.resolve(Object.freeze(notifications));
+      } catch (ex) {
+        // The only exception we reject from is if notifications is not
+        // an object. This happens when the checkpoints file contained
+        // just a numeric string.
+        deferred.reject(ex);
+      }
     });
 
-    CrashMonitorInternal.previousCheckpoints = promise;
-    return promise;
+    return deferred.promise;
   }
 };
 
 this.CrashMonitor = {
 
   /**
    * Notifications received during previous session.
    *
--- a/toolkit/content/tests/chrome/test_arrowpanel.xul
+++ b/toolkit/content/tests/chrome/test_arrowpanel.xul
@@ -260,19 +260,19 @@ function checkPanelPosition(panel)
   is(arrow.getAttribute("side"), expectedSide, "panel arrow side");
   is(arrow.hidden, false, "panel hidden");
   is(arrow.parentNode.pack, expectedPack, "panel arrow pack");
   is(panel.alignmentPosition, expectedAlignment, "panel alignmentPosition");
 
   panel.hidePopup();
 }
 
-function isWithinHalfPixel(a, b)
+function isWithinHalfPixel(a, b, desc)
 {
-  return Math.abs(a - b) <= 0.5;
+  ok(Math.abs(a - b) <= 0.5, desc);
 }
 
 function checkBigPanel(panel)
 {
   ok(panel.firstChild.getBoundingClientRect().height < 2800, "big panel height");
   panel.hidePopup();
 }
 
--- a/toolkit/content/widgets/text.xml
+++ b/toolkit/content/widgets/text.xml
@@ -54,45 +54,56 @@
 
   <binding id="label-control" extends="chrome://global/content/bindings/text.xml#text-label">
     <content>
       <children/><html:span anonid="accessKeyParens"></html:span>
     </content>
     <implementation implements="nsIDOMXULLabelElement">
       <constructor>
         <![CDATA[
-          try {
-            var prefs = Components.classes["@mozilla.org/preferences-service;1"].
-                                   getService(Components.interfaces.nsIPrefBranch);
-            this.mUnderlineAccesskey = (prefs.getIntPref("ui.key.menuAccessKey") != 0);
-
-            const nsIPrefLocalizedString =
-              Components.interfaces.nsIPrefLocalizedString;
-
-            const prefNameInsertSeparator =
-              "intl.menuitems.insertseparatorbeforeaccesskeys";
-            const prefNameAlwaysAppendAccessKey =
-              "intl.menuitems.alwaysappendaccesskeys";
-
-            var val = prefs.getComplexValue(prefNameInsertSeparator,
-                                            nsIPrefLocalizedString).data;
-            this.mInsertSeparator = (val == "true");
-
-            val = prefs.getComplexValue(prefNameAlwaysAppendAccessKey,
-                                        nsIPrefLocalizedString).data;
-            this.mAlwaysAppendAccessKey = (val == "true");
-          }
-          catch (e) { }
-          this.formatAccessKey();
+          this.formatAccessKey(true);
         ]]>
       </constructor>
 
       <method name="formatAccessKey">
+        <parameter name="firstTime"/>
         <body>
           <![CDATA[
+            var accessKey = this.accessKey;
+            // No need to remove existing formatting the first time.
+            if (firstTime && !accessKey)
+              return;
+
+            if (this.mInsertSeparator === undefined) {
+              try {
+                var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+                                       getService(Components.interfaces.nsIPrefBranch);
+                this.mUnderlineAccesskey = (prefs.getIntPref("ui.key.menuAccessKey") != 0);
+
+                const nsIPrefLocalizedString =
+                  Components.interfaces.nsIPrefLocalizedString;
+
+                const prefNameInsertSeparator =
+                  "intl.menuitems.insertseparatorbeforeaccesskeys";
+                const prefNameAlwaysAppendAccessKey =
+                  "intl.menuitems.alwaysappendaccesskeys";
+
+                var val = prefs.getComplexValue(prefNameInsertSeparator,
+                                                nsIPrefLocalizedString).data;
+                this.mInsertSeparator = (val == "true");
+
+                val = prefs.getComplexValue(prefNameAlwaysAppendAccessKey,
+                                            nsIPrefLocalizedString).data;
+                this.mAlwaysAppendAccessKey = (val == "true");
+              }
+              catch (e) {
+                this.mInsertSeparator = true;
+              }
+            }
+
             if (!this.mUnderlineAccesskey)
               return;
 
             var control = this.labeledControlElement;
             if (!control) {
               var bindingParent = document.getBindingParent(this);
               if (bindingParent instanceof Components.interfaces.nsIDOMXULLabeledControlElement) {
                 control = bindingParent; // For controls that make the <label> an anon child
@@ -111,17 +122,16 @@
             }
 
             var oldHiddenSpan =
               this.getElementsByAttribute('class', 'hiddenColon').item(0);
             if (oldHiddenSpan) {
               this.mergeElement(oldHiddenSpan);
             }
 
-            var accessKey = this.accessKey;
             var labelText = this.textContent;
             if (!accessKey || !labelText || !control) {
               return;
             }
             var accessKeyIndex = -1;
             if (!this.mAlwaysAppendAccessKey) {
               accessKeyIndex = labelText.indexOf(accessKey);
               if (accessKeyIndex < 0) { // Try again in upper case
@@ -213,17 +223,17 @@
             element.parentNode.removeChild(element);
           ]]>
         </body>
       </method>
 
       <field name="mUnderlineAccesskey">
         !/Mac/.test(navigator.platform)
       </field>
-      <field name="mInsertSeparator">true</field>
+      <field name="mInsertSeparator"/>
       <field name="mAlwaysAppendAccessKey">false</field>
 
       <property name="accessKey">
         <getter>
           <![CDATA[
             var accessKey = null;
             var labeledEl = this.labeledControlElement;
             if (labeledEl) {
@@ -240,34 +250,34 @@
             // If this label already has an accesskey attribute store it here as well
             if (this.hasAttribute('accesskey')) {
               this.setAttribute('accesskey', val);
             }
             var control = this.labeledControlElement;
             if (control) {
               control.setAttribute('accesskey', val);
             }
-            this.formatAccessKey();
+            this.formatAccessKey(false);
             return val;
           ]]>
         </setter>
       </property>
 
       <property name="labeledControlElement" readonly="true"
                 onget="var control = this.control; return control ? document.getElementById(control) : null;" />
 
       <property name="control" onget="return this.getAttribute('control');">
         <setter>
           <![CDATA[
             var control = this.labeledControlElement;
             if (control) {
               control.labelElement = null; // No longer pointed to be this label
             }
             this.setAttribute('control', val);
-            this.formatAccessKey();
+            this.formatAccessKey(false);
             return val;
           ]]>
         </setter>
       </property>
 
     </implementation>
 
     <handlers>
--- a/toolkit/devtools/server/child.js
+++ b/toolkit/devtools/server/child.js
@@ -1,33 +1,43 @@
-const {DevToolsUtils} = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
-const {DebuggerServer, ActorPool} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+/* 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/. */
 
-if (!DebuggerServer.initialized) {
-  DebuggerServer.init();
-}
+"use strict";
 
-// In case of apps being loaded in parent process, DebuggerServer is already
-// initialized, but child specific actors are not registered.
-// Otherwise, for apps in child process, we need to load actors the first
-// time we load child.js
-DebuggerServer.addChildActors();
+// Encapsulate in its own scope to allows loading this frame script
+// more than once.
+(function () {
+  const {DevToolsUtils} = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
+  const {DebuggerServer, ActorPool} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 
-let onConnect = DevToolsUtils.makeInfallible(function (msg) {
-  removeMessageListener("debug:connect", onConnect);
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init();
+  }
+
+  // In case of apps being loaded in parent process, DebuggerServer is already
+  // initialized, but child specific actors are not registered.
+  // Otherwise, for apps in child process, we need to load actors the first
+  // time we load child.js
+  DebuggerServer.addChildActors();
 
-  let mm = msg.target;
+  let onConnect = DevToolsUtils.makeInfallible(function (msg) {
+    removeMessageListener("debug:connect", onConnect);
 
-  let prefix = msg.data.prefix + docShell.appId;
+    let mm = msg.target;
 
-  let conn = DebuggerServer.connectToParent(prefix, mm);
+    let prefix = msg.data.prefix + docShell.appId;
+
+    let conn = DebuggerServer.connectToParent(prefix, mm);
 
-  let actor = new DebuggerServer.ContentAppActor(conn, content);
-  let actorPool = new ActorPool(conn);
-  actorPool.addActor(actor);
-  conn.addActorPool(actorPool);
+    let actor = new DebuggerServer.ContentAppActor(conn, content);
+    let actorPool = new ActorPool(conn);
+    actorPool.addActor(actor);
+    conn.addActorPool(actorPool);
 
-  sendAsyncMessage("debug:actor", {actor: actor.grip(),
-                                   appId: docShell.appId,
-                                   prefix: prefix});
-});
+    sendAsyncMessage("debug:actor", {actor: actor.grip(),
+                                     appId: docShell.appId,
+                                     prefix: prefix});
+  });
 
-addMessageListener("debug:connect", onConnect);
+  addMessageListener("debug:connect", onConnect);
+})();