Merge fx-team to m-c
authorWes Kocher <wkocher@mozilla.com>
Thu, 23 Jan 2014 18:31:45 -0800
changeset 180988 9d650c07b5476aec4e12f100e62d7f742548b9e3
parent 180974 624d042739e6ed0755fd9e953c65866e934af6a6 (current diff)
parent 180987 3ba925830469d57a7a08e1857f0f468596e4025f (diff)
child 181011 8bfc7447992df1e5866a191ecd776e694056fec0
child 181066 8be30d3b0b8037c66a1ff1ca0a70cf43cad229e0
child 181106 f9a4e354878bc5503def366a4c6275f4967798fd
child 181139 d7252c96a2350263c8df7560c4eceda0cd175549
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
9d650c07b547 / 29.0a1 / 20140124030216 / files
nightly linux64
9d650c07b547 / 29.0a1 / 20140124030216 / files
nightly mac
9d650c07b547 / 29.0a1 / 20140124030216 / files
nightly win32
9d650c07b547 / 29.0a1 / 20140124030216 / files
nightly win64
9d650c07b547 / 29.0a1 / 20140124030216 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -213,16 +213,17 @@
           <hbox id="UITourTooltipButtons" flex="1" align="end"/>
         </vbox>
       </hbox>
     </panel>
     <panel id="UITourHighlightContainer"
            hidden="true"
            noautofocus="true"
            noautohide="true"
+           flip="none"
            consumeoutsideclicks="false">
       <box id="UITourHighlight"></box>
     </panel>
 
     <panel id="social-share-panel"
            class="social-panel"
            type="arrow"
            orient="horizontal"
--- a/browser/devtools/webconsole/hudservice.js
+++ b/browser/devtools/webconsole/hudservice.js
@@ -6,19 +6,19 @@
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 
 let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
 let Heritage = require("sdk/core/heritage");
 
-loader.lazyGetter(this, "promise", () => require("sdk/core/promise"));
 loader.lazyGetter(this, "Telemetry", () => require("devtools/shared/telemetry"));
 loader.lazyGetter(this, "WebConsoleFrame", () => require("devtools/webconsole/webconsole").WebConsoleFrame);
+loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
 loader.lazyImporter(this, "DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm");
 loader.lazyImporter(this, "DebuggerClient", "resource://gre/modules/devtools/dbg-client.jsm");
 
 const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
@@ -105,16 +105,17 @@ HUD_SERVICE.prototype =
    *        The window of the browser console owner.
    * @return object
    *         A promise object for the opening of the new BrowserConsole instance.
    */
   openBrowserConsole:
   function HS_openBrowserConsole(aTarget, aIframeWindow, aChromeWindow)
   {
     let hud = new BrowserConsole(aTarget, aIframeWindow, aChromeWindow);
+    this._browserConsoleID = hud.hudId;
     this.consoles.set(hud.hudId, hud);
     return hud.init();
   },
 
   /**
    * Returns the Web Console object associated to a content window.
    *
    * @param nsIDOMWindow aContentWindow
@@ -236,17 +237,16 @@ HUD_SERVICE.prototype =
       });
 
       return deferred.promise;
     }
 
     connect().then(getTarget).then(openWindow).then((aWindow) => {
       this.openBrowserConsole(target, aWindow, aWindow)
         .then((aBrowserConsole) => {
-          this._browserConsoleID = aBrowserConsole.hudId;
           this._browserConsoleDefer.resolve(aBrowserConsole);
           this._browserConsoleDefer = null;
         })
     }, console.error);
 
     return this._browserConsoleDefer.promise;
   },
 
--- a/browser/devtools/webconsole/panel.js
+++ b/browser/devtools/webconsole/panel.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 
-loader.lazyGetter(this, "promise", () => require("sdk/core/promise"));
+loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
 loader.lazyGetter(this, "HUDService", () => require("devtools/webconsole/hudservice"));
 loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter"));
 
 /**
  * A DevToolPanel that controls the Web Console.
  */
 function WebConsolePanel(iframeWindow, toolbox)
 {
--- a/browser/devtools/webconsole/test/browser_console_dead_objects.js
+++ b/browser/devtools/webconsole/test/browser_console_dead_objects.js
@@ -44,17 +44,20 @@ function test()
           "dead object found");
 
     hud.jsterm.setInputValue("foobarzTezt");
 
     for (let c of ".hello") {
       EventUtils.synthesizeKey(c, {}, hud.iframeWindow);
     }
 
-    hud.jsterm.execute(null, onReadProperty.bind(null, msg));
+    hud.jsterm.execute(null, () => {
+      // executeSoon() is needed to get out of the execute() event loop.
+      executeSoon(onReadProperty.bind(null, msg));
+    });
   }
 
   function onReadProperty(deadObjectMessage)
   {
     isnot(hud.outputNode.textContent.indexOf("can't access dead object"), -1,
           "'cannot access dead object' message found");
 
     // Click the second execute output.
@@ -64,16 +67,17 @@ function test()
           "message text check");
 
     hud.jsterm.once("variablesview-fetched", onFetched);
     EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
   }
 
   function onFetched()
   {
+    ok(true, "variables view fetched");
     hud.jsterm.execute("delete window.foobarzTezt; 2013-26", onCalcResult);
   }
 
   function onCalcResult()
   {
     isnot(hud.outputNode.textContent.indexOf("1987"), -1, "result message found");
 
     // executeSoon() is needed to get out of the execute() event loop.
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_613642_maintain_scroll.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_613642_maintain_scroll.js
@@ -90,47 +90,34 @@ function testGen() {
       // Wait for scroll to bottom.
       return;
     }
     scrollBox.onscroll = null;
     isnot(scrollBox.scrollTop, 0, "scroll location updated (moved to bottom)");
     testNext();
   };
   EventUtils.synthesizeKey("VK_END", {});
-  yield;
+  yield undefined;
 
   let oldScrollTop = scrollBox.scrollTop;
 
   content.console.log("test message 151");
 
-  waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "test message 151",
-      category: CATEGORY_WEBDEV,
-      severity: SEVERITY_LOG,
-    }],
-  }).then(() => {
-    scrollBox.onscroll = () => {
-      if (scrollBox.scrollTop == oldScrollTop) {
-        // Wait for scroll to change.
-        return;
-      }
-      scrollBox.onscroll = null;
-      isnot(scrollBox.scrollTop, oldScrollTop, "scroll location updated (moved to bottom again)");
-      testNext();
-    };
-  });
+  scrollBox.onscroll = () => {
+    if (scrollBox.scrollTop == oldScrollTop) {
+      // Wait for scroll to change.
+      return;
+    }
+    scrollBox.onscroll = null;
+    isnot(scrollBox.scrollTop, oldScrollTop, "scroll location updated (moved to bottom again)");
+    hud = testDriver = null;
+    finishTest();
+  };
 
   yield undefined;
