Merge fx-team to m-c
authorWes Kocher <wkocher@mozilla.com>
Thu, 23 Jan 2014 18:31:45 -0800
changeset 164978 9d650c07b5476aec4e12f100e62d7f742548b9e3
parent 164964 624d042739e6ed0755fd9e953c65866e934af6a6 (current diff)
parent 164977 3ba925830469d57a7a08e1857f0f468596e4025f (diff)
child 165001 8bfc7447992df1e5866a191ecd776e694056fec0
child 165056 8be30d3b0b8037c66a1ff1ca0a70cf43cad229e0
child 165096 f9a4e354878bc5503def366a4c6275f4967798fd
child 165129 d7252c96a2350263c8df7560c4eceda0cd175549
push id26068
push userkwierso@gmail.com
push dateFri, 24 Jan 2014 02:31:56 +0000
treeherdermozilla-central@9d650c07b547 [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>