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 182639 ba2cc1eda988a1614d8986ae145d28e1268409b9
parent 182619 067123df4f779596c2acedcf66579de1dfd5e1ee (current diff)
parent 182638 5fb066c6d76c3ef0fc6c523fc0305b31fdac4f41 (diff)
child 182640 b13345c0c285761d5f8269c0969617398e7ea873
child 183821 bfbfeaf931b104c2728f4775273e9238f7733b06
child 183834 ad62bf4d62b4e3f8a652de98082fe61cfb61685f
child 183863 5d00b6b0908b408c7bd4a5dd8f042b2805942a0c
push id1
push usersledru@mozilla.com
push dateThu, 04 Dec 2014 17:57:20 +0000
milestone29.0a1
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);
+})();