-
-  hud = testDriver = null;
-  finishTest();
-  
-  yield undefined;
 }
 
 function test() {
   addTab("data:text/html;charset=utf-8,Web Console test for bug 613642: remember scroll location");
   browser.addEventListener("load", function tabLoad(aEvent) {
     browser.removeEventListener(aEvent.type, tabLoad, true);
     openConsole(null, function(aHud) {
       hud = aHud;
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -1,17 +1,17 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
 let WebConsoleUtils, TargetFactory, require;
 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
-let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 
 (() => {
   let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
   let utils = devtools.require("devtools/toolkit/webconsole/utils");
   TargetFactory = devtools.TargetFactory;
   WebConsoleUtils = utils.Utils;
   require = devtools.require;
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -9,17 +9,17 @@
 const {Cc, Ci, Cu} = require("chrome");
 
 let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
 
 loader.lazyServiceGetter(this, "clipboardHelper",
                          "@mozilla.org/widget/clipboardhelper;1",
                          "nsIClipboardHelper");
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
-loader.lazyGetter(this, "promise", () => require("sdk/core/promise"));
+loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
 loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter"));
 loader.lazyGetter(this, "AutocompletePopup",
                   () => require("devtools/shared/autocomplete-popup").AutocompletePopup);
 loader.lazyGetter(this, "ToolSidebar",
                   () => require("devtools/framework/sidebar").ToolSidebar);
 loader.lazyGetter(this, "NetworkPanel",
                   () => require("devtools/webconsole/network-panel").NetworkPanel);
 loader.lazyGetter(this, "ConsoleOutput",
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -82,17 +82,17 @@ nsMenuPopupFrame::nsMenuPopupFrame(nsIPr
   mPrefSize(-1, -1),
   mLastClientOffset(0, 0),
   mPopupType(ePopupTypePanel),
   mPopupState(ePopupClosed),
   mPopupAlignment(POPUPALIGNMENT_NONE),
   mPopupAnchor(POPUPALIGNMENT_NONE),
   mPosition(POPUPPOSITION_UNKNOWN),
   mConsumeRollupEvent(nsIPopupBoxObject::ROLLUP_DEFAULT),
-  mFlipBoth(false),
+  mFlip(FlipType_Default),
   mIsOpenChanged(false),
   mIsContextMenu(false),
   mAdjustOffsetForContextMenu(false),
   mGeneratedChildren(false),
   mMenuCanOverlapOSBar(false),
   mShouldAutoPosition(true),
   mInContentShell(true),
   mIsMenuLocked(false),
@@ -576,18 +576,23 @@ nsMenuPopupFrame::InitializePopup(nsICon
         position.Assign(aPosition);
       else
         mXPos = mYPos = 0;
     }
     else if (!aPosition.IsEmpty()) {
       position.Assign(aPosition);
     }
 
-    mFlipBoth = flip.EqualsLiteral("both");
-    mSlide = flip.EqualsLiteral("slide");
+    if (flip.EqualsLiteral("none")) {
+      mFlip = FlipType_None;
+    } else if (flip.EqualsLiteral("both")) {
+      mFlip = FlipType_Both;
+    } else if (flip.EqualsLiteral("slide")) {
+      mFlip = FlipType_Slide;
+    }
 
     position.CompressWhitespace();
     int32_t spaceIdx = position.FindChar(' ');
     // if there is a space in the position, assume it is the anchor and
     // alignment as two separate tokens.
     if (spaceIdx >= 0) {
       InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx), Substring(position, spaceIdx + 1));
     }
@@ -680,36 +685,34 @@ nsMenuPopupFrame::InitializePopupAtScree
 {
   EnsureWidget();
 
   mPopupState = ePopupShowing;
   mAnchorContent = nullptr;
   mTriggerContent = aTriggerContent;
   mScreenXPos = aXPos;
   mScreenYPos = aYPos;
-  mFlipBoth = false;
-  mSlide = false;
+  mFlip = FlipType_Default;
   mPopupAnchor = POPUPALIGNMENT_NONE;
   mPopupAlignment = POPUPALIGNMENT_NONE;
   mIsContextMenu = aIsContextMenu;
   mAdjustOffsetForContextMenu = aIsContextMenu;
 }
 
 void
 nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent,
                                                  nsAString& aAnchor,
                                                  nsAString& aAlign,
                                                  int32_t aXPos, int32_t aYPos)
 {
   EnsureWidget();
 
   mPopupState = ePopupShowing;
   mAdjustOffsetForContextMenu = false;
-  mFlipBoth = false;
-  mSlide = false;
+  mFlip = FlipType_Default;
 
   // this popup opening function is provided for backwards compatibility
   // only. It accepts either coordinates or an anchor and alignment value
   // but doesn't use both together.
   if (aXPos == -1 && aYPos == -1) {
     mAnchorContent = aAnchorContent;
     mScreenXPos = -1;
     mScreenYPos = -1;
@@ -976,17 +979,17 @@ nsMenuPopupFrame::AdjustPositionForAncho
       break;
     case POPUPALIGNMENT_TOPCENTER:
     case POPUPALIGNMENT_BOTTOMCENTER:
       aHFlip = FlipStyle_Inside;
       aVFlip = FlipStyle_Outside;
       break;
     default:
     {
-      FlipStyle anchorEdge = mFlipBoth ? FlipStyle_Inside : FlipStyle_None;
+      FlipStyle anchorEdge = mFlip == FlipType_Both ? FlipStyle_Inside : FlipStyle_None;
       aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge;
       if (((popupAnchor > 0) == (popupAlign > 0)) ||
           (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT))
         aVFlip = FlipStyle_Outside;
       else
         aVFlip = anchorEdge;
       break;
     }
@@ -1266,19 +1269,19 @@ nsMenuPopupFrame::SetPopupPosition(nsIFr
     // add the margins on the popup
     screenPoint.MoveBy(margin.left + offsetForContextMenu,
                        margin.top + offsetForContextMenu);
 
     // screen positioned popups can be flipped vertically but never horizontally
     vFlip = FlipStyle_Outside;
   }
 
-  // If a panel is being moved, don't constrain or flip it. But always do this for
+  // If a panel is being moved or has flip="none", don't constrain or flip it. But always do this for
   // content shells, so that the popup doesn't extend outside the containing frame.
-  if (mInContentShell || !aIsMove || mPopupType != ePopupTypePanel) {
+  if (mInContentShell || (mFlip != FlipType_None && (!aIsMove || mPopupType != ePopupTypePanel))) {
     nsRect screenRect = GetConstraintRect(anchorRect, rootScreenRect);
 
     // ensure that anchorRect is on screen
     if (!anchorRect.IntersectRect(anchorRect, screenRect)) {
       anchorRect.width = anchorRect.height = 0;
       // if the anchor isn't within the screen, move it to the edge of the screen.
       if (anchorRect.x < screenRect.x)
         anchorRect.x = screenRect.x;
@@ -1298,17 +1301,17 @@ nsMenuPopupFrame::SetPopupPosition(nsIFr
 
     // at this point the anchor (anchorRect) is within the available screen
     // area (screenRect) and the popup is known to be no larger than the screen.
 
     // We might want to "slide" an arrow if the panel is of the correct type -
     // but we can only slide on one axis - the other axis must be "flipped or
     // resized" as normal.
     bool slideHorizontal = false, slideVertical = false;
-    if (mSlide) {
+    if (mFlip == FlipType_Slide) {
       int8_t position = GetAlignmentPosition();
       slideHorizontal = position >= POPUPPOSITION_BEFORESTART &&
                         position <= POPUPPOSITION_AFTEREND;
       slideVertical = position >= POPUPPOSITION_STARTBEFORE &&
                       position <= POPUPPOSITION_ENDAFTER;
     }
 
     // Next, check if there is enough space to show the popup at full size when
--- a/layout/xul/nsMenuPopupFrame.h
+++ b/layout/xul/nsMenuPopupFrame.h
@@ -60,16 +60,24 @@ enum nsPopupState {
 // a submenu would work. The entire popup is flipped to the opposite side
 // of the anchor.
 enum FlipStyle {
   FlipStyle_None = 0,
   FlipStyle_Outside = 1,
   FlipStyle_Inside = 2
 };
 
+// Values for the flip attribute
+enum FlipType {
+  FlipType_Default = 0,
+  FlipType_None = 1,    // don't try to flip or translate to stay onscreen
+  FlipType_Both = 2,    // flip in both directions
+  FlipType_Slide = 3    // allow the arrow to "slide" instead of resizing
+};
+
 // values are selected so that the direction can be flipped just by
 // changing the sign
 #define POPUPALIGNMENT_NONE 0
 #define POPUPALIGNMENT_TOPLEFT 1
 #define POPUPALIGNMENT_TOPRIGHT -1
 #define POPUPALIGNMENT_BOTTOMLEFT 2
 #define POPUPALIGNMENT_BOTTOMRIGHT -2
 
@@ -454,18 +462,17 @@ protected:
 
   // popup alignment relative to the anchor node
   int8_t mPopupAlignment;
   int8_t mPopupAnchor;
   int8_t mPosition;
 
   // One of nsIPopupBoxObject::ROLLUP_DEFAULT/ROLLUP_CONSUME/ROLLUP_NO_CONSUME
   int8_t mConsumeRollupEvent;
-  bool mFlipBoth; // flip in both directions
-  bool mSlide; // allow the arrow to "slide" instead of resizing
+  FlipType mFlip; // Whether to flip
 
   bool mIsOpenChanged; // true if the open state changed since the last layout
   bool mIsContextMenu; // true for context menus
   // true if we need to offset the popup to ensure it's not under the mouse
   bool mAdjustOffsetForContextMenu;
   bool mGeneratedChildren; // true if the contents have been created
 
   bool mMenuCanOverlapOSBar;    // can we appear over the taskbar/menubar?
--- a/mobile/android/base/GeckoJavaSampler.java
+++ b/mobile/android/base/GeckoJavaSampler.java
@@ -2,22 +2,21 @@
  * 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;
 
 import android.os.SystemClock;
 import android.util.Log;
+import android.util.SparseArray;
 
 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 
 import java.lang.Thread;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Set;
 
 public class GeckoJavaSampler {
     private static final String LOGTAG = "JavaSampler";
     private static Thread sSamplingThread = null;
     private static SamplingThread sSamplingRunnable = null;
     private static Thread sMainThread = null;
     private static volatile boolean sLibsLoaded = false;
@@ -58,17 +57,17 @@ public class GeckoJavaSampler {
 
     private static class SamplingThread implements Runnable {
         private final int mInterval;
         private final int mSampleCount;
 
         private boolean mPauseSampler = false;
         private boolean mStopSampler = false;
 
-        private Map<Integer,Sample[]> mSamples = new HashMap<Integer,Sample[]>();
+        private SparseArray<Sample[]> mSamples = new SparseArray<Sample[]>();
         private int mSamplePos;
 
         public SamplingThread(final int aInterval, final int aSampleCount) {
             // If we sample faster then 10ms we get to many missed samples
             mInterval = Math.max(10, aInterval);
             mSampleCount = aSampleCount;
         }
 
--- a/mobile/android/base/PrefsHelper.java
+++ b/mobile/android/base/PrefsHelper.java
@@ -7,29 +7,28 @@ package org.mozilla.gecko;
 
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.util.Log;
+import android.util.SparseArray;
 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * Helper class to get/set gecko prefs.
  */
 public final class PrefsHelper {
     private static final String LOGTAG = "GeckoPrefsHelper";
 
     private static boolean sRegistered = false;
-    private static final Map<Integer, PrefHandler> sCallbacks = new HashMap<Integer, PrefHandler>();
+    private static final SparseArray<PrefHandler> sCallbacks = new SparseArray<PrefHandler>();
     private static int sUniqueRequestId = 1;
 
     public static int getPref(String prefName, PrefHandler callback) {
         return getPrefsInternal(new String[] { prefName }, callback);
     }
 
     public static int getPrefs(String[] prefNames, PrefHandler callback) {
         return getPrefsInternal(prefNames, callback);
@@ -68,17 +67,17 @@ public final class PrefsHelper {
             @Override public void handleMessage(String event, JSONObject message) {
                 try {
                     PrefHandler callback;
                     synchronized (PrefsHelper.class) {
                         try {
                             int requestId = message.getInt("requestId");
                             callback = sCallbacks.get(requestId);
                             if (callback != null && !callback.isObserver()) {
-                                sCallbacks.remove(requestId);
+                                sCallbacks.delete(requestId);
                             }
                         } catch (Exception e) {
                             callback = null;
                         }
                     }
                     if (callback == null) {
                         Log.d(LOGTAG, "Preferences:Data message had an unknown requestId; ignoring");
                         return;
@@ -139,17 +138,19 @@ public final class PrefsHelper {
     }
 
     public static void removeObserver(int requestId) {
         if (requestId < 0) {
             throw new IllegalArgumentException("Invalid request ID");
         }
 
         synchronized (PrefsHelper.class) {
-            PrefHandler callback = sCallbacks.remove(requestId);
+            PrefHandler callback = sCallbacks.get(requestId);
+            sCallbacks.delete(requestId);
+
             if (callback == null) {
                 Log.e(LOGTAG, "Unknown request ID " + requestId);
                 return;
             }
         }
 
         GeckoEvent event = GeckoEvent.createBroadcastEvent("Preferences:RemoveObserver",
                                                            Integer.toString(requestId));
--- a/mobile/android/base/favicons/Favicons.java
+++ b/mobile/android/base/favicons/Favicons.java
@@ -18,25 +18,24 @@ import org.mozilla.gecko.util.NonEvictin
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
 
 import java.io.File;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Map;
 import java.util.Set;
 
 public class Favicons {
     private static final String LOGTAG = "GeckoFavicons";
 
     // A magic URL representing the app's own favicon, used for about: pages.
     private static final String BUILT_IN_FAVICON_URL = "about:favicon";
 
@@ -57,17 +56,17 @@ public class Favicons {
     public static Bitmap sDefaultFavicon;
 
     // The density-adjusted default Favicon dimensions.
     public static int sDefaultFaviconSize;
 
     // The density-adjusted maximum Favicon dimensions.
     public static int sLargestFaviconSize;
 
-    private static final Map<Integer, LoadFaviconTask> sLoadTasks = Collections.synchronizedMap(new HashMap<Integer, LoadFaviconTask>());
+    private static final SparseArray<LoadFaviconTask> sLoadTasks = new SparseArray<LoadFaviconTask>();
 
     // Cache to hold mappings between page URLs and Favicon URLs. Used to avoid going to the DB when
     // doing so is not necessary.
     private static final NonEvictingLruCache<String, String> sPageURLMappings = new NonEvictingLruCache<String, String>(NUM_PAGE_URL_MAPPINGS_TO_STORE);
 
     public static String getFaviconURLForPageURLFromCache(String pageURL) {
         return sPageURLMappings.get(pageURL);
     }
@@ -212,17 +211,19 @@ public class Favicons {
                 // Victory - immediate response!
                 return dispatchResult(pageURL, targetURL, result, callback);
             }
         }
 
         // No joy using in-memory resources. Go to background thread and ask the database.
         LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageURL, targetURL, 0, callback, targetSize, true);
         int taskId = task.getId();
-        sLoadTasks.put(taskId, task);
+        synchronized(sLoadTasks) {
+            sLoadTasks.put(taskId, task);
+        }
         task.execute();
         return taskId;
     }
 
     public static int getSizedFaviconForPageFromLocal(final String pageURL, final OnFaviconLoadedListener callback) {
         return getSizedFaviconForPageFromLocal(pageURL, sDefaultFaviconSize, callback);
     }
 
@@ -277,17 +278,19 @@ public class Favicons {
         if (TextUtils.isEmpty(pageUrl)) {
             dispatchResult(null, null, null, listener);
             return NOT_LOADING;
         }
 
         LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageUrl, faviconUrl, flags, listener, targetSize, false);
 
         int taskId = task.getId();
-        sLoadTasks.put(taskId, task);
+        synchronized(sLoadTasks) {
+            sLoadTasks.put(taskId, task);
+        }
 
         task.execute();
 
         return taskId;
     }
 
     public static void putFaviconInMemCache(String pageUrl, Bitmap image) {
         sFaviconsCache.putSingleFavicon(pageUrl, image);
@@ -320,37 +323,35 @@ public class Favicons {
 
     public static boolean cancelFaviconLoad(int taskId) {
         if (taskId == NOT_LOADING) {
             return false;
         }
 
         boolean cancelled;
         synchronized (sLoadTasks) {
-            if (!sLoadTasks.containsKey(taskId))
+            if (sLoadTasks.indexOfKey(taskId) < 0)
                 return false;
 
             Log.d(LOGTAG, "Cancelling favicon load (" + taskId + ")");
 
             LoadFaviconTask task = sLoadTasks.get(taskId);
             cancelled = task.cancel(false);
         }
         return cancelled;
     }
 
     public static void close() {
         Log.d(LOGTAG, "Closing Favicons database");
 
         // Cancel any pending tasks
         synchronized (sLoadTasks) {
-            Set<Integer> taskIds = sLoadTasks.keySet();
-            Iterator<Integer> iter = taskIds.iterator();
-            while (iter.hasNext()) {
-                int taskId = iter.next();
-                cancelFaviconLoad(taskId);
+            final int count = sLoadTasks.size();
+            for (int i = 0; i < count; i++) {
+                cancelFaviconLoad(sLoadTasks.keyAt(i));
             }
             sLoadTasks.clear();
         }
 
         LoadFaviconTask.closeHTTPClient();
     }
 
     /**
@@ -444,17 +445,19 @@ public class Favicons {
                            null).toString();
         } catch (URISyntaxException e) {
             Log.e(LOGTAG, "URISyntaxException getting default favicon URL", e);
             return null;
         }
     }
 
     public static void removeLoadTask(int taskId) {
-        sLoadTasks.remove(taskId);
+        synchronized(sLoadTasks) {
+            sLoadTasks.delete(taskId);
+        }
     }
 
     /**
      * Method to wrap FaviconCache.isFailedFavicon for use by LoadFaviconTask.
      *
      * @param faviconURL Favicon URL to check for failure.
      */
     static boolean isFailedFavicon(String faviconURL) {
--- a/mobile/android/base/favicons/decoders/ICODecoder.java
+++ b/mobile/android/base/favicons/decoders/ICODecoder.java
@@ -3,18 +3,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.favicons.decoders;
 
 import android.graphics.Bitmap;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.gfx.BitmapUtils;
 
+import android.util.SparseArray;
+
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 /**
  * Utility class for determining the region of a provided array which contains the largest bitmap,
  * assuming the provided array is a valid ICO and the bitmap desired is square, and for pruning
  * unwanted entries from ICO files, if desired.
  *
@@ -147,17 +148,17 @@ public class ICODecoder implements Itera
 
         // We now iterate over the Icon Directory, decoding each entry as we go. We also need to
         // discard all entries except one >= the maximum interesting size.
 
         // Size of the smallest image larger than the limit encountered.
         int minimumMaximum = Integer.MAX_VALUE;
 
         // Used to track the best entry for each size. The entries we want to keep.
-        HashMap<Integer, IconDirectoryEntry> preferenceMap = new HashMap<Integer, IconDirectoryEntry>();
+        SparseArray<IconDirectoryEntry> preferenceArray = new SparseArray<IconDirectoryEntry>();
 
         for (int i = 0; i < numEncodedImages; i++, bufferIndex += ICO_ICONDIRENTRY_LENGTH_BYTES) {
             // Decode the Icon Directory Entry at this offset.
             IconDirectoryEntry newEntry = IconDirectoryEntry.createFromBuffer(mDecodand, mOffset, mLen, bufferIndex);
             newEntry.mIndex = i;
 
             if (newEntry.mIsErroneous) {
                 continue;
@@ -167,52 +168,49 @@ public class ICODecoder implements Itera
                 // If we already have a smaller image larger than the maximum size of interest, we
                 // don't care about the new one which is larger than the smallest image larger than
                 // the maximum size.
                 if (newEntry.mWidth >= minimumMaximum) {
                     continue;
                 }
 
                 // Remove the previous minimum-maximum.
-                if (preferenceMap.containsKey(minimumMaximum)) {
-                    preferenceMap.remove(minimumMaximum);
-                }
+                preferenceArray.delete(minimumMaximum);
 
                 minimumMaximum = newEntry.mWidth;
             }
 
-            IconDirectoryEntry oldEntry = preferenceMap.get(newEntry.mWidth);
+            IconDirectoryEntry oldEntry = preferenceArray.get(newEntry.mWidth);
             if (oldEntry == null) {
-                preferenceMap.put(newEntry.mWidth, newEntry);
+                preferenceArray.put(newEntry.mWidth, newEntry);
                 continue;
             }
 
             if (oldEntry.compareTo(newEntry) < 0) {
-                preferenceMap.put(newEntry.mWidth, newEntry);
+                preferenceArray.put(newEntry.mWidth, newEntry);
             }
         }
 
-        Collection<IconDirectoryEntry> entriesRetained = preferenceMap.values();
+        final int count = preferenceArray.size();
 
         // Abort if no entries are desired (Perhaps all are corrupt?)
-        if (entriesRetained.isEmpty()) {
+        if (count == 0) {
             return false;
         }
 
         // Allocate space for the icon directory entries in the decoded directory.
-        mIconDirectory = new IconDirectoryEntry[entriesRetained.size()];
+        mIconDirectory = new IconDirectoryEntry[count];
 
         // The size of the data in the buffer that we find useful.
         int retainedSpace = ICO_HEADER_LENGTH_BYTES;
 
-        int dirInd = 0;
-        for (IconDirectoryEntry e : entriesRetained) {
+        for (int i = 0; i < count; i++) {
+            IconDirectoryEntry e = preferenceArray.valueAt(i);
             retainedSpace += ICO_ICONDIRENTRY_LENGTH_BYTES + e.mPayloadSize;
-            mIconDirectory[dirInd] = e;
-            dirInd++;
+            mIconDirectory[i] = e;
         }
 
         mIsValid = true;
 
         // Set the number of images field in the buffer to reflect the number of retained entries.
         mDecodand[mOffset + 4] = (byte) mIconDirectory.length;
         mDecodand[mOffset + 5] = (byte) (mIconDirectory.length >>> 8);
 
--- a/mobile/android/base/home/PanelManager.java
+++ b/mobile/android/base/home/PanelManager.java
@@ -14,21 +14,20 @@ import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.preference.PreferenceManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 public class PanelManager implements GeckoEventListener {
     private static final String LOGTAG = "GeckoPanelManager";
 
     public class PanelInfo {
         public final String id;
         public final String title;
@@ -45,29 +44,29 @@ public class PanelManager implements Gec
 
     public interface RequestCallback {
         public void onComplete(List<PanelInfo> panelInfos);
     }
 
     private static AtomicInteger sRequestId = new AtomicInteger(0);
 
     // Stores set of pending request callbacks.
-    private static final Map<Integer, RequestCallback> sCallbacks = new HashMap<Integer, RequestCallback>();
+    private static final SparseArray<RequestCallback> sCallbacks = new SparseArray<RequestCallback>();
 
     /**
      * Asynchronously fetches list of available panels from Gecko.
      *
      * @param callback onComplete will be called on the UI thread.
      */
     public void requestAvailablePanels(RequestCallback callback) {
         final int requestId = sRequestId.getAndIncrement();
 
         synchronized(sCallbacks) {
             // If there are no pending callbacks, register the event listener.
-            if (sCallbacks.isEmpty()) {
+            if (sCallbacks.size() == 0) {
                 GeckoAppShell.getEventDispatcher().registerEventListener("HomePanels:Data", this);
             }
             sCallbacks.put(requestId, callback);
         }
 
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomePanels:Get", Integer.toString(requestId)));
     }
 
@@ -85,20 +84,21 @@ public class PanelManager implements Gec
                 final PanelInfo panelInfo = getPanelInfoFromJSON(panels.getJSONObject(i));
                 panelInfos.add(panelInfo);
             }
 
             final RequestCallback callback;
             final int requestId = message.getInt("requestId");
 
             synchronized(sCallbacks) {
-                callback = sCallbacks.remove(requestId);
+                callback = sCallbacks.get(requestId);
+                sCallbacks.delete(requestId);
 
                 // Unregister the event listener if there are no more pending callbacks.
-                if (sCallbacks.isEmpty()) {
+                if (sCallbacks.size() == 0) {
                     GeckoAppShell.getEventDispatcher().unregisterEventListener("HomePanels:Data", this);
                 }
             }
 
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     callback.onComplete(panelInfos);
--- a/mobile/android/base/mozglue/GeckoLoader.java.in
+++ b/mobile/android/base/mozglue/GeckoLoader.java.in
@@ -200,16 +200,23 @@ public final class GeckoLoader {
 
         // setup the libs cache
         String linkerCache = System.getenv("MOZ_LINKER_CACHE");
         if (linkerCache == null) {
             linkerCache = cacheFile.getPath();
             putenv("MOZ_LINKER_CACHE=" + linkerCache);
         }
 
+        // Disable on-demand decompression of the linker on devices where it
+        // is known to cause crashes.
+        if ("HTC".equals(android.os.Build.MANUFACTURER) &&
+            "HTC Vision".equals(android.os.Build.MODEL)) {
+            putenv("MOZ_LINKER_ONDEMAND=0");
+        }
+
 #ifdef MOZ_LINKER_EXTRACT
         putenv("MOZ_LINKER_EXTRACT=1");
         // Ensure that the cache dir is world-writable
         File cacheDir = new File(linkerCache);
         if (cacheDir.isDirectory()) {
             cacheDir.setWritable(true, false);
             cacheDir.setExecutable(true, false);
             cacheDir.setReadable(true, false);
--- a/mobile/android/base/tests/components/AboutHomeComponent.java
+++ b/mobile/android/base/tests/components/AboutHomeComponent.java
@@ -27,117 +27,117 @@ public class AboutHomeComponent extends 
     // The different types of panels that can be present on about:home
     public enum PanelType {
         HISTORY,
         TOP_SITES,
         BOOKMARKS,
         READING_LIST
     }
 
-    // TODO: Having a specific ordering of pages is prone to fail and thus temporary.
+    // TODO: Having a specific ordering of panels is prone to fail and thus temporary.
     // Hopefully the work in bug 940565 will alleviate the need for these enums.
-    // Explicit ordering of HomePager pages on a phone.
-    private enum PhonePage {
+    // Explicit ordering of HomePager panels on a phone.
+    private enum PhonePanel {
         HISTORY,
         TOP_SITES,
         BOOKMARKS,
         READING_LIST
     }
 
-    // Explicit ordering of HomePager pages on a tablet.
-    private enum TabletPage {
+    // Explicit ordering of HomePager panels on a tablet.
+    private enum TabletPanel {
         TOP_SITES,
         BOOKMARKS,
         READING_LIST,
         HISTORY
     }
 
-    // The percentage of the page to swipe between 0 and 1. This value was set through
+    // The percentage of the panel to swipe between 0 and 1. This value was set through
     // testing: 0.55f was tested on try and fails on armv6 devices.
     private static final float SWIPE_PERCENTAGE = 0.70f;
 
     public AboutHomeComponent(final UITestContext testContext) {
         super(testContext);
     }
 
     private ViewPager getHomePagerView() {
         return (ViewPager) mSolo.getView(R.id.home_pager);
     }
 
-    public AboutHomeComponent assertCurrentPage(final PanelType expectedPage) {
+    public AboutHomeComponent assertCurrentPanel(final PanelType expectedPanel) {
         assertVisible();
 
-        final int expectedPageIndex = getPageIndexForDevice(expectedPage.ordinal());
-        assertEquals("The current HomePager page is " + expectedPage,
-                     expectedPageIndex, getHomePagerView().getCurrentItem());
+        final int expectedPanelIndex = getPanelIndexForDevice(expectedPanel.ordinal());
+        assertEquals("The current HomePager panel is " + expectedPanel,
+                     expectedPanelIndex, getHomePagerView().getCurrentItem());
         return this;
     }
 
     public AboutHomeComponent assertNotVisible() {
         assertFalse("The HomePager is not visible",
                     getHomePagerView().getVisibility() == View.VISIBLE);
         return this;
     }
 
     public AboutHomeComponent assertVisible() {
         assertEquals("The HomePager is visible",
                      View.VISIBLE, getHomePagerView().getVisibility());
         return this;
     }
 
-    public AboutHomeComponent swipeToPageOnRight() {
-        mTestContext.dumpLog(LOGTAG, "Swiping to the page on the right.");
-        swipeToPage(Solo.RIGHT);
+    public AboutHomeComponent swipeToPanelOnRight() {
+        mTestContext.dumpLog(LOGTAG, "Swiping to the panel on the right.");
+        swipeToPanel(Solo.RIGHT);
         return this;
     }
 
-    public AboutHomeComponent swipeToPageOnLeft() {
-        mTestContext.dumpLog(LOGTAG, "Swiping to the page on the left.");
-        swipeToPage(Solo.LEFT);
+    public AboutHomeComponent swipeToPanelOnLeft() {
+        mTestContext.dumpLog(LOGTAG, "Swiping to the panel on the left.");
+        swipeToPanel(Solo.LEFT);
         return this;
     }
 
-    private void swipeToPage(final int pageDirection) {
-        assertTrue("Swiping in a vaild direction",
-                pageDirection == Solo.LEFT || pageDirection == Solo.RIGHT);
+    private void swipeToPanel(final int panelDirection) {
+        assertTrue("Swiping in a valid direction",
+                panelDirection == Solo.LEFT || panelDirection == Solo.RIGHT);
         assertVisible();
 
-        final int pageIndex = getHomePagerView().getCurrentItem();
+        final int panelIndex = getHomePagerView().getCurrentItem();
 
-        mSolo.scrollViewToSide(getHomePagerView(), pageDirection, SWIPE_PERCENTAGE);
+        mSolo.scrollViewToSide(getHomePagerView(), panelDirection, SWIPE_PERCENTAGE);
 
-        // The page on the left is a lower index and vice versa.
-        final int unboundedPageIndex = pageIndex + (pageDirection == Solo.LEFT ? -1 : 1);
-        final int pageCount = DeviceHelper.isTablet() ?
-                TabletPage.values().length : PhonePage.values().length;
-        final int maxPageIndex = pageCount - 1;
-        final int expectedPageIndex = Math.min(Math.max(0, unboundedPageIndex), maxPageIndex);
+        // The panel on the left is a lower index and vice versa.
+        final int unboundedPanelIndex = panelIndex + (panelDirection == Solo.LEFT ? -1 : 1);
+        final int panelCount = DeviceHelper.isTablet() ?
+                TabletPanel.values().length : PhonePanel.values().length;
+        final int maxPanelIndex = panelCount - 1;
+        final int expectedPanelIndex = Math.min(Math.max(0, unboundedPanelIndex), maxPanelIndex);
 
-        waitForPageIndex(expectedPageIndex);
+        waitForPanelIndex(expectedPanelIndex);
     }
 
-    private void waitForPageIndex(final int expectedIndex) {
-        final String pageName;
+    private void waitForPanelIndex(final int expectedIndex) {
+        final String panelName;
         if (DeviceHelper.isTablet()) {
-            pageName = TabletPage.values()[expectedIndex].name();
+            panelName = TabletPanel.values()[expectedIndex].name();
         } else {
-            pageName = PhonePage.values()[expectedIndex].name();
+            panelName = PhonePanel.values()[expectedIndex].name();
         }
 
-        WaitHelper.waitFor("HomePager " + pageName + " page", new Condition() {
+        WaitHelper.waitFor("HomePager " + panelName + " panel", new Condition() {
             @Override
             public boolean isSatisfied() {
                 return (getHomePagerView().getCurrentItem() == expectedIndex);
             }
         });
     }
 
     /**
-     * Gets the page index in the device specific Page enum for the given index in the
+     * Gets the panel index in the device specific Panel enum for the given index in the
      * PanelType enum.
      */
-    private int getPageIndexForDevice(final int pageIndex) {
-        final String pageName = PanelType.values()[pageIndex].name();
-        final Class devicePageEnum =
-                DeviceHelper.isTablet() ? TabletPage.class : PhonePage.class;
-        return Enum.valueOf(devicePageEnum, pageName).ordinal();
+    private int getPanelIndexForDevice(final int panelIndex) {
+        final String panelName = PanelType.values()[panelIndex].name();
+        final Class devicePanelEnum =
+                DeviceHelper.isTablet() ? TabletPanel.class : PhonePanel.class;
+        return Enum.valueOf(devicePanelEnum, panelName).ordinal();
     }
 }
--- a/mobile/android/base/tests/testAboutHomePageNavigation.java
+++ b/mobile/android/base/tests/testAboutHomePageNavigation.java
@@ -1,85 +1,85 @@
 package org.mozilla.gecko.tests;
 
 import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
 
 import org.mozilla.gecko.tests.components.AboutHomeComponent.PanelType;
 import org.mozilla.gecko.tests.helpers.*;
 
 /**
- * Tests functionality related to navigating between the various about:home pages.
+ * Tests functionality related to navigating between the various about:home panels.
  */
 public class testAboutHomePageNavigation extends UITest {
     // TODO: Define this test dynamically by creating dynamic representations of the Page
-    // enum for both phone and tablet, then swiping through the pages. This will also
-    // benefit having a HomePager with custom pages.
+    // enum for both phone and tablet, then swiping through the panels. This will also
+    // benefit having a HomePager with custom panels.
     public void testAboutHomePageNavigation() {
         GeckoHelper.blockForReady();
 
         mAboutHome.assertVisible()
-                  .assertCurrentPage(PanelType.TOP_SITES);
+                  .assertCurrentPanel(PanelType.TOP_SITES);
 
-        mAboutHome.swipeToPageOnRight();
-        mAboutHome.assertCurrentPage(PanelType.BOOKMARKS);
+        mAboutHome.swipeToPanelOnRight();
+        mAboutHome.assertCurrentPanel(PanelType.BOOKMARKS);
 
-        mAboutHome.swipeToPageOnRight();
-        mAboutHome.assertCurrentPage(PanelType.READING_LIST);
+        mAboutHome.swipeToPanelOnRight();
+        mAboutHome.assertCurrentPanel(PanelType.READING_LIST);
 
         // Ideally these helpers would just be their own tests. However, by keeping this within
         // one method, we're saving test setUp and tearDown resources.
         if (DeviceHelper.isTablet()) {
             helperTestTablet();
         } else {
             helperTestPhone();
         }
     }
 
     private void helperTestTablet() {
-        mAboutHome.swipeToPageOnRight();
-        mAboutHome.assertCurrentPage(PanelType.HISTORY);
+        mAboutHome.swipeToPanelOnRight();
+        mAboutHome.assertCurrentPanel(PanelType.HISTORY);
 
         // Edge case.
-        mAboutHome.swipeToPageOnRight();
-        mAboutHome.assertCurrentPage(PanelType.HISTORY);
+        mAboutHome.swipeToPanelOnRight();
+        mAboutHome.assertCurrentPanel(PanelType.HISTORY);
 
-        mAboutHome.swipeToPageOnLeft();
-        mAboutHome.assertCurrentPage(PanelType.READING_LIST);
+        mAboutHome.swipeToPanelOnLeft();
+        mAboutHome.assertCurrentPanel(PanelType.READING_LIST);
 
-        mAboutHome.swipeToPageOnLeft();
-        mAboutHome.assertCurrentPage(PanelType.BOOKMARKS);
+        mAboutHome.swipeToPanelOnLeft();
+        mAboutHome.assertCurrentPanel(PanelType.BOOKMARKS);
 
-        mAboutHome.swipeToPageOnLeft();
-        mAboutHome.assertCurrentPage(PanelType.TOP_SITES);
+        mAboutHome.swipeToPanelOnLeft();
+        mAboutHome.assertCurrentPanel(PanelType.TOP_SITES);
 
         // Edge case.
-        mAboutHome.swipeToPageOnLeft();
-        mAboutHome.assertCurrentPage(PanelType.TOP_SITES);
+        mAboutHome.swipeToPanelOnLeft();
+        mAboutHome.assertCurrentPanel(PanelType.TOP_SITES);
     }
 
     private void helperTestPhone() {
         // Edge case.
-        mAboutHome.swipeToPageOnRight();
-        mAboutHome.assertCurrentPage(PanelType.READING_LIST);
+        mAboutHome.swipeToPanelOnRight();
+        mAboutHome.assertCurrentPanel(PanelType.READING_LIST);
 
-        mAboutHome.swipeToPageOnLeft();
-        mAboutHome.assertCurrentPage(PanelType.BOOKMARKS);
+        mAboutHome.swipeToPanelOnLeft();
+        mAboutHome.assertCurrentPanel(PanelType.BOOKMARKS);
 
-        mAboutHome.swipeToPageOnLeft();
-        mAboutHome.assertCurrentPage(PanelType.TOP_SITES);
+        mAboutHome.swipeToPanelOnLeft();
+        mAboutHome.assertCurrentPanel(PanelType.TOP_SITES);
 
-        mAboutHome.swipeToPageOnLeft();
-        mAboutHome.assertCurrentPage(PanelType.HISTORY);
+        mAboutHome.swipeToPanelOnLeft();
+        mAboutHome.assertCurrentPanel(PanelType.HISTORY);
 
         // Edge case.
-        mAboutHome.swipeToPageOnLeft();
-        mAboutHome.assertCurrentPage(PanelType.HISTORY);
+        mAboutHome.swipeToPanelOnLeft();
+        mAboutHome.assertCurrentPanel(PanelType.HISTORY);
 
-        mAboutHome.swipeToPageOnRight();
-        mAboutHome.assertCurrentPage(PanelType.TOP_SITES);
+        mAboutHome.swipeToPanelOnRight();
+        mAboutHome.assertCurrentPanel(PanelType.TOP_SITES);
     }
 
     // TODO: bug 943706 - reimplement this old test code.
     /*
         //  Removed by Bug 896576 - [fig] Remove [getAllPagesList] from BaseTest
         //  ListView list = getAllPagesList("about:firefox");
 
         // Test normal sliding of the list left and right
--- a/mobile/android/base/tests/testAboutHomeVisibility.java
+++ b/mobile/android/base/tests/testAboutHomeVisibility.java
@@ -10,38 +10,38 @@ import org.mozilla.gecko.tests.helpers.*
  */
 public class testAboutHomeVisibility extends UITest {
     public void testAboutHomeVisibility() {
         GeckoHelper.blockForReady();
 
         // Check initial state on about:home.
         mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE);
         mAboutHome.assertVisible()
-                  .assertCurrentPage(PanelType.TOP_SITES);
+                  .assertCurrentPanel(PanelType.TOP_SITES);
 
         // Go to blank 01.
         NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
         mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
         mAboutHome.assertNotVisible();
 
         // Go to blank 02.
         NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
         mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
         mAboutHome.assertNotVisible();
 
         // Enter editing mode, where the about:home UI should be visible.
         mToolbar.enterEditingMode();
         mAboutHome.assertVisible()
-                  .assertCurrentPage(PanelType.TOP_SITES);
+                  .assertCurrentPanel(PanelType.TOP_SITES);
 
         // Dismiss editing mode, where the about:home UI should be gone.
         mToolbar.dismissEditingMode();
         mAboutHome.assertNotVisible();
 
         // Loading about:home should show about:home again.
         NavigationHelper.enterAndLoadUrl(StringHelper.ABOUT_HOME_URL);
         mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE);
         mAboutHome.assertVisible()
-                  .assertCurrentPage(PanelType.TOP_SITES);
+                  .assertCurrentPanel(PanelType.TOP_SITES);
 
         // TODO: Type in a url and assert the go button is visible.
     }
 }
--- a/mobile/android/base/util/ActivityResultHandlerMap.java
+++ b/mobile/android/base/util/ActivityResultHandlerMap.java
@@ -1,22 +1,24 @@
 /* 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.util;
 
-import java.util.HashMap;
-import java.util.Map;
+import android.util.SparseArray;
 
 public final class ActivityResultHandlerMap {
-    private Map<Integer, ActivityResultHandler> mMap = new HashMap<Integer, ActivityResultHandler>();
+    private SparseArray<ActivityResultHandler> mMap = new SparseArray<ActivityResultHandler>();
     private int mCounter = 0;
 
     public synchronized int put(ActivityResultHandler handler) {
         mMap.put(mCounter, handler);
         return mCounter++;
     }
 
     public synchronized ActivityResultHandler getAndRemove(int i) {
-        return mMap.remove(i);
+        ActivityResultHandler handler = mMap.get(i);
+        mMap.delete(i);
+
+        return handler;
     }
 }
--- a/toolkit/content/tests/chrome/window_panel.xul
+++ b/toolkit/content/tests/chrome/window_panel.xul
@@ -249,17 +249,64 @@ var tests = [
         synthesizeMouse(backdragspot, 5, 5, { type: "mousedown" });
         synthesizeMouse(backdragspot, 15, 20, { type: "mousemove" });
         synthesizeMouse(backdragspot, 15, 20, { type: "mouseup" });
 
         is(panel.getOuterScreenRect().left, 210, testname + "left");
         is(panel.getOuterScreenRect().top, 245, testname + "top");
       }
     }
-  }
+  },
+  {
+    // The panel should be allowed to appear and remain offscreen
+    testname: "normal panel with flip='none' off-screen",
+    attrs: { "flip": "none" },
+    test: function(panel) {
+      panel.openPopup(document.documentElement, "", -100 - mozInnerScreenX, -100 - mozInnerScreenY, false, false, null);
+    },
+    result: function(testname, panel) {
+      var panelrect = panel.getBoundingClientRect();
+      is(panelrect.left, -100 - mozInnerScreenX, testname + "left");
+      is(panelrect.top, -100 - mozInnerScreenY, testname + "top");
+      is(panelrect.width, 120, testname + "width");
+      is(panelrect.height, 40, testname + "height");
+
+      var screenRect = panel.getOuterScreenRect();
+      is(screenRect.left, -100, testname + " screen left");
+      is(screenRect.top, -100, testname + " screen top");
+      is(screenRect.width, 120, testname + " screen width");
+      is(screenRect.height, 40, testname + " screen height");
+    }
+  },
+  {
+    // The panel should be allowed to remain offscreen after moving and it should follow the anchor
+    testname: "normal panel with flip='none' moved off-screen",
+    attrs: { "flip": "none" },
+    test: function(panel) {
+      panel.openPopup(document.documentElement, "", -100 - mozInnerScreenX, -100 - mozInnerScreenY, false, false, null);
+      window.moveBy(-50, -50);
+    },
+    result: function(testname, panel) {
+      if (navigator.platform.indexOf("Linux") >= 0) {
+        // The window position doesn't get updated immediately on Linux.
+        return;
+      }
+      var panelrect = panel.getBoundingClientRect();
+      is(panelrect.left, -150 - mozInnerScreenX, testname + "left");
+      is(panelrect.top, -150 - mozInnerScreenY, testname + "top");
+      is(panelrect.width, 120, testname + "width");
+      is(panelrect.height, 40, testname + "height");
+
+      var screenRect = panel.getOuterScreenRect();
+      is(screenRect.left, -150, testname + " screen left");
+      is(screenRect.top, -150, testname + " screen top");
+      is(screenRect.width, 120, testname + " screen width");
+      is(screenRect.height, 40, testname + " screen height");
+    }
+  },
 ];
 
 window.opener.wrappedJSObject.SimpleTest.waitForFocus(test_panels, window);
 
 ]]>
 </script>
 
 </window>
--- a/webapprt/content/webapp.js
+++ b/webapprt/content/webapp.js
@@ -19,16 +19,37 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "nsICrashReporter");
 #endif
 
 let progressListener = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference]),
   onLocationChange: function onLocationChange(progress, request, location,
                                               flags) {
+
+    // Close tooltip (code adapted from /browser/base/content/browser.js)
+    let pageTooltip = document.getElementById("contentAreaTooltip");
+    let tooltipNode = pageTooltip.triggerNode;
+    if (tooltipNode) {
+      // Optimise for the common case
+      if (progress.isTopLevel) {
+        pageTooltip.hidePopup();
+      }
+      else {
+        for (let tooltipWindow = tooltipNode.ownerDocument.defaultView;
+             tooltipWindow != tooltipWindow.parent;
+             tooltipWindow = tooltipWindow.parent) {
+          if (tooltipWindow == progress.DOMWindow) {
+            pageTooltip.hidePopup();
+            break;
+          }
+        }
+      }
+    }
+
     // Set the title of the window to the name of the webapp, adding the origin
     // of the page being loaded if it's from a different origin than the app
     // (per security bug 741955, which specifies that other-origin pages loaded
     // in runtime windows must be identified in chrome).
     let title = WebappRT.config.app.manifest.name;
     let origin = location.prePath;
     if (origin != WebappRT.config.app.origin) {
       title = origin + " - " + title;
--- a/webapprt/content/webapp.xul
+++ b/webapprt/content/webapp.xul
@@ -150,18 +150,19 @@
                   label="&selectAllCmd.label;"
                   key="key_selectAll"
                   accesskey="&selectAllCmd.accesskey;"
                   command="cmd_selectAll"/>
       </menupopup>
     </menu>
   </menubar>
 
-  <browser type="content-primary" id="content" flex="1" context="contentAreaContextMenu" />
+  <browser type="content-primary" id="content" flex="1" context="contentAreaContextMenu" tooltip="contentAreaTooltip" />
 
   <popupset>
     <menupopup id="contentAreaContextMenu" pagemenu="start"
                onpopupshowing="return showContextMenu(event, this)"
                onpopuphiding="hideContextMenu(event, this)">
     </menupopup>
+    <tooltip id="contentAreaTooltip" page="true" />
   </popupset>
 
 </window>