Merge the last PGO-green inbound changeset to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 24 Apr 2013 21:48:57 -0400
changeset 140750 690b5e0f6562fef80ae3bfadf33ad05e36fc0595
parent 140709 e7ea557a28eff3b10f7515640e8285e963fcdcb9 (current diff)
parent 140749 b00d40e2c2782f0eaa698f799a6ba11b1bb9c1cf (diff)
child 140758 681bbf7717c16100fd7e4f3891acb3215e303c81
child 140767 722048a0587b6dbef660dd9ae2431f0563aa191a
child 140865 f8c9cbf1bc105384a62a74870199c59fd422f069
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
first release with
nightly linux32
690b5e0f6562 / 23.0a1 / 20130425030845 / files
nightly linux64
690b5e0f6562 / 23.0a1 / 20130425030845 / files
nightly mac
690b5e0f6562 / 23.0a1 / 20130425030845 / files
nightly win32
690b5e0f6562 / 23.0a1 / 20130425030845 / files
nightly win64
690b5e0f6562 / 23.0a1 / 20130425030845 / 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 the last PGO-green inbound changeset to m-c.
browser/base/content/test/test_bug787619.html
browser/metro/modules/video.jsm
mobile/android/base/resources/drawable-land-hdpi-v14/tabs_carat.png
mobile/android/base/resources/drawable-land-mdpi-v14/tabs_carat.png
mobile/android/base/resources/drawable-land-xhdpi-v14/tabs_carat.png
mobile/android/base/resources/layout-land-v14/browser_toolbar.xml
mobile/android/base/resources/layout-land-v14/browser_toolbar_menu.xml
mobile/android/base/resources/layout-large-v11/browser_toolbar_menu.xml
mobile/android/base/resources/layout/browser_toolbar.xml
mobile/android/base/resources/layout/browser_toolbar_menu.xml
mobile/android/base/resources/values-land-v14/dimens.xml
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -45,16 +45,22 @@ this.AccessFu = {
     }
 
     try {
       this._activatePref = this.prefsBranch.getIntPref('activate');
     } catch (x) {
       this._activatePref = ACCESSFU_DISABLE;
     }
 
+    try {
+      this._notifyOutput = this.prefsBranch.getBoolPref('notify_output');
+    } catch (x) {
+      this._notifyOutput = false;
+    }
+
     Input.quickNavMode.updateModes(this.prefsBranch);
 
     this._enableOrDisable();
   },
 
   /**
    * Shut down chrome-layer accessibility functionality from the outside.
    */
@@ -183,26 +189,31 @@ this.AccessFu = {
         break;
       case 'AccessFu:Input':
         Input.setEditState(aMessage.json);
         break;
     }
   },
 
   _output: function _output(aPresentationData, aBrowser) {
+    for each (let presenter in aPresentationData) {
+      if (!presenter)
+        continue;
+
       try {
-        for each (let presenter in aPresentationData) {
-          if (!presenter)
-            continue;
-
-          Output[presenter.type](presenter.details, aBrowser);
-        }
+        Output[presenter.type](presenter.details, aBrowser);
       } catch (x) {
         Logger.logException(x);
       }
+    }
+
+    if (this._notifyOutput) {
+      Services.obs.notifyObservers(null, 'accessfu-output',
+                                   JSON.stringify(aPresentationData));
+    }
   },
 
   _loadFrameScript: function _loadFrameScript(aMessageManager) {
     if (this._processedMessageManagers.indexOf(aMessageManager) < 0) {
       aMessageManager.loadFrameScript(
         'chrome://global/content/accessibility/content-script.js', true);
       this._processedMessageManagers.push(aMessageManager);
     } else if (this._enabled) {
@@ -248,21 +259,29 @@ this.AccessFu = {
         this._focused = JSON.parse(aData);
         if (this._focused) {
           let mm = Utils.getMessageManager(Utils.CurrentBrowser);
           mm.sendAsyncMessage('AccessFu:VirtualCursor',
                               {action: 'whereIsIt', move: true});
         }
         break;
       case 'nsPref:changed':
-        if (aData == 'activate') {
-          this._activatePref = this.prefsBranch.getIntPref('activate');
-          this._enableOrDisable();
-        } else if (aData == 'quicknav_modes') {
-          Input.quickNavMode.updateModes(this.prefsBranch);
+        switch (aData) {
+          case 'activate':
+            this._activatePref = this.prefsBranch.getIntPref('activate');
+            this._enableOrDisable();
+            break;
+          case 'quicknav_modes':
+            Input.quickNavMode.updateModes(this.prefsBranch);
+            break;
+          case 'notify_output':
+            this._notifyOutput = this.prefsBranch.getBoolPref('notify_output');
+            break;
+          default:
+            break;
         }
         break;
       case 'remote-browser-frame-shown':
       {
         let mm = aSubject.QueryInterface(Ci.nsIFrameLoader).messageManager;
         this._handleMessageManager(mm);
         break;
       }
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -31,17 +31,16 @@ MOCHITEST_FILES = \
 		test_bug364677.html \
 		bug364677-data.xml \
 		bug364677-data.xml^headers^ \
 		test_offline_gzip.html \
 		gZipOfflineChild.html \
 		gZipOfflineChild.html^headers^ \
 		gZipOfflineChild.cacheManifest \
 		gZipOfflineChild.cacheManifest^headers^ \
-		test_bug787619.html \
 		$(NULL)
 
 # test_contextmenu.html is disabled on Linux due to bug 513558
 ifneq (gtk2,$(MOZ_WIDGET_TOOLKIT))
 MOCHITEST_FILES += \
 		audio.ogg \
 		test_contextmenu.html \
 		subtst_contextmenu.html \
@@ -270,16 +269,17 @@ endif
                  plugin_alternate_content.html \
                  plugin_both.html \
                  plugin_both2.html \
                  plugin_add_dynamically.html \
                  plugin_clickToPlayAllow.html \
                  plugin_clickToPlayDeny.html \
                  plugin_bug744745.html \
                  plugin_bug749455.html \
+                 plugin_bug787619.html \
                  plugin_bug797677.html \
                  plugin_bug818009.html \
                  plugin_bug820497.html \
                  plugin_hidden_to_visible.html \
                  plugin_two_types.html \
                  alltabslistener.html \
                  zoom_test.html \
                  dummy_page.html \
@@ -299,16 +299,17 @@ endif
                  browser_minimize.js \
                  browser_aboutSyncProgress.js \
                  browser_middleMouse_inherit.js \
                  redirect_bug623155.sjs \
                  browser_tabDrop.js \
                  browser_lastAccessedTab.js \
                  browser_bug734076.js \
                  browser_bug744745.js \
+                 browser_bug787619.js \
                  browser_bug812562.js \
                  browser_bug818009.js \
                  browser_bug818118.js \
                  browser_bug820497.js \
                  blockPluginVulnerableUpdatable.xml \
                  blockPluginVulnerableNoUpdate.xml \
                  blockNoPlugins.xml \
                  browser_utilityOverlay.js \
--- a/browser/base/content/test/browser_CTPScriptPlugin.js
+++ b/browser/base/content/test/browser_CTPScriptPlugin.js
@@ -8,20 +8,24 @@ var gPluginScriptedFired = false;
 var gPluginScriptedFiredCount = 0;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("plugins.click_to_play");
+    var plugin = getTestPlugin();
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
     gTestBrowser.removeEventListener("load", pageLoad, true);
     gTestBrowser.removeEventListener("PluginScripted", pluginScripted, true);
   });
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  var plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("load", pageLoad, true);
   gTestBrowser.addEventListener("PluginScripted", pluginScripted, true);
 
   // This list is iterated in reverse order, since it uses Array.pop to get the next test.
   gNextTestList = [
--- a/browser/base/content/test/browser_CTP_drag_drop.js
+++ b/browser/base/content/test/browser_CTP_drag_drop.js
@@ -5,18 +5,24 @@ let gHttpTestRoot = getRootDirectory(gTe
 
 let gNextTest = null;
 let gNewWindow = null;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function test() {
   waitForExplicitFinish();
-  registerCleanupFunction(function() { Services.prefs.clearUserPref("plugins.click_to_play"); });
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    let plugin = getTestPlugin();
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+  });
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  let plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("PluginBindingAttached", handleEvent, true, true);
   gNextTest = part1;
   gBrowser.selectedBrowser.contentDocument.location = gHttpTestRoot + "plugin_test.html";
 }
 
 function handleEvent() {
--- a/browser/base/content/test/browser_bug743421.js
+++ b/browser/base/content/test/browser_bug743421.js
@@ -3,18 +3,24 @@ const gTestRoot = rootDir;
 
 var gTestBrowser = null;
 var gNextTest = null;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function test() {
   waitForExplicitFinish();
-  registerCleanupFunction(function() { Services.prefs.clearUserPref("plugins.click_to_play"); });
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    var plugin = getTestPlugin();
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+  });
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  var plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
   var newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("load", pageLoad, true);
   prepareTest(test1a, gTestRoot + "plugin_add_dynamically.html");
 }
 
--- a/browser/base/content/test/browser_bug744745.js
+++ b/browser/base/content/test/browser_bug744745.js
@@ -6,22 +6,26 @@ var gTestBrowser = null;
 var gNumPluginBindingsAttached = 0;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("plugins.click_to_play");
+    var plugin = getTestPlugin();
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
     gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
     gBrowser.removeCurrentTab();
     window.focus();
   });
 
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  var plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
   var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
   gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug744745.html";
 }
 
rename from browser/base/content/test/test_bug787619.html
rename to browser/base/content/test/browser_bug787619.js
--- a/browser/base/content/test/test_bug787619.html
+++ b/browser/base/content/test/browser_bug787619.js
@@ -1,46 +1,47 @@
-<html>
-<head>
-  <title>Test for Bug 787619</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="application/javascript;version=1.7" src="head_plain.js"></script>
-  <script>
-    SpecialPowers.setBoolPref('plugins.click_to_play', true);
-  </script>
-</head>
-<body>
+const gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+let gTestBrowser = null;
+let gWrapperClickCount = 0;
 
-  <a id="wrapper">
-    <embed id="plugin" style="width: 200px; height: 200px" type="application/x-test">
-  </a>
+function test() {
+  waitForExplicitFinish();
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    let plugin = getTestPlugin();
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+  });
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  let plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
-  <script class="testbody" type="application/javascript;version=1.7">
-  SimpleTest.waitForExplicitFinish();
-
-  const Ci = SpecialPowers.Ci;
-  let wrapperClickCount = 0;
+  gBrowser.selectedTab = gBrowser.addTab();
+  gTestBrowser = gBrowser.selectedBrowser;
+  gTestBrowser.addEventListener("load", pageLoad, true);
+  gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug787619.html";
+}
 
-  function test1() {
-    let plugin = document.getElementById('plugin');
-    ok(plugin, 'got plugin element');
-    let objLC = SpecialPowers.wrap(plugin);
-    ok(!objLC.activated, 'plugin should not be activated');
+function pageLoad() {
+  executeSoon(part1);
+}
 
-    synthesizeMouseAtCenter(plugin, {});
-    waitForCondition(function() objLC.activated, test2, 
-                     'waited too long for plugin to activate');
-  }
+function part1() {
+  let wrapper = gTestBrowser.contentDocument.getElementById('wrapper');
+  wrapper.addEventListener('click', function() ++gWrapperClickCount, false);
+
+  let plugin = gTestBrowser.contentDocument.getElementById('plugin');
+  ok(plugin, 'got plugin element');
+  let objLC = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(!objLC.activated, 'plugin should not be activated');
 
-  function test2() {
-    is(wrapperClickCount, 0, 'wrapper should not have received any clicks');
-    SpecialPowers.clearUserPref('plugins.click_to_play');
-    SimpleTest.finish();
-  }
+  EventUtils.synthesizeMouseAtCenter(plugin, {}, gTestBrowser.contentWindow);
+  waitForCondition(function() objLC.activated, part2,
+                   'waited too long for plugin to activate');
+}
 
-  let wrapper = document.getElementById('wrapper');
-  wrapper.addEventListener('click', function() ++wrapperClickCount, false);
-  SimpleTest.waitForFocus(test1);
-  </script>
-</body>
-</html>
+function part2() {
+  is(gWrapperClickCount, 0, 'wrapper should not have received any clicks');
+  gTestBrowser.removeEventListener("load", pageLoad, true);
+  gBrowser.removeCurrentTab();
+  window.focus();
+  finish();
+}
--- a/browser/base/content/test/browser_bug812562.js
+++ b/browser/base/content/test/browser_bug812562.js
@@ -3,19 +3,23 @@ var gTestBrowser = null;
 var gNextTest = null;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("plugins.click_to_play");
+    var plugin = getTestPlugin();
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
   });
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  var plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
-  Services.prefs.setBoolPref("plugins.click_to_play", false);
   var newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("load", pageLoad, true);
   setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml",
   function() {
     prepareTest(testPart1, gHttpTestRoot + "plugin_test.html");
   });
--- a/browser/base/content/test/browser_bug818009.js
+++ b/browser/base/content/test/browser_bug818009.js
@@ -2,19 +2,23 @@ var gHttpTestRoot = getRootDirectory(gTe
 var gTestBrowser = null;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("plugins.click_to_play");
+    var plugin = getTestPlugin();
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
     gTestBrowser.removeEventListener("load", pageLoad, true);
   });
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  var plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("load", pageLoad, true);
   gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug818009.html";
 }
 
 function pageLoad() {
--- a/browser/base/content/test/browser_bug818118.js
+++ b/browser/base/content/test/browser_bug818118.js
@@ -2,20 +2,25 @@ var gHttpTestRoot = getRootDirectory(gTe
 var gTestBrowser = null;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("plugins.click_to_play");
+    var plugin = getTestPlugin();
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
     gTestBrowser.removeEventListener("load", pageLoad, true);
   });
 
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  var plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
   gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("load", pageLoad, true);
   gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_both.html";
 }
 
 function pageLoad(aEvent) {
   // The plugin events are async dispatched and can come after the load event
--- a/browser/base/content/test/browser_bug820497.js
+++ b/browser/base/content/test/browser_bug820497.js
@@ -6,22 +6,26 @@ var gTestBrowser = null;
 var gNumPluginBindingsAttached = 0;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("plugins.click_to_play");
+    getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+    getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
     gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
     gBrowser.removeCurrentTab();
     window.focus();
   });
 
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+  getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
   var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
   gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug820497.html";
 }
 
--- a/browser/base/content/test/browser_pageInfo_plugins.js
+++ b/browser/base/content/test/browser_pageInfo_plugins.js
@@ -36,23 +36,27 @@ function pageInfoObserve(win, topic, dat
   Services.obs.removeObserver(pageInfoObserve, "page-info-dialog-loaded");
   executeSoon(gNextTest);
 }
 
 function finishTest() {
   gPermissionManager.remove("127.0.0.1:8888", gTestPermissionString);
   gPermissionManager.remove("127.0.0.1:8888", gSecondTestPermissionString);
   Services.prefs.clearUserPref("plugins.click_to_play");
+  getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+  getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test() {
   waitForExplicitFinish();
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+  getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
   gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
   doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart1a);
 }
 
 // By default, everything should be click-to-play. So: no plugins should be
 // activated, and the radio buttons in Page Info should be "Always Ask"
 function testPart1a() {
--- a/browser/base/content/test/browser_pluginnotification.js
+++ b/browser/base/content/test/browser_pluginnotification.js
@@ -53,18 +53,24 @@ TabOpenListener.prototype = {
       executeSoon(this.closecallback);
       this.closecallback = null;
     }
   }
 };
 
 function test() {
   waitForExplicitFinish();
-  registerCleanupFunction(function() { Services.prefs.clearUserPref("plugins.click_to_play"); });
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+    getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+  });
   Services.prefs.setBoolPref("plugins.click_to_play", false);
+  var plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
 
   var newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("load", pageLoad, true);
   gTestBrowser.addEventListener("PluginBindingAttached", handleBindingAttached, true, true);
   prepareTest(test1, gTestRoot + "plugin_unknown.html");
 }
@@ -191,20 +197,21 @@ function test6() {
 function test7() {
   var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
   ok(notificationBox.getNotificationWithValue("missing-plugins"), "Test 7, Should have displayed the missing plugin notification");
   ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 7, Should not have displayed the blocked plugin notification");
   ok(gTestBrowser.missingPlugins, "Test 7, Should be a missing plugin list");
   ok(gTestBrowser.missingPlugins.has("application/x-unknown"), "Test 7, Should know about application/x-unknown");
   ok(gTestBrowser.missingPlugins.has("application/x-test"), "Test 7, Should know about application/x-test");
 
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
   var plugin = getTestPlugin();
-  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
   plugin.blocklisted = false;
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+  getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
   prepareTest(test8, gTestRoot + "plugin_test.html");
 }
 
 // Tests a page with a working plugin that is click-to-play
 function test8() {
   var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
   ok(!notificationBox.getNotificationWithValue("missing-plugins"), "Test 8, Should not have displayed the missing plugin notification");
@@ -508,29 +515,32 @@ function test13e() {
   ok(objLoadingContent.activated, "Test 13e, Second Test plugin (A) should be activated");
 
   var secondtestB = gTestBrowser.contentDocument.getElementById("secondtestB");
   var objLoadingContent = secondtestB.QueryInterface(Ci.nsIObjectLoadingContent);
   ok(objLoadingContent.activated, "Test 13e, Second Test plugin (B) should be activated");
 
   Services.perms.remove("127.0.0.1:8888", gPluginHost.getPermissionStringForType("application/x-test"));
   Services.prefs.setBoolPref("plugins.click_to_play", false);
+  var plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
   prepareTest(test14, gTestRoot + "plugin_test2.html");
 }
 
 // Tests that the plugin's "activated" property is true for working plugins with click-to-play disabled.
 function test14() {
   var plugin = gTestBrowser.contentDocument.getElementById("test1");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   ok(objLoadingContent.activated, "Test 14, Plugin should be activated");
 
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
   var plugin = getTestPlugin();
-  plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
   plugin.blocklisted = false;
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+  getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
   prepareTest(test15, gTestRoot + "plugin_alternate_content.html");
 }
 
 // Tests that the overlay is shown instead of alternate content when
 // plugins are click to play
 function test15() {
   var plugin = gTestBrowser.contentDocument.getElementById("test");
   var doc = gTestBrowser.contentDocument;
@@ -959,16 +969,18 @@ function test21e() {
     var rect = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox").getBoundingClientRect();
     ok(rect.width == 0, "Test 21e, Plugin with id=" + plugin.id + " overlay rect should have 0px width after being clicked");
     ok(rect.height == 0, "Test 21e, Plugin with id=" + plugin.id + " overlay rect should have 0px height after being clicked");
     var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
     ok(objLoadingContent.activated, "Test 21e, Plugin with id=" + plugin.id + " should be activated");
   }
 
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+  getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
   prepareTest(test22, gTestRoot + "plugin_test.html");
 }
 
 // Tests that a click-to-play plugin retains its activated state upon reloading
 function test22() {
   ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser), "Test 22, Should have a click-to-play notification");
 
   // Plugin should start as CTP
--- a/browser/base/content/test/browser_pluginplaypreview.js
+++ b/browser/base/content/test/browser_pluginplaypreview.js
@@ -134,16 +134,18 @@ Components.utils.import("resource://gre/
 
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     if (gPlayPreviewRegistration)
       gPlayPreviewRegistration.unregister();
     Services.prefs.clearUserPref("plugins.click_to_play");
+    var plugin = getTestPlugin();
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
   });
 
   var newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("load", pageLoad, true);
   gTestBrowser.addEventListener("PluginBindingAttached", handleBindingAttached, true, true);
 
@@ -248,16 +250,18 @@ function test3() {
   var plugin = gTestBrowser.contentDocument.getElementById("test");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   ok(objLoadingContent.activated, "Test 3, Plugin should be activated");
 
   unregisterPlayPreview();
 
   registerPlayPreview('application/x-test', 'about:');
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  var plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
   prepareTest(test4a, gTestRoot + "plugin_test.html", 1);
 }
 
 // Test a fallback to the click-to-play
 function test4a() {
   var doc = gTestBrowser.contentDocument;
   var plugin = doc.getElementById("test");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
--- a/browser/base/content/test/browser_pluginplaypreview2.js
+++ b/browser/base/content/test/browser_pluginplaypreview2.js
@@ -34,25 +34,29 @@ Components.utils.import("resource://gre/
 
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     if (gPlayPreviewRegistration)
       gPlayPreviewRegistration.unregister();
     Services.prefs.clearUserPref("plugins.click_to_play");
+    var plugin = getTestPlugin();
+    plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
   });
 
   var newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("load", pageLoad, true);
   gTestBrowser.addEventListener("PluginBindingAttached", handleBindingAttached, true, true);
 
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  var plugin = getTestPlugin();
+  plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
   registerPlayPreview('application/x-test', 'about:');
   prepareTest(test1a, gTestRoot + "plugin_test.html", 1);
 }
 
 function finishTest() {
   gTestBrowser.removeEventListener("load", pageLoad, true);
   gTestBrowser.removeEventListener("PluginBindingAttached", handleBindingAttached, true, true);
--- a/browser/base/content/test/browser_plugins_added_dynamically.js
+++ b/browser/base/content/test/browser_plugins_added_dynamically.js
@@ -5,18 +5,22 @@ let gNextTest = null;
 let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("plugins.click_to_play");
+    getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+    getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
   });
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+  getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
   let newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   gTestBrowser = gBrowser.selectedBrowser;
   gTestBrowser.addEventListener("load", pageLoad, true);
   prepareTest(testActivateAllPart1, gTestRoot + "plugin_add_dynamically.html");
 }
 
--- a/browser/base/content/test/head.js
+++ b/browser/base/content/test/head.js
@@ -92,23 +92,24 @@ function waitForCondition(condition, nex
     if (condition()) {
       moveOn();
     }
     tries++;
   }, 100);
   var moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
-function getTestPlugin() {
+function getTestPlugin(aName) {
+  var pluginName = aName || "Test Plug-in";
   var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   var tags = ph.getPluginTags();
 
   // Find the test plugin
   for (var i = 0; i < tags.length; i++) {
-    if (tags[i].name == "Test Plug-in")
+    if (tags[i].name == pluginName)
       return tags[i];
   }
   ok(false, "Unable to find plugin");
   return null;
 }
 
 function updateBlocklist(aCallback) {
   var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
copy from browser/base/content/test/test_bug787619.html
copy to browser/base/content/test/plugin_bug787619.html
--- a/browser/base/content/test/test_bug787619.html
+++ b/browser/base/content/test/plugin_bug787619.html
@@ -1,46 +1,9 @@
+<!DOCTYPE html>
 <html>
-<head>
-  <title>Test for Bug 787619</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="application/javascript;version=1.7" src="head_plain.js"></script>
-  <script>
-    SpecialPowers.setBoolPref('plugins.click_to_play', true);
-  </script>
-</head>
+<head><meta charset="utf-8"/></head>
 <body>
-
   <a id="wrapper">
     <embed id="plugin" style="width: 200px; height: 200px" type="application/x-test">
   </a>
-
-  <script class="testbody" type="application/javascript;version=1.7">
-  SimpleTest.waitForExplicitFinish();
-
-  const Ci = SpecialPowers.Ci;
-  let wrapperClickCount = 0;
-
-  function test1() {
-    let plugin = document.getElementById('plugin');
-    ok(plugin, 'got plugin element');
-    let objLC = SpecialPowers.wrap(plugin);
-    ok(!objLC.activated, 'plugin should not be activated');
-
-    synthesizeMouseAtCenter(plugin, {});
-    waitForCondition(function() objLC.activated, test2, 
-                     'waited too long for plugin to activate');
-  }
-
-  function test2() {
-    is(wrapperClickCount, 0, 'wrapper should not have received any clicks');
-    SpecialPowers.clearUserPref('plugins.click_to_play');
-    SimpleTest.finish();
-  }
-
-  let wrapper = document.getElementById('wrapper');
-  wrapper.addEventListener('click', function() ++wrapperClickCount, false);
-  SimpleTest.waitForFocus(test1);
-  </script>
 </body>
 </html>
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -924,16 +924,24 @@ function runTest(testNum) {
                           "context-viewbgimage",  false,
                           "context-selectall",    true,
                           "---",                  null,
                           "context-viewsource",   true,
                           "context-viewinfo",     true
                          ].concat(inspectItems));
         closeContextMenu();
         SpecialPowers.clearUserPref("plugins.click_to_play");
+        var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+                                 .getService(SpecialPowers.Ci.nsIPluginHost);
+        var tags = ph.getPluginTags();
+        for (var tag of tags) {
+          if (tag.name == "Test Plug-in") {
+            tag.enabledState = SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED;
+          }
+        }
 
         // finish test
         subwindow.close();
         SimpleTest.finish();
         return;
 
     /*
      * Other things that would be nice to test:
@@ -1032,16 +1040,24 @@ function waitForEvents(event)
 }
 
 const isOSXMtnLion = navigator.userAgent.indexOf("Mac OS X 10.8") != -1;
 
 if (isOSXMtnLion) {
   todo(false, "Mountain Lion doesn't like this test (bug 792304)");
 } else {
   SpecialPowers.setBoolPref("plugins.click_to_play", true);
+  var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+                           .getService(SpecialPowers.Ci.nsIPluginHost);
+  var tags = ph.getPluginTags();
+  for (var tag of tags) {
+    if (tag.name == "Test Plug-in") {
+      tag.enabledState = SpecialPowers.Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+    }
+  }
 
   var subwindow = window.open("./subtst_contextmenu.html", "contextmenu-subtext", "width=600,height=800");
   subwindow.addEventListener("MozAfterPaint", waitForEvents, false);
   subwindow.onload = waitForEvents;
 
   SimpleTest.waitForExplicitFinish();
 }
 </script>
--- a/browser/config/mozconfigs/linux32/debug
+++ b/browser/config/mozconfigs/linux32/debug
@@ -1,14 +1,14 @@
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
 ac_add_options --enable-signmar
 ENABLE_MARIONETTE=1
 
-. $topsrcdir/build/unix/mozconfig.linux
+. $topsrcdir/build/unix/mozconfig.linux32
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 #Use ccache
--- a/browser/config/mozconfigs/linux32/l10n-mozconfig
+++ b/browser/config/mozconfigs/linux32/l10n-mozconfig
@@ -1,12 +1,12 @@
 ac_add_options --with-l10n-base=../../l10n-central
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
-. $topsrcdir/build/unix/mozconfig.linux
+. $topsrcdir/build/unix/mozconfig.linux32
 
 export MOZILLA_OFFICIAL=1
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux32/nightly
+++ b/browser/config/mozconfigs/linux32/nightly
@@ -2,17 +2,17 @@ ac_add_options --enable-update-channel=$
 ac_add_options --enable-update-packaging
 ac_add_options --enable-codesighs
 ac_add_options --enable-signmar
 ac_add_options --enable-profiling
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
-. $topsrcdir/build/unix/mozconfig.linux
+. $topsrcdir/build/unix/mozconfig.linux32
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # This will overwrite the default of stripping everything and keep the symbol table.
 # This is useful for profiling and debugging and only increases the package size
 # by 2 MBs.
 STRIP_FLAGS="--strip-debug"
--- a/browser/config/mozconfigs/linux32/qt
+++ b/browser/config/mozconfigs/linux32/qt
@@ -1,12 +1,12 @@
 ac_add_options --enable-update-packaging
 ac_add_options --enable-codesighs
 
-. $topsrcdir/build/unix/mozconfig.linux
+. $topsrcdir/build/unix/mozconfig.linux32
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 # PGO
--- a/browser/config/mozconfigs/linux32/release
+++ b/browser/config/mozconfigs/linux32/release
@@ -1,13 +1,13 @@
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-official-branding
 
-. $topsrcdir/build/unix/mozconfig.linux
+. $topsrcdir/build/unix/mozconfig.linux32
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # PGO
 mk_add_options MOZ_PGO=1
 mk_add_options PROFILE_GEN_SCRIPT='EXTRA_TEST_ARGS=10 $(MAKE) -C $(MOZ_OBJDIR) pgo-profile-run'
 
--- a/browser/config/mozconfigs/linux32/rpm
+++ b/browser/config/mozconfigs/linux32/rpm
@@ -4,17 +4,17 @@ ac_add_options --enable-codesighs
 # Options for rpm versions of mozconfigs
 PREFIX=/usr
 LIBDIR=${PREFIX}/lib
 ac_add_options --with-app-name=mozilla-nightly
 ac_add_options --disable-updater
 ac_add_options --prefix=$PREFIX
 ac_add_options --libdir=$LIBDIR
 
-. $topsrcdir/build/unix/mozconfig.linux
+. $topsrcdir/build/unix/mozconfig.linux32
 
 # Avoid dependency on libstdc++ 4.5
 ac_add_options --enable-stdcxx-compat
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
deleted file mode 100644
--- a/browser/metro/modules/video.jsm
+++ /dev/null
@@ -1,9 +0,0 @@
-/* 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/. */
-
-this.EXPORTED_SYMBOLS = ["Video"];
-
-this.Video = {
-  fullScreenSourceElement: null
-};
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -2064,17 +2064,17 @@ window[tabsontop="false"] richlistitem[t
 #historySwipeAnimationContainer {
   background: url("chrome://browser/skin/linen-pattern.png") #B3B9C1;
 }
 
 /* ----- SIDEBAR ELEMENTS ----- */
 
 #sidebar,
 sidebarheader {
-  background-color: #d4dde5;
+  background-color: #e2e7ed;
 }
 
 #sidebar:-moz-window-inactive,
 sidebarheader:-moz-window-inactive {
   background-color: #e8e8e8;
 }
 
 sidebarheader {
@@ -2084,17 +2084,17 @@ sidebarheader {
 
 #sidebar-box {
   -moz-appearance: dialog;
   -moz-appearance: none;
 }
 
 .sidebar-splitter {
   -moz-border-start: none;
-  -moz-border-end: 1px solid #404040;
+  -moz-border-end: 1px solid #bdbdbd;
   min-width: 1px;
   width: 3px;
   background-image: none !important;
   background-color: transparent;
   -moz-margin-start: -3px;
   position: relative;
 }
 
--- a/browser/themes/osx/places/organizer.css
+++ b/browser/themes/osx/places/organizer.css
@@ -70,17 +70,17 @@
 }
 
 #placesView {
   border-top: none !important;
 }
 
 #placesView > splitter {
   -moz-border-start: none !important;
-  -moz-border-end: 1px solid #404040;
+  -moz-border-end: 1px solid #bdbdbd;
   min-width: 1px;
   width: 1px;
   background-image: none !important;       
 }
 
 #placesToolbar > toolbarbutton {
   list-style-image: url("chrome://browser/skin/places/toolbar.png");
   margin: 4px 4px 5px;
@@ -177,17 +177,17 @@
 /* Root View */
 #placesView {
   border-top: 1px solid ThreeDDarkShadow;
   -moz-user-focus: ignore;
 }
 
 /* Place List, Place Content */
 #placesList {
-  background-color: #d2d8e2;
+  background-color: #e2e7ed;
   width: 160px;
 }
 
 #placesList:-moz-window-inactive {
   background-color: #e8e8e8;
 }
 
 /* Info box */
new file mode 100644
--- /dev/null
+++ b/build/unix/mozconfig.linux32
@@ -0,0 +1,9 @@
+. "$topsrcdir/build/unix/mozconfig.linux"
+
+if test `uname -m` = "x86_64"; then
+  CC="$CC -m32"
+  CXX="$CXX -m32"
+  ac_add_options --target=i686-pc-linux
+  ac_add_options --x-libraries=/usr/lib
+  export PKG_CONFIG_LIBDIR=/usr/lib/pkgconfig:/usr/share/pkgconfig
+fi
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -3142,17 +3142,19 @@ nsCxPusher::Push(JSContext *cx)
   DoPush(cx);
 }
 
 void
 nsCxPusher::DoPush(JSContext* cx)
 {
   nsIThreadJSContextStack* stack = nsContentUtils::ThreadJSContextStack();
   if (!stack) {
-    return;
+    // If someone tries to push a cx when we don't have the relevant state,
+    // it's probably safest to just crash.
+    MOZ_CRASH();
   }
 
   if (cx && IsContextOnStack(stack, cx)) {
     // If the context is on the stack, that means that a script
     // is running at the moment in the context.
     mScriptIsRunning = true;
   }
 
@@ -3173,17 +3175,18 @@ nsCxPusher::PushNull()
 {
   DoPush(nullptr);
 }
 
 void
 nsCxPusher::Pop()
 {
   nsIThreadJSContextStack* stack = nsContentUtils::ThreadJSContextStack();
-  if (!mPushedSomething || !stack) {
+  MOZ_ASSERT(stack);
+  if (!mPushedSomething) {
     mScx = nullptr;
     mPushedSomething = false;
 
     NS_ASSERTION(!mScriptIsRunning, "Huh, this can't be happening, "
                  "mScriptIsRunning can't be set here!");
 
     return;
   }
--- a/content/base/src/nsDOMBlobBuilder.cpp
+++ b/content/base/src/nsDOMBlobBuilder.cpp
@@ -184,24 +184,24 @@ nsDOMMultipartFile::InitBlob(JSContext* 
                              uint32_t aArgc,
                              JS::Value* aArgv,
                              UnwrapFuncPtr aUnwrapFunc)
 {
   bool nativeEOL = false;
   if (aArgc > 1) {
     if (NS_IsMainThread()) {
       BlobPropertyBag d;
-      if (!d.Init(aCx, JS::NullPtr(), aArgv[1])) {
+      if (!d.Init(aCx, aArgv[1])) {
         return NS_ERROR_TYPE_ERR;
       }
       mContentType = d.mType;
       nativeEOL = d.mEndings == EndingTypesValues::Native;
     } else {
       BlobPropertyBagWorkers d;
-      if (!d.Init(aCx, JS::NullPtr(), aArgv[1])) {
+      if (!d.Init(aCx, aArgv[1])) {
         return NS_ERROR_TYPE_ERR;
       }
       mContentType = d.mType;
       nativeEOL = d.mEndings == EndingTypesValues::Native;
     }
   }
 
   if (aArgc > 0) {
@@ -278,17 +278,17 @@ nsDOMMultipartFile::InitFile(JSContext* 
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR; // Real short trip
   }
 
   NS_ENSURE_TRUE(aArgc > 0, NS_ERROR_UNEXPECTED);
 
   if (aArgc > 1) {
     FilePropertyBag d;
-    if (!d.Init(aCx, JS::NullPtr(), aArgv[1])) {
+    if (!d.Init(aCx, aArgv[1])) {
       return NS_ERROR_TYPE_ERR;
     }
     mName = d.mName;
     mContentType = d.mType;
   }
 
 
   // We expect to get a path to represent as a File object or
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -4982,17 +4982,17 @@ nsDocument::Register(const nsAString& aN
                      JSContext* aCx, uint8_t aOptionalArgc,
                      jsval* aConstructor /* out param */)
 {
   ElementRegistrationOptions options;
   if (aOptionalArgc > 0) {
     JSAutoCompartment ac(aCx, GetWrapper());
     NS_ENSURE_TRUE(JS_WrapValue(aCx, const_cast<JS::Value*>(&aOptions)),
                    NS_ERROR_UNEXPECTED);
-    NS_ENSURE_TRUE(options.Init(aCx, JS::NullPtr(), aOptions),
+    NS_ENSURE_TRUE(options.Init(aCx, aOptions),
                    NS_ERROR_UNEXPECTED);
   }
 
   ErrorResult rv;
   JSObject* object = Register(aCx, aName, options, rv);
   if (rv.Failed()) {
     return rv.ErrorCode();
   }
--- a/content/base/src/nsINode.cpp
+++ b/content/base/src/nsINode.cpp
@@ -1391,17 +1391,17 @@ nsINode::doInsertChildAt(nsIContent* aKi
   }
 
   return NS_OK;
 }
 
 void
 nsINode::Remove()
 {
-  nsINode* parent = GetParentNode();
+  nsCOMPtr<nsINode> parent = GetParentNode();
   if (!parent) {
     return;
   }
   int32_t index = parent->IndexOf(this);
   if (index < 0) {
     NS_WARNING("Ignoring call to nsINode::Remove on anonymous child.");
     return;
   }
@@ -2097,30 +2097,21 @@ nsINode::SizeOfExcludingThis(nsMallocSiz
     }                                                                        \
   }                                                                          \
   NS_IMETHODIMP nsINode::GetOn##name_(JSContext *cx, JS::Value *vp) {        \
     EventHandlerNonNull* h = GetOn##name_();                                 \
     vp->setObjectOrNull(h ? h->Callable() : nullptr);                        \
     return NS_OK;                                                            \
   }                                                                          \
   NS_IMETHODIMP nsINode::SetOn##name_(JSContext *cx, const JS::Value &v) {   \
-    JSObject *obj = GetWrapper();                                            \
-    if (!obj) {                                                              \
-      /* Just silently do nothing */                                         \
-      return NS_OK;                                                          \
-    }                                                                        \
     nsRefPtr<EventHandlerNonNull> handler;                                   \
     JSObject *callable;                                                      \
     if (v.isObject() &&                                                      \
         JS_ObjectIsCallable(cx, callable = &v.toObject())) {                 \
-      bool ok;                                                               \
-      handler = new EventHandlerNonNull(cx, obj, callable, &ok);             \
-      if (!ok) {                                                             \
-        return NS_ERROR_OUT_OF_MEMORY;                                       \
-      }                                                                      \
+      handler = new EventHandlerNonNull(callable);                           \
     }                                                                        \
     ErrorResult rv;                                                          \
     SetOn##name_(handler, rv);                                               \
     return rv.ErrorCode();                                                   \
   }
 #define TOUCH_EVENT EVENT
 #define DOCUMENT_ONLY_EVENT EVENT
 #include "nsEventNameList.h"
--- a/content/base/src/nsXMLHttpRequest.h
+++ b/content/base/src/nsXMLHttpRequest.h
@@ -164,17 +164,17 @@ public:
   static already_AddRefed<nsXMLHttpRequest>
   Constructor(const mozilla::dom::GlobalObject& aGlobal,
               JSContext* aCx,
               const nsAString& ignored,
               ErrorResult& aRv)
   {
     // Pretend like someone passed null, so we can pick up the default values
     mozilla::dom::MozXMLHttpRequestParameters params;
-    if (!params.Init(aCx, JS::NullPtr(), JS::NullValue())) {
+    if (!params.Init(aCx, JS::NullValue())) {
       aRv.Throw(NS_ERROR_UNEXPECTED);
       return nullptr;
     }
 
     return Constructor(aGlobal, aCx, params, aRv);
   }
 
   void Construct(nsIPrincipal* aPrincipal,
--- a/content/base/test/file_mixed_content_frameNavigation_secure_grandchild.html
+++ b/content/base/test/file_mixed_content_frameNavigation_secure_grandchild.html
@@ -15,20 +15,21 @@ https://bugzilla.mozilla.org/show_bug.cg
   // For tests that require setTimeout, set the maximum polling time to 50 x 100ms = 5 seconds.
   var MAX_COUNT = 50;
   var TIMEOUT_INTERVAL = 100;
   var counter = 0;
 
   var child = document.getElementById("child");
   function navigationStatus(child)
   {
+    var loc;
     // When the page is navigating, it goes through about:blank and we will get a permission denied for loc.
     // Catch that specific exception and return
     try {
-      var loc = child.contentDocument.location;
+      loc = document.getElementById("child").contentDocument.location;
     } catch(e) {
       if (e.message && e.message.indexOf("Permission denied to access property") == -1) {
         // We received an exception we didn't expect.
         throw e;
       }
       counter++;
       return;
     }
@@ -37,16 +38,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     }
     else {
       if(counter < MAX_COUNT) {
         counter++;
         setTimeout(navigationStatus, TIMEOUT_INTERVAL, child);
       }
       else {
         // After we have called setTimeout the maximum number of times, assume navigating the iframe is blocked
+        dump("\nThe current location of the grandchild iframe is: "+loc+".\n");
         dump("\nWe have past the maximum timeout.  Navigating a grandchild iframe from an https location to an http location on a secure page failed.  We are about to post message to the top level page\n");
         parent.parent.postMessage({"test": "securePage_navigate_grandchild", "msg": "navigating to insecure grandchild iframe blocked on secure page"}, "http://mochi.test:8888");
         dump("\nAttempted postMessage\n");
       }
     }
   }
 
   setTimeout(navigationStatus, TIMEOUT_INTERVAL, child);
--- a/content/base/test/test_mixed_content_blocker_frameNavigation.html
+++ b/content/base/test/test_mixed_content_blocker_frameNavigation.html
@@ -9,17 +9,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Tests for Bug 840388</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script>
   var counter = 0;
   var origBlockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content");
 
-  SpecialPowers.setBoolPref("security.mixed_content.block_active_content", false);
+  SpecialPowers.setBoolPref("security.mixed_content.block_active_content", true);
   var blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content");
 
 
   var testsToRunInsecure = {
     insecurePage_navigate_child: false,
     insecurePage_navigate_grandchild: false,
   };
 
@@ -56,17 +56,17 @@ https://bugzilla.mozilla.org/show_bug.cg
        for (var prop in testsToRunSecure) {
          testsToRunSecure[prop] = false;
        }
        for (var prop in testsToRunInsecure) {
          testsToRunInsecure[prop] = false;
        }
       //call to change the preferences
       counter++;
-      SpecialPowers.setBoolPref("security.mixed_content.block_active_content", true);
+      SpecialPowers.setBoolPref("security.mixed_content.block_active_content", false);
       blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content");
       log("blockActive set to "+blockActive+".");
       secureTestsStarted = false;
       document.getElementById('framediv').innerHTML = '<iframe src="http://example.com/tests/content/base/test/file_mixed_content_frameNavigation.html" id="testing_frame"></iframe>';
     }
     else {
       //set the prefs back to what they were set to originally
       SpecialPowers.setBoolPref("security.mixed_content.block_active_content", origBlockActive);
--- a/content/events/src/nsDOMEventTargetHelper.cpp
+++ b/content/events/src/nsDOMEventTargetHelper.cpp
@@ -278,30 +278,21 @@ nsDOMEventTargetHelper::DispatchTrustedE
   return DispatchEvent(event, &dummy);
 }
 
 nsresult
 nsDOMEventTargetHelper::SetEventHandler(nsIAtom* aType,
                                         JSContext* aCx,
                                         const JS::Value& aValue)
 {
-  JSObject* obj = GetWrapper();
-  if (!obj) {
-    return NS_OK;
-  }
-
   nsRefPtr<EventHandlerNonNull> handler;
   JSObject* callable;
   if (aValue.isObject() &&
       JS_ObjectIsCallable(aCx, callable = &aValue.toObject())) {
-    bool ok;
-    handler = new EventHandlerNonNull(aCx, obj, callable, &ok);
-    if (!ok) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
+    handler = new EventHandlerNonNull(callable);
   }
   ErrorResult rv;
   SetEventHandler(aType, handler, rv);
   return rv.ErrorCode();
 }
 
 void
 nsDOMEventTargetHelper::GetEventHandler(nsIAtom* aType,
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -869,47 +869,26 @@ nsEventListenerManager::CompileEventHand
   }
 
   if (handler) {
     // Bind it
     JS::Rooted<JSObject*> boundHandler(cx);
     context->BindCompiledEventHandler(mTarget, listener->GetEventScope(),
                                       handler, &boundHandler);
     if (listener->EventName() == nsGkAtoms::onerror && win) {
-      bool ok;
-      JSAutoRequest ar(cx);
       nsRefPtr<OnErrorEventHandlerNonNull> handlerCallback =
-        new OnErrorEventHandlerNonNull(cx, listener->GetEventScope(),
-                                       boundHandler, &ok);
-      if (!ok) {
-        // JS_WrapObject failed, which means OOM allocating the JSObject.
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
+        new OnErrorEventHandlerNonNull(boundHandler);
       listener->SetHandler(handlerCallback);
     } else if (listener->EventName() == nsGkAtoms::onbeforeunload && win) {
-      bool ok;
-      JSAutoRequest ar(cx);
       nsRefPtr<BeforeUnloadEventHandlerNonNull> handlerCallback =
-        new BeforeUnloadEventHandlerNonNull(cx, listener->GetEventScope(),
-                                            boundHandler, &ok);
-      if (!ok) {
-        // JS_WrapObject failed, which means OOM allocating the JSObject.
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
+        new BeforeUnloadEventHandlerNonNull(boundHandler);
       listener->SetHandler(handlerCallback);
     } else {
-      bool ok;
-      JSAutoRequest ar(cx);
       nsRefPtr<EventHandlerNonNull> handlerCallback =
-        new EventHandlerNonNull(cx, listener->GetEventScope(),
-                                boundHandler, &ok);
-      if (!ok) {
-        // JS_WrapObject failed, which means OOM allocating the JSObject.
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
+        new EventHandlerNonNull(boundHandler);
       listener->SetHandler(handlerCallback);
     }
   }
 
   return result;
 }
 
 nsresult
--- a/content/html/content/src/HTMLBodyElement.cpp
+++ b/content/html/content/src/HTMLBodyElement.cpp
@@ -501,30 +501,21 @@ HTMLBodyElement::IsEventAttributeName(ns
   {                                                                            \
     getter_type_ h = forwardto_::GetOn##name_();                               \
     vp->setObjectOrNull(h ? h->Callable() : nullptr);                          \
     return NS_OK;                                                              \
   }                                                                            \
   NS_IMETHODIMP                                                                \
   HTMLBodyElement::SetOn##name_(JSContext *cx, const JS::Value &v)             \
   {                                                                            \
-    JSObject *obj = GetWrapper();                                              \
-    if (!obj) {                                                                \
-      /* Just silently do nothing */                                           \
-      return NS_OK;                                                            \
-    }                                                                          \
     nsRefPtr<type_> handler;                                                   \
     JSObject *callable;                                                        \
     if (v.isObject() &&                                                        \
         JS_ObjectIsCallable(cx, callable = &v.toObject())) {                   \
-      bool ok;                                                                 \
-      handler = new type_(cx, obj, callable, &ok);                             \
-      if (!ok) {                                                               \
-        return NS_ERROR_OUT_OF_MEMORY;                                         \
-      }                                                                        \
+      handler = new type_(callable);                                           \
     }                                                                          \
     ErrorResult rv;                                                            \
     forwardto_::SetOn##name_(handler, rv);                                     \
     return rv.ErrorCode();                                                     \
   }
 #define FORWARDED_EVENT(name_, id_, type_, struct_)                            \
   FORWARDED_EVENT_HELPER(name_, nsGenericHTMLElement, EventHandlerNonNull,     \
                          EventHandlerNonNull*)
--- a/content/html/content/src/HTMLFrameSetElement.cpp
+++ b/content/html/content/src/HTMLFrameSetElement.cpp
@@ -366,30 +366,21 @@ HTMLFrameSetElement::IsEventAttributeNam
   {                                                                            \
     getter_type_ h = forwardto_::GetOn##name_();                               \
     vp->setObjectOrNull(h ? h->Callable() : nullptr);                          \
     return NS_OK;                                                              \
   }                                                                            \
   NS_IMETHODIMP                                                                \
   HTMLFrameSetElement::SetOn##name_(JSContext *cx, const JS::Value &v)         \
   {                                                                            \
-    JSObject *obj = GetWrapper();                                              \
-    if (!obj) {                                                                \
-      /* Just silently do nothing */                                           \
-      return NS_OK;                                                            \
-    }                                                                          \
     nsRefPtr<type_> handler;                                                   \
     JSObject *callable;                                                        \
     if (v.isObject() &&                                                        \
         JS_ObjectIsCallable(cx, callable = &v.toObject())) {                   \
-      bool ok;                                                                 \
-      handler = new type_(cx, obj, callable, &ok);                             \
-      if (!ok) {                                                               \
-        return NS_ERROR_OUT_OF_MEMORY;                                         \
-      }                                                                        \
+      handler = new type_(callable);                                           \
     }                                                                          \
     ErrorResult rv;                                                            \
     forwardto_::SetOn##name_(handler, rv);                                     \
     return rv.ErrorCode();                                                     \
   }
 #define FORWARDED_EVENT(name_, id_, type_, struct_)                            \
   FORWARDED_EVENT_HELPER(name_, nsGenericHTMLElement, EventHandlerNonNull,     \
                          EventHandlerNonNull*)
--- a/content/media/AudioNodeEngine.h
+++ b/content/media/AudioNodeEngine.h
@@ -3,16 +3,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/. */
 #ifndef MOZILLA_AUDIONODEENGINE_H_
 #define MOZILLA_AUDIONODEENGINE_H_
 
 #include "AudioSegment.h"
 #include "mozilla/dom/AudioParam.h"
+#include "mozilla/Mutex.h"
 
 namespace mozilla {
 
 namespace dom {
 class AudioNode;
 struct ThreeDPoint;
 }
 
@@ -146,16 +147,17 @@ AudioBlockPanStereoToStereo(const float 
 /**
  * All methods of this class and its subclasses are called on the
  * MediaStreamGraph thread.
  */
 class AudioNodeEngine {
 public:
   explicit AudioNodeEngine(dom::AudioNode* aNode)
     : mNode(aNode)
+    , mNodeMutex("AudioNodeEngine::mNodeMutex")
   {
     MOZ_ASSERT(mNode, "The engine is constructed with a null node");
     MOZ_COUNT_CTOR(AudioNodeEngine);
   }
   virtual ~AudioNodeEngine()
   {
     MOZ_ASSERT(!mNode, "The node reference must be already cleared");
     MOZ_COUNT_DTOR(AudioNodeEngine);
@@ -201,22 +203,32 @@ public:
   virtual void ProduceAudioBlock(AudioNodeStream* aStream,
                                  const AudioChunk& aInput,
                                  AudioChunk* aOutput,
                                  bool* aFinished)
   {
     *aOutput = aInput;
   }
 
+  Mutex& NodeMutex() { return mNodeMutex;}
+
   dom::AudioNode* Node() const
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    mNodeMutex.AssertCurrentThreadOwns();
     return mNode;
   }
 
-protected:
-  friend class dom::AudioNode;
+  void ClearNode()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mNode != nullptr);
+    mNodeMutex.AssertCurrentThreadOwns();
+    mNode = nullptr;
+  }
+
+private:
   dom::AudioNode* mNode;
+  Mutex mNodeMutex;
 };
 
 }
 
 #endif /* MOZILLA_AUDIONODEENGINE_H_ */
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -920,17 +920,17 @@ void
 MediaStreamGraphImpl::ProduceDataForStreamsBlockByBlock(uint32_t aStreamIndex,
                                                         GraphTime aFrom,
                                                         GraphTime aTo)
 {
   GraphTime t = aFrom;
   while (t < aTo) {
     GraphTime next = RoundUpToAudioBlock(t + 1);
     for (uint32_t i = aStreamIndex; i < mStreams.Length(); ++i) {
-      ProcessedMediaStream* ps = mStreams[i]->AsProcessedStream();
+      nsRefPtr<ProcessedMediaStream> ps = mStreams[i]->AsProcessedStream();
       if (ps) {
         ps->ProduceOutput(t, next);
       }
     }
     t = next;
   }
   NS_ASSERTION(t == aTo, "Something went wrong with rounding to block boundaries");
 }
--- a/content/media/webaudio/AnalyserNode.cpp
+++ b/content/media/webaudio/AnalyserNode.cpp
@@ -25,17 +25,25 @@ class AnalyserNodeEngine : public AudioN
                    const AudioChunk& aChunk)
       : mStream(aStream)
       , mChunk(aChunk)
     {
     }
 
     NS_IMETHOD Run()
     {
-      nsRefPtr<AnalyserNode> node = static_cast<AnalyserNode*>(mStream->Engine()->Node());
+      nsRefPtr<AnalyserNode> node;
+      {
+        // No need to keep holding the lock for the whole duration of this
+        // function, since we're holding a strong reference to it, so if
+        // we can obtain the reference, we will hold the node alive in
+        // this function.
+        MutexAutoLock lock(mStream->Engine()->NodeMutex());
+        node = static_cast<AnalyserNode*>(mStream->Engine()->Node());
+      }
       if (node) {
         node->AppendChunk(mChunk);
       }
       return NS_OK;
     }
 
   private:
     nsRefPtr<AudioNodeStream> mStream;
@@ -51,17 +59,19 @@ public:
 
   virtual void ProduceAudioBlock(AudioNodeStream* aStream,
                                  const AudioChunk& aInput,
                                  AudioChunk* aOutput,
                                  bool* aFinished)
   {
     *aOutput = aInput;
 
-    if (mNode &&
+    MutexAutoLock lock(NodeMutex());
+
+    if (Node() &&
         aInput.mChannelData.Length() > 0) {
       nsRefPtr<TransferBuffer> transfer = new TransferBuffer(aStream, aInput);
       NS_DispatchToMainThread(transfer);
     }
   }
 };
 
 AnalyserNode::AnalyserNode(AudioContext* aContext)
--- a/content/media/webaudio/AudioNode.cpp
+++ b/content/media/webaudio/AudioNode.cpp
@@ -200,21 +200,27 @@ AudioNode::Disconnect(uint32_t aOutput, 
   // This disconnection may have disconnected a panner and a source.
   Context()->UpdatePannerSource();
 }
 
 void
 AudioNode::DestroyMediaStream()
 {
   if (mStream) {
-    // Remove the node reference on the engine
-    AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
-    MOZ_ASSERT(ns, "How come we don't have a stream here?");
-    MOZ_ASSERT(ns->Engine()->mNode == this, "Invalid node reference");
-    ns->Engine()->mNode = nullptr;
+    {
+      // Remove the node reference on the engine, and take care to not
+      // hold the lock when the stream gets destroyed, because that will
+      // cause the engine to be destroyed as well, and we don't want to
+      // be holding the lock as we're trying to destroy it!
+      AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
+      MutexAutoLock lock(ns->Engine()->NodeMutex());
+      MOZ_ASSERT(ns, "How come we don't have a stream here?");
+      MOZ_ASSERT(ns->Engine()->Node() == this, "Invalid node reference");
+      ns->Engine()->ClearNode();
+    }
 
     mStream->Destroy();
     mStream = nullptr;
   }
 }
 
 }
 }
--- a/content/media/webaudio/DelayNode.cpp
+++ b/content/media/webaudio/DelayNode.cpp
@@ -32,17 +32,25 @@ class DelayNodeEngine : public AudioNode
     PlayingRefChanged(AudioNodeStream* aStream, ChangeType aChange)
       : mStream(aStream)
       , mChange(aChange)
     {
     }
 
     NS_IMETHOD Run()
     {
-      nsRefPtr<DelayNode> node = static_cast<DelayNode*>(mStream->Engine()->Node());
+      nsRefPtr<DelayNode> node;
+      {
+        // No need to keep holding the lock for the whole duration of this
+        // function, since we're holding a strong reference to it, so if
+        // we can obtain the reference, we will hold the node alive in
+        // this function.
+        MutexAutoLock lock(mStream->Engine()->NodeMutex());
+        node = static_cast<DelayNode*>(mStream->Engine()->Node());
+      }
       if (node) {
         if (mChange == ADDREF) {
           node->mPlayingRef.Take(node);
         } else if (mChange == RELEASE) {
           node->mPlayingRef.Drop(node);
         }
       }
       return NS_OK;
--- a/content/media/webaudio/DynamicsCompressorNode.cpp
+++ b/content/media/webaudio/DynamicsCompressorNode.cpp
@@ -135,17 +135,25 @@ private:
       Command(AudioNodeStream* aStream, float aReduction)
         : mStream(aStream)
         , mReduction(aReduction)
       {
       }
 
       NS_IMETHODIMP Run()
       {
-        nsRefPtr<DynamicsCompressorNode> node = static_cast<DynamicsCompressorNode*>(mStream->Engine()->Node());
+        nsRefPtr<DynamicsCompressorNode> node;
+        {
+          // No need to keep holding the lock for the whole duration of this
+          // function, since we're holding a strong reference to it, so if
+          // we can obtain the reference, we will hold the node alive in
+          // this function.
+          MutexAutoLock lock(mStream->Engine()->NodeMutex());
+          node = static_cast<DynamicsCompressorNode*>(mStream->Engine()->Node());
+        }
         if (node) {
           AudioParam* reduction = node->Reduction();
           reduction->CancelAllEvents();
           reduction->SetValue(mReduction);
         }
         return NS_OK;
       }
 
--- a/content/media/webaudio/ScriptProcessorNode.cpp
+++ b/content/media/webaudio/ScriptProcessorNode.cpp
@@ -183,18 +183,20 @@ public:
     mSource = aSource;
   }
 
   virtual void ProduceAudioBlock(AudioNodeStream* aStream,
                                  const AudioChunk& aInput,
                                  AudioChunk* aOutput,
                                  bool* aFinished) MOZ_OVERRIDE
   {
+    MutexAutoLock lock(NodeMutex());
+
     // If our node is dead, just output silence
-    if (!mNode) {
+    if (!Node()) {
       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
       return;
     }
 
     // First, record our input buffer
     for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
       if (aInput.IsNull()) {
         PodZero(mInputChannels[i] + mInputWriteIndex,
@@ -270,17 +272,25 @@ private:
       NS_IMETHODIMP Run()
       {
         // If it's not safe to run scripts right now, schedule this to run later
         if (!nsContentUtils::IsSafeToRunScript()) {
           nsContentUtils::AddScriptRunner(this);
           return NS_OK;
         }
 
-        nsRefPtr<ScriptProcessorNode> node = static_cast<ScriptProcessorNode*>(mStream->Engine()->Node());
+        nsRefPtr<ScriptProcessorNode> node;
+        {
+          // No need to keep holding the lock for the whole duration of this
+          // function, since we're holding a strong reference to it, so if
+          // we can obtain the reference, we will hold the node alive in
+          // this function.
+          MutexAutoLock lock(mStream->Engine()->NodeMutex());
+          node = static_cast<ScriptProcessorNode*>(mStream->Engine()->Node());
+        }
         if (!node) {
           return NS_OK;
         }
 
         AutoPushJSContext cx(node->Context()->GetJSContext());
         if (cx) {
           JSAutoRequest ar(cx);
 
--- a/content/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/content/media/webspeech/recognition/SpeechRecognition.cpp
@@ -33,20 +33,16 @@ namespace dom {
 static const uint32_t kSAMPLE_RATE = 16000;
 static const uint32_t kSPEECH_DETECTION_TIMEOUT_MS = 10000;
 
 // number of frames corresponding to 300ms of audio to send to endpointer while
 // it's in environment estimation mode
 // kSAMPLE_RATE frames = 1s, kESTIMATION_FRAMES frames = 300ms
 static const uint32_t kESTIMATION_SAMPLES = 300 * kSAMPLE_RATE / 1000;
 
-#define STATE_EQUALS(state) (mCurrentState == state)
-#define STATE_BETWEEN(state1, state2) \
-  (mCurrentState >= (state1) && mCurrentState <= (state2))
-
 #ifdef PR_LOGGING
 PRLogModuleInfo*
 GetSpeechRecognitionLog()
 {
   static PRLogModuleInfo* sLog;
   if (!sLog) {
     sLog = PR_NewLogModule("SpeechRecognition");
   }
@@ -63,18 +59,17 @@ NS_INTERFACE_MAP_BEGIN(SpeechRecognition
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(SpeechRecognition, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(SpeechRecognition, nsDOMEventTargetHelper)
 
 struct SpeechRecognition::TestConfig SpeechRecognition::mTestConfig;
 
 SpeechRecognition::SpeechRecognition()
-  : mProcessingEvent(false)
-  , mEndpointer(kSAMPLE_RATE)
+  : mEndpointer(kSAMPLE_RATE)
   , mAudioSamplesPerChunk(mEndpointer.FrameSize())
   , mSpeechDetectionTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
 {
   SR_LOG("created SpeechRecognition");
   SetIsDOMBinding();
 
   mTestConfig.Init();
   if (mTestConfig.mEnableTests) {
@@ -84,17 +79,31 @@ SpeechRecognition::SpeechRecognition()
   }
 
   mEndpointer.set_speech_input_complete_silence_length(
       Preferences::GetInt(PREFERENCE_ENDPOINTER_SILENCE_LENGTH, 500000));
   mEndpointer.set_long_speech_input_complete_silence_length(
       Preferences::GetInt(PREFERENCE_ENDPOINTER_LONG_SILENCE_LENGTH, 1000000));
   mEndpointer.set_long_speech_length(
       Preferences::GetInt(PREFERENCE_ENDPOINTER_SILENCE_LENGTH, 3 * 1000000));
-  mCurrentState = Reset();
+  Reset();
+}
+
+bool
+SpeechRecognition::StateBetween(FSMState begin, FSMState end)
+{
+  return mCurrentState >= begin && mCurrentState <= end;
+}
+
+void
+SpeechRecognition::SetState(FSMState state)
+{
+  mCurrentState = state;
+  SR_LOG("Transitioned to state %s", GetName(mCurrentState));
+  return;
 }
 
 JSObject*
 SpeechRecognition::WrapObject(JSContext* aCx, JSObject* aScope)
 {
   return SpeechRecognitionBinding::Wrap(aCx, aScope, this);
 }
 
@@ -116,140 +125,198 @@ nsISupports*
 SpeechRecognition::GetParentObject() const
 {
   return GetOwner();
 }
 
 void
 SpeechRecognition::ProcessEvent(SpeechEvent* aEvent)
 {
-  SR_LOG("Processing event %d", aEvent->mType);
+  SR_LOG("Processing %s, current state is %s",
+         GetName(aEvent),
+         GetName(mCurrentState));
 
-  MOZ_ASSERT(!mProcessingEvent, "Event dispatch should be sequential!");
-  mProcessingEvent = true;
+  // Run priority events first
+  for (uint32_t i = 0; i < mPriorityEvents.Length(); ++i) {
+    nsRefPtr<SpeechEvent> event = mPriorityEvents[i];
 
-  SR_LOG("Current state: %d", mCurrentState);
-  mCurrentState = TransitionAndGetNextState(aEvent);
-  SR_LOG("Transitioned to state: %d", mCurrentState);
-  mProcessingEvent = false;
+    SR_LOG("Processing priority %s", GetName(event));
+    Transition(event);
+  }
+
+  mPriorityEvents.Clear();
+
+  SR_LOG("Processing %s received as argument", GetName(aEvent));
+  Transition(aEvent);
 }
 
-SpeechRecognition::FSMState
-SpeechRecognition::TransitionAndGetNextState(SpeechEvent* aEvent)
+void
+SpeechRecognition::Transition(SpeechEvent* aEvent)
 {
   switch (mCurrentState) {
     case STATE_IDLE:
       switch (aEvent->mType) {
         case EVENT_START:
           // TODO: may want to time out if we wait too long
           // for user to approve
-          return STATE_STARTING;
+          WaitForAudioData(aEvent);
+          break;
         case EVENT_STOP:
         case EVENT_ABORT:
         case EVENT_AUDIO_DATA:
         case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
         case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
-          return DoNothing(aEvent);
+          DoNothing(aEvent);
+          break;
         case EVENT_AUDIO_ERROR:
         case EVENT_RECOGNITIONSERVICE_ERROR:
-          return AbortError(aEvent);
+          AbortError(aEvent);
+          break;
+        case EVENT_COUNT:
+          MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
       }
+      break;
     case STATE_STARTING:
       switch (aEvent->mType) {
         case EVENT_AUDIO_DATA:
-          return StartedAudioCapture(aEvent);
+          StartedAudioCapture(aEvent);
+          break;
         case EVENT_AUDIO_ERROR:
         case EVENT_RECOGNITIONSERVICE_ERROR:
-          return AbortError(aEvent);
+          AbortError(aEvent);
+          break;
         case EVENT_ABORT:
-          return AbortSilently(aEvent);
+          AbortSilently(aEvent);
+          break;
         case EVENT_STOP:
-          return Reset();
+          Reset();
+          break;
         case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
         case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
-          return DoNothing(aEvent);
+          DoNothing(aEvent);
+          break;
         case EVENT_START:
-          SR_LOG("STATE_STARTING: Unhandled event %d", aEvent->mType);
+          SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent));
           MOZ_NOT_REACHED("");
+        case EVENT_COUNT:
+          MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
       }
+      break;
     case STATE_ESTIMATING:
       switch (aEvent->mType) {
         case EVENT_AUDIO_DATA:
-          return WaitForEstimation(aEvent);
+          WaitForEstimation(aEvent);
+          break;
         case EVENT_STOP:
-          return StopRecordingAndRecognize(aEvent);
+          StopRecordingAndRecognize(aEvent);
+          break;
         case EVENT_ABORT:
-          return AbortSilently(aEvent);
+          AbortSilently(aEvent);
+          break;
         case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
         case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
         case EVENT_RECOGNITIONSERVICE_ERROR:
-          return DoNothing(aEvent);
+          DoNothing(aEvent);
+          break;
+        case EVENT_AUDIO_ERROR:
+          AbortError(aEvent);
+          break;
         case EVENT_START:
-        case EVENT_AUDIO_ERROR:
           SR_LOG("STATE_ESTIMATING: Unhandled event %d", aEvent->mType);
           MOZ_NOT_REACHED("");
+        case EVENT_COUNT:
+          MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
       }
+      break;
     case STATE_WAITING_FOR_SPEECH:
       switch (aEvent->mType) {
         case EVENT_AUDIO_DATA:
-          return DetectSpeech(aEvent);
+          DetectSpeech(aEvent);
+          break;
         case EVENT_STOP:
-          return StopRecordingAndRecognize(aEvent);
+          StopRecordingAndRecognize(aEvent);
+          break;
         case EVENT_ABORT:
-          return AbortSilently(aEvent);
+          AbortSilently(aEvent);
+          break;
         case EVENT_AUDIO_ERROR:
-          return AbortError(aEvent);
+          AbortError(aEvent);
+          break;
         case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
         case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
         case EVENT_RECOGNITIONSERVICE_ERROR:
-          return DoNothing(aEvent);
+          DoNothing(aEvent);
+          break;
         case EVENT_START:
-          SR_LOG("STATE_STARTING: Unhandled event %d", aEvent->mType);
+          SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent));
           MOZ_NOT_REACHED("");
+        case EVENT_COUNT:
+          MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
       }
+      break;
     case STATE_RECOGNIZING:
       switch (aEvent->mType) {
         case EVENT_AUDIO_DATA:
-          return WaitForSpeechEnd(aEvent);
+          WaitForSpeechEnd(aEvent);
+          break;
         case EVENT_STOP:
-          return StopRecordingAndRecognize(aEvent);
+          StopRecordingAndRecognize(aEvent);
+          break;
         case EVENT_AUDIO_ERROR:
         case EVENT_RECOGNITIONSERVICE_ERROR:
-          return AbortError(aEvent);
+          AbortError(aEvent);
+          break;
         case EVENT_ABORT:
-          return AbortSilently(aEvent);
+          AbortSilently(aEvent);
+          break;
         case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
         case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
-          return DoNothing(aEvent);
+          DoNothing(aEvent);
+          break;
         case EVENT_START:
-          SR_LOG("STATE_RECOGNIZING: Unhandled aEvent %d", aEvent->mType);
+          SR_LOG("STATE_RECOGNIZING: Unhandled aEvent %s", GetName(aEvent));
           MOZ_NOT_REACHED("");
+        case EVENT_COUNT:
+          MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
       }
+      break;
     case STATE_WAITING_FOR_RESULT:
       switch (aEvent->mType) {
         case EVENT_STOP:
-          return DoNothing(aEvent);
+          DoNothing(aEvent);
+          break;
         case EVENT_AUDIO_ERROR:
         case EVENT_RECOGNITIONSERVICE_ERROR:
-          return AbortError(aEvent);
+          AbortError(aEvent);
+          break;
         case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
-          return NotifyFinalResult(aEvent);
+          NotifyFinalResult(aEvent);
+          break;
         case EVENT_AUDIO_DATA:
-          return DoNothing(aEvent);
+          DoNothing(aEvent);
+          break;
         case EVENT_ABORT:
-          return AbortSilently(aEvent);
+          AbortSilently(aEvent);
+          break;
         case EVENT_START:
         case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
-          SR_LOG("STATE_WAITING_FOR_RESULT: Unhandled aEvent %d", aEvent->mType);
+          SR_LOG("STATE_WAITING_FOR_RESULT: Unhandled aEvent %s", GetName(aEvent));
           MOZ_NOT_REACHED("");
+        case EVENT_COUNT:
+          MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
       }
+      break;
+    case STATE_ABORTING:
+      DoNothing(aEvent);
+      break;
+    case STATE_COUNT:
+      MOZ_NOT_REACHED("Invalid state STATE_COUNT");
   }
-  SR_LOG("Unhandled state %d", mCurrentState);
-  MOZ_NOT_REACHED("");
-  return mCurrentState;
+
+  return;
 }
 
 /*
  * Handle a segment of recorded audio data.
  * Returns the number of samples that were processed.
  */
 uint32_t
 SpeechRecognition::ProcessAudioSegment(AudioSegment* aSegment)
@@ -289,154 +356,171 @@ SpeechRecognition::GetRecognitionService
 
   aResultCID =
     NS_LITERAL_CSTRING(NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX) +
     speechRecognitionService;
 
   return;
 }
 
-/****************************
- * FSM Transition functions *
- ****************************/
+/****************************************************************************
+ * FSM Transition functions
+ *
+ * If a transition function may cause a DOM event to be fired,
+ * it may also be re-entered, since the event handler may cause the
+ * event loop to spin and new SpeechEvents to be processed.
+ *
+ * Rules:
+ * 1) These methods should call SetState as soon as possible.
+ * 2) If these methods dispatch DOM events, or call methods that dispatch
+ * DOM events, that should be done as late as possible.
+ * 3) If anything must happen after dispatching a DOM event, make sure
+ * the state is still what the method expected it to be.
+ ****************************************************************************/
 
-SpeechRecognition::FSMState
+void
 SpeechRecognition::Reset()
 {
+  SetState(STATE_IDLE);
   mRecognitionService = nullptr;
   mEstimationSamples = 0;
   mBufferedSamples = 0;
   mSpeechDetectionTimer->Cancel();
+}
 
-  return STATE_IDLE;
+void
+SpeechRecognition::ResetAndEnd()
+{
+  Reset();
+  DispatchTrustedEvent(NS_LITERAL_STRING("end"));
 }
 
-/*
- * Since the handler for "end" may call
- * start(), we want to fully reset before dispatching
- * the event.
- */
-SpeechRecognition::FSMState
-SpeechRecognition::ResetAndEnd()
+void
+SpeechRecognition::WaitForAudioData(SpeechEvent* aEvent)
 {
-  mCurrentState = Reset();
-  DispatchTrustedEvent(NS_LITERAL_STRING("end"));
-  return mCurrentState;
+  SetState(STATE_STARTING);
 }
 
-SpeechRecognition::FSMState
+void
 SpeechRecognition::StartedAudioCapture(SpeechEvent* aEvent)
 {
+  SetState(STATE_ESTIMATING);
+
   mEndpointer.SetEnvironmentEstimationMode();
   mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
 
-  DispatchTrustedEvent(NS_LITERAL_STRING("start"));
   DispatchTrustedEvent(NS_LITERAL_STRING("audiostart"));
-
-  return STATE_ESTIMATING;
+  if (mCurrentState == STATE_ESTIMATING) {
+    DispatchTrustedEvent(NS_LITERAL_STRING("start"));
+  }
 }
 
-SpeechRecognition::FSMState
+void
 SpeechRecognition::StopRecordingAndRecognize(SpeechEvent* aEvent)
 {
-  StopRecording();
+  SetState(STATE_WAITING_FOR_RESULT);
+
   MOZ_ASSERT(mRecognitionService, "Service deleted before recording done");
   mRecognitionService->SoundEnd();
 
-  return STATE_WAITING_FOR_RESULT;
+  StopRecording();
 }
 
-SpeechRecognition::FSMState
+void
 SpeechRecognition::WaitForEstimation(SpeechEvent* aEvent)
 {
+  SetState(STATE_ESTIMATING);
+
   mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
-
   if (mEstimationSamples > kESTIMATION_SAMPLES) {
     mEndpointer.SetUserInputMode();
-    return STATE_WAITING_FOR_SPEECH;
+    SetState(STATE_WAITING_FOR_SPEECH);
   }
-
-  return STATE_ESTIMATING;
 }
 
-SpeechRecognition::FSMState
+void
 SpeechRecognition::DetectSpeech(SpeechEvent* aEvent)
 {
+  SetState(STATE_WAITING_FOR_SPEECH);
+
   ProcessAudioSegment(aEvent->mAudioSegment);
-
   if (mEndpointer.DidStartReceivingSpeech()) {
     mSpeechDetectionTimer->Cancel();
+    SetState(STATE_RECOGNIZING);
     DispatchTrustedEvent(NS_LITERAL_STRING("speechstart"));
-    return STATE_RECOGNIZING;
   }
-
-  return STATE_WAITING_FOR_SPEECH;
 }
 
-SpeechRecognition::FSMState
+void
 SpeechRecognition::WaitForSpeechEnd(SpeechEvent* aEvent)
 {
+  SetState(STATE_RECOGNIZING);
+
   ProcessAudioSegment(aEvent->mAudioSegment);
-
   if (mEndpointer.speech_input_complete()) {
-    // FIXME: StopRecordingAndRecognize should only be called for single
-    // shot services for continous we should just inform the service
     DispatchTrustedEvent(NS_LITERAL_STRING("speechend"));
-    return StopRecordingAndRecognize(aEvent);
+
+    if (mCurrentState == STATE_RECOGNIZING) {
+      // FIXME: StopRecordingAndRecognize should only be called for single
+      // shot services for continuous we should just inform the service
+      StopRecordingAndRecognize(aEvent);
+    }
   }
-
-   return STATE_RECOGNIZING;
 }
 
-SpeechRecognition::FSMState
+void
 SpeechRecognition::NotifyFinalResult(SpeechEvent* aEvent)
 {
+  ResetAndEnd();
+
   nsCOMPtr<nsIDOMEvent> domEvent;
   NS_NewDOMSpeechRecognitionEvent(getter_AddRefs(domEvent), nullptr, nullptr, nullptr);
 
   nsCOMPtr<nsIDOMSpeechRecognitionEvent> srEvent = do_QueryInterface(domEvent);
   nsRefPtr<SpeechRecognitionResultList> rlist = aEvent->mRecognitionResultList;
   nsCOMPtr<nsISupports> ilist = do_QueryInterface(rlist);
   srEvent->InitSpeechRecognitionEvent(NS_LITERAL_STRING("result"),
                                       true, false, 0, ilist,
                                       NS_LITERAL_STRING("NOT_IMPLEMENTED"),
                                       NULL);
   domEvent->SetTrusted(true);
 
   bool defaultActionEnabled;
   this->DispatchEvent(domEvent, &defaultActionEnabled);
-  return ResetAndEnd();
 }
 
-SpeechRecognition::FSMState
+void
 SpeechRecognition::DoNothing(SpeechEvent* aEvent)
 {
-  return mCurrentState;
 }
 
-SpeechRecognition::FSMState
+void
 SpeechRecognition::AbortSilently(SpeechEvent* aEvent)
 {
+  bool stopRecording = StateBetween(STATE_ESTIMATING, STATE_RECOGNIZING);
+
+  // prevent reentrancy from DOM events
+  SetState(STATE_ABORTING);
+
   if (mRecognitionService) {
     mRecognitionService->Abort();
   }
 
-  if (STATE_BETWEEN(STATE_ESTIMATING, STATE_RECOGNIZING)) {
+  if (stopRecording) {
     StopRecording();
   }
 
-  return ResetAndEnd();
+  ResetAndEnd();
 }
 
-SpeechRecognition::FSMState
+void
 SpeechRecognition::AbortError(SpeechEvent* aEvent)
 {
-  FSMState nextState = AbortSilently(aEvent);
+  AbortSilently(aEvent);
   NotifyError(aEvent);
-  return nextState;
 }
 
 void
 SpeechRecognition::NotifyError(SpeechEvent* aEvent)
 {
   nsCOMPtr<nsIDOMEvent> domEvent = do_QueryInterface(aEvent->mError);
   domEvent->SetTrusted(true);
 
@@ -484,17 +568,17 @@ SpeechRecognition::StopRecording()
 
 NS_IMETHODIMP
 SpeechRecognition::Observe(nsISupports* aSubject, const char* aTopic,
                            const PRUnichar* aData)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Observer invoked off the main thread");
 
   if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) &&
-      STATE_BETWEEN(STATE_IDLE, STATE_WAITING_FOR_SPEECH)) {
+      StateBetween(STATE_IDLE, STATE_WAITING_FOR_SPEECH)) {
 
     DispatchError(SpeechRecognition::EVENT_AUDIO_ERROR,
                   nsIDOMSpeechRecognitionError::NO_SPEECH,
                   NS_LITERAL_STRING("No speech detected (timeout)"));
   } else if (!strcmp(aTopic, SPEECH_RECOGNITION_TEST_END_TOPIC)) {
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     obs->RemoveObserver(this, SPEECH_RECOGNITION_TEST_EVENT_REQUEST_TOPIC);
     obs->RemoveObserver(this, SPEECH_RECOGNITION_TEST_END_TOPIC);
@@ -616,17 +700,17 @@ SpeechRecognition::SetServiceURI(const n
 {
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
   return;
 }
 
 void
 SpeechRecognition::Start(ErrorResult& aRv)
 {
-  if (!STATE_EQUALS(STATE_IDLE)) {
+  if (!mCurrentState == STATE_IDLE) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsAutoCString speechRecognitionServiceCID;
   GetRecognitionServiceCID(speechRecognitionServiceCID);
 
   nsresult rv;
@@ -799,16 +883,53 @@ SpeechRecognition::FeedAudioData(already
   nsRefPtr<SpeechEvent> event = new SpeechEvent(this, EVENT_AUDIO_DATA);
   event->mAudioSegment = segment;
   event->mProvider = aProvider;
   NS_DispatchToMainThread(event);
 
   return;
 }
 
+const char*
+SpeechRecognition::GetName(FSMState aId)
+{
+  static const char* names[] = {
+    "STATE_IDLE",
+    "STATE_STARTING",
+    "STATE_ESTIMATING",
+    "STATE_WAITING_FOR_SPEECH",
+    "STATE_RECOGNIZING",
+    "STATE_WAITING_FOR_RESULT",
+    "STATE_ABORTING"
+  };
+
+  MOZ_ASSERT(aId < STATE_COUNT);
+  MOZ_ASSERT(ArrayLength(names) == STATE_COUNT);
+  return names[aId];
+}
+
+const char*
+SpeechRecognition::GetName(SpeechEvent* aEvent)
+{
+  static const char* names[] = {
+    "EVENT_START",
+    "EVENT_STOP",
+    "EVENT_ABORT",
+    "EVENT_AUDIO_DATA",
+    "EVENT_AUDIO_ERROR",
+    "EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT",
+    "EVENT_RECOGNITIONSERVICE_FINAL_RESULT",
+    "EVENT_RECOGNITIONSERVICE_ERROR"
+  };
+
+  MOZ_ASSERT(aEvent->mType < EVENT_COUNT);
+  MOZ_ASSERT(ArrayLength(names) == EVENT_COUNT);
+  return names[aEvent->mType];
+}
+
 NS_IMPL_ISUPPORTS1(SpeechRecognition::GetUserMediaStreamOptions, nsIMediaStreamOptions)
 
 NS_IMETHODIMP
 SpeechRecognition::GetUserMediaStreamOptions::GetFake(bool* aFake)
 {
   *aFake = false;
   return NS_OK;
 }
--- a/content/media/webspeech/recognition/SpeechRecognition.h
+++ b/content/media/webspeech/recognition/SpeechRecognition.h
@@ -116,17 +116,18 @@ public:
   enum EventType {
     EVENT_START,
     EVENT_STOP,
     EVENT_ABORT,
     EVENT_AUDIO_DATA,
     EVENT_AUDIO_ERROR,
     EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT,
     EVENT_RECOGNITIONSERVICE_FINAL_RESULT,
-    EVENT_RECOGNITIONSERVICE_ERROR
+    EVENT_RECOGNITIONSERVICE_ERROR,
+    EVENT_COUNT
   };
 
   void DispatchError(EventType aErrorType, int aErrorCode, const nsAString& aMessage);
   uint32_t FillSamplesBuffer(const int16_t* aSamples, uint32_t aSampleCount);
   uint32_t SplitSamplesBuffer(const int16_t* aSamplesBuffer, uint32_t aSampleCount, nsTArray<already_AddRefed<SharedBuffer> >& aResult);
   AudioSegment* CreateAudioSegment(nsTArray<already_AddRefed<SharedBuffer> >& aChunks);
   void FeedAudioData(already_AddRefed<SharedBuffer> aSamples, uint32_t aDuration, MediaStreamListener* aProvider);
 
@@ -161,18 +162,23 @@ public:
 private:
   enum FSMState {
     STATE_IDLE,
     STATE_STARTING,
     STATE_ESTIMATING,
     STATE_WAITING_FOR_SPEECH,
     STATE_RECOGNIZING,
     STATE_WAITING_FOR_RESULT,
+    STATE_ABORTING,
+    STATE_COUNT
   };
 
+  void SetState(FSMState state);
+  bool StateBetween(FSMState begin, FSMState end);
+
   class GetUserMediaStreamOptions : public nsIMediaStreamOptions
   {
   public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIMEDIASTREAMOPTIONS
 
     GetUserMediaStreamOptions() {}
     virtual ~GetUserMediaStreamOptions() {}
@@ -212,52 +218,56 @@ private:
 
   NS_IMETHOD StartRecording(DOMMediaStream* aDOMStream);
   NS_IMETHOD StopRecording();
 
   uint32_t ProcessAudioSegment(AudioSegment* aSegment);
   void NotifyError(SpeechEvent* aEvent);
 
   void ProcessEvent(SpeechEvent* aEvent);
-  FSMState TransitionAndGetNextState(SpeechEvent* aEvent);
+  void Transition(SpeechEvent* aEvent);
 
-  FSMState Reset();
-  FSMState ResetAndEnd();
-  FSMState StartedAudioCapture(SpeechEvent* aEvent);
-  FSMState StopRecordingAndRecognize(SpeechEvent* aEvent);
-  FSMState WaitForEstimation(SpeechEvent* aEvent);
-  FSMState DetectSpeech(SpeechEvent* aEvent);
-  FSMState WaitForSpeechEnd(SpeechEvent* aEvent);
-  FSMState NotifyFinalResult(SpeechEvent* aEvent);
-  FSMState DoNothing(SpeechEvent* aEvent);
-  FSMState AbortSilently(SpeechEvent* aEvent);
-  FSMState AbortError(SpeechEvent* aEvent);
+  void Reset();
+  void ResetAndEnd();
+  void WaitForAudioData(SpeechEvent* aEvent);
+  void StartedAudioCapture(SpeechEvent* aEvent);
+  void StopRecordingAndRecognize(SpeechEvent* aEvent);
+  void WaitForEstimation(SpeechEvent* aEvent);
+  void DetectSpeech(SpeechEvent* aEvent);
+  void WaitForSpeechEnd(SpeechEvent* aEvent);
+  void NotifyFinalResult(SpeechEvent* aEvent);
+  void DoNothing(SpeechEvent* aEvent);
+  void AbortSilently(SpeechEvent* aEvent);
+  void AbortError(SpeechEvent* aEvent);
 
   nsRefPtr<DOMMediaStream> mDOMStream;
   nsRefPtr<SpeechStreamListener> mSpeechListener;
   nsCOMPtr<nsISpeechRecognitionService> mRecognitionService;
 
   void GetRecognitionServiceCID(nsACString& aResultCID);
 
   FSMState mCurrentState;
-  bool mProcessingEvent;
+  nsTArray<nsRefPtr<SpeechEvent> > mPriorityEvents;
 
   Endpointer mEndpointer;
   uint32_t mEstimationSamples;
 
   uint32_t mAudioSamplesPerChunk;
 
   // buffer holds one chunk of mAudioSamplesPerChunk
   // samples before feeding it to mEndpointer
   nsRefPtr<SharedBuffer> mAudioSamplesBuffer;
   uint32_t mBufferedSamples;
 
   nsCOMPtr<nsITimer> mSpeechDetectionTimer;
 
   void ProcessTestEventRequest(nsISupports* aSubject, const nsAString& aEventName);
+
+  const char* GetName(FSMState aId);
+  const char* GetName(SpeechEvent* aId);
 };
 
 class SpeechEvent : public nsRunnable
 {
 public:
   SpeechEvent(SpeechRecognition* aRecognition, SpeechRecognition::EventType aType)
   : mAudioSegment(0)
   , mRecognitionResultList(0)
--- a/content/media/webspeech/recognition/test/Makefile.in
+++ b/content/media/webspeech/recognition/test/Makefile.in
@@ -18,13 +18,14 @@ MOCHITEST_FILES := \
   head.js \
   test_success_without_recognition_service.html \
   test_timeout.html \
   test_recognition_service_error.html \
   test_audio_capture_error.html \
   test_abort.html \
   test_call_start_from_end_handler.html \
   test_preference_enable.html \
+  test_nested_eventloop.html \
   hello.ogg \
   silence.ogg \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/content/media/webspeech/recognition/test/head.js
+++ b/content/media/webspeech/recognition/test/head.js
@@ -38,33 +38,38 @@ function EventManager(sr) {
   ];
 
   var eventDependencies = {
     "speechend": "speechstart",
     "soundent": "soundstart",
     "audioend": "audiostart"
   };
 
+  var isDone = false;
+
   // AUDIO_DATA events are asynchronous,
   // so we queue events requested while they are being
   // issued to make them seem synchronous
   var isSendingAudioData = false;
   var queuedEventRequests = [];
 
   // register default handlers
   for (var i = 0; i < allEvents.length; i++) {
     (function (eventName) {
       sr["on" + eventName] = function (evt) {
         var message = "unexpected event: " + eventName;
         if (eventName == "error") {
           message += " -- " + evt.message;
         }
 
         ok(false, message);
-        if (self.done) self.done();
+        if (self.doneFunc && !isDone) {
+          isDone = true;
+          self.doneFunc();
+        }
       };
     })(allEvents[i]);
   }
 
   self.expect = function EventManager_expect(eventName, cb) {
     nEventsExpected++;
 
     sr["on" + eventName] = function(evt) {
@@ -73,18 +78,20 @@ function EventManager(sr) {
 
       var dep = eventDependencies[eventName];
       if (dep) {
         ok(self.eventsReceived.indexOf(dep) >= 0,
            eventName + " must come after " + dep);
       }
 
       cb && cb(evt, sr);
-      if (self.done && nEventsExpected === self.eventsReceived.length) {
-        self.done();
+      if (self.doneFunc && !isDone &&
+          nEventsExpected === self.eventsReceived.length) {
+        isDone = true;
+        self.doneFunc();
       }
     }
   }
 
   self.requestFSMEvent = function EventManager_requestFSMEvent(eventName) {
     if (isSendingAudioData) {
       info("Queuing event " + eventName + " until we're done sending audio data");
       queuedEventRequests.push(eventName);
@@ -145,19 +152,21 @@ function performTest(options) {
     var sr = new SpeechRecognition();
     var em = new EventManager(sr);
 
     for (var eventName in options.expectedEvents) {
       var cb = options.expectedEvents[eventName];
       em.expect(eventName, cb);
     }
 
-    em.done = function() {
+    em.doneFunc = function() {
       em.requestTestEnd();
-      options.doneFunc();
+      if (options.doneFunc) {
+        options.doneFunc();
+      }
     }
 
     em.audioSampleFile = DEFAULT_AUDIO_SAMPLE_FILE;
     if (options.audioSampleFile) {
       em.audioSampleFile = options.audioSampleFile;
     }
 
     for (var i = 0; i < options.eventsToRequest.length; i++) {
new file mode 100644
--- /dev/null
+++ b/content/media/webspeech/recognition/test/test_nested_eventloop.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650295
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 650295 -- Spin the event loop from inside a callback</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="head.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650295">Mozilla Bug 650295</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript">
+  SimpleTest.waitForExplicitFinish();
+
+  /*
+   * window.showModalDialog() can be used to spin the event loop, causing
+   * queued SpeechEvents (such as those created by calls to start(), stop()
+   * or abort()) to be processed immediately.
+   * When this is done from inside DOM event handlers, it is possible to
+   * cause reentrancy in our C++ code, which we should be able to withstand.
+   */
+
+  // Garbage collecting the windows created in this test can
+  // cause assertions (Bug 600703).
+  if (!navigator.platform.startsWith("Win")) {
+    SimpleTest.expectAssertions(2);
+  }
+
+  function abortAndSpinEventLoop(evt, sr) {
+    sr.abort();
+    window.showModalDialog("javascript:window.close()");
+  }
+
+  function doneFunc() {
+    // Trigger gc now and wait some time to make sure this test gets the blame
+    // for any assertions caused by showModalDialog
+    var count = 0, GC_COUNT = 4;
+
+    function triggerGCOrFinish() {
+      SpecialPowers.gc();
+      count++;
+
+      if (count == GC_COUNT) {
+        SimpleTest.finish();
+      }
+    }
+
+    for (var i = 0; i < GC_COUNT; i++) {
+      setTimeout(triggerGCOrFinish, 0);
+    }
+  }
+
+  /*
+   * We start by performing a normal start, then abort from the audiostart
+   * callback and force the EVENT_ABORT to be processed while still inside
+   * the event handler. This causes the recording to stop, which raises
+   * the audioend and (later on) end events.
+   * Then, we abort (once again spinning the event loop) from the audioend
+   * handler, attempting to cause a re-entry into the abort code. This second
+   * call should be ignored, and we get the end callback and finish.
+   */
+
+  performTest({
+    eventsToRequest: [
+      "EVENT_START",
+      "EVENT_AUDIO_DATA",
+    ],
+    expectedEvents: {
+      "audiostart": abortAndSpinEventLoop,
+      "audioend": abortAndSpinEventLoop,
+      "end": null
+    },
+    doneFunc: doneFunc,
+    prefs: [["media.webspeech.test.fake_fsm_events", true],
+            ["media.webspeech.test.fake_recognition_service", true]]
+  });
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/media/webspeech/synth/nsSpeechTask.cpp
+++ b/content/media/webspeech/synth/nsSpeechTask.cpp
@@ -258,24 +258,26 @@ nsSpeechTask::DispatchEndImpl(float aEla
   NS_ENSURE_FALSE(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED,
                   NS_ERROR_NOT_AVAILABLE);
 
   // XXX: This should not be here, but it prevents a crash in MSG.
   if (mStream) {
     mStream->Destroy();
   }
 
+  nsRefPtr<SpeechSynthesisUtterance> utterance = mUtterance;
+
   if (mSpeechSynthesis) {
     mSpeechSynthesis->OnEnd(this);
   }
 
-  mUtterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
-  mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("end"),
-                                           aCharIndex, aElapsedTime,
-                                           NS_LITERAL_STRING(""));
+  utterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
+  utterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("end"),
+                                          aCharIndex, aElapsedTime,
+                                          NS_LITERAL_STRING(""));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchPause(float aElapsedTime, uint32_t aCharIndex)
 {
   if (!mIndirectAudio) {
     NS_WARNING("Can't call DispatchPause() from a direct audio speech service");
--- a/content/xbl/src/nsXBLPrototypeHandler.cpp
+++ b/content/xbl/src/nsXBLPrototypeHandler.cpp
@@ -320,23 +320,19 @@ nsXBLPrototypeHandler::ExecuteHandler(Ev
   JS::Rooted<JSObject*> bound(cx, JS_CloneFunctionObject(cx, genericHandler, &targetV.toObject()));
   NS_ENSURE_TRUE(bound, NS_ERROR_FAILURE);
 
   // Now, wrap the bound handler into the content compartment and use it.
   JSAutoCompartment ac2(cx, globalObject);
   if (!JS_WrapObject(cx, bound.address())) {
     return NS_ERROR_FAILURE;
   }
-  JS::Rooted<JSObject*> boundHandler(cx, bound);
 
   nsRefPtr<EventHandlerNonNull> handlerCallback =
-    new EventHandlerNonNull(cx, globalObject, boundHandler, &ok);
-  if (!ok) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
+    new EventHandlerNonNull(bound);
 
   nsEventHandler eventHandler(handlerCallback);
 
   // Execute it.
   nsCOMPtr<nsIJSEventListener> eventListener;
   rv = NS_NewJSEventListener(nullptr, globalObject,
                              scriptTarget, onEventAtom,
                              eventHandler,
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -11794,30 +11794,21 @@ nsGlobalWindow::DisableNetworkEvent(uint
   NS_IMETHODIMP nsGlobalWindow::GetOn##name_(JSContext *cx,                  \
                                              JS::Value *vp) {                \
     EventHandlerNonNull* h = GetOn##name_();                                 \
     vp->setObjectOrNull(h ? h->Callable() : nullptr);                        \
     return NS_OK;                                                            \
   }                                                                          \
   NS_IMETHODIMP nsGlobalWindow::SetOn##name_(JSContext *cx,                  \
                                              const JS::Value &v) {           \
-    JSObject *obj = mJSObject;                                               \
-    if (!obj) {                                                              \
-      /* Just silently do nothing */                                         \
-      return NS_OK;                                                          \
-    }                                                                        \
     nsRefPtr<EventHandlerNonNull> handler;                                   \
     JSObject *callable;                                                      \
     if (v.isObject() &&                                                      \
         JS_ObjectIsCallable(cx, callable = &v.toObject())) {                 \
-      bool ok;                                                               \
-      handler = new EventHandlerNonNull(cx, obj, callable, &ok);             \
-      if (!ok) {                                                             \
-        return NS_ERROR_OUT_OF_MEMORY;                                       \
-      }                                                                      \
+      handler = new EventHandlerNonNull(callable);                           \
     }                                                                        \
     ErrorResult rv;                                                          \
     SetOn##name_(handler, rv);                                               \
     return rv.ErrorCode();                                                   \
   }
 #define ERROR_EVENT(name_, id_, type_, struct_)                              \
   NS_IMETHODIMP nsGlobalWindow::GetOn##name_(JSContext *cx,                  \
                                              JS::Value *vp) {                \
@@ -11834,29 +11825,21 @@ nsGlobalWindow::DisableNetworkEvent(uint
   }                                                                          \
   NS_IMETHODIMP nsGlobalWindow::SetOn##name_(JSContext *cx,                  \
                                              const JS::Value &v) {           \
     nsEventListenerManager *elm = GetListenerManager(true);                  \
     if (!elm) {                                                              \
       return NS_ERROR_OUT_OF_MEMORY;                                         \
     }                                                                        \
                                                                              \
-    JSObject *obj = mJSObject;                                               \
-    if (!obj) {                                                              \
-      return NS_ERROR_UNEXPECTED;                                            \
-    }                                                                        \
     nsRefPtr<OnErrorEventHandlerNonNull> handler;                            \
     JSObject *callable;                                                      \
     if (v.isObject() &&                                                      \
         JS_ObjectIsCallable(cx, callable = &v.toObject())) {                 \
-      bool ok;                                                               \
-      handler = new OnErrorEventHandlerNonNull(cx, obj, callable, &ok);      \
-      if (!ok) {                                                             \
-        return NS_ERROR_OUT_OF_MEMORY;                                       \
-      }                                                                      \
+      handler = new OnErrorEventHandlerNonNull(callable);                    \
     }                                                                        \
     return elm->SetEventHandler(handler);                                    \
   }
 #define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_)                       \
   NS_IMETHODIMP nsGlobalWindow::GetOn##name_(JSContext *cx,                  \
                                              JS::Value *vp) {                \
     nsEventListenerManager *elm = GetListenerManager(false);                 \
     if (elm) {                                                               \
@@ -11872,29 +11855,21 @@ nsGlobalWindow::DisableNetworkEvent(uint
   }                                                                          \
   NS_IMETHODIMP nsGlobalWindow::SetOn##name_(JSContext *cx,                  \
                                              const JS::Value &v) {           \
     nsEventListenerManager *elm = GetListenerManager(true);                  \
     if (!elm) {                                                              \
       return NS_ERROR_OUT_OF_MEMORY;                                         \
     }                                                                        \
                                                                              \
-    JSObject *obj = mJSObject;                                               \
-    if (!obj) {                                                              \
-      return NS_ERROR_UNEXPECTED;                                            \
-    }                                                                        \
     nsRefPtr<BeforeUnloadEventHandlerNonNull> handler;                       \
     JSObject *callable;                                                      \
     if (v.isObject() &&                                                      \
         JS_ObjectIsCallable(cx, callable = &v.toObject())) {                 \
-      bool ok;                                                               \
-      handler = new BeforeUnloadEventHandlerNonNull(cx, obj, callable, &ok); \
-      if (!ok) {                                                             \
-        return NS_ERROR_OUT_OF_MEMORY;                                       \
-      }                                                                      \
+      handler = new BeforeUnloadEventHandlerNonNull(callable);               \
     }                                                                        \
     return elm->SetEventHandler(handler);                                    \
   }
 #define WINDOW_ONLY_EVENT EVENT
 #define TOUCH_EVENT EVENT
 #include "nsEventNameList.h"
 #undef TOUCH_EVENT
 #undef WINDOW_ONLY_EVENT
--- a/dom/base/nsJSTimeoutHandler.cpp
+++ b/dom/base/nsJSTimeoutHandler.cpp
@@ -288,22 +288,17 @@ nsJSScriptTimeoutHandler::Init(nsGlobalW
     // Get the calling location.
     const char *filename;
     if (nsJSUtils::GetCallingLocation(cx, &filename, &mLineNo)) {
       mFileName.Assign(filename);
     }
   } else if (funobj) {
     NS_HOLD_JS_OBJECTS(this, nsJSScriptTimeoutHandler);
 
-    bool ok;
-    mFunction = new Function(cx, aWindow->FastGetGlobalJSObject(), funobj, &ok);
-    if (!ok) {
-      NS_DROP_JS_OBJECTS(this, nsJSScriptTimeoutHandler);
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
+    mFunction = new Function(funobj);
 
     // Create our arg array.  argc is the number of arguments passed
     // to setTimeout or setInterval; the first two are our callback
     // and the delay, so only arguments after that need to go in our
     // array.
     // std::max(argc - 2, 0) wouldn't work right because argc is unsigned.
     uint32_t argCount = std::max(argc, 2u) - 2;
     FallibleTArray<JS::Value> args;
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -547,16 +547,17 @@ WrapNewBindingForSameCompartment(JSConte
 // in *vp.  "value" must be a concrete class that implements a
 // GetWrapperPreserveColor() which can return its existing wrapper, if any, and
 // a WrapObject() which will try to create a wrapper. Typically, this is done by
 // having "value" inherit from nsWrapperCache.
 template <class T>
 MOZ_ALWAYS_INLINE bool
 WrapNewBindingObject(JSContext* cx, JSObject* scope, T* value, JS::Value* vp)
 {
+  MOZ_ASSERT(value);
   JSObject* obj = value->GetWrapperPreserveColor();
   bool couldBeDOMBinding = CouldBeDOMBinding(value);
   if (obj) {
     xpc_UnmarkNonNullGrayObject(obj);
   } else {
     // Inline this here while we have non-dom objects in wrapper caches.
     if (!couldBeDOMBinding) {
       return false;
@@ -609,16 +610,17 @@ WrapNewBindingObject(JSContext* cx, JSOb
 // Create a JSObject wrapping "value", for cases when "value" is a
 // non-wrapper-cached object using WebIDL bindings.  "value" must implement a
 // WrapObject() method taking a JSContext and a scope.
 template <class T>
 inline bool
 WrapNewBindingNonWrapperCachedObject(JSContext* cx, JSObject* scope, T* value,
                                      JS::Value* vp)
 {
+  MOZ_ASSERT(value);
   // We try to wrap in the compartment of the underlying object of "scope"
   JSObject* obj;
   {
     // scope for the JSAutoCompartment so that we restore the compartment
     // before we call JS_WrapValue.
     Maybe<JSAutoCompartment> ac;
     if (js::IsWrapper(scope)) {
       scope = js::CheckedUnwrap(scope, /* stopAtOuter = */ false);
@@ -644,16 +646,21 @@ WrapNewBindingNonWrapperCachedObject(JSC
 // non-wrapper-cached owned object using WebIDL bindings.  "value" must implement a
 // WrapObject() method taking a JSContext, a scope, and a boolean outparam that
 // is true if the JSObject took ownership
 template <class T>
 inline bool
 WrapNewBindingNonWrapperCachedOwnedObject(JSContext* cx, JSObject* scope,
                                           nsAutoPtr<T>& value, JS::Value* vp)
 {
+  // We do a runtime check on value, because otherwise we might in
+  // fact end up wrapping a null and invoking methods on it later.
+  if (!value) {
+    NS_RUNTIMEABORT("Don't try to wrap null objects");
+  }
   // We try to wrap in the compartment of the underlying object of "scope"
   JSObject* obj;
   {
     // scope for the JSAutoCompartment so that we restore the compartment
     // before we call JS_WrapValue.
     Maybe<JSAutoCompartment> ac;
     if (js::IsWrapper(scope)) {
       scope = js::CheckedUnwrap(scope, /* stopAtOuter = */ false);
--- a/dom/bindings/CallbackFunction.h
+++ b/dom/bindings/CallbackFunction.h
@@ -20,28 +20,20 @@
 #include "mozilla/dom/CallbackObject.h"
 
 namespace mozilla {
 namespace dom {
 
 class CallbackFunction : public CallbackObject
 {
 public:
-  /**
-   * Create a CallbackFunction.  aCallable is the callable we're wrapping.
-   * aOwner is the object that will be receiving this CallbackFunction as a
-   * method argument, if any.  We need this so we can store our callable in the
-   * same compartment as our owner.  If *aInited is set to false, an exception
-   * has been thrown.
-   */
-  CallbackFunction(JSContext* cx, JSObject* aOwner, JSObject* aCallable,
-                   bool* aInited)
-    : CallbackObject(cx, aOwner, aCallable, aInited)
+  explicit CallbackFunction(JSObject* aCallable)
+    : CallbackObject(aCallable)
   {
-    MOZ_ASSERT(JS_ObjectIsCallable(cx, aCallable));
+    MOZ_ASSERT(JS_ObjectIsCallable(nullptr, aCallable));
   }
 
   JSObject* Callable() const
   {
     return Callback();
   }
 
   bool HasGrayCallable() const
--- a/dom/bindings/CallbackInterface.h
+++ b/dom/bindings/CallbackInterface.h
@@ -19,34 +19,16 @@
 #include "mozilla/dom/CallbackObject.h"
 
 namespace mozilla {
 namespace dom {
 
 class CallbackInterface : public CallbackObject
 {
 public:
-  /**
-   * Create a CallbackInterface.  aCallback is the callback object we're
-   * wrapping.  aOwner is the object that will be receiving this
-   * CallbackInterface as a method argument, if any.  We need this so we can
-   * store our callable in the same compartment as our owner.  If *aInited is
-   * set to false, an exception has been thrown.
-   */
-  CallbackInterface(JSContext* cx, JSObject* aOwner, JSObject* aCallback,
-                   bool* aInited)
-    : CallbackObject(cx, aOwner, aCallback, aInited)
-  {
-  }
-
-  /*
-   * Create a CallbackInterface without any sort of interesting games with
-   * compartments, for cases when you want to just use the existing object
-   * as-is.  This constructor can never fail.
-   */
   explicit CallbackInterface(JSObject* aCallback)
     : CallbackObject(aCallback)
   {
   }
 
 protected:
   bool GetCallableProperty(JSContext* cx, const char* aPropName,
                            JS::Value* aCallable);
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -41,47 +41,16 @@ namespace dom {
 class CallbackObject : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(DOM_CALLBACKOBJECT_IID)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackObject)
 
-  /**
-   * Create a CallbackObject.  aCallback is the callback object we're wrapping.
-   * aOwner is the object that will be receiving this CallbackObject as a method
-   * argument, if any.  We need this so we can store our callback object in the
-   * same compartment as our owner.  If *aInited is set to false, an exception
-   * has been thrown.
-   */
-  CallbackObject(JSContext* cx, JSObject* aOwner, JSObject* aCallback,
-                 bool* aInited)
-    : mCallback(nullptr)
-  {
-    // If aOwner is not null, enter the compartment of aOwner's
-    // underlying object.
-    if (aOwner) {
-      aOwner = js::UncheckedUnwrap(aOwner);
-      JSAutoCompartment ac(cx, aOwner);
-      if (!JS_WrapObject(cx, &aCallback)) {
-        *aInited = false;
-        return;
-      }
-    }
-
-    Init(aCallback);
-    *aInited = true;
-  }
-
-  /*
-   * Create a CallbackObject without any sort of interesting games with
-   * compartments, for cases when you want to just use the existing object
-   * as-is.  This constructor can never fail.
-   */
   explicit CallbackObject(JSObject* aCallback)
   {
     Init(aCallback);
   }
 
   virtual ~CallbackObject()
   {
     DropCallback();
@@ -372,22 +341,17 @@ public:
     JSObject* obj;
     if (NS_FAILED(wrappedJS->GetJSObject(&obj)) || !obj) {
       return nullptr;
     }
 
     SafeAutoJSContext cx;
     JSAutoCompartment ac(cx, obj);
 
-    bool inited;
-    nsRefPtr<WebIDLCallbackT> newCallback =
-      new WebIDLCallbackT(cx, nullptr, obj, &inited);
-    if (!inited) {
-      return nullptr;
-    }
+    nsRefPtr<WebIDLCallbackT> newCallback = new WebIDLCallbackT(obj);
     return newCallback.forget();
   }
 
 private:
   static const uintptr_t XPCOMCallbackFlag = 1u;
 
   friend void
   ImplCycleCollectionUnlink<WebIDLCallbackT,
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2265,31 +2265,33 @@ if (NS_FAILED(rv) || !wrappedJS) {
 nsCOMPtr<${nativeType}> tmp = do_QueryObject(wrappedJS.get());
 if (!tmp) {
 ${codeOnFailure}
 }
 ${target} = tmp.forget();""").substitute(self.substitution)
 
 # If this function is modified, modify CGNativeMember.getArg and
 # CGNativeMember.getRetvalInfo accordingly.  The latter cares about the decltype
-# and holdertype we end up using.
+# and holdertype we end up using, because it needs to be able to return the code
+# that will convert those to the actual return value of the callback function.
 def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
                                     isDefinitelyObject=False,
                                     isMember=False,
                                     isOptional=False,
                                     invalidEnumValueFatal=True,
                                     defaultValue=None,
                                     treatNullAs="Default",
                                     treatUndefinedAs="Default",
                                     isEnforceRange=False,
                                     isClamp=False,
                                     isNullOrUndefined=False,
                                     exceptionCode=None,
                                     lenientFloatCode=None,
-                                    allowTreatNonCallableAsNull=False):
+                                    allowTreatNonCallableAsNull=False,
+                                    isCallbackReturnValue=False):
     """
     Get a template for converting a JS value to a native object based on the
     given type and descriptor.  If failureCode is given, then we're actually
     testing whether we can convert the argument to the desired type.  That
     means that failures to convert due to the JS value being the wrong type of
     value need to use failureCode instead of throwing exceptions.  Failures to
     convert that are due to JS exceptions (from toString or valueOf methods) or
     out of memory conditions need to throw exceptions no matter what
@@ -2323,32 +2325,32 @@ def getJSToNativeConversionTemplate(type
     value is out of range.
 
     If lenientFloatCode is not None, it should be used in cases when
     we're a non-finite float that's not unrestricted.
 
     If allowTreatNonCallableAsNull is true, then [TreatNonCallableAsNull]
     extended attributes on nullable callback functions will be honored.
 
+    If isCallbackReturnValue is true, then the declType may be adjusted to make
+    it easier to return from a callback.  Since that type is never directly
+    observable by any consumers of the callback code, this is OK.
+
     The return value from this function is a tuple consisting of four things:
 
     1)  A string representing the conversion code.  This will have template
         substitution performed on it as follows:
 
           ${val} replaced by an expression for the JS::Value in question
           ${valPtr} is a pointer to the JS::Value in question
           ${holderName} replaced by the holder's name, if any
           ${declName} replaced by the declaration's name
           ${haveValue} replaced by an expression that evaluates to a boolean
                        for whether we have a JS::Value.  Only used when
                        defaultValue is not None.
-          ${obj} replaced by an object which, when unwrapped, tells us which
-                 compartment we really want to be working with here, in case
-                 that matters for our conversion.  This is allowed to be null if
-                 we just want to work with the compartment we're already in.
 
     2)  A CGThing representing the native C++ type we're converting to
         (declType).  This is allowed to be None if the conversion code is
         supposed to be used as-is.
     3)  A CGThing representing the type of a "holder" (holderType) which will
         hold a possible reference to the C++ thing whose type we returned in #1,
         or None if no such holder is needed.
     4)  A boolean indicating whether the caller has to do optional-argument handling.
@@ -2485,41 +2487,42 @@ def getJSToNativeConversionTemplate(type
         # deal with this, we typically map WebIDL sequences to our Sequence
         # type, which is in fact memmovable.  The one exception is when we're
         # passing in a sequence directly as an argument without any sort of
         # optional or nullable complexity going on.  In that situation, we can
         # use an AutoSequence instead.  We have to keep using Sequence in the
         # nullable and optional cases because we don't want to leak the
         # AutoSequence type to consumers, which would be unavoidable with
         # Nullable<AutoSequence> or Optional<AutoSequence>.
-        if isMember or isOptional or nullable:
+        if isMember or isOptional or nullable or isCallbackReturnValue:
             sequenceClass = "Sequence"
         else:
             sequenceClass = "AutoSequence"
 
         (elementTemplate, elementDeclType,
          elementHolderType, dealWithOptional) = getJSToNativeConversionTemplate(
             elementType, descriptorProvider, isMember=True,
-            exceptionCode=exceptionCode, lenientFloatCode=lenientFloatCode)
+            exceptionCode=exceptionCode, lenientFloatCode=lenientFloatCode,
+            isCallbackReturnValue=isCallbackReturnValue)
         if dealWithOptional:
             raise TypeError("Shouldn't have optional things in sequences")
         if elementHolderType is not None:
             raise TypeError("Shouldn't need holders for sequences")
 
         typeName = CGTemplatedType(sequenceClass, elementDeclType)
         sequenceType = typeName.define()
         if nullable:
             typeName = CGTemplatedType("Nullable", typeName)
             arrayRef = "const_cast<Nullable<" + sequenceType + " >& >(${declName}).SetValue()"
         else:
             arrayRef = "${declName}"
         # If we're optional or a member, the const will come from the Optional
         # or whatever we're a member of.
         mutableTypeName = typeName
-        if not isOptional and not isMember:
+        if not (isOptional or isMember or isCallbackReturnValue):
             typeName = CGWrapper(typeName, pre="const ")
 
         # NOTE: Keep this in sync with variadic conversions as needed
         templateBody = ("""JSObject* seq = &${val}.toObject();\n
 if (!IsArrayLike(cx, seq)) {
 %s
 }
 uint32_t length;
@@ -2552,18 +2555,16 @@ for (uint32_t i = 0; i < length; ++i) {
                     {
                         "val" : "temp",
                         "valPtr": "temp.address()",
                         "declName" : "slot",
                         # We only need holderName here to handle isExternal()
                         # interfaces, which use an internal holder for the
                         # conversion even when forceOwningType ends up true.
                         "holderName": "tempHolder",
-                        # Use the same ${obj} as for the sequence itself
-                        "obj": "${obj}"
                         }
                     ))).define()
 
         templateBody += "\n}"
         templateBody = wrapObjectTemplate(templateBody, type,
                                           "const_cast< %s & >(${declName}).SetNull()" % mutableTypeName.define())
         return (templateBody, typeName, None, isOptional)
 
@@ -2588,28 +2589,28 @@ for (uint32_t i = 0; i < length; ++i) {
         interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes)
         if len(interfaceMemberTypes) > 0:
             interfaceObject = []
             for memberType in interfaceMemberTypes:
                 if type.isGeckoInterface():
                     name = memberType.inner.identifier.name
                 else:
                     name = memberType.name
-                interfaceObject.append(CGGeneric("(failed = !%s.TrySetTo%s(cx, ${obj}, ${val}, ${valPtr}, tryNext)) || !tryNext" % (unionArgumentObj, name)))
+                interfaceObject.append(CGGeneric("(failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext" % (unionArgumentObj, name)))
                 names.append(name)
             interfaceObject = CGWrapper(CGList(interfaceObject, " ||\n"), pre="done = ", post=";\n", reindent=True)
         else:
             interfaceObject = None
 
         arrayObjectMemberTypes = filter(lambda t: t.isArray() or t.isSequence(), memberTypes)
         if len(arrayObjectMemberTypes) > 0:
             assert len(arrayObjectMemberTypes) == 1
             memberType = arrayObjectMemberTypes[0]
             name = memberType.name
-            arrayObject = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${obj}, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name))
+            arrayObject = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name))
             arrayObject = CGIfWrapper(arrayObject, "IsArrayLike(cx, &argObj)")
             names.append(name)
         else:
             arrayObject = None
 
         dateObjectMemberTypes = filter(lambda t: t.isDate(), memberTypes)
         if len(dateObjectMemberTypes) > 0:
             assert len(dateObjectMemberTypes) == 1
@@ -2622,17 +2623,17 @@ for (uint32_t i = 0; i < length; ++i) {
         else:
             dateObject = None
 
         callbackMemberTypes = filter(lambda t: t.isCallback() or t.isCallbackInterface(), memberTypes)
         if len(callbackMemberTypes) > 0:
             assert len(callbackMemberTypes) == 1
             memberType = callbackMemberTypes[0]
             name = memberType.name
-            callbackObject = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${obj}, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name))
+            callbackObject = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name))
             names.append(name)
         else:
             callbackObject = None
 
         dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes)
         if len(dictionaryMemberTypes) > 0:
             raise TypeError("No support for unwrapping dictionaries as member "
                             "of a union")
@@ -2681,17 +2682,17 @@ for (uint32_t i = 0; i < length; ++i) {
         otherMemberTypes.extend(t for t in memberTypes if t.isPrimitive())
         if len(otherMemberTypes) > 0:
             assert len(otherMemberTypes) == 1
             memberType = otherMemberTypes[0]
             if memberType.isEnum():
                 name = memberType.inner.identifier.name
             else:
                 name = memberType.name
-            other = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${obj}, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name))
+            other = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name))
             names.append(name)
             if hasObjectTypes:
                 other = CGWrapper(CGIndenter(other), "{\n", post="\n}")
                 if object:
                     join = " else "
                 else:
                     other = CGWrapper(other, pre="if (!done) ")
                     join = "\n"
@@ -2769,43 +2770,43 @@ for (uint32_t i = 0; i < length; ++i) {
         assert not isEnforceRange and not isClamp
 
         descriptor = descriptorProvider.getDescriptor(
             type.unroll().inner.identifier.name)
 
         if (descriptor.interface.isCallback() and
             descriptor.interface.identifier.name != "EventListener"):
             if descriptor.workers:
-                if type.nullable():
+                if type.nullable() or isCallbackReturnValue:
                     declType = CGGeneric("JSObject*")
                 else:
                     declType = CGGeneric("NonNull<JSObject>")
                 conversion = "  ${declName} = &${val}.toObject();\n"
             else:
                 name = descriptor.interface.identifier.name
-                if type.nullable():
+                if type.nullable() or isCallbackReturnValue:
                     declType = CGGeneric("nsRefPtr<%s>" % name);
                 else:
                     declType = CGGeneric("OwningNonNull<%s>" % name)
                 conversion = (
-                    "  bool inited;\n"
-                    "  ${declName} = new %s(cx, ${obj}, &${val}.toObject(), &inited);\n"
-                    "  if (!inited) {\n"
-                    "%s\n"
-                    "  }\n" % (name, CGIndenter(exceptionCodeIndented).define()))
+                    "  ${declName} = new %s(&${val}.toObject());\n" % name)
+
             template = wrapObjectTemplate(conversion, type,
                                           "${declName} = nullptr",
                                           failureCode)
             return (template, declType, None, isOptional)
 
         # This is an interface that we implement as a concrete class
         # or an XPCOM interface.
 
-        # Allow null pointers for nullable types and old-binding classes
-        argIsPointer = type.nullable() or type.unroll().inner.isExternal()
+        # Allow null pointers for nullable types and old-binding classes, and
+        # use an nsRefPtr or raw pointer for callback return values to make
+        # them easier to return.
+        argIsPointer = (type.nullable() or type.unroll().inner.isExternal() or
+                        isCallbackReturnValue)
 
         # Sequences and non-worker callbacks have to hold a strong ref to the
         # thing being passed down.
         forceOwningType = (descriptor.interface.isCallback() and
                            not descriptor.workers) or isMember
 
         typeName = descriptor.nativeType
         typePtr = typeName + "*"
@@ -3097,21 +3098,17 @@ for (uint32_t i = 0; i < length; ++i) {
             conversion = "  ${declName} = &${val}.toObject();\n"
         else:
             name = type.unroll().identifier.name
             if type.nullable():
                 declType = CGGeneric("nsRefPtr<%s>" % name);
             else:
                 declType = CGGeneric("OwningNonNull<%s>" % name)
             conversion = (
-                "  bool inited;\n"
-                "  ${declName} = new %s(cx, ${obj}, &${val}.toObject(), &inited);\n"
-                "  if (!inited) {\n"
-                "%s\n"
-                "  }\n" % (name, CGIndenter(exceptionCodeIndented).define()))
+                "  ${declName} = new %s(&${val}.toObject());\n" % name)
 
         if allowTreatNonCallableAsNull and type.treatNonCallableAsNull():
             haveCallable = "JS_ObjectIsCallable(cx, &${val}.toObject())"
             if not isDefinitelyObject:
                 haveCallable = "${val}.isObject() && " + haveCallable
             if defaultValue is not None:
                 assert(isinstance(defaultValue, IDLNullValue))
                 haveCallable = "${haveValue} && " + haveCallable
@@ -3231,17 +3228,17 @@ for (uint32_t i = 0; i < length; ++i) {
             # Check that the value we have can in fact be converted to
             # a dictionary, and return failureCode if not.
             template = CGIfWrapper(
                 CGGeneric(failureCode),
                 "!IsConvertibleToDictionary(cx, &${val}.toObject())").define() + "\n\n"
         else:
             template = ""
 
-        template += ("if (!%s.Init(cx, ${obj}, %s)) {\n"
+        template += ("if (!%s.Init(cx, %s)) {\n"
                      "%s\n"
                      "}" % (selfRef, val, exceptionCodeIndented.define()))
 
         return (template, declType, None, False)
 
     if type.isVoid():
         assert not isOptional
         # This one only happens for return values, and its easy: Just
@@ -3878,17 +3875,17 @@ def dictionaryNeedsCx(dictionary, descri
     return (any(typeNeedsCx(m.type, descriptorProvider) for m in dictionary.members) or
         (dictionary.parent and dictionaryNeedsCx(dictionary.parent, descriptorProvider)))
 
 # Returns a tuple consisting of a CGThing containing the type of the return
 # value, or None if there is no need for a return value, and a boolean signaling
 # whether the return value is passed in an out parameter.
 #
 # Whenever this is modified, please update CGNativeMember.getRetvalInfo as
-# needed
+# needed to keep the types compatible.
 def getRetvalDeclarationForType(returnType, descriptorProvider,
                                 resultAlreadyAddRefed,
                                 isMember=False):
     if returnType is None or returnType.isVoid():
         # Nothing to declare
         return None, False
     if returnType.isPrimitive() and returnType.tag() in builtinNames:
         result = CGGeneric(builtinNames[returnType.tag()])
@@ -5273,24 +5270,23 @@ return true;"""
                            "}")
     else:
         jsConversion = string.Template(template).substitute(
             {
                 "val": "value",
                 "valPtr": "pvalue",
                 "declName": "SetAs" + name + "()",
                 "holderName": "m" + name + "Holder",
-                "obj": "scopeObj"
                 }
             )
         jsConversion = CGWrapper(CGGeneric(jsConversion),
                                  post="\n"
                                       "return true;")
         setter = CGWrapper(CGIndenter(jsConversion),
-                           pre="bool TrySetTo" + name + "(JSContext* cx, JSObject* scopeObj, const JS::Value& value, JS::Value* pvalue, bool& tryNext)\n"
+                           pre="bool TrySetTo" + name + "(JSContext* cx, const JS::Value& value, JS::Value* pvalue, bool& tryNext)\n"
                                "{\n"
                                "  tryNext = false;\n",
                            post="\n"
                                 "}")
 
     return {
                 "name": name,
                 "structType": structType,
@@ -6464,27 +6460,25 @@ class CGDOMJSProxyHandler_delete(ClassMe
             deleter = self.descriptor.operations[type + 'Deleter']
             if deleter:
                 if (not deleter.signatures()[0][0].isPrimitive() or
                     deleter.signatures()[0][0].nullable() or
                     deleter.signatures()[0][0].tag() != IDLType.Tags.bool):
                     setBp = "*bp = true;"
                 else:
                     setBp = ("if (found) {\n"
-                             "  // XXXbz we should throw as needed if Throw is true\n"
                              "  *bp = result;\n"
                              "} else {\n"
                              "  *bp = true;\n"
                              "}")
                 body = (eval("CGProxy%sDeleter" % type)(self.descriptor).define() +
                         setBp)
             elif eval("self.descriptor.supports%sProperties()" % type):
                 body = (eval("CGProxy%sPresenceChecker" % type)(self.descriptor).define() +
                         "if (found) {\n"
-                        "  // XXXbz we should throw if Throw is true!\n"
                         "  *bp = false;\n"
                         "} else {\n"
                         "  *bp = true;\n"
                         "}")
             else:
                 body = None
             return body
 
@@ -6503,17 +6497,16 @@ class CGDOMJSProxyHandler_delete(ClassMe
                        "}\n") % self.descriptor.nativeType
 
         if UseHolderForUnforgeable(self.descriptor):
             unforgeable = ("JSBool hasUnforgeable;\n"
                            "if (!JS_HasPropertyById(cx, ${holder}, id, &hasUnforgeable)) {\n"
                            "  return false;\n"
                            "}\n"
                            "if (hasUnforgeable) {\n"
-                           "  // We should throw if Throw is true!\n"
                            "  *bp = false;\n"
                            "  return true;\n"
                            "}")
             delete += CallOnUnforgeableHolder(self.descriptor, unforgeable)
             delete += "\n"
 
         namedBody = getDeleterBody("Named")
         if namedBody is not None:
@@ -7069,53 +7062,53 @@ class CGDictionary(CGThing):
         memberDecls = ["  %s %s;" %
                        (self.getMemberType(m),
                         self.makeMemberName(m[0].identifier.name))
                        for m in self.memberInfo]
 
         return (string.Template(
                 "struct ${selfName} ${inheritance}{\n"
                 "  ${selfName}() {}\n"
-                "  bool Init(JSContext* cx, JS::Handle<JSObject*> scopeObj, const JS::Value& val);\n"
+                "  bool Init(JSContext* cx, const JS::Value& val);\n"
                 "  bool ToObject(JSContext* cx, JSObject* parentObject, JS::Value *vp);\n"
                 "\n" +
                 ("  bool Init(const nsAString& aJSON)\n"
                  "  {\n"
                  "    Maybe<JSAutoRequest> ar;\n"
                  "    Maybe<JSAutoCompartment> ac;\n"
                  "    Maybe< JS::Rooted<JS::Value> > json;\n"
                  "    JSContext* cx = ParseJSON(aJSON, ar, ac, json);\n"
                  "    NS_ENSURE_TRUE(cx, false);\n"
-                 "    return Init(cx, JS::NullPtr(), json.ref());\n"
+                 "    return Init(cx, json.ref());\n"
                  "  }\n" if not self.workers else "") +
                 "\n" +
                 "\n".join(memberDecls) + "\n"
                 "private:\n"
                 "  // Disallow copy-construction\n"
                 "  ${selfName}(const ${selfName}&) MOZ_DELETE;\n" +
                 # NOTE: jsids are per-runtime, so don't use them in workers
                 ("  static bool InitIds(JSContext* cx);\n"
                  "  static bool initedIds;\n" if self.needToInitIds else "") +
                 "\n".join("  static jsid " +
                           self.makeIdName(m.identifier.name) + ";" for
                           m in d.members) + "\n"
                 "};\n"
                 "struct ${selfName}Initializer : public ${selfName} {\n"
                 "  ${selfName}Initializer() {\n"
                 "    // Safe to pass a null context if we pass a null value\n"
-                "    Init(nullptr, JS::NullPtr(), JS::NullValue());\n"
+                "    Init(nullptr, JS::NullValue());\n"
                 "  }\n"
                 "};").substitute( { "selfName": self.makeClassName(d),
                                     "inheritance": inheritance }))
 
     def define(self):
         d = self.dictionary
         if d.parent:
             initParent = ("// Per spec, we init the parent's members first\n"
-                          "if (!%s::Init(cx, scopeObj, val)) {\n"
+                          "if (!%s::Init(cx, val)) {\n"
                           "  return false;\n"
                           "}\n" % self.makeClassName(d.parent))
             toObjectParent = ("// Per spec, we define the parent's members first\n"
                               "if (!%s::ToObject(cx, parentObject, vp)) {\n"
                               "  return false;\n"
                               "}\n" % self.makeClassName(d.parent))
             ensureObject = "JS::Rooted<JSObject*> obj(cx, &vp->toObject());\n"
         else:
@@ -7153,17 +7146,17 @@ class CGDictionary(CGThing):
              "{\n"
              "  MOZ_ASSERT(!initedIds);\n"
              "${idInit}\n"
              "  initedIds = true;\n"
              "  return true;\n"
              "}\n"
              "\n" if self.needToInitIds else "") +
             "bool\n"
-            "${selfName}::Init(JSContext* cx, JS::Handle<JSObject*> scopeObj, const JS::Value& val)\n"
+            "${selfName}::Init(JSContext* cx, const JS::Value& val)\n"
             "{\n"
             "  // Passing a null JSContext is OK only if we're initing from null,\n"
             "  // Since in that case we will not have to do any property gets\n"
             "  MOZ_ASSERT_IF(!cx, val.isNull());\n" +
             # NOTE: jsids are per-runtime, so don't use them in workers
             ("  if (cx && !initedIds && !InitIds(cx)) {\n"
              "    return false;\n"
              "  }\n" if self.needToInitIds else "") +
@@ -7231,18 +7224,17 @@ class CGDictionary(CGThing):
         (member, (templateBody, declType,
                   holderType, dealWithOptional)) = memberInfo
         replacements = { "val": "temp",
                          "valPtr": "temp.address()",
                          "declName": self.makeMemberName(member.identifier.name),
                          # We need a holder name for external interfaces, but
                          # it's scoped down to the conversion so we can just use
                          # anything we want.
-                         "holderName": "holder",
-                         "obj": "scopeObj" }
+                         "holderName": "holder" }
         # We can't handle having a holderType here
         assert holderType is None
         if dealWithOptional:
             replacements["declName"] = "(" + replacements["declName"] + ".Value())"
         if member.defaultValue:
             replacements["haveValue"] = "found"
 
         # NOTE: jsids are per-runtime, so don't use them in workers
@@ -7775,19 +7767,19 @@ class CGNativeMember(ClassMethod):
                 # The decl is an OwningNonNull or nsRefPtr, depending
                 # on whether we're nullable.
                 returnCode = "return ${declName}.forget();"
             elif type.nullable():
                 # Decl is a raw pointer
                 returnCode = ("NS_IF_ADDREF(${declName});\n"
                               "return ${declName};")
             else:
-                # Decl is a NonNull.
-                returnCode = ("NS_ADDREF(${declName}.Ptr());\n"
-                              "return ${declName}.Ptr();")
+                # Decl is a non-null raw pointer.
+                returnCode = ("NS_ADDREF(${declName});\n"
+                              "return ${declName};")
             return result.define(), "nullptr", returnCode
         if type.isCallback():
             return ("already_AddRefed<%s>" % type.unroll().identifier.name,
                     "nullptr", "return ${declName}.forget();")
         if type.isAny():
             return "JS::Value", "JS::UndefinedValue()", "return ${declName};"
         if type.isObject():
             if type.nullable():
@@ -7797,35 +7789,29 @@ class CGNativeMember(ClassMethod):
             return "JSObject*", "nullptr", returnCode
         if type.isSpiderMonkeyInterface():
             if type.nullable():
                 returnCode = "return ${declName} ? ${declName}->Obj() : nullptr;"
             else:
                 returnCode = ("return static_cast<%s&>(${declName}).Obj();" % type.name)
             return "JSObject*", "nullptr", returnCode
         if type.isSequence():
+            # If we want to handle sequence-of-sequences return values, we're
+            # going to need to fix example codegen to not produce nsTArray<void>
+            # for the relevant argument...
             assert not isMember
-            # Outparam.  Copying is a bit suboptimal, but hard to avoid: We
-            # could SwapElements if we cast away const, but even then our
-            # Sequence is an auto-array, and will tend to copy.  The good news
-            # is that callbacks that return sequences should be pretty rare
-            # anyway, and if we have to we can rejigger the codegen here to use
-            # a non-const non-auto array if it's ever necessary.
-            # In any case, we only support sequences of primitive values for
-            # returning from a callback, for now.
-            if not type.unroll().isPrimitive():
-                return "void", ""
+            # Outparam.
             if type.nullable():
                 returnCode = ("if (${declName}.IsNull()) {\n"
                               "  retval.SetNull();\n"
                               "} else {\n"
-                              "  retval.SetValue() = ${declName}.Value();\n"
+                              "  retval.SetValue().SwapElements(${declName}.Value());\n"
                               "}")
             else:
-                returnCode = "retval = ${declName};"
+                returnCode = "retval.SwapElements(${declName});"
             return "void", "", returnCode
         raise TypeError("Don't know how to declare return value for %s" %
                         type)
 
     def getArgs(self, returnType, argList):
         args = [self.getArg(arg) for arg in argList]
         # Now the outparams
         if returnType.isString():
@@ -8502,24 +8488,22 @@ class CGCallback(CGClass):
                 realMethods.extend(self.getMethodImpls(method))
         CGClass.__init__(self, name,
                          bases=[ClassBase(baseName)],
                          constructors=self.getConstructors(),
                          methods=realMethods+getters+setters)
 
     def getConstructors(self):
         return [ClassConstructor(
-            [Argument("JSContext*", "cx"),
-             Argument("JSObject*", "aOwner"),
-             Argument("JSObject*", "aCallback"),
-             Argument("bool*", "aInited")],
+            [Argument("JSObject*", "aCallback")],
             bodyInHeader=True,
             visibility="public",
+            explicit=True,
             baseConstructors=[
-                "%s(cx, aOwner, aCallback, aInited)" % self.baseName
+                "%s(aCallback)" % self.baseName
                 ])]
 
     def getMethodImpls(self, method):
         assert method.needThisHandling
         args = list(method.args)
         # Strip out the JSContext*/JSObject* args
         # that got added.
         assert args[0].name == "cx" and args[0].argType == "JSContext*"
@@ -8602,26 +8586,16 @@ class CGCallbackInterface(CGCallback):
                    if not a.readonly]
         methods = [m for m in iface.members
                    if m.isMethod() and not m.isStatic()]
         methods = [CallbackOperation(m, sig, descriptor) for m in methods
                    for sig in m.signatures()]
         CGCallback.__init__(self, iface, descriptor, "CallbackInterface",
                             methods, getters=getters, setters=setters)
 
-    def getConstructors(self):
-        return CGCallback.getConstructors(self) + [
-            ClassConstructor(
-                [Argument("JSObject*", "aCallback")],
-                bodyInHeader=True,
-                visibility="public",
-                explicit=True,
-                baseConstructors=["CallbackInterface(aCallback)"])
-            ]
-
 class FakeMember():
     def __init__(self):
         self.treatUndefinedAs = self.treatNullAs = "Default"
     def isStatic(self):
         return False
     def isAttr(self):
         return False
     def isMethod(self):
@@ -8713,17 +8687,18 @@ class CallbackMember(CGNativeMember):
             # wrapping things into our current compartment (that of mCallback)
             # is what we want.
             "obj": "nullptr"
             }
 
         convertType = instantiateJSToNativeConversionTemplate(
             getJSToNativeConversionTemplate(self.retvalType,
                                             self.descriptor,
-                                            exceptionCode=self.exceptionCode),
+                                            exceptionCode=self.exceptionCode,
+                                            isCallbackReturnValue=True),
             replacements)
         assignRetval = string.Template(
             self.getRetvalInfo(self.retvalType,
                                False)[2]).substitute(replacements)
         return convertType.define() + "\n" + assignRetval
 
     def getArgConversions(self):
         # Just reget the arglist from self.originalSig, because our superclasses
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -15,16 +15,25 @@ interface TestRenamedInterface {
 
 callback interface TestCallbackInterface {
   readonly attribute long foo;
   attribute DOMString bar;
   void doSomething();
   long doSomethingElse(DOMString arg, TestInterface otherArg);
   void doSequenceLongArg(sequence<long> arg);
   void doSequenceStringArg(sequence<DOMString> arg);
+  sequence<long> getSequenceOfLong();
+  sequence<TestInterface> getSequenceOfInterfaces();
+  sequence<TestInterface>? getNullableSequenceOfInterfaces();
+  sequence<TestInterface?> getSequenceOfNullableInterfaces();
+  sequence<TestInterface?>? getNullableSequenceOfNullableInterfaces();
+  sequence<TestCallbackInterface> getSequenceOfCallbackInterfaces();
+  sequence<TestCallbackInterface>? getNullableSequenceOfCallbackInterfaces();
+  sequence<TestCallbackInterface?> getSequenceOfNullableCallbackInterfaces();
+  sequence<TestCallbackInterface?>? getNullableSequenceOfNullableCallbackInterfaces();
 };
 
 callback interface TestSingleOperationCallbackInterface {
   TestInterface doSomething(short arg, sequence<double> anotherArg);
 };
 
 enum TestEnum {
   "a",
--- a/dom/bindings/test/TestJSImplGen.webidl
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -136,25 +136,24 @@ interface TestJSImplInterface {
   void passOptionalSelfWithDefault(optional TestJSImplInterface? arg = null);
 
   // Non-wrapper-cache interface types
   [Creator]
   TestNonWrapperCacheInterface receiveNonWrapperCacheInterface();
   [Creator]
   TestNonWrapperCacheInterface? receiveNullableNonWrapperCacheInterface();
 
-  // Can't return sequences of interfaces from callback interface methods.  See bug 843264.
-  //[Creator]
-  //sequence<TestNonWrapperCacheInterface> receiveNonWrapperCacheInterfaceSequence();
-  //[Creator]
-  //sequence<TestNonWrapperCacheInterface?> receiveNullableNonWrapperCacheInterfaceSequence();
-  //[Creator]
-  //sequence<TestNonWrapperCacheInterface>? receiveNonWrapperCacheInterfaceNullableSequence();
-  //[Creator]
-  //sequence<TestNonWrapperCacheInterface?>? receiveNullableNonWrapperCacheInterfaceNullableSequence();
+  [Creator]
+  sequence<TestNonWrapperCacheInterface> receiveNonWrapperCacheInterfaceSequence();
+  [Creator]
+  sequence<TestNonWrapperCacheInterface?> receiveNullableNonWrapperCacheInterfaceSequence();
+  [Creator]
+  sequence<TestNonWrapperCacheInterface>? receiveNonWrapperCacheInterfaceNullableSequence();
+  [Creator]
+  sequence<TestNonWrapperCacheInterface?>? receiveNullableNonWrapperCacheInterfaceNullableSequence();
 
   // Non-castable interface types
   IndirectlyImplementedInterface receiveOther();
   IndirectlyImplementedInterface? receiveNullableOther();
   // Callback interface ignores 'resultNotAddRefed'. See bug 843272.
   //IndirectlyImplementedInterface receiveWeakOther();
   //IndirectlyImplementedInterface? receiveWeakNullableOther();
 
@@ -215,41 +214,38 @@ interface TestJSImplInterface {
   sequence<long>? receiveNullableSequence();
   sequence<long?> receiveSequenceOfNullableInts();
   sequence<long?>? receiveNullableSequenceOfNullableInts();
   void passSequence(sequence<long> arg);
   void passNullableSequence(sequence<long>? arg);
   void passSequenceOfNullableInts(sequence<long?> arg);
   void passOptionalSequenceOfNullableInts(optional sequence<long?> arg);
   void passOptionalNullableSequenceOfNullableInts(optional sequence<long?>? arg);
-  // Can't return sequences of interfaces from callback interface methods.  See bug 843264.
-  //sequence<TestJSImplInterface> receiveCastableObjectSequence();
-  //sequence<TestCallbackInterface> receiveCallbackObjectSequence();
-  //sequence<TestJSImplInterface?> receiveNullableCastableObjectSequence();
-  //sequence<TestCallbackInterface?> receiveNullableCallbackObjectSequence();
-  //sequence<TestJSImplInterface>? receiveCastableObjectNullableSequence();
-  //sequence<TestJSImplInterface?>? receiveNullableCastableObjectNullableSequence();
-  // Callback interface ignores 'resultNotAddRefed'. See bug 843272.
-  //sequence<TestJSImplInterface> receiveWeakCastableObjectSequence();
-  //sequence<TestJSImplInterface?> receiveWeakNullableCastableObjectSequence();
-  //sequence<TestJSImplInterface>? receiveWeakCastableObjectNullableSequence();
-  //sequence<TestJSImplInterface?>? receiveWeakNullableCastableObjectNullableSequence();
+  sequence<TestJSImplInterface> receiveCastableObjectSequence();
+  sequence<TestCallbackInterface> receiveCallbackObjectSequence();
+  sequence<TestJSImplInterface?> receiveNullableCastableObjectSequence();
+  sequence<TestCallbackInterface?> receiveNullableCallbackObjectSequence();
+  sequence<TestJSImplInterface>? receiveCastableObjectNullableSequence();
+  sequence<TestJSImplInterface?>? receiveNullableCastableObjectNullableSequence();
+  sequence<TestJSImplInterface> receiveWeakCastableObjectSequence();
+  sequence<TestJSImplInterface?> receiveWeakNullableCastableObjectSequence();
+  sequence<TestJSImplInterface>? receiveWeakCastableObjectNullableSequence();
+  sequence<TestJSImplInterface?>? receiveWeakNullableCastableObjectNullableSequence();
   void passCastableObjectSequence(sequence<TestJSImplInterface> arg);
   void passNullableCastableObjectSequence(sequence<TestJSImplInterface?> arg);
   void passCastableObjectNullableSequence(sequence<TestJSImplInterface>? arg);
   void passNullableCastableObjectNullableSequence(sequence<TestJSImplInterface?>? arg);
   void passOptionalSequence(optional sequence<long> arg);
   void passOptionalNullableSequence(optional sequence<long>? arg);
   void passOptionalNullableSequenceWithDefaultValue(optional sequence<long>? arg = null);
   void passOptionalObjectSequence(optional sequence<TestJSImplInterface> arg);
   void passExternalInterfaceSequence(sequence<TestExternalInterface> arg);
   void passNullableExternalInterfaceSequence(sequence<TestExternalInterface?> arg);
 
-  // Can't return sequences of interfaces from callback interface methods.  See bug 843264.
-  //sequence<DOMString> receiveStringSequence();
+  sequence<DOMString> receiveStringSequence();
   // Callback interface problem.  See bug 843261.
   //void passStringSequence(sequence<DOMString> arg);
   // "Can't handle sequence member 'any'; need to sort out rooting issues"
   //sequence<any> receiveAnySequence();
   //sequence<any>? receiveNullableAnySequence();
 
   void passSequenceOfSequences(sequence<sequence<long>> arg);
   //sequence<sequence<long>> receiveSequenceOfSequences();
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1073,17 +1073,17 @@ nsPluginHost::IsPluginClickToPlayForType
   if (!plugin) {
     return NS_ERROR_UNEXPECTED;
   }
 
   uint32_t blocklistState = nsIBlocklistService::STATE_NOT_BLOCKED;
   nsresult rv = GetBlocklistStateForType(aMimeType.Data(), &blocklistState);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (mPluginsClickToPlay ||
+  if ((mPluginsClickToPlay && plugin->IsClicktoplay()) ||
       blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE ||
       blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE) {
     *aResult = true;
   }
   else {
     *aResult = false;
   }
 
--- a/dom/plugins/test/mochitest/Makefile.in
+++ b/dom/plugins/test/mochitest/Makefile.in
@@ -59,17 +59,16 @@ MOCHITEST_FILES = \
   crashing_subpage.html \
   test_GCrace.html \
   test_propertyAndMethod.html \
   test_bug539565-1.html \
   test_bug539565-2.html \
   test_bug771202.html \
   file_bug771202.html \
   test_bug777098.html \
-  test_bug751809.html \
   test_bug813906.html \
   test_bug784131.html \
   test_bug854082.html \
   test_bug863792.html \
   file_bug863792.html \
   test_enumerate.html \
   test_npruntime_construct.html \
   307-xo-redirect.sjs \
@@ -101,17 +100,19 @@ MOCHITEST_FILES += \
 endif
 
 MOCHITEST_CHROME_FILES = \
   utils.js \
   test_clear_site_data.html \
   test_npruntime.xul   \
   test_wmode.xul \
   test_bug479979.xul \
+  test_bug751809.html \
   test_refresh_navigator_plugins.html \
+  test_plugin_tag_clicktoplay.html \
   privatemode_perwindowpb.xul \
   test_privatemode_perwindowpb.xul \
   $(NULL)
 
 ifneq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 MOCHITEST_FILES += \
   test_instance_re-parent-windowed.html \
   test_visibility.html \
--- a/dom/plugins/test/mochitest/test_bug751809.html
+++ b/dom/plugins/test/mochitest/test_bug751809.html
@@ -1,24 +1,40 @@
 <html>
 <head>
   <title>Bug 751809</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
-  <script type="application/javascript">
-  SpecialPowers.setBoolPref("plugins.click_to_play", true);
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript;version=1.7">
+  function getTestPlugin() {
+    let ph = Components.classes["@mozilla.org/plugin/host;1"]
+               .getService(Components.interfaces.nsIPluginHost);
+    let tags = ph.getPluginTags();
+
+    // Find the test plugin
+    for (let i = 0; i < tags.length; i++) {
+      if (tags[i].name == "Test Plug-in")
+        return tags[i];
+    }
+      ok(false, "Unable to find plugin");
+      return null;
+    }
+
+  Components.utils.import("resource://gre/modules/Services.jsm");
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  let plugin = getTestPlugin();
+  plugin.enabledState = Components.interfaces.nsIPluginTag.STATE_CLICKTOPLAY;
   </script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 
 <body onload="go();">
   <embed id="plugin" type="application/x-test" width="400" height="400" drawmode="solid" color="FF00FFFF"></embed>
 
-  <script type="application/javascript">
+  <script type="application/javascript;version=1.7">
 
   SimpleTest.waitForExplicitFinish();
   netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
 
   const Ci = Components.interfaces;
   const utils = window.QueryInterface(Ci.nsIInterfaceRequestor).
                                     getInterface(Ci.nsIDOMWindowUtils);
 
@@ -81,15 +97,17 @@
   function afterFirstClick() {
     var plugin = document.getElementById('plugin');
     try {
       is(plugin.getMouseUpEventCount(), 1, "Plugin should have received 1 mouse up event.");
     } catch(e) {
       ok(false, "plugin.getMouseUpEventCount() shouldn't throw");
     }
 
-    SpecialPowers.clearUserPref("plugins.click_to_play");
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    let plugin = getTestPlugin();
+    plugin.enabledState = Components.interfaces.nsIPluginTag.STATE_ENABLED;
     SimpleTest.finish();
   }
 
   </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/test_plugin_tag_clicktoplay.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta><charset="utf-8"/>
+    <title>Test Modifying Plugin click-to-play Flag</title>
+    <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
+  </head>
+  <body>
+    <script class="testbody" type="application/javascript">
+      Components.utils.import("resource://gre/modules/Services.jsm");
+      var pluginHost = Components.classes["@mozilla.org/plugin/host;1"]
+                       .getService(Components.interfaces.nsIPluginHost);
+      ok(!pluginHost.isPluginClickToPlayForType("application/x-test"), "click-to-play should be off to begin with");
+      Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+      var pluginTags = pluginHost.getPluginTags();
+      var testPlugin = null;
+      for (var plugin of pluginTags) {
+        if (plugin.name == "Test Plug-in") {
+          testPlugin = plugin;
+          break;
+        }
+      }
+      ok(testPlugin, "Should have Test Plug-in");
+      testPlugin.enabledState = Components.interfaces.nsIPluginTag.STATE_CLICKTOPLAY;
+      ok(pluginHost.isPluginClickToPlayForType("application/x-test"), "click-to-play should be on for Test Plug-in now");
+      ok(!pluginHost.isPluginClickToPlayForType("application/x-second-test"), "click-to-play should still be off for the Second Test Plug-in");
+
+      testPlugin.enabledState = Components.interfaces.nsIPluginTag.STATE_ENABLED;
+      ok(!pluginHost.isPluginClickToPlayForType("application/x-test"), "click-to-play should be off for Test Plug-in now");
+
+      Services.prefs.clearUserPref("plugins.click_to_play");
+    </script>
+  </body>
+</html>
--- a/dom/workers/XMLHttpRequest.h
+++ b/dom/workers/XMLHttpRequest.h
@@ -78,17 +78,17 @@ public:
               ErrorResult& aRv);
 
   static XMLHttpRequest*
   Constructor(const WorkerGlobalObject& aGlobal, const nsAString& ignored,
               ErrorResult& aRv)
   {
     // Pretend like someone passed null, so we can pick up the default values
     MozXMLHttpRequestParametersWorkers params;
-    if (!params.Init(aGlobal.GetContext(), JS::NullPtr(), JS::NullValue())) {
+    if (!params.Init(aGlobal.GetContext(), JS::NullValue())) {
       aRv.Throw(NS_ERROR_UNEXPECTED);
       return nullptr;
     }
 
     return Constructor(aGlobal, params, aRv);
   }
 
   void
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -248,16 +248,22 @@ public:
 
   /**
    * Mostly the compositor will pull the size from a widget and this method will
    * be ignored, but compositor implementations are free to use it if they like.
    */
   virtual void SetDestinationSurfaceSize(const gfx::IntSize& aSize) = 0;
 
   /**
+   * Declare an offset to use when rendering layers. This will be ignored when
+   * rendering to a target instead of the screen.
+   */
+  virtual void SetScreenRenderOffset(const gfx::Point& aOffset) = 0;
+
+  /**
    * Tell the compositor to actually draw a quad. What to do draw and how it is
    * drawn is specified by aEffectChain. aRect is the quad to draw, in user space.
    * aTransform transforms from user space to screen space. aOffset is the
    * offset of the render target from 0,0 of the screen. If texture coords are
    * required, these will be in the primary effect in the effect chain.
    */
   virtual void DrawQuad(const gfx::Rect& aRect, const gfx::Rect& aClipRect,
                         const EffectChain& aEffectChain,
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -880,17 +880,17 @@ void
 CompositorParent::TransformScrollableLayer(Layer* aLayer, const gfx3DMatrix& aRootTransform)
 {
   ShadowLayer* shadow = aLayer->AsShadowLayer();
   ContainerLayer* container = aLayer->AsContainerLayer();
 
   const FrameMetrics& metrics = container->GetFrameMetrics();
   // We must apply the resolution scale before a pan/zoom transform, so we call
   // GetTransform here.
-  const gfx3DMatrix& currentTransform = aLayer->GetTransform();
+  gfx3DMatrix currentTransform = aLayer->GetTransform();
 
   gfx3DMatrix treeTransform;
 
   // Translate fixed position layers so that they stay in the correct position
   // when mScrollOffset and metricsScrollOffset differ.
   gfxPoint offset;
   gfxSize scaleDiff;
 
@@ -929,20 +929,25 @@ CompositorParent::TransformScrollableLay
     NS_lround(displayPortLayersPixels.y * devPixelRatioY),
     NS_lround(displayPortLayersPixels.width * devPixelRatioX),
     NS_lround(displayPortLayersPixels.height * devPixelRatioY));
 
   displayPortDevPixels.x += scrollOffsetDevPixels.x;
   displayPortDevPixels.y += scrollOffsetDevPixels.y;
 
   gfx::Margin fixedLayerMargins(0, 0, 0, 0);
+  float offsetX = 0, offsetY = 0;
   SyncViewportInfo(displayPortDevPixels, 1/rootScaleX, mLayersUpdated,
-                   mScrollOffset, mXScale, mYScale, fixedLayerMargins);
+                   mScrollOffset, mXScale, mYScale, fixedLayerMargins,
+                   offsetX, offsetY);
   mLayersUpdated = false;
 
+  // Apply the render offset
+  mLayerManager->GetCompositor()->SetScreenRenderOffset(gfx::Point(offsetX, offsetY));
+
   // Handle transformations for asynchronous panning and zooming. We determine the
   // zoom used by Gecko from the transformation set on the root layer, and we
   // determine the scroll offset used by Gecko from the frame metrics of the
   // primary scrollable layer. We compare this to the desired zoom and scroll
   // offset in the view transform we obtained from Java in order to compute the
   // transformation we need to apply.
   float tempScaleDiffX = rootScaleX * mXScale;
   float tempScaleDiffY = rootScaleY * mYScale;
@@ -1086,21 +1091,23 @@ CompositorParent::SetPageRect(const gfx:
   AndroidBridge::Bridge()->SetPageRect(aCssPageRect);
 #endif
 }
 
 void
 CompositorParent::SyncViewportInfo(const nsIntRect& aDisplayPort,
                                    float aDisplayResolution, bool aLayersUpdated,
                                    nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
-                                   gfx::Margin& aFixedLayerMargins)
+                                   gfx::Margin& aFixedLayerMargins, float& aOffsetX,
+                                   float& aOffsetY)
 {
 #ifdef MOZ_WIDGET_ANDROID
   AndroidBridge::Bridge()->SyncViewportInfo(aDisplayPort, aDisplayResolution, aLayersUpdated,
-                                            aScrollOffset, aScaleX, aScaleY, aFixedLayerMargins);
+                                            aScrollOffset, aScaleX, aScaleY, aFixedLayerMargins,
+                                            aOffsetX, aOffsetY);
 #endif
 }
 
 PLayerTransactionParent*
 CompositorParent::AllocPLayerTransaction(const LayersBackend& aBackendHint,
                                          const uint64_t& aId,
                                          TextureFactoryIdentifier* aTextureFactoryIdentifier)
 {
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -175,17 +175,17 @@ protected:
   virtual bool DeallocPLayerTransaction(PLayerTransactionParent* aLayers);
   virtual void ScheduleTask(CancelableTask*, int);
   virtual void Composite();
   virtual void ComposeToTarget(gfxContext* aTarget);
   virtual void SetFirstPaintViewport(const nsIntPoint& aOffset, float aZoom, const nsIntRect& aPageRect, const gfx::Rect& aCssPageRect);
   virtual void SetPageRect(const gfx::Rect& aCssPageRect);
   virtual void SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
                                 nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
-                                gfx::Margin& aFixedLayerMargins);
+                                gfx::Margin& aFixedLayerMargins, float& aOffsetX, float& aOffsetY);
   void SetEGLSurfaceSize(int width, int height);
 
 private:
   void PauseComposition();
   void ResumeComposition();
   void ResumeCompositionAndResize(int width, int height);
   void ForceComposition();
 
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -616,16 +616,19 @@ CompositorOGL::PrepareViewport(const gfx
   // though.
 
   // Matrix to transform (0, 0, aWidth, aHeight) to viewport space (-1.0, 1.0,
   // 2, 2) and flip the contents.
   gfxMatrix viewMatrix;
   viewMatrix.Translate(-gfxPoint(1.0, -1.0));
   viewMatrix.Scale(2.0f / float(aSize.width), 2.0f / float(aSize.height));
   viewMatrix.Scale(1.0f, -1.0f);
+  if (!mTarget) {
+    viewMatrix.Translate(gfxPoint(mRenderOffset.x, mRenderOffset.y));
+  }
 
   viewMatrix = aWorldTransform * viewMatrix;
 
   gfx3DMatrix matrix3d = gfx3DMatrix::From2D(viewMatrix);
   matrix3d._33 = 0.0f;
 
   SetLayerProgramProjectionMatrix(matrix3d);
 }
--- a/gfx/layers/opengl/CompositorOGL.h
+++ b/gfx/layers/opengl/CompositorOGL.h
@@ -85,16 +85,20 @@ public:
   }
 
   /**
    * Set the size of the EGL surface we're rendering to, if we're rendering to
    * an EGL surface.
    */
   virtual void SetDestinationSurfaceSize(const gfx::IntSize& aSize) MOZ_OVERRIDE;
 
+  virtual void SetScreenRenderOffset(const gfx::Point& aOffset) MOZ_OVERRIDE {
+    mRenderOffset = aOffset;
+  }
+
   virtual void MakeCurrent(MakeCurrentFlags aFlags = 0) MOZ_OVERRIDE {
     if (mDestroyed) {
       NS_WARNING("Call on destroyed layer manager");
       return;
     }
     mGLContext->MakeCurrent(aFlags & ForceMakeCurrent);
   }
 
@@ -136,16 +140,18 @@ private:
   /** Widget associated with this compositor */
   nsIWidget *mWidget;
   nsIntSize mWidgetSize;
   nsRefPtr<GLContext> mGLContext;
 
   /** The size of the surface we are rendering to */
   nsIntSize mSurfaceSize;
 
+  gfx::Point mRenderOffset;
+
   /** Helper-class used by Initialize **/
   class ReadDrawFPSPref MOZ_FINAL : public nsRunnable {
   public:
     NS_IMETHOD Run() MOZ_OVERRIDE;
   };
 
   already_AddRefed<mozilla::gl::GLContext> CreateContext();
 
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -87,16 +87,17 @@
 #include "FrameLayerBuilder.h"
 #include "nsAutoLayoutPhase.h"
 #include "nsCSSRenderingBorders.h"
 #include "nsRenderingContext.h"
 #include "nsStyleStructInlines.h"
 #include "nsAnimationManager.h"
 #include "nsTransitionManager.h"
 #include "nsSVGIntegrationUtils.h"
+#include "nsViewportFrame.h"
 #include <algorithm>
 
 #ifdef MOZ_XUL
 #include "nsIRootBox.h"
 #include "nsIDOMXULCommandDispatcher.h"
 #include "nsIDOMXULDocument.h"
 #include "nsIXULDocument.h"
 #endif
@@ -12448,17 +12449,17 @@ nsCSSFrameConstructor::RecomputePosition
       position->mHeight.GetUnit() != eStyleUnit_Auto) {
     // For the absolute positioning case, set up a fake HTML reflow state for
     // the frame, and then get the offsets from it.
     nsRefPtr<nsRenderingContext> rc = aFrame->PresContext()->GetPresShell()->
       GetReferenceRenderingContext();
 
     // Construct a bogus parent reflow state so that there's a usable
     // containing block reflow state.
-    nsIFrame *parentFrame = aFrame->GetParent();
+    nsIFrame* parentFrame = aFrame->GetParent();
     nsSize parentSize = parentFrame->GetSize();
 
     nsFrameState savedState = parentFrame->GetStateBits();
     nsHTMLReflowState parentReflowState(aFrame->PresContext(), parentFrame,
                                         rc, parentSize);
     parentFrame->RemoveStateBits(~nsFrameState(0));
     parentFrame->AddStateBits(savedState);
 
@@ -12472,17 +12473,20 @@ nsCSSFrameConstructor::RecomputePosition
 
     parentReflowState.mComputedPadding = parentFrame->GetUsedPadding();
     parentReflowState.mComputedBorderPadding =
       parentFrame->GetUsedBorderAndPadding();
 
     nsSize availSize(parentSize.width, NS_INTRINSICSIZE);
 
     nsSize size = aFrame->GetSize();
-    nsSize cbSize = aFrame->GetContainingBlock()->GetSize();
+    ViewportFrame* viewport = do_QueryFrame(parentFrame);
+    nsSize cbSize = viewport ?
+      viewport->AdjustReflowStateAsContainingBlock(&parentReflowState).Size()
+      : aFrame->GetContainingBlock()->GetSize();
     const nsMargin& parentBorder =
       parentReflowState.mStyleBorder->GetComputedBorder();
     cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom());
     nsHTMLReflowState reflowState(aFrame->PresContext(), parentReflowState,
                                   aFrame, availSize, cbSize.width,
                                   cbSize.height);
 
     // If we're solving for 'left' or 'top', then compute it here, in order to
--- a/layout/generic/nsViewportFrame.cpp
+++ b/layout/generic/nsViewportFrame.cpp
@@ -23,16 +23,19 @@ using namespace mozilla;
 
 nsIFrame*
 NS_NewViewportFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) ViewportFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(ViewportFrame)
+NS_QUERYFRAME_HEAD(ViewportFrame)
+  NS_QUERYFRAME_ENTRY(ViewportFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
 
 void
 ViewportFrame::Init(nsIContent*      aContent,
                     nsIFrame*        aParent,
                     nsIFrame*        aPrevInFlow)
 {
   Super::Init(aContent, aParent, aPrevInFlow);
 
@@ -127,36 +130,58 @@ ViewportFrame::GetPrefWidth(nsRenderingC
     result = mFrames.FirstChild()->GetPrefWidth(aRenderingContext);
 
   return result;
 }
 
 nsPoint
 ViewportFrame::AdjustReflowStateForScrollbars(nsHTMLReflowState* aReflowState) const
 {
-  // Calculate how much room is available for fixed frames. That means
-  // determining if the viewport is scrollable and whether the vertical and/or
-  // horizontal scrollbars are visible
-
   // Get our prinicpal child frame and see if we're scrollable
   nsIFrame* kidFrame = mFrames.FirstChild();
-  nsIScrollableFrame *scrollingFrame = do_QueryFrame(kidFrame);
+  nsIScrollableFrame* scrollingFrame = do_QueryFrame(kidFrame);
 
   if (scrollingFrame) {
     nsMargin scrollbars = scrollingFrame->GetActualScrollbarSizes();
     aReflowState->SetComputedWidth(aReflowState->ComputedWidth() -
                                    scrollbars.LeftRight());
     aReflowState->availableWidth -= scrollbars.LeftRight();
     aReflowState->SetComputedHeightWithoutResettingResizeFlags(
       aReflowState->ComputedHeight() - scrollbars.TopBottom());
     return nsPoint(scrollbars.left, scrollbars.top);
   }
   return nsPoint(0, 0);
 }
 
+nsRect
+ViewportFrame::AdjustReflowStateAsContainingBlock(nsHTMLReflowState* aReflowState) const
+{
+#ifdef DEBUG
+  nsPoint offset =
+#endif
+    AdjustReflowStateForScrollbars(aReflowState);
+
+  NS_ASSERTION(GetAbsoluteContainingBlock()->GetChildList().IsEmpty() ||
+               (offset.x == 0 && offset.y == 0),
+               "We don't handle correct positioning of fixed frames with "
+               "scrollbars in odd positions");
+
+  // If a scroll position clamping scroll-port size has been set, layout
+  // fixed position elements to this size instead of the computed size.
+  nsRect rect(0, 0, aReflowState->ComputedWidth(), aReflowState->ComputedHeight());
+  nsIPresShell* ps = PresContext()->PresShell();
+  if (ps->IsScrollPositionClampingScrollPortSizeSet()) {
+    rect.SizeTo(ps->GetScrollPositionClampingScrollPortSize());
+  }
+
+  // Make sure content document fixed-position margins are respected.
+  rect.Deflate(ps->GetContentDocumentFixedPositionMargins());
+  return rect;
+}
+
 NS_IMETHODIMP
 ViewportFrame::Reflow(nsPresContext*           aPresContext,
                       nsHTMLReflowMetrics&     aDesiredSize,
                       const nsHTMLReflowState& aReflowState,
                       nsReflowStatus&          aStatus)
 {
   DO_GLOBAL_REFLOW_COUNT("ViewportFrame");
   DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
@@ -233,38 +258,17 @@ ViewportFrame::Reflow(nsPresContext*    
       // Set the available height and mComputedHeight to our chosen height.
       reflowState.availableHeight = aDesiredSize.height;
       // Not having border/padding simplifies things
       NS_ASSERTION(reflowState.mComputedBorderPadding == nsMargin(0,0,0,0),
                    "Viewports can't have border/padding");
       reflowState.SetComputedHeight(aDesiredSize.height);
     }
 
-#ifdef DEBUG
-    nsPoint offset =
-#endif
-      AdjustReflowStateForScrollbars(&reflowState);
-
-    NS_ASSERTION(GetAbsoluteContainingBlock()->GetChildList().IsEmpty() ||
-                 (offset.x == 0 && offset.y == 0),
-                 "We don't handle correct positioning of fixed frames with "
-                 "scrollbars in odd positions");
-
-    // If a scroll position clamping scroll-port size has been set, layout
-    // fixed position elements to this size instead of the computed size.
-    nsRect rect(0, 0, reflowState.ComputedWidth(), reflowState.ComputedHeight());
-    if (aPresContext->PresShell()->IsScrollPositionClampingScrollPortSizeSet()) {
-      nsSize size = aPresContext->PresShell()->
-        GetScrollPositionClampingScrollPortSize();
-      rect.width = size.width;
-      rect.height = size.height;
-    }
-
-    // Make sure content document fixed-position margins are respected.
-    rect.Deflate(aPresContext->PresShell()->GetContentDocumentFixedPositionMargins());
+    nsRect rect = AdjustReflowStateAsContainingBlock(&reflowState);
 
     // Just reflow all the fixed-pos frames.
     rv = GetAbsoluteContainingBlock()->Reflow(this, aPresContext, reflowState, aStatus,
                                               rect,
                                               false, true, true, // XXX could be optimized
                                               &aDesiredSize.mOverflowAreas);
   }
 
--- a/layout/generic/nsViewportFrame.h
+++ b/layout/generic/nsViewportFrame.h
@@ -19,16 +19,18 @@ class nsPresContext;
 
 /**
   * ViewportFrame is the parent of a single child - the doc root frame or a scroll frame 
   * containing the doc root frame. ViewportFrame stores this child in its primary child 
   * list.
   */
 class ViewportFrame : public nsContainerFrame {
 public:
+  NS_DECL_QUERYFRAME_TARGET(ViewportFrame)
+  NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
   typedef nsContainerFrame Super;
 
   ViewportFrame(nsStyleContext* aContext)
     : nsContainerFrame(aContext)
   {}
   virtual ~ViewportFrame() { } // useful for debugging
@@ -63,21 +65,36 @@ public:
 
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::viewportFrame
    */
   virtual nsIAtom* GetType() const MOZ_OVERRIDE;
 
+  /**
+   * Adjust aReflowState to account for scrollbars and pres shell
+   * GetScrollPositionClampingScrollPortSizeSet and
+   * GetContentDocumentFixedPositionMargins adjustments.
+   * @return the rect to use as containing block rect
+   */
+  nsRect AdjustReflowStateAsContainingBlock(nsHTMLReflowState* aReflowState) const;
+
 #ifdef DEBUG
   NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE;
 #endif
 
 private:
   virtual mozilla::layout::FrameChildListID GetAbsoluteListID() const { return kFixedList; }
 
 protected:
+  /**
+   * Calculate how much room is available for fixed frames. That means
+   * determining if the viewport is scrollable and whether the vertical and/or
+   * horizontal scrollbars are visible.  Adjust the computed width/height and
+   * available width for aReflowState accordingly.
+   * @return the current scroll position, or 0,0 if not scrollable
+   */
   nsPoint AdjustReflowStateForScrollbars(nsHTMLReflowState* aReflowState) const;
 };
 
 
 #endif // nsViewportFrame_h___
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/844178-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html><head>
+    <meta charset="utf-8">
+    <title>Testcase for bug 844178</title>
+    <style type="text/css">
+
+        html,body {
+            color:black; background-color:white; font-size:16px; padding:0; margin:0;
+        }
+	
+body {
+  position: fixed;
+  right: 5px;
+  top: 100px;
+  width: 100px;
+  height: 10px;
+  margin:0;
+  padding:0;
+}
+
+:root { overflow:scroll; }
+
+span {
+    background:lime;
+    display:inline-block;
+    width:100px;
+}
+
+</style>
+</head>
+<body><span>Hello</span></body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/844178.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+    <meta charset="utf-8">
+    <title>Testcase for bug 844178</title>
+    <style type="text/css">
+
+        html,body {
+            color:black; background-color:white; font-size:16px; padding:0; margin:0;
+        }
+	
+body {
+  position: fixed;
+  right: 0px;
+  top: 100px;
+  width: 100px;
+  height: 10px;
+  margin:0;
+  padding:0;
+}
+
+:root { overflow:scroll; }
+
+span {
+    background:lime;
+    display:inline-block;
+    width:100px;
+}
+
+</style>
+<script>
+function doTest() {
+  document.body.style.right='5px';
+  document.body.offsetHeight;
+  document.documentElement.removeAttribute('class');
+}
+document.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+</head>
+<body><span>Hello</span></body>
+</html>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1744,15 +1744,16 @@ skip-if(B2G) == 814952-1.html 814952-1-r
 == 817019-1.html about:blank
 skip-if(B2G) == 818276-1.html 818276-1-ref.html
 == 825999.html 825999-ref.html
 == 827577-1a.html 827577-1-ref.html
 == 827577-1b.html 827577-1-ref.html
 == 827799-1.html about:blank
 == 836844-1.html 836844-1-ref.html
 == 841192-1.html 841192-1-ref.html
+== 844178.html 844178-ref.html
 == 846144-1.html 846144-1-ref.html
 == 847850-1.html 847850-1-ref.html
 == 848421-1.html 848421-1-ref.html
 test-pref(layout.css.flexbox.enabled,true) == 849407-1.html 849407-1-ref.html
 == 849996-1.html 849996-1-ref.html
 == 858803-1.html 858803-1-ref.html
 != 860370.html 860370-notref.html
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -1078,22 +1078,25 @@ Loader::CreateSheet(nsIURI* aURI,
         if (cache->IsEnabled()) {
           sheet = cache->GetStyleSheet(aURI);
           LOG(("  From XUL cache: %p", sheet.get()));
         }
       }
     }
 #endif
 
+    bool fromCompleteSheets = false;
     if (!sheet) {
       // Then our per-document complete sheets.
       URIPrincipalAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode);
 
       mCompleteSheets.Get(&key, getter_AddRefs(sheet));
       LOG(("  From completed: %p", sheet.get()));
+
+      fromCompleteSheets = !!sheet;
     }
 
     if (sheet) {
       // This sheet came from the XUL cache or our per-document hashtable; it
       // better be a complete sheet.
       NS_ASSERTION(sheet->IsComplete(),
                    "Sheet thinks it's not complete while we think it is");
 
@@ -1151,16 +1154,25 @@ Loader::CreateSheet(nsIURI* aURI,
     if (sheet) {
       // The sheet we have now should be either incomplete or unmodified
       NS_ASSERTION(!sheet->IsModified() || !sheet->IsComplete(),
                    "Unexpected modified complete sheet");
       NS_ASSERTION(sheet->IsComplete() || aSheetState != eSheetComplete,
                    "Sheet thinks it's not complete while we think it is");
 
       *aSheet = sheet->Clone(nullptr, nullptr, nullptr, nullptr).get();
+      if (*aSheet && fromCompleteSheets &&
+          !sheet->GetOwnerNode() && !sheet->GetParentSheet()) {
+        // The sheet we're cloning isn't actually referenced by
+        // anyone.  Replace it in the cache, so that if our CSSOM is
+        // later modified we don't end up with two copies of our inner
+        // hanging around.
+        URIPrincipalAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode);
+        mCompleteSheets.Put(&key, *aSheet);
+      }
     }
   }
 
   if (!*aSheet) {
     aSheetState = eSheetNeedsParser;
     nsIURI *sheetURI;
     nsCOMPtr<nsIURI> baseURI;
     nsIURI* originalURI;
@@ -1754,32 +1766,45 @@ Loader::DoSheetComplete(SheetLoadData* a
     data = data->mNext;
   }
 
   // Now that it's marked complete, put the sheet in our cache.
   // If we ever start doing this for failure aStatus, we'll need to
   // adjust the PostLoadEvent code that thinks anything already
   // complete must have loaded succesfully.
   if (NS_SUCCEEDED(aStatus) && aLoadData->mURI) {
+    // Pick our sheet to cache carefully.  Ideally, we want to cache
+    // one of the sheets that will be kept alive by a document or
+    // parent sheet anyway, so that if someone then accesses it via
+    // CSSOM we won't have extra clones of the inner lying around.
+    data = aLoadData;
+    nsCSSStyleSheet* sheet = aLoadData->mSheet;
+    while (data) {
+      if (data->mSheet->GetParentSheet() || data->mSheet->GetOwnerNode()) {
+        sheet = data->mSheet;
+        break;
+      }
+      data = data->mNext;
+    }
 #ifdef MOZ_XUL
     if (IsChromeURI(aLoadData->mURI)) {
       nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
       if (cache && cache->IsEnabled()) {
         if (!cache->GetStyleSheet(aLoadData->mURI)) {
           LOG(("  Putting sheet in XUL prototype cache"));
-          cache->PutStyleSheet(aLoadData->mSheet);
+          cache->PutStyleSheet(sheet);
         }
       }
     }
     else {
 #endif
       URIPrincipalAndCORSModeHashKey key(aLoadData->mURI,
                                          aLoadData->mLoaderPrincipal,
                                          aLoadData->mSheet->GetCORSMode());
-      mCompleteSheets.Put(&key, aLoadData->mSheet);
+      mCompleteSheets.Put(&key, sheet);
 #ifdef MOZ_XUL
     }
 #endif
   }
 
   NS_RELEASE(aLoadData);  // this will release parents and siblings and all that
 }
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -379,17 +379,17 @@ PeerConnectionImpl::ConvertRTCConfigurat
                                             JSContext* aCx)
 {
 #ifdef MOZILLA_INTERNAL_API
   if (!aSrc.isObject()) {
     return NS_ERROR_FAILURE;
   }
   JSAutoCompartment ac(aCx, &aSrc.toObject());
   RTCConfiguration config;
-  if (!(config.Init(aCx, JS::NullPtr(), aSrc) && config.mIceServers.WasPassed())) {
+  if (!(config.Init(aCx, aSrc) && config.mIceServers.WasPassed())) {
     return NS_ERROR_FAILURE;
   }
   for (uint32_t i = 0; i < config.mIceServers.Value().Length(); i++) {
     // XXXbz once this moves to WebIDL, remove RTCConfiguration in DummyBinding.webidl.
     RTCIceServer& server = config.mIceServers.Value()[i];
     if (!server.mUrl.WasPassed()) {
       return NS_ERROR_FAILURE;
     }
--- a/mobile/android/base/AwesomeBar.java
+++ b/mobile/android/base/AwesomeBar.java
@@ -64,17 +64,16 @@ public class AwesomeBar extends GeckoAct
     public static final String READING_LIST_KEY = "reading_list";
     public static enum Target { NEW_TAB, CURRENT_TAB, PICK_SITE };
 
     private String mTarget;
     private AwesomeBarTabs mAwesomeTabs;
     private CustomEditText mText;
     private ImageButton mGoButton;
     private ContextMenuSubject mContextMenuSubject;
-    private boolean mIsUsingGestureKeyboard;
     private boolean mDelayRestartInput;
     // The previous autocomplete result returned to us
     private String mAutoCompleteResult = "";
     // The user typed part of the autocomplete result
     private String mAutoCompletePrefix = null;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -271,36 +270,16 @@ public class AwesomeBar extends GeckoAct
 
         // If mAwesomeTabs.onBackPressed() returned false, we didn't move up
         // a folder level, so just exit the activity.
         cancelAndFinish();
         return true;
     }
 
     @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        super.onWindowFocusChanged(hasFocus);
-
-        // The Awesome Bar will receive focus when the Awesome Screen first opens or after the user
-        // closes the "Select Input Method" window. If the input method changes to or from Swype,
-        // then toggle the URL mode flag. Swype's URL mode disables the automatic word spacing that
-        // Swype users expect when entering search queries, but does not add any special VKB keys
-        // like ".com" or "/" that would be useful for entering URLs.
-
-        if (!hasFocus)
-            return;
-
-        boolean wasUsingGestureKeyboard = mIsUsingGestureKeyboard;
-        mIsUsingGestureKeyboard = InputMethods.isGestureKeyboard(this);
-        if (mIsUsingGestureKeyboard != wasUsingGestureKeyboard) {
-            updateKeyboardInputType();
-        }
-    }
-
-    @Override
     public void onConfigurationChanged(Configuration newConfiguration) {
         super.onConfigurationChanged(newConfiguration);
     }
 
     @Override
     public boolean onSearchRequested() {
         cancelAndFinish();
         return true;
@@ -350,22 +329,23 @@ public class AwesomeBar extends GeckoAct
             updateKeyboardInputType();
             imm.restartInput(mText);
             mGoButton.setImageResource(imageResource);
             mGoButton.setContentDescription(contentDescription);
         }
     }
 
     private void updateKeyboardInputType() {
-        // If the user enters a space, then we know they are entering search terms, not a URL. If
-        // we're using a gesture keyboard, we can then switch to text mode so the IME auto-inserts
-        // spaces between words.
+        // If the user enters a space, then we know they are entering search terms, not a URL.
+        // We can then switch to text mode so,
+        // 1) the IME auto-inserts spaces between words
+        // 2) the IME doesn't reset input keyboard to Latin keyboard.
         String text = mText.getText().toString();
         int currentInputType = mText.getInputType();
-        int newInputType = mIsUsingGestureKeyboard && StringUtils.isSearchQuery(text, false)
+        int newInputType = StringUtils.isSearchQuery(text, false)
                            ? (currentInputType & ~InputType.TYPE_TEXT_VARIATION_URI) // Text mode
                            : (currentInputType | InputType.TYPE_TEXT_VARIATION_URI); // URL mode
         if (newInputType != currentInputType) {
             mText.setRawInputType(newInputType);
         }
     }
 
     private void cancelAndFinish() {
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -3,16 +3,17 @@
  * 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 org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
@@ -61,16 +62,17 @@ import java.io.InputStream;
 import java.net.URL;
 import java.util.EnumSet;
 import java.util.Vector;
 
 abstract public class BrowserApp extends GeckoApp
                                  implements TabsPanel.TabsLayoutChangeListener,
                                             PropertyAnimator.PropertyAnimationListener,
                                             View.OnKeyListener,
+                                            GeckoLayerClient.OnMetricsChangedListener,
                                             AboutHome.UriLoadListener,
                                             AboutHome.LoadCompleteListener {
     private static final String LOGTAG = "GeckoBrowserApp";
 
     private static final String PREF_CHROME_DYNAMICTOOLBAR = "browser.chrome.dynamictoolbar";
 
     private static final int TABS_ANIMATION_DURATION = 450;
 
@@ -108,66 +110,44 @@ abstract public class BrowserApp extends
 
     private FindInPageBar mFindInPageBar;
 
     private boolean mAccessibilityEnabled = false;
 
     // We'll ask for feedback after the user launches the app this many times.
     private static final int FEEDBACK_LAUNCH_COUNT = 15;
 
-    // Variables used for scrolling the toolbar on/off the page;
-
-    // A drag has to move this amount multiplied by the height of the toolbar
-    // before the toolbar will appear or disappear.
-    private static final float TOOLBAR_MOVEMENT_THRESHOLD = 0.3f;
-
     // Whether the dynamic toolbar pref is enabled.
     private boolean mDynamicToolbarEnabled = false;
 
-    // The last recorded touch event from onInterceptTouchEvent. These are
-    // not updated until the movement threshold has been exceeded.
-    private float mLastTouchX = 0.0f;
-    private float mLastTouchY = 0.0f;
-
-    // Because we can only scroll by integer amounts, we store the fractional
-    // amounts to be applied here.
-    private float mToolbarSubpixelAccumulation = 0.0f;
-
-    // Used by onInterceptTouchEvent to lock the toolbar into an off or on
-    // position.
-    private boolean mToolbarLocked = false;
-
-    // Whether the toolbar movement threshold has been passed by the current
-    // drag.
-    private boolean mToolbarThresholdPassed = false;
-
-    // Toggled when the tabs tray is made visible to disable toolbar movement.
-    private boolean mToolbarPinned = false;
-
     // Stored value of the toolbar height, so we know when it's changed.
     private int mToolbarHeight = 0;
 
     private Integer mPrefObserverId;
 
+    // Tag for the AboutHome fragment. The fragment is automatically attached
+    // after restoring from a saved state, so we use this tag to identify it.
+    private static final String ABOUTHOME_TAG = "abouthome";
+
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
         switch(msg) {
             case LOCATION_CHANGE:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     maybeCancelFaviconLoad(tab);
                 }
                 // fall through
             case SELECTED:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     if ("about:home".equals(tab.getURL())) {
                         showAboutHome();
 
                         if (isDynamicToolbarEnabled()) {
                             // Show the toolbar.
-                            mBrowserToolbar.animateVisibility(true);
+                            mLayerView.getLayerMarginsAnimator().showMargins(false);
                         }
                     } else {
                         hideAboutHome();
                     }
 
                     // Dismiss any SiteIdentity Popup
                     SiteIdentityPopup.getInstance().dismiss();
 
@@ -186,17 +166,17 @@ abstract public class BrowserApp extends
                 }
                 break;
             case START:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     invalidateOptionsMenu();
 
                     if (isDynamicToolbarEnabled()) {
                         // Show the toolbar.
-                        mBrowserToolbar.animateVisibility(true);
+                        mLayerView.getLayerMarginsAnimator().showMargins(false);
                     }
                 }
                 break;
             case LOAD_ERROR:
             case STOP:
             case MENU_UPDATED:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     invalidateOptionsMenu();
@@ -220,168 +200,44 @@ abstract public class BrowserApp extends
 
     @Override
     void handleClearHistory() {
         super.handleClearHistory();
         updateAboutHomeTopSites();
     }
 
     @Override
-    public boolean onInterceptTouchEvent(View view, MotionEvent event) {
-        if (!isDynamicToolbarEnabled() || mToolbarPinned) {
-            return super.onInterceptTouchEvent(view, event);
-        }
-
-        // Don't let the toolbar scroll at all if the page is shorter than
-        // the screen height.
-        ImmutableViewportMetrics metrics =
-            mLayerView.getLayerClient().getViewportMetrics();
-        if (metrics.getPageHeight() < metrics.getHeight()) {
-            return super.onInterceptTouchEvent(view, event);
-        }
-
-        int action = event.getActionMasked();
-        int pointerCount = event.getPointerCount();
-
-        // Whenever there are no pointers left on the screen, tell the page
-        // to clamp the viewport on fixed layer margin changes. This lets the
-        // toolbar scrolling off the top of the page make the page scroll up
-        // if it'd cause the page to go into overscroll, but only when there
-        // are no pointers held down.
-        mLayerView.getLayerClient().setClampOnFixedLayerMarginsChange(
-            pointerCount == 0 || action == MotionEvent.ACTION_CANCEL ||
-            action == MotionEvent.ACTION_UP);
-
-        View toolbarView = mBrowserToolbar.getLayout();
-        if (action == MotionEvent.ACTION_DOWN ||
-            action == MotionEvent.ACTION_POINTER_DOWN) {
-            if (pointerCount == 1) {
-                mToolbarLocked = mToolbarThresholdPassed = false;
-                mToolbarSubpixelAccumulation = 0.0f;
-                mLastTouchX = event.getX();
-                mLastTouchY = event.getY();
-                return super.onInterceptTouchEvent(view, event);
-            }
-
-            // Animate the toolbar to the fully on/off position.
-            mBrowserToolbar.animateVisibility(
-                toolbarView.getScrollY() > toolbarView.getHeight() / 2 ?
-                    false : true);
-        }
-
-        // If more than one pointer has been tracked, or we've locked the
-        // toolbar movement, let the event pass through and be handled by the
-        // PanZoomController for zooming.
-        if (pointerCount > 1 || mToolbarLocked) {
-            return super.onInterceptTouchEvent(view, event);
-        }
-
-        // If a pointer has been lifted so that there's only one pointer left,
-        // unlock the toolbar and track that remaining pointer.
-        if (pointerCount == 1 && action == MotionEvent.ACTION_POINTER_UP) {
-            mLastTouchY = event.getY(1 - event.getActionIndex());
-            return super.onInterceptTouchEvent(view, event);
-        }
-
-        // Handle scrolling the toolbar
-        float eventX = event.getX();
-        float eventY = event.getY();
-        float deltaX = mLastTouchX - eventX;
-        float deltaY = mLastTouchY - eventY;
-        int toolbarY = toolbarView.getScrollY();
-        int toolbarHeight = toolbarView.getHeight();
-
-        // Check if we've passed the toolbar movement threshold
-        if (!mToolbarThresholdPassed) {
-            float threshold = toolbarHeight * TOOLBAR_MOVEMENT_THRESHOLD;
-            if (Math.abs(deltaY) > threshold) {
-                mToolbarThresholdPassed = true;
-                // If we're scrolling downwards and the toolbar was hidden
-                // when we started scrolling, lock it.
-                if (deltaY > 0 && toolbarY == toolbarHeight) {
-                    mToolbarLocked = true;
-                    return super.onInterceptTouchEvent(view, event);
-                }
-            } else if (Math.abs(deltaX) > threshold) {
-                // Any horizontal scrolling past the threshold should
-                // initiate toolbar lock.
-                mToolbarLocked = true;
-                mToolbarThresholdPassed = true;
-                return super.onInterceptTouchEvent(view, event);
-            } else {
-                // The threshold hasn't been passed. We don't want to update
-                // the stored last touch position, so return here.
-                return super.onInterceptTouchEvent(view, event);
-            }
-        } else if (action == MotionEvent.ACTION_MOVE) {
-            // Cancel any ongoing animation before we start moving the toolbar.
-            mBrowserToolbar.cancelVisibilityAnimation();
-
-            // Move the toolbar by the amount the touch event has moved,
-            // clamping to fully visible or fully hidden.
-
-            // Don't let the toolbar scroll off the top if it's just exposing
-            // overscroll area.
-            float toolbarMaxY = Math.min(toolbarHeight,
-                Math.max(0, toolbarHeight - (metrics.pageRectTop -
-                                             metrics.viewportRectTop)));
-
-            float newToolbarYf = Math.max(0, Math.min(toolbarMaxY,
-                toolbarY + deltaY + mToolbarSubpixelAccumulation));
-            int newToolbarY = Math.round(newToolbarYf);
-            mToolbarSubpixelAccumulation = (newToolbarYf - newToolbarY);
-
-            toolbarView.scrollTo(0, newToolbarY);
-
-            // Reset tracking when the toolbar is fully visible or hidden.
-            if (newToolbarY == 0 || newToolbarY == toolbarHeight) {
-                mLastTouchY = eventY;
-            }
-        } else if (action == MotionEvent.ACTION_UP ||
-                   action == MotionEvent.ACTION_CANCEL) {
-            // Animate the toolbar to fully on or off, depending on how much
-            // of it is hidden and the current swipe velocity.
-            PanZoomController pzc = mLayerView.getPanZoomController();
-            float yVelocity = (pzc == null ? 0.0f : pzc.getVelocityVector().y);
-            mBrowserToolbar.animateVisibilityWithVelocityBias(
-                toolbarY > toolbarHeight / 2 ? false : true, yVelocity);
-        }
-
-        // Update the last recorded position.
-        mLastTouchX = eventX;
-        mLastTouchY = eventY;
-
-        return super.onInterceptTouchEvent(view, event);
-    }
-
-    @Override
     public boolean onKey(View v, int keyCode, KeyEvent event) {
         // Global onKey handler. This is called if the focused UI doesn't
         // handle the key event, and before Gecko swallows the events.
         if (event.getAction() != KeyEvent.ACTION_DOWN) {
             return false;
         }
 
         // Gamepad support only exists in API-level >= 9
         if (Build.VERSION.SDK_INT >= 9 &&
             (event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
             switch (keyCode) {
                 case KeyEvent.KEYCODE_BUTTON_Y:
                     // Toggle/focus the address bar on gamepad-y button.
                     if (mBrowserToolbar.isVisible()) {
                         if (isDynamicToolbarEnabled() && !mAboutHome.getUserVisibleHint()) {
-                            mBrowserToolbar.animateVisibility(false);
-                            mLayerView.requestFocus();
+                            if (mLayerView != null) {
+                                mLayerView.getLayerMarginsAnimator().hideMargins(false);
+                                mLayerView.requestFocus();
+                            }
                         } else {
                             // Just focus the address bar when about:home is visible
                             // or when the dynamic toolbar isn't enabled.
                             mBrowserToolbar.requestFocusFromTouch();
                         }
                     } else {
-                        mBrowserToolbar.animateVisibility(true);
+                        if (mLayerView != null) {
+                            mLayerView.getLayerMarginsAnimator().showMargins(false);
+                        }
                         mBrowserToolbar.requestFocusFromTouch();
                     }
                     return true;
                 case KeyEvent.KEYCODE_BUTTON_L1:
                     // Go back on L1
                     Tabs.getInstance().getSelectedTab().doBack();
                     return true;
                 case KeyEvent.KEYCODE_BUTTON_R1:
@@ -490,35 +346,39 @@ abstract public class BrowserApp extends
         mMainLayout.addView(actionBar, 2);
 
         ((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideTabsTouchListener());
         ((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() {
             @Override
             public boolean onInterceptMotionEvent(View view, MotionEvent event) {
                 // If we get a gamepad panning MotionEvent while the focus is not on the layerview,
                 // put the focus on the layerview and carry on
-                LayerView layerView = mLayerView;
-                if (layerView != null && !layerView.hasFocus() && GamepadUtils.isPanningControl(event)) {
+                if (mLayerView != null && !mLayerView.hasFocus() && GamepadUtils.isPanningControl(event)) {
                     if (mAboutHome.getUserVisibleHint()) {
-                        layerView.requestFocus();
+                        mLayerView.requestFocus();
                     } else {
                         mAboutHome.requestFocus();
                     }
                 }
                 return false;
             }
         });
 
-        // AboutHome will be dynamically attached and detached as
-        // about:home is shown. Adding/removing the fragment is not synchronous,
-        // so we can't use Fragment#isVisible() to determine whether the
-        // about:home is shown. Instead, we use Fragment#getUserVisibleHint()
-        // with the hint we set ourselves.
-        mAboutHome = AboutHome.newInstance();
-        mAboutHome.setUserVisibleHint(false);
+        // Find the Fragment if it was already added from a restored instance state.
+        mAboutHome = (AboutHome) getSupportFragmentManager().findFragmentByTag(ABOUTHOME_TAG);
+
+        if (mAboutHome == null) {
+            // AboutHome will be dynamically attached and detached as
+            // about:home is shown. Adding/removing the fragment is not synchronous,
+            // so we can't use Fragment#isVisible() to determine whether the
+            // about:home is shown. Instead, we use Fragment#getUserVisibleHint()
+            // with the hint we set ourselves.
+            mAboutHome = AboutHome.newInstance();
+            mAboutHome.setUserVisibleHint(false);
+        }
 
         mBrowserToolbar = new BrowserToolbar(this);
         mBrowserToolbar.from(actionBar);
 
         // Intercept key events for gamepad shortcuts
         actionBar.setOnKeyListener(this);
 
         if (mTabsPanel != null) {
@@ -581,28 +441,33 @@ abstract public class BrowserApp extends
                 // without restarting.
                 return true;
             }
         });
     }
 
     private void setDynamicToolbarEnabled(boolean enabled) {
         if (enabled) {
+            if (mLayerView != null) {
+                mLayerView.getLayerClient().setOnMetricsChangedListener(this);
+            }
             setToolbarMargin(0);
         } else {
             // Immediately show the toolbar when disabling the dynamic
             // toolbar.
+            if (mLayerView != null) {
+                mLayerView.getLayerClient().setOnMetricsChangedListener(null);
+            }
             mAboutHome.setPadding(0, 0, 0, 0);
-            mBrowserToolbar.cancelVisibilityAnimation();
-            mBrowserToolbar.getLayout().scrollTo(0, 0);
+            if (mBrowserToolbar != null) {
+                mBrowserToolbar.getLayout().scrollTo(0, 0);
+            }
         }
 
-        // Refresh the margins to reset the padding on the spacer and
-        // make sure that Gecko is in sync.
-        ((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
+        refreshToolbarHeight();
     }
 
     private boolean isDynamicToolbarEnabled() {
         return mDynamicToolbarEnabled && !mAccessibilityEnabled;
     }
 
     @Override
     public void setAccessibilityEnabled(boolean enabled) {
@@ -657,99 +522,111 @@ abstract public class BrowserApp extends
 
     @Override
     protected void initializeChrome(String uri, boolean isExternalURL) {
         super.initializeChrome(uri, isExternalURL);
 
         mBrowserToolbar.updateBackButton(false);
         mBrowserToolbar.updateForwardButton(false);
 
-        // Reset mToolbarHeight before setting margins so we force the
-        // Viewport:FixedMarginsChanged message to be sent again now that
-        // Gecko has loaded.
-        mToolbarHeight = 0;
-        ((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
-
         mDoorHangerPopup.setAnchor(mBrowserToolbar.mFavicon);
 
         if (isExternalURL || mRestoreMode != RESTORE_NONE) {
             mAboutHomeStartupTimer.cancel();
         }
 
         if (!mIsRestoringActivity) {
             if (!isExternalURL) {
                 // show about:home if we aren't restoring previous session
                 if (mRestoreMode == RESTORE_NONE) {
                     Tab tab = Tabs.getInstance().loadUrl("about:home", Tabs.LOADURL_NEW_TAB);
-                } else {
-                    hideAboutHome();
                 }
             } else {
                 int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED;
                 Tabs.getInstance().loadUrl(uri, flags);
             }
         }
 
+        // Listen to margin changes to position the toolbar correctly
+        if (isDynamicToolbarEnabled()) {
+            refreshToolbarHeight();
+            mLayerView.getLayerMarginsAnimator().showMargins(true);
+            mLayerView.getLayerClient().setOnMetricsChangedListener(this);
+        }
+
         // Intercept key events for gamepad shortcuts
         mLayerView.setOnKeyListener(this);
     }
 
     private void setSidebarMargin(int margin) {
         ((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).leftMargin = margin;
         mGeckoLayout.requestLayout();
     }
 
     private void setToolbarMargin(int margin) {
         ((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin;
         mGeckoLayout.requestLayout();
     }
 
-    public void setToolbarHeight(int aHeight, int aVisibleHeight) {
+    @Override
+    public void onMetricsChanged(ImmutableViewportMetrics aMetrics) {
+        if (mAboutHome.getUserVisibleHint() || mBrowserToolbar == null) {
+            return;
+        }
+
+        final View toolbarLayout = mBrowserToolbar.getLayout();
+        final int marginTop = Math.round(aMetrics.marginTop);
+        ThreadUtils.postToUiThread(new Runnable() {
+            public void run() {
+                toolbarLayout.scrollTo(0, toolbarLayout.getHeight() - marginTop);
+            }
+        });
+    }
+
+    @Override
+    public void onPanZoomStopped() {
+        if (!isDynamicToolbarEnabled() || mAboutHome.getUserVisibleHint()) {
+            return;
+        }
+
+        ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
+        if (metrics.marginTop >= mToolbarHeight / 2) {
+            mLayerView.getLayerMarginsAnimator().showMargins(false);
+        } else {
+            mLayerView.getLayerMarginsAnimator().hideMargins(false);
+        }
+    }
+
+    public void refreshToolbarHeight() {
+        int height = 0;
+        if (mBrowserToolbar != null) {
+            height = mBrowserToolbar.getLayout().getHeight();
+        }
+
         if (!isDynamicToolbarEnabled() || mAboutHome.getUserVisibleHint()) {
             // Use aVisibleHeight here so that when the dynamic toolbar is
             // enabled, the padding will animate with the toolbar becoming
             // visible.
             if (isDynamicToolbarEnabled()) {
                 // When the dynamic toolbar is enabled, set the padding on the
                 // about:home widget directly - this is to avoid resizing the
                 // LayerView, which can cause visible artifacts.
-                mAboutHome.setPadding(0, aVisibleHeight, 0, 0);
+                mAboutHome.setPadding(0, height, 0, 0);
             } else {
-                setToolbarMargin(aVisibleHeight);
+                setToolbarMargin(height);
+                height = 0;
             }
-            aHeight = aVisibleHeight = 0;
         } else {
             setToolbarMargin(0);
         }
 
-        // Update the Gecko-side global for fixed viewport margins.
-        if (aHeight != mToolbarHeight) {
-            mToolbarHeight = aHeight;
-
-            // In the current UI, this is the only place we have need of
-            // viewport margins (to stop the toolbar from obscuring fixed-pos
-            // content).
-            GeckoAppShell.sendEventToGecko(
-                GeckoEvent.createBroadcastEvent("Viewport:FixedMarginsChanged",
-                    "{ \"top\" : " + aHeight + ", \"right\" : 0, \"bottom\" : 0, \"left\" : 0 }"));
-        }
-
-        if (mLayerView != null) {
-            mLayerView.getLayerClient().setFixedLayerMargins(0, aVisibleHeight, 0, 0);
-
-            // Force a redraw when the view isn't moving and the toolbar is
-            // fully visible or fully hidden. This is to make sure that the
-            // Gecko-side fixed viewport margins are in sync when the view and
-            // bar aren't animating.
-            PointF velocityVector = mLayerView.getPanZoomController().getVelocityVector();
-            if ((aVisibleHeight == 0 || aVisibleHeight == aHeight) &&
-                FloatUtils.fuzzyEquals(velocityVector.x, 0.0f) &&
-                FloatUtils.fuzzyEquals(velocityVector.y, 0.0f)) {
-                mLayerView.getLayerClient().forceRedraw();
-            }
+        if (mLayerView != null && height != mToolbarHeight) {
+            mToolbarHeight = height;
+            mLayerView.getLayerMarginsAnimator().setMaxMargins(0, height, 0, 0);
+            mLayerView.getLayerMarginsAnimator().showMargins(true);
         }
     }
 
     @Override
     void toggleChrome(final boolean aShow) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
@@ -775,31 +652,16 @@ abstract public class BrowserApp extends
                 mBrowserToolbar.show();
                 mBrowserToolbar.requestFocusFromTouch();
             }
         });
     }
 
     @Override
     public void refreshChrome() {
-        // Only ICS phones use a smaller action-bar in landscape mode.
-        if (Build.VERSION.SDK_INT >= 14 && !HardwareUtils.isTablet()) {
-            int index = mMainLayout.indexOfChild(mBrowserToolbar.getLayout());
-            mMainLayout.removeViewAt(index);
-
-            LinearLayout actionBar = (LinearLayout) getActionBarLayout();
-            mMainLayout.addView(actionBar, index);
-            mBrowserToolbar.from(actionBar);
-            mBrowserToolbar.refresh();
-
-            // The favicon view is different now, so we need to update the DoorHangerPopup anchor view.
-            if (mDoorHangerPopup != null)
-                mDoorHangerPopup.setAnchor(mBrowserToolbar.mFavicon);
-        }
-
         invalidateOptionsMenu();
         updateSideBarState();
         mTabsPanel.refresh();
     }
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
@@ -1106,21 +968,23 @@ abstract public class BrowserApp extends
         } else {
             mMainLayoutAnimator.attach(mMainLayout,
                                        PropertyAnimator.Property.SCROLL_Y,
                                        -height);
         }
 
         // If the tabs layout is animating onto the screen, pin the dynamic
         // toolbar.
-        if (width > 0 && height > 0) {
-            mToolbarPinned = true;
-            mBrowserToolbar.animateVisibility(true);
-        } else {
-            mToolbarPinned = false;
+        if (mLayerView != null && isDynamicToolbarEnabled()) {
+            if (width > 0 && height > 0) {
+                mLayerView.getLayerMarginsAnimator().showMargins(false);
+                mLayerView.getLayerMarginsAnimator().setMarginsPinned(true);
+            } else {
+                mLayerView.getLayerMarginsAnimator().setMarginsPinned(false);
+            }
         }
 
         mMainLayoutAnimator.start();
     }
 
     @Override
     public void onPropertyAnimationStart() {
         mBrowserToolbar.updateTabs(true);
@@ -1212,48 +1076,54 @@ abstract public class BrowserApp extends
         mAboutHome.update(EnumSet.of(AboutHome.UpdateFlags.TOP_SITES));
     }
 
     private void showAboutHome() {
         if (mAboutHome.getUserVisibleHint()) {
             return;
         }
 
+        // Refresh toolbar height to possibly restore the toolbar padding
+        refreshToolbarHeight();
+
+        // Show the toolbar before hiding about:home so the
+        // onMetricsChanged callback still works.
+        if (isDynamicToolbarEnabled() && mLayerView != null) {
+            mLayerView.getLayerMarginsAnimator().showMargins(true);
+        }
+
         // We use commitAllowingStateLoss() instead of commit() here to avoid an
         // IllegalStateException. showAboutHome() and hideAboutHome() are
         // executed inside of tab's onChange() callback. Since that callback can
         // be triggered asynchronously from Gecko, it's possible that this
         // method can be called while Fennec is in the background. If that
         // happens, using commit() would throw an IllegalStateException since
         // it can't be used between the Activity's onSaveInstanceState() and
         // onResume().
         getSupportFragmentManager().beginTransaction()
-                .add(R.id.gecko_layout, mAboutHome).commitAllowingStateLoss();
+                .add(R.id.gecko_layout, mAboutHome, ABOUTHOME_TAG).commitAllowingStateLoss();
         mAboutHome.setUserVisibleHint(true);
 
         mBrowserToolbar.setNextFocusDownId(R.id.abouthome_content);
-
-        // Refresh margins to possibly restore the toolbar padding
-        ((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
     }
 
     private void hideAboutHome() {
         if (!mAboutHome.getUserVisibleHint()) {
             return;
         }
 
         getSupportFragmentManager().beginTransaction()
                 .remove(mAboutHome).commitAllowingStateLoss();
         mAboutHome.setUserVisibleHint(false);
 
         mBrowserToolbar.setShadowVisibility(true);
         mBrowserToolbar.setNextFocusDownId(R.id.layer_view);
 
-        // Refresh margins to possibly restore the toolbar padding
-        ((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
+        // Refresh toolbar height to possibly restore the toolbar padding
+        refreshToolbarHeight();
     }
 
     private class HideTabsTouchListener implements TouchEventInterceptor {
         private boolean mIsHidingTabs = false;
 
         @Override
         public boolean onInterceptTouchEvent(View view, MotionEvent event) {
             // We need to account for scroll state for the touched view otherwise
@@ -1466,18 +1336,18 @@ abstract public class BrowserApp extends
 
         // Scroll custom menu to the top
         if (mMenuPanel != null)
             mMenuPanel.scrollTo(0, 0);
 
         if (!mBrowserToolbar.openOptionsMenu())
             super.openOptionsMenu();
 
-        if (isDynamicToolbarEnabled())
-            mBrowserToolbar.animateVisibility(true);
+        if (isDynamicToolbarEnabled() && mLayerView != null)
+            mLayerView.getLayerMarginsAnimator().showMargins(false);
     }
 
     @Override
     public void closeOptionsMenu() {
         if (!mBrowserToolbar.closeOptionsMenu())
             super.closeOptionsMenu();
     }
 
--- a/mobile/android/base/BrowserToolbar.java
+++ b/mobile/android/base/BrowserToolbar.java
@@ -109,42 +109,31 @@ public class BrowserToolbar implements V
     private int mDefaultForwardMargin;
     private PropertyAnimator mForwardAnim = null;
 
     private int mCount;
     private int mFaviconSize;
 
     private PropertyAnimator mVisibilityAnimator;
 
-    private enum ToolbarVisibility {
-        VISIBLE,
-        HIDDEN,
-        INCONSISTENT
-    };
-    private ToolbarVisibility mVisibility;
-
     private static final int TABS_CONTRACTED = 1;
     private static final int TABS_EXPANDED = 2;
 
     private static final int FORWARD_ANIMATION_DURATION = 450;
 
-    private static final int VISIBILITY_ANIMATION_DURATION = 250;
-
     public BrowserToolbar(BrowserApp activity) {
         // BrowserToolbar is attached to BrowserApp only.
         mActivity = activity;
         mInflater = LayoutInflater.from(activity);
 
         sActionItems = new ArrayList<View>();
         Tabs.registerOnTabsChangedListener(this);
         mAnimateSiteSecurity = true;
 
         mAnimatingEntry = false;
-
-        mVisibility = ToolbarVisibility.INCONSISTENT;
     }
 
     public void from(LinearLayout layout) {
         if (mLayout != null) {
             // make sure we retain the visibility property on rotation
             layout.setVisibility(mLayout.getVisibility());
         }
         mLayout = layout;
@@ -500,81 +489,18 @@ public class BrowserToolbar implements V
             case READER_ENABLED:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     setReaderMode(tab.getReaderEnabled());
                 }
                 break;
         }
     }
 
-    private boolean canToolbarHide() {
-        // Forbid the toolbar from hiding if hiding the toolbar would cause
-        // the page to go into overscroll.
-        LayerView layerView = GeckoApp.mAppContext.getLayerView();
-        if (layerView != null) {
-            ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-            return (metrics.getPageHeight() >= metrics.getHeight());
-        }
-        return false;
-    }
-
-    public void animateVisibility(boolean show) {
-        // Do nothing if there's a delayed animation pending that does the
-        // same thing and this request also has a delay.
-        if (mVisibility != ToolbarVisibility.INCONSISTENT &&
-            show == isVisible()) {
-            return;
-        }
-
-        cancelVisibilityAnimation();
-        mVisibility = show ? ToolbarVisibility.VISIBLE : ToolbarVisibility.HIDDEN;
-
-        mVisibilityAnimator = new PropertyAnimator(VISIBILITY_ANIMATION_DURATION);
-        mVisibilityAnimator.attach(mLayout, PropertyAnimator.Property.SCROLL_Y,
-                                   show ? 0 : mLayout.getHeight());
-
-        // Only start the animation if we're showing the toolbar, or it's ok
-        // to hide it.
-        if (mVisibility == ToolbarVisibility.VISIBLE ||
-            canToolbarHide()) {
-            mVisibilityAnimator.start();
-        }
-    }
-
-    /**
-     * Animate the visibility of the toolbar, but take into account the
-     * velocity of what's moving underneath the toolbar. If that velocity
-     * is greater than the default animation velocity, it will determine
-     * the direction of the toolbar animation. Velocity is specified in
-     * pixels per 1/60 seconds (a 60Hz frame).
-     */
-    public void animateVisibilityWithVelocityBias(boolean show, float velocity) {
-        // Work out the default animation velocity. This assumes a linear
-        // animation which is incorrect, but the animation is short enough that
-        // there's very little difference.
-        float defaultVelocity =
-            mLayout.getHeight() / ((VISIBILITY_ANIMATION_DURATION / 1000.0f) * 60);
-
-        if (Math.abs(velocity) > defaultVelocity) {
-            show = (velocity > 0) ? false : true;
-        }
-
-        animateVisibility(show);
-    }
-
-    public void cancelVisibilityAnimation() {
-        if (mVisibilityAnimator != null) {
-            mVisibility = ToolbarVisibility.INCONSISTENT;
-            mVisibilityAnimator.stop(false);
-            mVisibilityAnimator = null;
-        }
-    }
-
     public boolean isVisible() {
-        return mVisibility == ToolbarVisibility.VISIBLE;
+        return mLayout.getScrollY() == 0;
     }
 
     public void setNextFocusDownId(int nextId) {
         mAwesomeBar.setNextFocusDownId(nextId);
         mTabs.setNextFocusDownId(nextId);
         mBack.setNextFocusDownId(nextId);
         mForward.setNextFocusDownId(nextId);
         mFavicon.setNextFocusDownId(nextId);
--- a/mobile/android/base/BrowserToolbarLayout.java
+++ b/mobile/android/base/BrowserToolbarLayout.java
@@ -24,39 +24,25 @@ public class BrowserToolbarLayout extend
         if (event != null && event.getY() > getHeight() - getScrollY()) {
             return false;
         }
 
         return super.onTouchEvent(event);
     }
 
     @Override
-    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
-        super.onScrollChanged(l, t, oldl, oldt);
-
-        if (t != oldt) {
-            refreshMargins();
-        }
-    }
-
-    @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
 
         if (h != oldh) {
             // Post this to happen outside of onSizeChanged, as this may cause
             // a layout change and relayouts within a layout change don't work.
+            final int height = h;
             post(new Runnable() {
                 @Override
                 public void run() {
-                    refreshMargins();
+                    ((BrowserApp)GeckoApp.mAppContext).refreshToolbarHeight();
                 }
             });
         }
     }
-
-    public void refreshMargins() {
-        int height = getHeight();
-        int visibleHeight = height - getScrollY();
-        ((BrowserApp)GeckoApp.mAppContext).setToolbarHeight(height, visibleHeight);
-    }
 }
 
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -761,20 +761,22 @@ abstract public class GeckoApp
                 JSONArray permissions = message.getJSONArray("permissions");
                 showSiteSettingsDialog(host, permissions);
             } else if (event.equals("Tab:ViewportMetadata")) {
                 int tabId = message.getInt("tabID");
                 Tab tab = Tabs.getInstance().getTab(tabId);
                 if (tab == null)
                     return;
                 tab.setZoomConstraints(new ZoomConstraints(message));
+                tab.setIsRTL(message.getBoolean("isRTL"));
                 // Sync up the layer view and the tab if the tab is currently displayed.
                 LayerView layerView = mLayerView;
                 if (layerView != null && Tabs.getInstance().isSelectedTab(tab)) {
                     layerView.setZoomConstraints(tab.getZoomConstraints());
+                    layerView.setIsRTL(tab.getIsRTL());
                 }
             } else if (event.equals("Session:StatePurged")) {
                 onStatePurged();
             } else if (event.equals("Bookmark:Insert")) {
                 final String url = message.getString("url");
                 final String title = message.getString("title");
                 ThreadUtils.postToUiThread(new Runnable() {
                     @Override
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -585,20 +585,20 @@ public class GeckoEvent {
 
     public static GeckoEvent createViewportEvent(ImmutableViewportMetrics metrics, DisplayPortMetrics displayPort) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.VIEWPORT);
         event.mCharacters = "Viewport:Change";
         StringBuffer sb = new StringBuffer(256);
         sb.append("{ \"x\" : ").append(metrics.viewportRectLeft)
           .append(", \"y\" : ").append(metrics.viewportRectTop)
           .append(", \"zoom\" : ").append(metrics.zoomFactor)
-          .append(", \"fixedMarginLeft\" : ").append(metrics.fixedLayerMarginLeft)
-          .append(", \"fixedMarginTop\" : ").append(metrics.fixedLayerMarginTop)
-          .append(", \"fixedMarginRight\" : ").append(metrics.fixedLayerMarginRight)
-          .append(", \"fixedMarginBottom\" : ").append(metrics.fixedLayerMarginBottom)
+          .append(", \"fixedMarginLeft\" : ").append(metrics.marginLeft)
+          .append(", \"fixedMarginTop\" : ").append(metrics.marginTop)
+          .append(", \"fixedMarginRight\" : ").append(metrics.marginRight)
+          .append(", \"fixedMarginBottom\" : ").append(metrics.marginBottom)
           .append(", \"displayPort\" :").append(displayPort.toJSON())
           .append('}');
         event.mCharactersExtra = sb.toString();
         return event;
     }
 
     public static GeckoEvent createURILoadEvent(String uri) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.LOAD_URI);
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -186,16 +186,17 @@ FENNEC_JAVA_FILES = \
   gfx/GeckoLayerClient.java \
   gfx/GfxInfoThread.java \
   gfx/GLController.java \
   gfx/ImmutableViewportMetrics.java \
   gfx/InputConnectionHandler.java \
   gfx/IntSize.java \
   gfx/JavaPanZoomController.java \
   gfx/Layer.java \
+  gfx/LayerMarginsAnimator.java \
   gfx/LayerRenderer.java \
   gfx/LayerView.java \
   gfx/PluginLayer.java \
   gfx/NinePatchTileLayer.java \
   gfx/PanningPerfAPI.java \
   gfx/PanZoomController.java \
   gfx/PanZoomTarget.java \
   gfx/PointUtils.java \
@@ -441,21 +442,16 @@ RES_LAYOUT = \
   res/layout/abouthome_last_tabs_row.xml \
   res/layout/abouthome_section.xml \
   res/layout/abouthome_remote_tab_row.xml \
   res/layout/abouthome_topsite_item.xml \
   res/layout/validation_message.xml \
   res/layout/videoplayer.xml \
   $(NULL)
 
-RES_LAYOUT_LAND_V14 = \
-  res/layout-land-v14/browser_toolbar.xml \
-  res/layout-land-v14/browser_toolbar_menu.xml \
-  $(NULL)
-
 RES_LAYOUT_LARGE_V11 = \
   res/layout-large-v11/awesomebar_search.xml \
   res/layout-large-v11/browser_toolbar_menu.xml \
   $(NULL)
 
 RES_LAYOUT_LARGE_LAND_V11 = \
   res/layout-large-land-v11/tabs_panel.xml \
   res/layout-large-land-v11/tabs_panel_header.xml \
@@ -512,20 +508,16 @@ RES_VALUES_LARGE_LAND_V11 = \
   $(NULL)
 
 RES_VALUES_XLARGE_V11 = \
   res/values-xlarge-v11/dimens.xml \
   res/values-xlarge-v11/integers.xml \
   res/values-xlarge-v11/styles.xml \
   $(NULL)
 
-RES_VALUES_LAND_V14 = \
-  res/values-land-v14/dimens.xml \
-  $(NULL)
-
 RES_VALUES_V14 = \
   res/values-v14/styles.xml \
   $(NULL)
 
 RES_XML = \
   res/xml/preference_headers.xml \
   res/xml/preferences_general.xml \
   res/xml/preferences_privacy.xml \
@@ -895,28 +887,16 @@ RES_DRAWABLE_XHDPI_V11 = \
   res/drawable-xhdpi-v11/ic_menu_save_as_pdf.png \
   res/drawable-xhdpi-v11/ic_menu_settings.png \
   res/drawable-xhdpi-v11/ic_menu_share.png \
   res/drawable-xhdpi-v11/ic_menu_tools.png \
   res/drawable-xhdpi-v11/ic_menu_quit.png \
   res/drawable-xhdpi-v11/ic_status_logo.png \
   $(NULL)
 
-RES_DRAWABLE_LAND_MDPI_V14 = \
-  res/drawable-land-mdpi-v14/tabs_carat.png \
-  $(NULL)
-
-RES_DRAWABLE_LAND_HDPI_V14 = \
-  res/drawable-land-hdpi-v14/tabs_carat.png \
-  $(NULL)
-
-RES_DRAWABLE_LAND_XHDPI_V14 = \
-  res/drawable-land-xhdpi-v14/tabs_carat.png \
-  $(NULL)
-
 RES_DRAWABLE_LARGE_LAND_V11 = \
   res/drawable-large-land-v11/tabs_level.xml \
   $(NULL)
 
 RES_DRAWABLE_LARGE_MDPI_V11 = \
   res/drawable-large-mdpi-v11/doorhanger_popup_bg.9.png \
   res/drawable-large-mdpi-v11/ic_menu_reload.png \
   res/drawable-large-mdpi-v11/ic_menu_forward.png \
@@ -1046,21 +1026,20 @@ MOZ_ANDROID_DRAWABLES += \
   mobile/android/base/resources/drawable/tabs_level.xml                         \
   mobile/android/base/resources/drawable/tabs_panel_indicator.xml               \
   mobile/android/base/resources/drawable/textbox_bg.xml                         \
   mobile/android/base/resources/drawable/webapp_titlebar_bg.xml                 \
   $(NULL)
 
 MOZ_BRANDING_DRAWABLE_MDPI = $(shell if test -e $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn; then cat $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn | tr '\n' ' ';  fi)
 
-RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_LAND_V14) $(RES_LAYOUT_LARGE_LAND_V11) $(RES_LAYOUT_LARGE_V11) $(RES_LAYOUT_XLARGE_V11) $(RES_LAYOUT_XLARGE_LAND_V11) $(RES_VALUES) $(RES_VALUES_LAND) $(RES_VALUES_V11) $(RES_VALUES_LARGE_V11) $(RES_VALUES_LARGE_LAND_V11) $(RES_VALUES_XLARGE_V11) $(RES_VALUES_LAND_V14) $(RES_VALUES_V14) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_MDPI) $(RES_DRAWABLE_LDPI) $(RES_DRAWABLE_HDPI) $(RES_DRAWABLE_XHDPI) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_DRAWABLE_LARGE_LAND_V11) $(RES_DRAWABLE_LARGE_MDPI_V11) $(RES_DRAWABLE_LARGE_HDPI_V11) $(RES_DRAWABLE_LARGE_XHDPI_V11) $(RES_DRAWABLE_XLARGE_MDPI_V11) $(RES_DRAWABLE_XLARGE_HDPI_V11) $(RES_DRAWABLE_XLARGE_XHDPI_V11) $(RES_COLOR) $(RES_MENU)
+RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_LARGE_LAND_V11) $(RES_LAYOUT_LARGE_V11) $(RES_LAYOUT_XLARGE_V11) $(RES_LAYOUT_XLARGE_LAND_V11) $(RES_VALUES) $(RES_VALUES_LAND) $(RES_VALUES_V11) $(RES_VALUES_LARGE_V11) $(RES_VALUES_LARGE_LAND_V11) $(RES_VALUES_XLARGE_V11) $(RES_VALUES_LAND_V14) $(RES_VALUES_V14) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_MDPI) $(RES_DRAWABLE_LDPI) $(RES_DRAWABLE_HDPI) $(RES_DRAWABLE_XHDPI) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LARGE_LAND_V11) $(RES_DRAWABLE_LARGE_MDPI_V11) $(RES_DRAWABLE_LARGE_HDPI_V11) $(RES_DRAWABLE_LARGE_XHDPI_V11) $(RES_DRAWABLE_XLARGE_MDPI_V11) $(RES_DRAWABLE_XLARGE_HDPI_V11) $(RES_DRAWABLE_XLARGE_XHDPI_V11) $(RES_COLOR) $(RES_MENU)
 
 RES_DIRS= \
   res/layout                    \
-  res/layout-land-v14           \
   res/layout-large-v11          \
   res/layout-large-land-v11     \
   res/layout-xlarge-v11         \
   res/layout-xlarge-land-v11    \
   res/values                    \
   res/values-v11                \
   res/values-large-v11          \
   res/values-xlarge-v11         \
@@ -1070,20 +1049,16 @@ RES_DIRS= \
   res/drawable-ldpi             \
   res/drawable-mdpi             \
   res/drawable-hdpi             \
   res/drawable-xhdpi            \
   res/drawable                  \
   res/drawable-mdpi-v11         \
   res/drawable-hdpi-v11         \
   res/drawable-xhdpi-v11        \
-  res/drawable-land-v14         \
-  res/drawable-land-mdpi-v14    \
-  res/drawable-land-hdpi-v14    \
-  res/drawable-land-xhdpi-v14   \
   res/drawable-large-mdpi-v11   \
   res/drawable-large-hdpi-v11   \
   res/drawable-large-xhdpi-v11  \
   res/drawable-xlarge-mdpi-v11  \
   res/drawable-xlarge-hdpi-v11  \
   res/drawable-xlarge-xhdpi-v11 \
   res/color                     \
   res/menu                      \
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -51,16 +51,17 @@ public class Tab {
     private boolean mExternal;
     private boolean mBookmark;
     private boolean mReadingListItem;
     private long mFaviconLoadId;
     private String mDocumentURI;
     private String mContentType;
     private boolean mHasTouchListeners;
     private ZoomConstraints mZoomConstraints;
+    private boolean mIsRTL;
     private ArrayList<View> mPluginViews;
     private HashMap<Object, Layer> mPluginLayers;
     private int mBackgroundColor;
     private int mState;
     private Bitmap mThumbnailBitmap;
     private boolean mDesktopMode;
     private boolean mEnteringReaderMode;
     private Context mContext;
@@ -290,16 +291,24 @@ public class Tab {
     public void setZoomConstraints(ZoomConstraints constraints) {
         mZoomConstraints = constraints;
     }
 
     public ZoomConstraints getZoomConstraints() {
         return mZoomConstraints;
     }
 
+    public void setIsRTL(boolean aIsRTL) {
+        mIsRTL = aIsRTL;
+    }
+
+    public boolean getIsRTL() {
+        return mIsRTL;
+    }
+
     public void setHasTouchListeners(boolean aValue) {
         mHasTouchListeners = aValue;
     }
 
     public boolean getHasTouchListeners() {
         return mHasTouchListeners;
     }
 
--- a/mobile/android/base/TextSelection.java
+++ b/mobile/android/base/TextSelection.java
@@ -117,31 +117,35 @@ class TextSelection extends Layer implem
         });
     }
 
     @Override
     public void draw(final RenderContext context) {
         // cache the relevant values from the context and bail out if they are the same. we do this
         // because this draw function gets called a lot (once per compositor frame) and we want to
         // avoid doing a lot of extra work in cases where it's not needed.
-        if (FloatUtils.fuzzyEquals(mViewLeft, context.viewport.left)
-                && FloatUtils.fuzzyEquals(mViewTop, context.viewport.top)
-                && FloatUtils.fuzzyEquals(mViewZoom, context.zoomFactor)) {
+        final float viewLeft = context.viewport.left - context.offset.x;
+        final float viewTop = context.viewport.top - context.offset.y;
+        final float viewZoom = context.zoomFactor;
+
+        if (FloatUtils.fuzzyEquals(mViewLeft, viewLeft)
+                && FloatUtils.fuzzyEquals(mViewTop, viewTop)
+                && FloatUtils.fuzzyEquals(mViewZoom, viewZoom)) {
             return;
         }
-        mViewLeft = context.viewport.left;
-        mViewTop = context.viewport.top;
-        mViewZoom = context.zoomFactor;
+        mViewLeft = viewLeft;
+        mViewTop = viewTop;
+        mViewZoom = viewZoom;
 
         mActivity.runOnUiThread(new Runnable() {
             @Override
             public void run() {
-                mStartHandle.repositionWithViewport(context.viewport.left, context.viewport.top, context.zoomFactor);
-                mMiddleHandle.repositionWithViewport(context.viewport.left, context.viewport.top, context.zoomFactor);
-                mEndHandle.repositionWithViewport(context.viewport.left, context.viewport.top, context.zoomFactor);
+                mStartHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
+                mMiddleHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
+                mEndHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
             }
         });
     }
 
     private void registerEventListener(String event) {
         mEventDispatcher.registerEventListener(event, this);
     }
 
--- a/mobile/android/base/TextSelectionHandle.java
+++ b/mobile/android/base/TextSelectionHandle.java
@@ -150,17 +150,18 @@ class TextSelectionHandle extends ImageV
 
         mGeckoPoint = new PointF(left, top);
         if (mIsRTL != rtl) {
             mIsRTL = rtl;
             setImageLevel(mIsRTL ? IMAGE_LEVEL_RTL : IMAGE_LEVEL_LTR);
         }
 
         ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-        repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
+        PointF offset = metrics.getMarginOffset();
+        repositionWithViewport(metrics.viewportRectLeft - offset.x, metrics.viewportRectTop - offset.y, metrics.zoomFactor);
     }
 
     void repositionWithViewport(float x, float y, float zoom) {
         PointF viewPoint = new PointF((mGeckoPoint.x * zoom) - x,
                                       (mGeckoPoint.y * zoom) - y);
 
         mLeft = viewPoint.x - adjustLeftForHandle();
         mTop = viewPoint.y;
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -78,50 +78,50 @@ public class GeckoLayerClient implements
      * Specifically:
      * 1) reading mViewportMetrics from any thread is fine without synchronization
      * 2) writing to mViewportMetrics requires synchronizing on the layer controller object
      * 3) whenver reading multiple fields from mViewportMetrics without synchronization (i.e. in
      *    case 1 above) you should always frist grab a local copy of the reference, and then use
      *    that because mViewportMetrics might get reassigned in between reading the different
      *    fields. */
     private volatile ImmutableViewportMetrics mViewportMetrics;
+    private OnMetricsChangedListener mViewportChangeListener;
 
     private ZoomConstraints mZoomConstraints;
 
     private boolean mGeckoIsReady;
 
     private final PanZoomController mPanZoomController;
+    private final LayerMarginsAnimator mMarginsAnimator;
     private LayerView mView;
 
-    private boolean mClampOnMarginChange;
-
     public GeckoLayerClient(Context context, LayerView view, EventDispatcher eventDispatcher) {
         // we can fill these in with dummy values because they are always written
         // to before being read
         mContext = context;
         mScreenSize = new IntSize(0, 0);
         mWindowSize = new IntSize(0, 0);
         mDisplayPort = new DisplayPortMetrics();
         mRecordDrawTimes = true;
         mDrawTimingQueue = new DrawTimingQueue();
         mCurrentViewTransform = new ViewTransform(0, 0, 1);
         mCurrentViewTransformMargins = new RectF();
         mProgressiveUpdateData = new ProgressiveUpdateData();
         mProgressiveUpdateDisplayPort = new DisplayPortMetrics();
         mLastProgressiveUpdateWasLowPrecision = false;
         mProgressiveUpdateWasInDanger = false;
-        mClampOnMarginChange = true;
 
         mForceRedraw = true;
         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
         mViewportMetrics = new ImmutableViewportMetrics(displayMetrics)
                            .setViewportSize(view.getWidth(), view.getHeight());
         mZoomConstraints = new ZoomConstraints(false);
 
         mPanZoomController = PanZoomController.Factory.create(this, view, eventDispatcher);
+        mMarginsAnimator = new LayerMarginsAnimator(this);
         mView = view;
         mView.setListener(this);
     }
 
     /** Attaches to root layer so that Gecko appears. */
     public void notifyGeckoReady() {
         mGeckoIsReady = true;
 
@@ -201,16 +201,20 @@ public class GeckoLayerClient implements
             GeckoAppShell.viewSizeChanged();
         }
     }
 
     PanZoomController getPanZoomController() {
         return mPanZoomController;
     }
 
+    LayerMarginsAnimator getLayerMarginsAnimator() {
+        return mMarginsAnimator;
+    }
+
     /* Informs Gecko that the screen size has changed. */
     private void sendResizeEventIfNecessary(boolean force) {
         DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
 
         IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
         IntSize newWindowSize = new IntSize(mView.getWidth(), mView.getHeight());
 
         boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
@@ -254,72 +258,70 @@ public class GeckoLayerClient implements
             @Override
             public void run() {
                 mPanZoomController.pageRectUpdated();
                 mView.requestRender();
             }
         });
     }
 
-    private void adjustFixedLayerMarginsForOverscroll(ImmutableViewportMetrics metrics, RectF adjustedMargins) {
-        // When the page is in overscroll, we want that to 'eat into' the fixed
-        // margin on that side of the viewport. This is because overscroll
-        // equates to extra visible area and we use the fixed margins to stop
-        // fixed position elements from being obscured by chrome.
-        // When we're overscrolled, we also want to make sure that the fixed
-        // content on the non-overscroll side isn't obscured by the edge of the
-        // window, so when adjusting one side of a margin, we apply the opposite
-        // adjustment to the other side of the margin.
-        adjustedMargins.left = metrics.fixedLayerMarginLeft;
-        adjustedMargins.top = metrics.fixedLayerMarginTop;
-        adjustedMargins.right = metrics.fixedLayerMarginRight;
-        adjustedMargins.bottom = metrics.fixedLayerMarginBottom;
+    /**
+     * Derives content document fixed position margins/fixed layer margins from
+     * the view margins in the given metrics object.
+     */
+    private void getFixedMargins(ImmutableViewportMetrics metrics, RectF fixedMargins) {
+        fixedMargins.left = 0;
+        fixedMargins.top = 0;
+        fixedMargins.right = 0;
+        fixedMargins.bottom = 0;
 
         // The maximum margins are determined by the scrollable area of the page.
         float maxMarginWidth = Math.max(0, metrics.getPageWidth() - metrics.getWidthWithoutMargins());
         float maxMarginHeight = Math.max(0, metrics.getPageHeight() - metrics.getHeightWithoutMargins());
 
-        // Adjust for left overscroll
-        float leftOverscroll = metrics.pageRectLeft - metrics.viewportRectLeft;
-        if (leftOverscroll > 0) {
-            adjustedMargins.left = FloatUtils.clamp(adjustedMargins.left - leftOverscroll, 0, maxMarginWidth);
-            adjustedMargins.right = FloatUtils.clamp(adjustedMargins.right + leftOverscroll, 0, maxMarginWidth - adjustedMargins.left);
+        PointF offset = metrics.getMarginOffset();
+        RectF overscroll = metrics.getOverscroll();
+        if (offset.x >= 0) {
+            fixedMargins.right = Math.max(0, Math.min(offset.x - overscroll.right, maxMarginWidth));
+        } else {
+            fixedMargins.left = Math.max(0, Math.min(-offset.x - overscroll.left, maxMarginWidth));
+        }
+        if (offset.y >= 0) {
+            fixedMargins.bottom = Math.max(0, Math.min(offset.y - overscroll.bottom, maxMarginHeight));
+        } else {
+            fixedMargins.top = Math.max(0, Math.min(-offset.y - overscroll.top, maxMarginHeight));
         }
 
-        // Adjust for right overscroll
-        float rightOverscroll = metrics.viewportRectRight - metrics.pageRectRight;
-        if (rightOverscroll > 0) {
-            adjustedMargins.right = FloatUtils.clamp(adjustedMargins.right - rightOverscroll, 0, maxMarginWidth);
-            adjustedMargins.left = FloatUtils.clamp(adjustedMargins.left + rightOverscroll, 0, maxMarginWidth - adjustedMargins.right);
+        // Adjust for overscroll. If we're overscrolled on one side, add that
+        // distance to the margins of the other side (limiting to the maximum
+        // margin size calculated above).
+        if (overscroll.left > 0) {
+            fixedMargins.right = Math.min(maxMarginWidth - fixedMargins.left,
+                                          fixedMargins.right + overscroll.left);
+        } else if (overscroll.right > 0) {
+            fixedMargins.left = Math.min(maxMarginWidth - fixedMargins.right,
+                                         fixedMargins.left + overscroll.right);
         }
-
-        // Adjust for top overscroll
-        float topOverscroll = metrics.pageRectTop - metrics.viewportRectTop;
-        if (topOverscroll > 0) {
-            adjustedMargins.top = FloatUtils.clamp(adjustedMargins.top - topOverscroll, 0, maxMarginHeight);
-            adjustedMargins.bottom = FloatUtils.clamp(adjustedMargins.bottom + topOverscroll, 0, maxMarginHeight - adjustedMargins.top);
-        }
-
-        // Adjust for bottom overscroll
-        float bottomOverscroll = metrics.viewportRectBottom - metrics.pageRectBottom;
-        if (bottomOverscroll > 0) {
-            adjustedMargins.bottom = FloatUtils.clamp(adjustedMargins.bottom - bottomOverscroll, 0, maxMarginHeight);
-            adjustedMargins.top = FloatUtils.clamp(adjustedMargins.top + bottomOverscroll, 0, maxMarginHeight - adjustedMargins.bottom);
+        if (overscroll.top > 0) {
+            fixedMargins.bottom = Math.min(maxMarginHeight - fixedMargins.top,
+                                           fixedMargins.bottom + overscroll.top);
+        } else if (overscroll.bottom > 0) {
+            fixedMargins.top = Math.min(maxMarginHeight - fixedMargins.bottom,
+                                        fixedMargins.top + overscroll.bottom);
         }
     }
 
     private void adjustViewport(DisplayPortMetrics displayPort) {
         ImmutableViewportMetrics metrics = getViewportMetrics();
         ImmutableViewportMetrics clampedMetrics = metrics.clamp();
 
-        RectF fixedLayerMargins = new RectF();
-        adjustFixedLayerMarginsForOverscroll(metrics, fixedLayerMargins);
-        clampedMetrics = clampedMetrics.setFixedLayerMargins(
-            fixedLayerMargins.left, fixedLayerMargins.top,
-            fixedLayerMargins.right, fixedLayerMargins.bottom);
+        RectF margins = new RectF();
+        getFixedMargins(metrics, margins);
+        clampedMetrics = clampedMetrics.setMargins(
+            margins.left, margins.top, margins.right, margins.bottom);
 
         if (displayPort == null) {
             displayPort = DisplayPortCalculator.calculate(metrics, mPanZoomController.getVelocityVector());
         }
 
         mDisplayPort = displayPort;
         mGeckoViewport = clampedMetrics;
 
@@ -381,31 +383,16 @@ public class GeckoLayerClient implements
             final ImmutableViewportMetrics geckoMetrics = newMetrics.clamp();
             post(new Runnable() {
                 @Override
                 public void run() {
                     mGeckoViewport = geckoMetrics;
                 }
             });
 
-            // If we're meant to be scrolled to the top, take into account
-            // the current fixed layer margins and offset the local viewport
-            // accordingly.
-            // XXX We should also do this for the left on an ltr document, and
-            //     the right on an rtl document, but we don't currently have
-            //     a way of determining the text direction from Java.
-            //     This also applies to setFirstPaintViewport.
-            if (type == ViewportMessageType.UPDATE
-                    && FloatUtils.fuzzyEquals(newMetrics.viewportRectTop,
-                                              newMetrics.pageRectTop)
-                    && oldMetrics.fixedLayerMarginTop > 0) {
-                newMetrics = newMetrics.setViewportOrigin(newMetrics.viewportRectLeft,
-                                 newMetrics.pageRectTop - oldMetrics.fixedLayerMarginTop);
-            }
-
             setViewportMetrics(newMetrics, type == ViewportMessageType.UPDATE);
             mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null);
         }
         return mDisplayPort;
     }
 
     public DisplayPortMetrics getDisplayPort(boolean pageSizeUpdate, boolean isBrowserContentDisplayed, int tabId, ImmutableViewportMetrics metrics) {
         Tabs tabs = Tabs.getInstance();
@@ -418,68 +405,16 @@ public class GeckoLayerClient implements
             // for background tabs, request a new display port calculation, so that
             // when we do switch to that tab, we have the correct display port and
             // don't need to draw twice (once to allow the first-paint viewport to
             // get to java, and again once java figures out the display port).
             return DisplayPortCalculator.calculate(metrics, null);
         }
     }
 
-    /**
-     * Sets margins on fixed-position layers, to be used when compositing.
-     * Must be called on the UI thread!
-     */
-    public synchronized void setFixedLayerMargins(float left, float top, float right, float bottom) {
-        ImmutableViewportMetrics oldMetrics = getViewportMetrics();
-        ImmutableViewportMetrics newMetrics = oldMetrics.setFixedLayerMargins(left, top, right, bottom);
-
-        if (mClampOnMarginChange) {
-            // Only clamp on decreased margins
-            boolean changed = false;
-            float viewportRectLeft = oldMetrics.viewportRectLeft;
-            float viewportRectTop = oldMetrics.viewportRectTop;
-
-            // Clamp the x-axis if the page was over-scrolled into the margin
-            // area.
-            if (oldMetrics.fixedLayerMarginLeft > left &&
-                viewportRectLeft < oldMetrics.pageRectLeft - left) {
-                viewportRectLeft = oldMetrics.pageRectLeft - left;
-                changed = true;
-            } else if (oldMetrics.fixedLayerMarginRight > right &&
-                       oldMetrics.viewportRectRight > oldMetrics.pageRectRight + right) {
-                viewportRectLeft = oldMetrics.pageRectRight + right - oldMetrics.getWidth();
-                changed = true;
-            }
-
-            // Do the same for the y-axis.
-            if (oldMetrics.fixedLayerMarginTop > top &&
-                viewportRectTop < oldMetrics.pageRectTop - top) {
-                viewportRectTop = oldMetrics.pageRectTop - top;
-                changed = true;
-            } else if (oldMetrics.fixedLayerMarginBottom > bottom &&
-                       oldMetrics.viewportRectBottom > oldMetrics.pageRectBottom + bottom) {
-                viewportRectTop = oldMetrics.pageRectBottom + bottom - oldMetrics.getHeight();
-                changed = true;
-            }
-
-            // Set the new metrics, if they're different.
-            if (changed) {
-                newMetrics = newMetrics.setViewportOrigin(viewportRectLeft, viewportRectTop);
-            }
-        }
-
-        mViewportMetrics = newMetrics;
-        mView.requestRender();
-        setShadowVisibility();
-    }
-
-    public void setClampOnFixedLayerMarginsChange(boolean aClamp) {
-        mClampOnMarginChange = aClamp;
-    }
-
     // This is called on the Gecko thread to determine if we're still interested
     // in the update of this display-port to continue. We can return true here
     // to abort the current update and continue with any subsequent ones. This
     // is useful for slow-to-render pages when the display-port starts lagging
     // behind enough that continuing to draw it is wasted effort.
     public ProgressiveUpdateData progressiveUpdateCallback(boolean aHasPendingNewThebesContent,
                                                            float x, float y, float width, float height,
                                                            float resolution, boolean lowPrecision) {
@@ -575,56 +510,55 @@ public class GeckoLayerClient implements
         }
         return mProgressiveUpdateData;
     }
 
     void setZoomConstraints(ZoomConstraints constraints) {
         mZoomConstraints = constraints;
     }
 
+    void setIsRTL(boolean aIsRTL) {
+        ImmutableViewportMetrics newMetrics = getViewportMetrics().setIsRTL(aIsRTL);
+        setViewportMetrics(newMetrics, false);
+    }
+
     /** This function is invoked by Gecko via JNI; be careful when modifying signature.
       * The compositor invokes this function just before compositing a frame where the document
       * is different from the document composited on the last frame. In these cases, the viewport
       * information we have in Java is no longer valid and needs to be replaced with the new
       * viewport information provided. setPageRect will never be invoked on the same frame that
       * this function is invoked on; and this function will always be called prior to syncViewportInfo.
       */
     public void setFirstPaintViewport(float offsetX, float offsetY, float zoom,
             float pageLeft, float pageTop, float pageRight, float pageBottom,
             float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
         synchronized (this) {
             ImmutableViewportMetrics currentMetrics = getViewportMetrics();
 
+            Tab tab = Tabs.getInstance().getSelectedTab();
+
             final ImmutableViewportMetrics newMetrics = currentMetrics
                 .setViewportOrigin(offsetX, offsetY)
                 .setZoomFactor(zoom)
                 .setPageRect(new RectF(pageLeft, pageTop, pageRight, pageBottom),
-                             new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom));
+                             new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom))
+                .setIsRTL(tab.getIsRTL());
             // Since we have switched to displaying a different document, we need to update any
             // viewport-related state we have lying around. This includes mGeckoViewport and
             // mViewportMetrics. Usually this information is updated via handleViewportMessage
             // while we remain on the same document.
             post(new Runnable() {
                 @Override
                 public void run() {
                     mGeckoViewport = newMetrics;
                 }
             });
 
-            // If we're meant to be scrolled to the top, take into account any
-            // margin set on the pan zoom controller.
-            if (FloatUtils.fuzzyEquals(offsetY, pageTop)
-                  && newMetrics.fixedLayerMarginTop > 0) {
-                setViewportMetrics(newMetrics.setViewportOrigin(offsetX,
-                    -newMetrics.fixedLayerMarginTop));
-            } else {
-                setViewportMetrics(newMetrics);
-            }
+            setViewportMetrics(newMetrics);
 
-            Tab tab = Tabs.getInstance().getSelectedTab();
             mView.setBackgroundColor(tab.getBackgroundColor());
             setZoomConstraints(tab.getZoomConstraints());
 
             // At this point, we have just switched to displaying a different document than we
             // we previously displaying. This means we need to abort any panning/zooming animations
             // that are in progress and send an updated display port request to browser.js as soon
             // as possible. The call to PanZoomController.abortAnimation accomplishes this by calling the
             // forceRedraw function, which sends the viewport to gecko. The display port request is
@@ -678,23 +612,33 @@ public class GeckoLayerClient implements
         // of the compositor thread.
         mFrameMetrics = getViewportMetrics();
 
         mCurrentViewTransform.x = mFrameMetrics.viewportRectLeft;
         mCurrentViewTransform.y = mFrameMetrics.viewportRectTop;
         mCurrentViewTransform.scale = mFrameMetrics.zoomFactor;
 
         // Adjust the fixed layer margins so that overscroll subtracts from them.
-        adjustFixedLayerMarginsForOverscroll(mFrameMetrics, mCurrentViewTransformMargins);
+        getFixedMargins(mFrameMetrics, mCurrentViewTransformMargins);
         mCurrentViewTransform.fixedLayerMarginLeft = mCurrentViewTransformMargins.left;
         mCurrentViewTransform.fixedLayerMarginTop = mCurrentViewTransformMargins.top;
         mCurrentViewTransform.fixedLayerMarginRight = mCurrentViewTransformMargins.right;
         mCurrentViewTransform.fixedLayerMarginBottom = mCurrentViewTransformMargins.bottom;
 
-        mRootLayer.setPositionAndResolution(x, y, x + width, y + height, resolution);
+        // Offset the view transform so that it renders in the correct place.
+        PointF offset = mFrameMetrics.getMarginOffset();
+        mCurrentViewTransform.offsetX = offset.x;
+        mCurrentViewTransform.offsetY = offset.y;
+
+        mRootLayer.setPositionAndResolution(
+            Math.round(x + mCurrentViewTransform.offsetX),
+            Math.round(y + mCurrentViewTransform.offsetY),
+            Math.round(x + width + mCurrentViewTransform.offsetX),
+            Math.round(y + height + mCurrentViewTransform.offsetY),
+            resolution);
 
         if (layersUpdated && mRecordDrawTimes) {
             // If we got a layers update, that means a draw finished. Check to see if the area drawn matches
             // one of our requested displayports; if it does calculate the draw time and notify the
             // DisplayPortCalculator
             DisplayPortMetrics drawn = new DisplayPortMetrics(x, y, x + width, y + height, resolution);
             long time = mDrawTimingQueue.findTimeFor(drawn);
             if (time >= 0) {
@@ -813,35 +757,88 @@ public class GeckoLayerClient implements
      */
     private void setViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko) {
         // This class owns the viewport size and the fixed layer margins; don't let other pieces
         // of code clobber either of them. The only place the viewport size should ever be
         // updated is in GeckoLayerClient.setViewportSize, and the only place the margins should
         // ever be updated is in GeckoLayerClient.setFixedLayerMargins; both of these assign to
         // mViewportMetrics directly.
         metrics = metrics.setViewportSize(mViewportMetrics.getWidth(), mViewportMetrics.getHeight());
-        metrics = metrics.setFixedLayerMarginsFrom(mViewportMetrics);
+        metrics = metrics.setMarginsFrom(mViewportMetrics);
         mViewportMetrics = metrics;
 
+        viewportMetricsChanged(notifyGecko);
+    }
+
+    /*
+     * You must hold the monitor while calling this.
+     */
+    private void viewportMetricsChanged(boolean notifyGecko) {
+        if (mViewportChangeListener != null) {
+            mViewportChangeListener.onMetricsChanged(mViewportMetrics);
+        }
+
         mView.requestRender();
         if (notifyGecko && mGeckoIsReady) {
             geometryChanged();
         }
         setShadowVisibility();
     }
 
+    /*
+     * Updates the viewport metrics, overriding the viewport size and margins
+     * which are normally retained when calling setViewportMetrics.
+     * You must hold the monitor while calling this.
+     */
+    void forceViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko, boolean forceRedraw) {
+        if (forceRedraw) {
+            mForceRedraw = true;
+        }
+        mViewportMetrics = metrics;
+        viewportMetricsChanged(notifyGecko);
+    }
+
+    /** Implementation of PanZoomTarget
+     * Scroll the viewport by a certain amount. This will take viewport margins
+     * and margin animation into account. If margins are currently animating,
+     * this will just go ahead and modify the viewport origin, otherwise the
+     * delta will be applied to the margins and the remainder will be applied to
+     * the viewport origin.
+     *
+     * You must hold the monitor while calling this.
+     */
+    @Override
+    public void scrollBy(float dx, float dy) {
+        // Set mViewportMetrics manually so the margin changes take.
+        mViewportMetrics = mMarginsAnimator.scrollBy(mViewportMetrics, dx, dy);
+        viewportMetricsChanged(true);
+    }
+
+    /** Implementation of PanZoomTarget */
+    @Override
+    public void panZoomStopped() {
+        if (mViewportChangeListener != null) {
+            mViewportChangeListener.onPanZoomStopped();
+        }
+    }
+
+    public interface OnMetricsChangedListener {
+        public void onMetricsChanged(ImmutableViewportMetrics viewport);
+        public void onPanZoomStopped();
+    }
+
     private void setShadowVisibility() {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 if (BrowserApp.mBrowserToolbar == null) {
                     return;
                 }
                 ImmutableViewportMetrics m = mViewportMetrics;
-                BrowserApp.mBrowserToolbar.setShadowVisibility(m.viewportRectTop >= m.pageRectTop - m.fixedLayerMarginTop);
+                BrowserApp.mBrowserToolbar.setShadowVisibility(m.viewportRectTop >= m.pageRectTop);
             }
         });
     }
 
     /** Implementation of PanZoomTarget */
     @Override
     public void forceRedraw() {
         mForceRedraw = true;
@@ -872,33 +869,39 @@ public class GeckoLayerClient implements
     @Override
     public PointF convertViewPointToLayerPoint(PointF viewPoint) {
         if (!mGeckoIsReady) {
             return null;
         }
 
         ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
         PointF origin = viewportMetrics.getOrigin();
+        PointF offset = viewportMetrics.getMarginOffset();
+        origin.offset(-offset.x, -offset.y);
         float zoom = viewportMetrics.zoomFactor;
         ImmutableViewportMetrics geckoViewport = mGeckoViewport;
         PointF geckoOrigin = geckoViewport.getOrigin();
         float geckoZoom = geckoViewport.zoomFactor;
 
-        // viewPoint + origin gives the coordinate in device pixels from the top-left corner of the page.
+        // viewPoint + origin - offset gives the coordinate in device pixels from the top-left corner of the page.
         // Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page.
         // geckoOrigin / geckoZoom is where Gecko thinks it is (scrollTo position) in CSS pixels from
         // the top-left corner of the page. Subtracting the two gives us the offset of the viewPoint from
         // the current Gecko coordinate in CSS pixels.
         PointF layerPoint = new PointF(
                 ((viewPoint.x + origin.x) / zoom) - (geckoOrigin.x / geckoZoom),
                 ((viewPoint.y + origin.y) / zoom) - (geckoOrigin.y / geckoZoom));
 
         return layerPoint;
     }
 
+    public void setOnMetricsChangedListener(OnMetricsChangedListener listener) {
+        mViewportChangeListener = listener;
+    }
+
     /** Used by robocop for testing purposes. Not for production use! */
     public void setDrawListener(DrawListener listener) {
         mDrawListener = listener;
     }
 
     /** Used by robocop for testing purposes. Not for production use! */
     public static interface DrawListener {
         public void drawFinished();
--- a/mobile/android/base/gfx/ImmutableViewportMetrics.java
+++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java
@@ -27,91 +27,104 @@ public class ImmutableViewportMetrics {
     public final float cssPageRectLeft;
     public final float cssPageRectTop;
     public final float cssPageRectRight;
     public final float cssPageRectBottom;
     public final float viewportRectLeft;
     public final float viewportRectTop;
     public final float viewportRectRight;
     public final float viewportRectBottom;
-    public final float fixedLayerMarginLeft;
-    public final float fixedLayerMarginTop;
-    public final float fixedLayerMarginRight;
-    public final float fixedLayerMarginBottom;
+    public final float marginLeft;
+    public final float marginTop;
+    public final float marginRight;
+    public final float marginBottom;
     public final float zoomFactor;
+    public final boolean isRTL;
 
     public ImmutableViewportMetrics(DisplayMetrics metrics) {
         viewportRectLeft   = pageRectLeft   = cssPageRectLeft   = 0;
         viewportRectTop    = pageRectTop    = cssPageRectTop    = 0;
         viewportRectRight  = pageRectRight  = cssPageRectRight  = metrics.widthPixels;
         viewportRectBottom = pageRectBottom = cssPageRectBottom = metrics.heightPixels;
-        fixedLayerMarginLeft = fixedLayerMarginTop = fixedLayerMarginRight = fixedLayerMarginBottom = 0;
+        marginLeft = marginTop = marginRight = marginBottom = 0;
         zoomFactor = 1.0f;
+        isRTL = false;
     }
 
+    /** This constructor is used by native code in AndroidJavaWrappers.cpp, be
+     * careful when modifying the signature.
+     */
     private ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop,
         float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft,
         float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom,
         float aViewportRectLeft, float aViewportRectTop, float aViewportRectRight,
         float aViewportRectBottom, float aZoomFactor)
     {
         this(aPageRectLeft, aPageRectTop,
              aPageRectRight, aPageRectBottom, aCssPageRectLeft,
              aCssPageRectTop, aCssPageRectRight, aCssPageRectBottom,
              aViewportRectLeft, aViewportRectTop, aViewportRectRight,
-             aViewportRectBottom, 0.0f, 0.0f, 0.0f, 0.0f, aZoomFactor);
+             aViewportRectBottom, 0.0f, 0.0f, 0.0f, 0.0f, aZoomFactor, false);
     }
 
     private ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop,
         float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft,
         float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom,
         float aViewportRectLeft, float aViewportRectTop, float aViewportRectRight,
-        float aViewportRectBottom, float aFixedLayerMarginLeft,
-        float aFixedLayerMarginTop, float aFixedLayerMarginRight,
-        float aFixedLayerMarginBottom, float aZoomFactor)
+        float aViewportRectBottom, float aMarginLeft,
+        float aMarginTop, float aMarginRight,
+        float aMarginBottom, float aZoomFactor, boolean aIsRTL)
     {
         pageRectLeft = aPageRectLeft;
         pageRectTop = aPageRectTop;
         pageRectRight = aPageRectRight;
         pageRectBottom = aPageRectBottom;
         cssPageRectLeft = aCssPageRectLeft;
         cssPageRectTop = aCssPageRectTop;
         cssPageRectRight = aCssPageRectRight;
         cssPageRectBottom = aCssPageRectBottom;
         viewportRectLeft = aViewportRectLeft;
         viewportRectTop = aViewportRectTop;
         viewportRectRight = aViewportRectRight;
         viewportRectBottom = aViewportRectBottom;
-        fixedLayerMarginLeft = aFixedLayerMarginLeft;
-        fixedLayerMarginTop = aFixedLayerMarginTop;
-        fixedLayerMarginRight = aFixedLayerMarginRight;
-        fixedLayerMarginBottom = aFixedLayerMarginBottom;
+        marginLeft = aMarginLeft;
+        marginTop = aMarginTop;
+        marginRight = aMarginRight;
+        marginBottom = aMarginBottom;
         zoomFactor = aZoomFactor;
+        isRTL = aIsRTL;
     }
 
     public float getWidth() {
         return viewportRectRight - viewportRectLeft;
     }
 
     public float getHeight() {
         return viewportRectBottom - viewportRectTop;
     }
 
     public float getWidthWithoutMargins() {
-        return viewportRectRight - viewportRectLeft - fixedLayerMarginLeft - fixedLayerMarginRight;
+        return viewportRectRight - viewportRectLeft - marginLeft - marginRight;
     }
 
     public float getHeightWithoutMargins() {
-        return viewportRectBottom - viewportRectTop - fixedLayerMarginTop - fixedLayerMarginBottom;
+        return viewportRectBottom - viewportRectTop - marginTop - marginBottom;
     }
 
     public PointF getOrigin() {
         return new PointF(viewportRectLeft, viewportRectTop);
     }
 
+    public PointF getMarginOffset() {
+        if (isRTL) {
+            return new PointF(marginLeft - marginRight, marginTop);
+        }
+        return new PointF(marginLeft, marginTop);
+    }
+
     public FloatSize getSize() {
         return new FloatSize(viewportRectRight - viewportRectLeft, viewportRectBottom - viewportRectTop);
     }
 
     public RectF getViewport() {
         return new RectF(viewportRectLeft,
                          viewportRectTop,
                          viewportRectRight,
@@ -125,24 +138,39 @@ public class ImmutableViewportMetrics {
     public RectF getPageRect() {
         return new RectF(pageRectLeft, pageRectTop, pageRectRight, pageRectBottom);
     }
 
     public float getPageWidth() {
         return pageRectRight - pageRectLeft;
     }
 
+    public float getPageWidthWithMargins() {
+        return (pageRectRight - pageRectLeft) + marginLeft + marginRight;
+    }
+
     public float getPageHeight() {
         return pageRectBottom - pageRectTop;
     }
 
+    public float getPageHeightWithMargins() {
+        return (pageRectBottom - pageRectTop) + marginTop + marginBottom;
+    }
+
     public RectF getCssPageRect() {
         return new RectF(cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom);
     }
 
+    public RectF getOverscroll() {
+        return new RectF(Math.max(0, pageRectLeft - viewportRectLeft),
+                         Math.max(0, pageRectTop - viewportRectTop),
+                         Math.max(0, viewportRectRight - pageRectRight),
+                         Math.max(0, viewportRectBottom - pageRectBottom));
+    }
+
     /*
      * Returns the viewport metrics that represent a linear transition between "this" and "to" at
      * time "t", which is on the scale [0, 1). This function interpolates all values stored in
      * the viewport metrics.
      */
     public ImmutableViewportMetrics interpolate(ImmutableViewportMetrics to, float t) {
         return new ImmutableViewportMetrics(
             FloatUtils.interpolate(pageRectLeft, to.pageRectLeft, t),
@@ -152,87 +180,111 @@ public class ImmutableViewportMetrics {
             FloatUtils.interpolate(cssPageRectLeft, to.cssPageRectLeft, t),
             FloatUtils.interpolate(cssPageRectTop, to.cssPageRectTop, t),
             FloatUtils.interpolate(cssPageRectRight, to.cssPageRectRight, t),
             FloatUtils.interpolate(cssPageRectBottom, to.cssPageRectBottom, t),
             FloatUtils.interpolate(viewportRectLeft, to.viewportRectLeft, t),
             FloatUtils.interpolate(viewportRectTop, to.viewportRectTop, t),
             FloatUtils.interpolate(viewportRectRight, to.viewportRectRight, t),
             FloatUtils.interpolate(viewportRectBottom, to.viewportRectBottom, t),
-            FloatUtils.interpolate(fixedLayerMarginLeft, to.fixedLayerMarginLeft, t),
-            FloatUtils.interpolate(fixedLayerMarginTop, to.fixedLayerMarginTop, t),
-            FloatUtils.interpolate(fixedLayerMarginRight, to.fixedLayerMarginRight, t),
-            FloatUtils.interpolate(fixedLayerMarginBottom, to.fixedLayerMarginBottom, t),
-            FloatUtils.interpolate(zoomFactor, to.zoomFactor, t));
+            FloatUtils.interpolate(marginLeft, to.marginLeft, t),
+            FloatUtils.interpolate(marginTop, to.marginTop, t),
+            FloatUtils.interpolate(marginRight, to.marginRight, t),
+            FloatUtils.interpolate(marginBottom, to.marginBottom, t),
+            FloatUtils.interpolate(zoomFactor, to.zoomFactor, t),
+            t >= 0.5 ? to.isRTL : isRTL);
     }
 
     public ImmutableViewportMetrics setViewportSize(float width, float height) {
         if (FloatUtils.fuzzyEquals(width, getWidth()) && FloatUtils.fuzzyEquals(height, getHeight())) {
             return this;
         }
 
         return new ImmutableViewportMetrics(
             pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
             cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
             viewportRectLeft, viewportRectTop, viewportRectLeft + width, viewportRectTop + height,
-            fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
-            zoomFactor);
+            marginLeft, marginTop, marginRight, marginBottom,
+            zoomFactor, isRTL);
     }
 
     public ImmutableViewportMetrics setViewportOrigin(float newOriginX, float newOriginY) {
         return new ImmutableViewportMetrics(
             pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
             cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
             newOriginX, newOriginY, newOriginX + getWidth(), newOriginY + getHeight(),
-            fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
-            zoomFactor);
+            marginLeft, marginTop, marginRight, marginBottom,
+            zoomFactor, isRTL);
     }
 
     public ImmutableViewportMetrics setZoomFactor(float newZoomFactor) {
         return new ImmutableViewportMetrics(
             pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
             cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
             viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom,
-            fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
-            newZoomFactor);
+            marginLeft, marginTop, marginRight, marginBottom,
+            newZoomFactor, isRTL);
     }
 
     public ImmutableViewportMetrics offsetViewportBy(float dx, float dy) {
         return setViewportOrigin(viewportRectLeft + dx, viewportRectTop + dy);
     }
 
+    public ImmutableViewportMetrics offsetViewportByAndClamp(float dx, float dy) {
+        if (isRTL) {
+            return setViewportOrigin(
+                Math.min(pageRectRight - getWidthWithoutMargins(), Math.max(viewportRectLeft + dx, pageRectLeft)),
+                Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeightWithoutMargins())));
+        }
+        return setViewportOrigin(
+            Math.max(pageRectLeft, Math.min(viewportRectLeft + dx, pageRectRight - getWidthWithoutMargins())),
+            Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeightWithoutMargins())));
+    }
+
     public ImmutableViewportMetrics setPageRect(RectF pageRect, RectF cssPageRect) {
         return new ImmutableViewportMetrics(
             pageRect.left, pageRect.top, pageRect.right, pageRect.bottom,
             cssPageRect.left, cssPageRect.top, cssPageRect.right, cssPageRect.bottom,
             viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom,
-            fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
-            zoomFactor);
+            marginLeft, marginTop, marginRight, marginBottom,
+            zoomFactor, isRTL);
     }
 
-    public ImmutableViewportMetrics setFixedLayerMargins(float left, float top, float right, float bottom) {
-        if (FloatUtils.fuzzyEquals(left, fixedLayerMarginLeft)
-                && FloatUtils.fuzzyEquals(top, fixedLayerMarginTop)
-                && FloatUtils.fuzzyEquals(right, fixedLayerMarginRight)
-                && FloatUtils.fuzzyEquals(bottom, fixedLayerMarginBottom)) {
+    public ImmutableViewportMetrics setMargins(float left, float top, float right, float bottom) {
+        if (FloatUtils.fuzzyEquals(left, marginLeft)
+                && FloatUtils.fuzzyEquals(top, marginTop)
+                && FloatUtils.fuzzyEquals(right, marginRight)
+                && FloatUtils.fuzzyEquals(bottom, marginBottom)) {
             return this;
         }
 
         return new ImmutableViewportMetrics(
             pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
             cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
             viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom,
-            left, top, right, bottom, zoomFactor);
+            left, top, right, bottom, zoomFactor, isRTL);
+    }
+
+    public ImmutableViewportMetrics setMarginsFrom(ImmutableViewportMetrics fromMetrics) {
+        return setMargins(fromMetrics.marginLeft,
+                          fromMetrics.marginTop,
+                          fromMetrics.marginRight,
+                          fromMetrics.marginBottom);
     }
 
-    public ImmutableViewportMetrics setFixedLayerMarginsFrom(ImmutableViewportMetrics fromMetrics) {
-        return setFixedLayerMargins(fromMetrics.fixedLayerMarginLeft,
-                                    fromMetrics.fixedLayerMarginTop,
-                                    fromMetrics.fixedLayerMarginRight,
-                                    fromMetrics.fixedLayerMarginBottom);
+    public ImmutableViewportMetrics setIsRTL(boolean aIsRTL) {
+        if (isRTL == aIsRTL) {
+            return this;
+        }
+
+        return new ImmutableViewportMetrics(
+            pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
+            cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
+            viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom,
+            marginLeft, marginTop, marginRight, marginBottom, zoomFactor, aIsRTL);
     }
 
     /* This will set the zoom factor and re-scale page-size and viewport offset
      * accordingly. The given focus will remain at the same point on the screen
      * after scaling.
      */
     public ImmutableViewportMetrics scaleTo(float newZoomFactor, PointF focus) {
         // cssPageRect* is invariant, since we're setting the scale factor
@@ -246,61 +298,61 @@ public class ImmutableViewportMetrics {
         origin.offset(focus.x, focus.y);
         origin = PointUtils.scale(origin, newZoomFactor / zoomFactor);
         origin.offset(-focus.x, -focus.y);
 
         return new ImmutableViewportMetrics(
             newPageRectLeft, newPageRectTop, newPageRectRight, newPageRectBottom,
             cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
             origin.x, origin.y, origin.x + getWidth(), origin.y + getHeight(),
-            fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
-            newZoomFactor);
+            marginLeft, marginTop, marginRight, marginBottom,
+            newZoomFactor, isRTL);
     }
 
     /** Clamps the viewport to remain within the page rect. */
     private ImmutableViewportMetrics clamp(float marginLeft, float marginTop,
                                            float marginRight, float marginBottom) {
         RectF newViewport = getViewport();
+        PointF offset = getMarginOffset();
 
         // The viewport bounds ought to never exceed the page bounds.
-        if (newViewport.right > pageRectRight + marginRight)
-            newViewport.offset((pageRectRight + marginRight) - newViewport.right, 0);
-        if (newViewport.left < pageRectLeft - marginLeft)
-            newViewport.offset((pageRectLeft - marginLeft) - newViewport.left, 0);
+        if (newViewport.right > pageRectRight + marginLeft + marginRight)
+            newViewport.offset((pageRectRight + marginLeft + marginRight) - newViewport.right, 0);
+        if (newViewport.left < pageRectLeft)
+            newViewport.offset(pageRectLeft - newViewport.left, 0);
 
-        if (newViewport.bottom > pageRectBottom + marginBottom)
-            newViewport.offset(0, (pageRectBottom + marginBottom) - newViewport.bottom);
-        if (newViewport.top < pageRectTop - marginTop)
-            newViewport.offset(0, (pageRectTop - marginTop) - newViewport.top);
+        if (newViewport.bottom > pageRectBottom + marginTop + marginBottom)
+            newViewport.offset(0, (pageRectBottom + marginTop + marginBottom) - newViewport.bottom);
+        if (newViewport.top < pageRectTop)
+            newViewport.offset(0, pageRectTop - newViewport.top);
 
         return new ImmutableViewportMetrics(
             pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
             cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
             newViewport.left, newViewport.top, newViewport.right, newViewport.bottom,
-            fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
-            zoomFactor);
+            marginLeft, marginTop, marginRight, marginBottom,
+            zoomFactor, isRTL);
     }
 
     public ImmutableViewportMetrics clamp() {
         return clamp(0, 0, 0, 0);
     }
 
     public ImmutableViewportMetrics clampWithMargins() {
-        return clamp(fixedLayerMarginLeft, fixedLayerMarginTop,
-                     fixedLayerMarginRight, fixedLayerMarginBottom);
+        return clamp(marginLeft, marginTop,
+                     marginRight, marginBottom);
     }
 
     public boolean fuzzyEquals(ImmutableViewportMetrics other) {
         // Don't bother checking the pageRectXXX values because they are a product
         // of the cssPageRectXXX values and the zoomFactor, except with more rounding
         // error. Checking those is both inefficient and can lead to false negatives.
         //
-        // This doesn't return false if the fixed layer margins differ as none
-        // of the users of this function are interested in the margins in that
-        // way.
+        // This doesn't return false if the margins differ as none of the users
+        // of this function are interested in the margins in that way.
         return FloatUtils.fuzzyEquals(cssPageRectLeft, other.cssPageRectLeft)
             && FloatUtils.fuzzyEquals(cssPageRectTop, other.cssPageRectTop)
             && FloatUtils.fuzzyEquals(cssPageRectRight, other.cssPageRectRight)
             && FloatUtils.fuzzyEquals(cssPageRectBottom, other.cssPageRectBottom)
             && FloatUtils.fuzzyEquals(viewportRectLeft, other.viewportRectLeft)
             && FloatUtils.fuzzyEquals(viewportRectTop, other.viewportRectTop)
             && FloatUtils.fuzzyEquals(viewportRectRight, other.viewportRectRight)
             && FloatUtils.fuzzyEquals(viewportRectBottom, other.viewportRectBottom)
@@ -308,13 +360,13 @@ public class ImmutableViewportMetrics {
     }
 
     @Override
     public String toString() {
         return "ImmutableViewportMetrics v=(" + viewportRectLeft + "," + viewportRectTop + ","
                 + viewportRectRight + "," + viewportRectBottom + ") p=(" + pageRectLeft + ","
                 + pageRectTop + "," + pageRectRight + "," + pageRectBottom + ") c=("
                 + cssPageRectLeft + "," + cssPageRectTop + "," + cssPageRectRight + ","
-                + cssPageRectBottom + ") m=(" + fixedLayerMarginLeft + ","
-                + fixedLayerMarginTop + "," + fixedLayerMarginRight + ","
-                + fixedLayerMarginBottom + ") z=" + zoomFactor;
+                + cssPageRectBottom + ") m=(" + marginLeft + ","
+                + marginTop + "," + marginRight + ","
+                + marginBottom + ") z=" + zoomFactor + ", rtl=" + isRTL;
     }
 }
--- a/mobile/android/base/gfx/JavaPanZoomController.java
+++ b/mobile/android/base/gfx/JavaPanZoomController.java
@@ -192,16 +192,21 @@ class JavaPanZoomController
     private void unregisterEventListener(String event) {
         mEventDispatcher.unregisterEventListener(event, this);
     }
 
     private void setState(PanZoomState state) {
         if (state != mState) {
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PanZoom:StateChange", state.toString()));
             mState = state;
+
+            // Let the target know we've finished with it (for now)
+            if (state == PanZoomState.NOTHING) {
+                mTarget.panZoomStopped();
+            }
         }
     }
 
     private ImmutableViewportMetrics getMetrics() {
         return mTarget.getViewportMetrics();
     }
 
     private void checkMainThread() {
@@ -701,18 +706,17 @@ class JavaPanZoomController
         }
 
         mX.startPan();
         mY.startPan();
         updatePosition();
     }
 
     private void scrollBy(float dx, float dy) {
-        ImmutableViewportMetrics scrolled = getMetrics().offsetViewportBy(dx, dy);
-        mTarget.setViewportMetrics(scrolled);
+        mTarget.scrollBy(dx, dy);
     }
 
     private void fling() {
         updatePosition();
 
         stopAnimationTimer();
 
         boolean stopped = stopped();
@@ -993,27 +997,27 @@ class JavaPanZoomController
         if (!constraints.getAllowZoom()) {
             // If allowZoom is false, clamp to the default zoom level.
             maxZoomFactor = minZoomFactor = constraints.getDefaultZoom();
         }
 
         // Ensure minZoomFactor keeps the page at least as big as the viewport.
         if (pageRect.width() > 0) {
             float pageWidth = pageRect.width() +
-              viewportMetrics.fixedLayerMarginLeft +
-              viewportMetrics.fixedLayerMarginRight;
+              viewportMetrics.marginLeft +
+              viewportMetrics.marginRight;
             float scaleFactor = viewport.width() / pageWidth;
             minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
             if (viewport.width() > pageWidth)
                 focusX = 0.0f;
         }
         if (pageRect.height() > 0) {
             float pageHeight = pageRect.height() +
-              viewportMetrics.fixedLayerMarginTop +
-              viewportMetrics.fixedLayerMarginBottom;
+              viewportMetrics.marginTop +
+              viewportMetrics.marginBottom;
             float scaleFactor = viewport.height() / pageHeight;
             minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
             if (viewport.height() > pageHeight)
                 focusY = 0.0f;
         }
 
         maxZoomFactor = Math.max(maxZoomFactor, minZoomFactor);
 
@@ -1038,43 +1042,31 @@ class JavaPanZoomController
 
     private class AxisX extends Axis {
         AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); }
         @Override
         public float getOrigin() { return getMetrics().viewportRectLeft; }
         @Override
         protected float getViewportLength() { return getMetrics().getWidth(); }
         @Override
-        protected float getPageStart() {
-            ImmutableViewportMetrics metrics = getMetrics();
-            return metrics.pageRectLeft - metrics.fixedLayerMarginLeft;
-        }
+        protected float getPageStart() { return getMetrics().pageRectLeft; }
         @Override
-        protected float getPageLength() {
-            ImmutableViewportMetrics metrics = getMetrics();
-            return metrics.getPageWidth() + metrics.fixedLayerMarginLeft + metrics.fixedLayerMarginRight;
-        }
+        protected float getPageLength() { return getMetrics().getPageWidthWithMargins(); }
     }
 
     private class AxisY extends Axis {
         AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); }
         @Override
         public float getOrigin() { return getMetrics().viewportRectTop; }
         @Override
         protected float getViewportLength() { return getMetrics().getHeight(); }
         @Override
-        protected float getPageStart() {
-            ImmutableViewportMetrics metrics = getMetrics();
-            return metrics.pageRectTop - metrics.fixedLayerMarginTop;
-        }
+        protected float getPageStart() { return getMetrics().pageRectTop; }
         @Override
-        protected float getPageLength() {
-            ImmutableViewportMetrics metrics = getMetrics();
-            return metrics.getPageHeight() + metrics.fixedLayerMarginTop + metrics.fixedLayerMarginBottom;
-        }
+        protected float getPageLength() { return getMetrics().getPageHeightWithMargins(); }
     }
 
     /*
      * Zooming
      */
     @Override
     public boolean onScaleBegin(SimpleScaleGestureDetector detector) {
         if (mState == PanZoomState.ANIMATED_ZOOM)
--- a/mobile/android/base/gfx/Layer.java
+++ b/mobile/android/base/gfx/Layer.java
@@ -2,16 +2,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/. */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.util.FloatUtils;
 
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 
 import java.nio.FloatBuffer;
 import java.util.concurrent.locks.ReentrantLock;
 
 public abstract class Layer {
     private final ReentrantLock mTransactionLock;
@@ -171,33 +172,36 @@ public abstract class Layer {
         dest[18] = cropRect.right / texWidth;
         dest[19] = cropRect.bottom / texHeight;
     }
 
     public static class RenderContext {
         public final RectF viewport;
         public final RectF pageRect;
         public final float zoomFactor;
+        public final PointF offset;
         public final int positionHandle;
         public final int textureHandle;
         public final FloatBuffer coordBuffer;
 
-        public RenderContext(RectF aViewport, RectF aPageRect, float aZoomFactor,
+        public RenderContext(RectF aViewport, RectF aPageRect, float aZoomFactor, PointF aOffset,
                              int aPositionHandle, int aTextureHandle, FloatBuffer aCoordBuffer) {
             viewport = aViewport;
             pageRect = aPageRect;
             zoomFactor = aZoomFactor;
+            offset = aOffset;
             positionHandle = aPositionHandle;
             textureHandle = aTextureHandle;
             coordBuffer = aCoordBuffer;
         }
 
         public boolean fuzzyEquals(RenderContext other) {
             if (other == null) {
                 return false;
             }
             return RectUtils.fuzzyEquals(viewport, other.viewport)
                 && RectUtils.fuzzyEquals(pageRect, other.pageRect)
-                && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor);
+                && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor)
+                && FloatUtils.fuzzyEquals(offset, other.offset);
         }
     }
 }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/LayerMarginsAnimator.java
@@ -0,0 +1,184 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.util.FloatUtils;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.animation.DecelerateInterpolator;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class LayerMarginsAnimator {
+    private static final String LOGTAG = "GeckoLayerMarginsAnimator";
+    private static final float MS_PER_FRAME = 1000.0f / 60.0f;
+    private static final long MARGIN_ANIMATION_DURATION = 250;
+
+    /* This rect stores the maximum value margins can grow to when scrolling */
+    private final RectF mMaxMargins;
+    /* If this boolean is true, scroll changes will not affect margins */
+    private boolean mMarginsPinned;
+    /* The timer that handles showing/hiding margins */
+    private Timer mAnimationTimer;
+    /* This interpolator is used for the above mentioned animation */
+    private final DecelerateInterpolator mInterpolator;
+    /* The GeckoLayerClient whose margins will be animated */
+    private final GeckoLayerClient mTarget;
+
+    public LayerMarginsAnimator(GeckoLayerClient aTarget) {
+        // Assign member variables from parameters
+        mTarget = aTarget;
+
+        // Create other member variables
+        mMaxMargins = new RectF();
+        mInterpolator = new DecelerateInterpolator();
+    }
+
+    /**
+     * Sets the maximum values for margins to grow to, in pixels.
+     */
+    public synchronized void setMaxMargins(float left, float top, float right, float bottom) {
+        mMaxMargins.set(left, top, right, bottom);
+
+        // Update the Gecko-side global for fixed viewport margins.
+        GeckoAppShell.sendEventToGecko(
+            GeckoEvent.createBroadcastEvent("Viewport:FixedMarginsChanged",
+                "{ \"top\" : " + top + ", \"right\" : " + right
+                + ", \"bottom\" : " + bottom + ", \"left\" : " + left + " }"));
+    }
+
+    private void animateMargins(final float left, final float top, final float right, final float bottom, boolean immediately) {
+        if (mAnimationTimer != null) {
+            mAnimationTimer.cancel();
+            mAnimationTimer = null;
+        }
+
+        if (immediately) {
+            ImmutableViewportMetrics newMetrics = mTarget.getViewportMetrics().setMargins(left, top, right, bottom);
+            mTarget.forceViewportMetrics(newMetrics, true, true);
+            return;
+        }
+
+        ImmutableViewportMetrics metrics = mTarget.getViewportMetrics();
+
+        final long startTime = SystemClock.uptimeMillis();
+        final float startLeft = metrics.marginLeft;
+        final float startTop = metrics.marginTop;
+        final float startRight = metrics.marginRight;
+        final float startBottom = metrics.marginBottom;
+
+        mAnimationTimer = new Timer("Margin Animation Timer");
+        mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                float progress = mInterpolator.getInterpolation(
+                    Math.min(1.0f, (SystemClock.uptimeMillis() - startTime)
+                                     / (float)MARGIN_ANIMATION_DURATION));
+
+                synchronized(mTarget.getLock()) {
+                    ImmutableViewportMetrics oldMetrics = mTarget.getViewportMetrics();
+                    ImmutableViewportMetrics newMetrics = oldMetrics.setMargins(
+                        FloatUtils.interpolate(startLeft, left, progress),
+                        FloatUtils.interpolate(startTop, top, progress),
+                        FloatUtils.interpolate(startRight, right, progress),
+                        FloatUtils.interpolate(startBottom, bottom, progress));
+                    PointF oldOffset = oldMetrics.getMarginOffset();
+                    PointF newOffset = newMetrics.getMarginOffset();
+                    newMetrics =
+                        newMetrics.offsetViewportByAndClamp(newOffset.x - oldOffset.x,
+                                                            newOffset.y - oldOffset.y);
+
+                    if (progress >= 1.0f) {
+                        mAnimationTimer.cancel();
+                        mAnimationTimer = null;
+
+                        // Force a redraw and update Gecko
+                        mTarget.forceViewportMetrics(newMetrics, true, true);
+                    } else {
+                        mTarget.forceViewportMetrics(newMetrics, false, false);
+                    }
+                }
+            }
+        }, 0, (int)MS_PER_FRAME);
+    }
+
+    /**
+     * Exposes the margin area by growing the margin components of the current
+     * metrics to the values set in setMaxMargins.
+     */
+    public synchronized void showMargins(boolean immediately) {
+        animateMargins(mMaxMargins.left, mMaxMargins.top, mMaxMargins.right, mMaxMargins.bottom, immediately);
+    }
+
+    public synchronized void hideMargins(boolean immediately) {
+        animateMargins(0, 0, 0, 0, immediately);
+    }
+
+    public void setMarginsPinned(boolean pin) {
+        mMarginsPinned = pin;
+    }
+
+    /*
+     * Taking maximum margins into account, offsets the margins and then the
+     * viewport origin and returns the modified metrics.
+     */
+    ImmutableViewportMetrics scrollBy(ImmutableViewportMetrics aMetrics, float aDx, float aDy) {
+        // Make sure to cancel any margin animations when scrolling begins
+        if (mAnimationTimer != null) {
+            mAnimationTimer.cancel();
+            mAnimationTimer = null;
+        }
+
+        float newMarginLeft = aMetrics.marginLeft;
+        float newMarginTop = aMetrics.marginTop;
+        float newMarginRight = aMetrics.marginRight;
+        float newMarginBottom = aMetrics.marginBottom;
+
+        // Only alter margins if the toolbar isn't pinned
+        if (!mMarginsPinned) {
+            RectF overscroll = aMetrics.getOverscroll();
+            if (aDx >= 0) {
+              // Scrolling right.
+              float marginDx = Math.max(0, aDx - overscroll.left);
+              newMarginLeft = aMetrics.marginLeft - Math.min(marginDx, aMetrics.marginLeft);
+              newMarginRight = aMetrics.marginRight + Math.min(marginDx, mMaxMargins.right - aMetrics.marginRight);
+
+              aDx -= aMetrics.marginLeft - newMarginLeft;
+            } else {
+              // Scrolling left.
+              float marginDx = Math.max(0, -aDx - overscroll.right);
+              newMarginLeft = aMetrics.marginLeft + Math.min(marginDx, mMaxMargins.left - aMetrics.marginLeft);
+              newMarginRight = aMetrics.marginRight - Math.min(marginDx, aMetrics.marginRight);
+
+              aDx -= aMetrics.marginLeft - newMarginLeft;
+            }
+
+            if (aDy >= 0) {
+              // Scrolling down.
+              float marginDy = Math.max(0, aDy - overscroll.top);
+              newMarginTop = aMetrics.marginTop - Math.min(marginDy, aMetrics.marginTop);
+              newMarginBottom = aMetrics.marginBottom + Math.min(marginDy, mMaxMargins.bottom - aMetrics.marginBottom);
+
+              aDy -= aMetrics.marginTop - newMarginTop;
+            } else {
+              // Scrolling up.
+              float marginDy = Math.max(0, -aDy - overscroll.bottom);
+              newMarginTop = aMetrics.marginTop + Math.min(marginDy, mMaxMargins.top - aMetrics.marginTop);
+              newMarginBottom = aMetrics.marginBottom - Math.min(marginDy, aMetrics.marginBottom);
+
+              aDy -= aMetrics.marginTop - newMarginTop;
+            }
+        }
+
+        return aMetrics.setMargins(newMarginLeft, newMarginTop, newMarginRight, newMarginBottom).offsetViewportBy(aDx, aDy);
+    }
+}
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -14,16 +14,17 @@ import org.mozilla.gecko.mozglue.DirectB
 
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.opengl.GLES20;
 import android.os.SystemClock;
 import android.util.Log;
 
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -272,31 +273,33 @@ public class LayerRenderer implements Ta
                 pixelBuffer.wait();
             } catch (InterruptedException ie) {
             }
             mPixelBuffer = null;
         }
         return pixelBuffer;
     }
 
-    private RenderContext createScreenContext(ImmutableViewportMetrics metrics) {
+    private RenderContext createScreenContext(ImmutableViewportMetrics metrics, PointF offset) {
         RectF viewport = new RectF(0.0f, 0.0f, metrics.getWidth(), metrics.getHeight());
-        RectF pageRect = new RectF(metrics.getPageRect());
-        return createContext(viewport, pageRect, 1.0f);
+        RectF pageRect = metrics.getPageRect();
+
+        return createContext(viewport, pageRect, 1.0f, offset);
     }
 
-    private RenderContext createPageContext(ImmutableViewportMetrics metrics) {
-        Rect viewport = RectUtils.round(metrics.getViewport());
+    private RenderContext createPageContext(ImmutableViewportMetrics metrics, PointF offset) {
+        RectF viewport = metrics.getViewport();
         RectF pageRect = metrics.getPageRect();
         float zoomFactor = metrics.zoomFactor;
-        return createContext(new RectF(viewport), pageRect, zoomFactor);
+
+        return createContext(new RectF(RectUtils.round(viewport)), pageRect, zoomFactor, offset);
     }
 
-    private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor) {
-        return new RenderContext(viewport, pageRect, zoomFactor, mPositionHandle, mTextureHandle,
+    private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor, PointF offset) {
+        return new RenderContext(viewport, pageRect, zoomFactor, offset, mPositionHandle, mTextureHandle,
                                  mCoordBuffer);
     }
 
     private void updateDroppedFrames(long frameStartTime) {
         int frameElapsedTime = (int)(SystemClock.uptimeMillis() - frameStartTime);
 
         /* Update the running statistics. */
         mFrameTimingsSum -= mFrameTimings[mCurrentFrame];
@@ -405,27 +408,37 @@ public class LayerRenderer implements Ta
         // A fixed snapshot of the viewport metrics that this frame is using to render content.
         private ImmutableViewportMetrics mFrameMetrics;
         // A rendering context for page-positioned layers, and one for screen-positioned layers.
         private RenderContext mPageContext, mScreenContext;
         // Whether a layer was updated.
         private boolean mUpdated;
         private final Rect mPageRect;
         private final Rect mAbsolutePageRect;
+        private final PointF mRenderOffset;
 
         public Frame(ImmutableViewportMetrics metrics) {
             mFrameMetrics = metrics;
-            mPageContext = createPageContext(metrics);
-            mScreenContext = createScreenContext(metrics);
+
+            // Work out the offset due to margins
+            Layer rootLayer = mView.getLayerClient().getRoot();
+            mRenderOffset = mFrameMetrics.getMarginOffset();
+            float scaleDiff = mFrameMetrics.zoomFactor / rootLayer.getResolution();
+            mRenderOffset.set(mRenderOffset.x * scaleDiff,
+                              mRenderOffset.y * scaleDiff);
 
-            Point origin = PointUtils.round(mFrameMetrics.getOrigin());
-            Rect pageRect = RectUtils.round(mFrameMetrics.getPageRect());
-            mAbsolutePageRect = new Rect(pageRect);
+            mPageContext = createPageContext(metrics, mRenderOffset);
+            mScreenContext = createScreenContext(metrics, mRenderOffset);
+
+            RectF pageRect = mFrameMetrics.getPageRect();
+            mAbsolutePageRect = RectUtils.round(pageRect);
+
+            PointF origin = mFrameMetrics.getOrigin();
             pageRect.offset(-origin.x, -origin.y);
-            mPageRect = pageRect;
+            mPageRect = RectUtils.round(pageRect);
         }
 
         private void setScissorRect() {
             Rect scissorRect = transformToScissorRect(mPageRect);
             GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
             GLES20.glScissor(scissorRect.left, scissorRect.top,
                              scissorRect.width(), scissorRect.height());
         }
@@ -433,18 +446,21 @@ public class LayerRenderer implements Ta
         private Rect transformToScissorRect(Rect rect) {
             IntSize screenSize = new IntSize(mFrameMetrics.getSize());
 
             int left = Math.max(0, rect.left);
             int top = Math.max(0, rect.top);
             int right = Math.min(screenSize.width, rect.right);
             int bottom = Math.min(screenSize.height, rect.bottom);
 
-            return new Rect(left, screenSize.height - bottom, right,
-                            (screenSize.height - bottom) + (bottom - top));
+            Rect scissorRect = new Rect(left, screenSize.height - bottom, right,
+                                        (screenSize.height - bottom) + (bottom - top));
+            scissorRect.offset(Math.round(-mRenderOffset.x), Math.round(-mRenderOffset.y));
+
+            return scissorRect;
         }
 
         /** This function is invoked via JNI; be careful when modifying signature. */
         public void beginDrawing() {
             mFrameStartTime = SystemClock.uptimeMillis();
 
             TextureReaper.get().reap();
             TextureGenerator.get().fill();
@@ -539,17 +555,19 @@ public class LayerRenderer implements Ta
             mBackgroundColor = mView.getBackgroundColor();
 
             // Clear the page area to the page background colour.
             setScissorRect();
             clear(mBackgroundColor);
             GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
 
             // Draw the drop shadow, if we need to.
-            if (!new RectF(mAbsolutePageRect).contains(mFrameMetrics.getViewport()))
+            RectF offsetAbsPageRect = new RectF(mAbsolutePageRect);
+            offsetAbsPageRect.offset(mRenderOffset.x, mRenderOffset.y);
+            if (!offsetAbsPageRect.contains(mFrameMetrics.getViewport()))
                 mShadowLayer.draw(mPageContext);
         }
 
         // Draws the layer the client added to us.
         void drawRootLayer() {
             Layer rootLayer = mView.getLayerClient().getRoot();
             if (rootLayer == null) {
                 return;
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -42,16 +42,17 @@ import java.nio.IntBuffer;
  *
  * Note that LayerView is accessed by Robocop via reflection.
  */
 public class LayerView extends FrameLayout {
     private static String LOGTAG = "GeckoLayerView";
 
     private GeckoLayerClient mLayerClient;
     private PanZoomController mPanZoomController;
+    private LayerMarginsAnimator mMarginsAnimator;
     private GLController mGLController;
     private InputConnectionHandler mInputConnectionHandler;
     private LayerRenderer mRenderer;
     /* Must be a PAINT_xxx constant */
     private int mPaintState;
     private int mBackgroundColor;
     private boolean mFullScreen;
 
@@ -95,16 +96,17 @@ public class LayerView extends FrameLayo
         mGLController = GLController.getInstance(this);
         mPaintState = PAINT_START;
         mBackgroundColor = Color.WHITE;
     }
 
     public void initializeView(EventDispatcher eventDispatcher) {
         mLayerClient = new GeckoLayerClient(getContext(), this, eventDispatcher);
         mPanZoomController = mLayerClient.getPanZoomController();
+        mMarginsAnimator = mLayerClient.getLayerMarginsAnimator();
 
         mRenderer = new LayerRenderer(this);
         mInputConnectionHandler = null;
 
         setFocusable(true);
         setFocusableInTouchMode(true);
 
         GeckoAccessibility.setDelegate(this);
@@ -202,16 +204,17 @@ public class LayerView extends FrameLayo
             SurfaceHolder holder = mSurfaceView.getHolder();
             holder.addCallback(new SurfaceListener());
             holder.setFormat(PixelFormat.RGB_565);
         }
     }
 
     public GeckoLayerClient getLayerClient() { return mLayerClient; }
     public PanZoomController getPanZoomController() { return mPanZoomController; }
+    public LayerMarginsAnimator getLayerMarginsAnimator() { return mMarginsAnimator; }
 
     public ImmutableViewportMetrics getViewportMetrics() {
         return mLayerClient.getViewportMetrics();
     }
 
     public void abortPanning() {
         if (mPanZoomController != null) {
             mPanZoomController.abortPanning();
@@ -231,16 +234,20 @@ public class LayerView extends FrameLayo
         mBackgroundColor = newColor;
         requestRender();
     }
 
     public void setZoomConstraints(ZoomConstraints constraints) {
         mLayerClient.setZoomConstraints(constraints);
     }
 
+    public void setIsRTL(boolean aIsRTL) {
+        mLayerClient.setIsRTL(aIsRTL);
+    }
+
     public void setInputConnectionHandler(InputConnectionHandler inputConnectionHandler) {
         mInputConnectionHandler = inputConnectionHandler;
         mLayerClient.forceRedraw();
     }
 
     @Override
     public Handler getHandler() {
         if (mInputConnectionHandler != null)
--- a/mobile/android/base/gfx/NinePatchTileLayer.java
+++ b/mobile/android/base/gfx/NinePatchTileLayer.java
@@ -70,18 +70,18 @@ public class NinePatchTileLayer extends 
         drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE,                                     /* 7 */
                   page.right, page.bottom, PATCH_SIZE, PATCH_SIZE);
     }
 
     private void drawPatch(RenderContext context, int textureX, int textureY,
                            float tileX, float tileY, float tileWidth, float tileHeight) {
         RectF viewport = context.viewport;
         float viewportHeight = viewport.height();
-        float drawX = tileX - viewport.left;
-        float drawY = viewportHeight - (tileY + tileHeight - viewport.top);
+        float drawX = tileX - viewport.left - context.offset.x;
+        float drawY = viewportHeight - (tileY + tileHeight - viewport.top) - context.offset.y;
 
         float[] coords = {
             //x, y, z, texture_x, texture_y
             drawX/viewport.width(), drawY/viewport.height(), 0,
             textureX/(float)TEXTURE_SIZE, textureY/(float)TEXTURE_SIZE,
 
             drawX/viewport.width(), (drawY+tileHeight)/viewport.height(), 0,
             textureX/(float)TEXTURE_SIZE, (textureY+PATCH_SIZE)/(float)TEXTURE_SIZE,
--- a/mobile/android/base/gfx/PanZoomTarget.java
+++ b/mobile/android/base/gfx/PanZoomTarget.java
@@ -11,15 +11,17 @@ import android.graphics.PointF;
 
 public interface PanZoomTarget {
     public ImmutableViewportMetrics getViewportMetrics();
     public ZoomConstraints getZoomConstraints();
     public boolean isFullScreen();
 
     public void setAnimationTarget(ImmutableViewportMetrics viewport);
     public void setViewportMetrics(ImmutableViewportMetrics viewport);
+    public void scrollBy(float dx, float dy);
+    public void panZoomStopped();
     /** This triggers an (asynchronous) viewport update/redraw. */
     public void forceRedraw();
 
     public boolean post(Runnable action);
     public Object getLock();
     public PointF convertViewPointToLayerPoint(PointF viewPoint);
 }
--- a/mobile/android/base/gfx/ScrollbarLayer.java
+++ b/mobile/android/base/gfx/ScrollbarLayer.java
@@ -183,16 +183,17 @@ public class ScrollbarLayer extends Tile
 
         GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
         GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
 
         float viewWidth = context.viewport.width();
         float viewHeight = context.viewport.height();
 
         mBarRectF.set(mBarRect.left, viewHeight - mBarRect.top, mBarRect.right, viewHeight - mBarRect.bottom);
+        mBarRectF.offset(context.offset.x, -context.offset.y);
 
         // We take a 1-pixel slice from the center of the image and scale it to become the bar
         fillRectCoordBuffer(mCoords, mBarRectF, viewWidth, viewHeight, mBodyTexCoords, mTexWidth, mTexHeight);
 
         // Get the buffer and handles from the context
         FloatBuffer coordBuffer = context.coordBuffer;
         int positionHandle = mPositionHandle;
         int textureHandle = mTextureHandle;
--- a/mobile/android/base/gfx/ViewTransform.java
+++ b/mobile/android/base/gfx/ViewTransform.java
@@ -8,20 +8,24 @@ package org.mozilla.gecko.gfx;
 public class ViewTransform {
     public float x;
     public float y;
     public float scale;
     public float fixedLayerMarginLeft;
     public float fixedLayerMarginTop;
     public float fixedLayerMarginRight;
     public float fixedLayerMarginBottom;
+    public float offsetX;
+    public float offsetY;
 
     public ViewTransform(float inX, float inY, float inScale) {
         x = inX;
         y = inY;
         scale = inScale;
         fixedLayerMarginLeft = 0;
         fixedLayerMarginTop = 0;
         fixedLayerMarginRight = 0;
         fixedLayerMarginBottom = 0;
+        offsetX = 0;
+        offsetY = 0;
     }
 }
 
--- a/mobile/android/base/mozglue/NativeZip.java
+++ b/mobile/android/base/mozglue/NativeZip.java
@@ -2,16 +2,18 @@
  * 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.mozglue;
 
 import java.io.InputStream;
 import java.nio.ByteBuffer;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
 
 public class NativeZip implements NativeReference {
     private static final int DEFLATE = 8;
     private static final int STORE = 0;
 
     private volatile long mObj;
     private InputStream mInput;
 
@@ -62,14 +64,21 @@ public class NativeZip implements Native
     }
 
     private static native long getZip(String path);
     private static native long getZipFromByteBuffer(ByteBuffer buffer);
     private static native void _release(long obj);
     private native InputStream _getInputStream(long obj, String path);
 
     private InputStream createInputStream(ByteBuffer buffer, int compression) {
-        if (compression != STORE) {
-            throw new IllegalArgumentException("Got compression " + compression + ", but expected 0 (STORE)!");
+        if (compression != STORE && compression != DEFLATE) {
+            throw new IllegalArgumentException("Unexpected compression: " + compression);
         }
-        return new ByteBufferInputStream(buffer, this);
+
+        InputStream input = new ByteBufferInputStream(buffer, this);
+        if (compression == DEFLATE) {
+            Inflater inflater = new Inflater(true);
+            input = new InflaterInputStream(input, inflater);
+        }
+
+        return input;
     }
 }
deleted file mode 100644
index f24b835a13202ec6c2a4c9e75a19b0d89801def5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index a1eb02fb088fa7347ad91268541692ee97c8b203..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 044c763fb1a7552b8abc40375fb27e8d1d307c8e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/mobile/android/base/resources/layout-land-v14/browser_toolbar.xml
+++ /dev/null
@@ -1,167 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.BrowserToolbarLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              xmlns:gecko="http://schemas.android.com/apk/res-auto"
-              android:id="@+id/browser_toolbar"
-              style="@style/BrowserToolbar">
-
-    <RelativeLayout android:id="@+id/address_bar"
-                    style="@style/AddressBar">
-
-        <ImageButton android:id="@+id/back"
-                     android:contentDescription="@string/back"
-                     style="@style/AddressBar.ImageButton.Unused"/>
-
-        <ImageButton style="@style/AddressBar.ImageButton.Forward"
-                     android:id="@+id/forward"/>
- 
-        <org.mozilla.gecko.BrowserToolbarBackground android:id="@+id/address_bar_bg"
-                                                    android:layout_width="fill_parent"
-                                                    android:layout_height="fill_parent"
-                                                    android:layout_marginRight="14dip"
-                                                    android:layout_alignParentTop="true"
-                                                    android:layout_alignParentRight="true"
-                                                    gecko:curveTowards="right"
-                                                    android:background="@drawable/address_bar_bg"/>
-
-         <FrameLayout style="@style/AddressBar.Button.Container"
-                      android:id="@+id/addressbar">
-
-            <Gecko.RelativeLayout android:id="@+id/awesome_bar"
-                                  style="@style/AddressBar.Button"
-                                  android:layout_centerVertical="true"
-                                  android:clickable="true"
-                                  android:focusable="true">
-
-                <ImageView android:id="@+id/awesome_bar_entry"
-                           style="@style/AddressBar.Button"
-                           android:duplicateParentState="true"
-                           android:layout_marginTop="4dp"
-                           android:layout_marginBottom="4dp"
-                           android:layout_marginRight="45dp"
-                           android:clickable="false"
-                           android:focusable="false"
-                           android:background="@drawable/address_bar_url"/>
-
-                <view class="org.mozilla.gecko.BrowserToolbar$RightEdge"
-                      android:id="@+id/awesome_bar_right_edge"
-                      style="@style/AddressBar.ImageButton"
-                      android:layout_width="50dp"
-                      android:layout_height="fill_parent"
-                      android:paddingTop="4dp"
-                      android:paddingBottom="4dp"
-                      android:layout_centerVertical="true"
-                      android:layout_alignParentRight="true"
-                      android:layout_marginRight="20dp"
-                      android:duplicateParentState="true"
-                      android:visibility="invisible"
-                      android:background="@drawable/address_bar_bg">
-
-                    <ImageView android:layout_width="50dp"
-                               android:layout_height="fill_parent"
-                               android:scaleType="fitXY"
-                               android:layout_marginLeft="-26dp"
-                               android:duplicateParentState="true"
-                               android:clickable="false"
-                               android:focusable="false"
-                               android:src="@drawable/address_bar_url"/>
-
-                </view>
-
-            </Gecko.RelativeLayout>
-
-            <LinearLayout style="@style/AddressBar.Button"
-                          android:paddingRight="45dp"
-                          android:orientation="horizontal">
-
-                <ImageButton android:id="@+id/favicon"
-                             style="@style/AddressBar.ImageButton"
-                             android:layout_width="@dimen/browser_toolbar_favicon_size"
-                             android:layout_height="fill_parent"
-                             android:scaleType="fitCenter"
-                             android:paddingLeft="8dip"
-                             android:layout_marginRight="4dip"
-                             android:layout_gravity="center_vertical"
-                             android:src="@drawable/favicon"/>
-
-                <ImageButton android:id="@+id/site_security"
-                             style="@style/AddressBar.ImageButton"
-                             android:layout_width="@dimen/browser_toolbar_lock_width"
-                             android:scaleType="fitCenter"
-                             android:layout_marginLeft="-4dip"
-                             android:paddingLeft="1dp"
-                             android:paddingRight="1dp"
-                             android:src="@drawable/site_security_level"
-                             android:contentDescription="@string/site_security"
-                             android:visibility="gone"/>
-
-                <Gecko.TextView android:id="@+id/awesome_bar_title"
-                                style="@style/AddressBar.Button"
-                                android:layout_width="fill_parent"
-                                android:layout_height="fill_parent"
-                                android:layout_weight="1.0"
-                                android:singleLine="true"
-                                android:paddingRight="8dp"
-                                android:textColor="@color/awesome_bar_title"
-                                android:textColorHint="@color/awesome_bar_title_hint"
-                                android:gravity="center_vertical|left"
-                                android:hint="@string/awesomebar_default_text"
-                                android:layout_gravity="center_vertical"
-                                gecko:autoUpdateTheme="false"/>
-
-                <ImageButton android:id="@+id/reader"
-                             style="@style/AddressBar.ImageButton.Icon"
-                             android:src="@drawable/reader"
-                             android:contentDescription="@string/reader"
-                             android:visibility="gone"/>
-
-                <ImageButton android:id="@+id/stop"
-                             style="@style/AddressBar.ImageButton.Icon"
-                             android:src="@drawable/urlbar_stop"
-                             android:contentDescription="@string/stop"
-                             android:visibility="gone"/>
-
-            </LinearLayout>
-
-        </FrameLayout>
-
-        <LinearLayout android:id="@+id/menu_items"
-                      android:layout_width="0dip"
-                      android:layout_height="0dip"
-                      android:visibility="gone"/>
-
-        <Gecko.ShapedButton android:id="@+id/menu"
-                            style="@style/AddressBar.ImageButton.Unused"/>
-
-        <Gecko.ShapedButton android:id="@+id/tabs"
-                            style="@style/AddressBar.ImageButton"
-                            android:layout_width="60dip"
-                            android:layout_alignParentRight="true"
-                            gecko:curveTowards="right"
-                            android:background="@drawable/shaped_button"
-                            android:gravity="center_vertical"
-                            android:src="@drawable/tabs_level"
-                            android:paddingLeft="30dip"
-                            android:paddingRight="10dip"/>
-
-        <Gecko.TextSwitcher android:id="@+id/tabs_count"
-                            style="@style/AddressBar.ImageButton"
-                            android:layout_width="39dip"
-                            android:layout_height="wrap_content"
-                            android:layout_marginTop="4.5dp"
-                            android:layout_alignRight="@id/tabs"
-                            android:gravity="center_horizontal"/>
-
-         <ImageView android:id="@+id/shadow"
-                    android:layout_width="fill_parent"
-                    android:layout_height="2dp"
-                    android:layout_alignParentBottom="true"
-                    android:background="@drawable/address_bar_bg_shadow_repeat"
-                    android:visibility="gone"/>
-
-    </RelativeLayout>
-
-</org.mozilla.gecko.BrowserToolbarLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/layout-land-v14/browser_toolbar_menu.xml
+++ /dev/null
@@ -1,174 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.BrowserToolbarLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              xmlns:gecko="http://schemas.android.com/apk/res-auto"
-              android:id="@+id/browser_toolbar"
-              style="@style/BrowserToolbar">
-
-    <RelativeLayout android:id="@+id/address_bar"
-                    style="@style/AddressBar">
-
-        <ImageButton android:id="@+id/back"
-                     android:contentDescription="@string/back"
-                     style="@style/AddressBar.ImageButton.Unused"/>
-
-        <ImageButton style="@style/AddressBar.ImageButton.Forward"
-                     android:id="@+id/forward"/>
-
-        <org.mozilla.gecko.BrowserToolbarBackground android:id="@+id/address_bar_bg"
-                                                    android:layout_width="fill_parent"
-                                                    android:layout_height="fill_parent"
-                                                    android:layout_marginRight="50dip"
-                                                    android:layout_alignParentTop="true"
-                                                    android:layout_alignParentRight="true"
-                                                    gecko:curveTowards="right"
-                                                    android:background="@drawable/address_bar_bg"/>
-        
-        <FrameLayout style="@style/AddressBar.Button.Container"
-                     android:id="@+id/addressbar">
-
-            <Gecko.RelativeLayout android:id="@+id/awesome_bar"
-                                  style="@style/AddressBar.Button"
-                                  android:layout_centerVertical="true"
-                                  android:clickable="true"
-                                  android:focusable="true">
-
-                <ImageView android:id="@+id/awesome_bar_entry"
-                           style="@style/AddressBar.Button"
-                           android:duplicateParentState="true"
-                           android:layout_marginTop="4dp"
-                           android:layout_marginBottom="4dp"
-                           android:layout_marginRight="80dp"
-                           android:clickable="false"
-                           android:focusable="false"
-                           android:background="@drawable/address_bar_url"/>
-
-                <view class="org.mozilla.gecko.BrowserToolbar$RightEdge"
-                      android:id="@+id/awesome_bar_right_edge"
-                      style="@style/AddressBar.ImageButton"
-                      android:layout_width="50dp"
-                      android:layout_height="fill_parent"
-                      android:paddingTop="4dp"
-                      android:paddingBottom="4dp"
-                      android:layout_centerVertical="true"
-                      android:layout_alignParentRight="true"
-                      android:layout_marginRight="55dp"
-                      android:duplicateParentState="true"
-                      android:visibility="invisible"
-                      android:background="@drawable/address_bar_bg">
-
-                    <ImageView android:layout_width="50dp"
-                               android:layout_height="fill_parent"
-                               android:scaleType="fitXY"
-                               android:layout_marginLeft="-26dp"
-                               android:duplicateParentState="true"
-                               android:clickable="false"
-                               android:focusable="false"
-                               android:src="@drawable/address_bar_url"/>
-
-                </view>
-
-            </Gecko.RelativeLayout>
-
-            <LinearLayout style="@style/AddressBar.Button"
-                          android:paddingRight="80dp"
-                          android:orientation="horizontal">
-
-                <ImageButton android:id="@+id/favicon"
-                             style="@style/AddressBar.ImageButton"
-                             android:layout_width="@dimen/browser_toolbar_favicon_size"
-                             android:layout_height="fill_parent"
-                             android:scaleType="fitCenter"
-                             android:paddingLeft="8dip"
-                             android:layout_marginRight="4dip"
-                             android:layout_gravity="center_vertical"
-                             android:src="@drawable/favicon"/>
-
-                <ImageButton android:id="@+id/site_security"
-                             style="@style/AddressBar.ImageButton"
-                             android:layout_width="@dimen/browser_toolbar_lock_width"
-                             android:scaleType="fitCenter"
-                             android:layout_marginLeft="-4dip"
-                             android:paddingLeft="1dp"
-                             android:paddingRight="1dp"
-                             android:src="@drawable/site_security_level"
-                             android:contentDescription="@string/site_security"
-                             android:visibility="gone"/>
-
-                <Gecko.TextView android:id="@+id/awesome_bar_title"
-                                style="@style/AddressBar.ImageButton"
-                                android:layout_width="fill_parent"
-                                android:layout_height="fill_parent"
-                                android:layout_weight="1.0"
-                                android:singleLine="true"
-                                android:paddingRight="8dp"
-                                android:textColor="@color/awesome_bar_title"
-                                android:textColorHint="@color/awesome_bar_title_hint"
-                                android:gravity="center_vertical|left"
-                                android:hint="@string/awesomebar_default_text"
-                                android:layout_gravity="center_vertical"
-                                gecko:autoUpdateTheme="false"/>
-
-                <ImageButton android:id="@+id/reader"
-                             style="@style/AddressBar.ImageButton.Icon"
-                             android:src="@drawable/reader"
-                             android:contentDescription="@string/reader"
-                             android:visibility="gone"/>
-
-                <ImageButton android:id="@+id/stop"
-                             style="@style/AddressBar.ImageButton.Icon"
-                             android:src="@drawable/urlbar_stop"
-                             android:contentDescription="@string/stop"
-                             android:visibility="gone"/>
-
-            </LinearLayout>
-
-        </FrameLayout>
-
-        <LinearLayout android:id="@+id/menu_items"
-                      android:layout_width="0dip"
-                      android:layout_height="0dip"
-                      android:visibility="gone"/>
-
-        <Gecko.ShapedButton android:id="@+id/menu"
-                            style="@style/AddressBar.ImageButton"
-                            android:layout_width="40dip"
-                            android:layout_alignParentRight="true"
-                            android:gravity="center_vertical"
-                            android:src="@drawable/menu_level"
-                            android:contentDescription="@string/menu"
-                            android:background="@drawable/shaped_button"/>
-
-        <Gecko.ShapedButton android:id="@+id/tabs"
-                            style="@style/AddressBar.ImageButton"
-                            android:layout_width="60dip"
-                            android:layout_marginRight="40dip"
-                            android:layout_alignParentRight="true"
-                            gecko:curveTowards="right"
-                            android:background="@drawable/shaped_button"
-                            android:gravity="center_vertical"
-                            android:src="@drawable/tabs_level"
-                            android:paddingLeft="30dip"
-                            android:paddingRight="10dip"/>
-
-        <Gecko.TextSwitcher android:id="@+id/tabs_count"
-                            style="@style/AddressBar.ImageButton"
-                            android:layout_width="39dip"
-                            android:layout_height="wrap_content"
-                            android:layout_marginTop="4.5dp"
-                            android:layout_alignRight="@id/tabs"
-                            android:gravity="center_horizontal"/>
-
-         <ImageView android:id="@+id/shadow"
-                    android:layout_width="fill_parent"
-                    android:layout_height="2dp"
-                    android:layout_alignParentBottom="true"
-                    android:background="@drawable/address_bar_bg_shadow_repeat"
-                    android:visibility="gone"/>
-
-    </RelativeLayout>
-
-</org.mozilla.gecko.BrowserToolbarLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/values-land-v14/dimens.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
-    <dimen name="browser_toolbar_height">40dp</dimen>
-    <dimen name="browser_toolbar_button_padding">8dp</dimen>
-    <dimen name="browser_toolbar_icon_width">46dp</dimen>
-    <dimen name="tabs_counter_size">18sp</dimen>
-    <dimen name="tabs_panel_indicator_width">80dp</dimen>
-
-</resources>
--- a/mobile/android/base/widget/AddonsSection.java
+++ b/mobile/android/base/widget/AddonsSection.java
@@ -5,32 +5,34 @@
 
 package org.mozilla.gecko.widget;
 
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.Favicons;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UiAsyncTask;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.text.SpannableString;
 import android.text.style.TextAppearanceSpan;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.TextView;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.zip.ZipEntry;
@@ -133,19 +135,19 @@ public class AddonsSection extends About
         } catch (MalformedURLException e) {
             // Defaults to pageUrl = iconUrl in case of error
         }
 
         return pageUrl;
     }
 
     public void readRecommendedAddons() {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        new UiAsyncTask<Void, Void, JSONArray>(ThreadUtils.getBackgroundHandler()) {
             @Override
-            public void run() {
+            public JSONArray doInBackground(Void... params) {
                 final String addonsFilename = "recommended-addons.json";
                 String jsonString;
                 try {
                     jsonString = mActivity.getProfile().readFile(addonsFilename);
                 } catch (IOException ioe) {
                     Log.i(LOGTAG, "filestream is null");
                     jsonString = readFromZipFile(addonsFilename);
                 }
@@ -154,73 +156,77 @@ public class AddonsSection extends About
                 if (jsonString != null) {
                     try {
                         addonsArray = new JSONObject(jsonString).getJSONArray("addons");
                     } catch (JSONException e) {
                         Log.i(LOGTAG, "error reading json file", e);
                     }
                 }
 
-                final JSONArray array = addonsArray;
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            if (array == null || array.length() == 0) {
-                                hide();
-                                return;
-                            }
+                return addonsArray;
+            }
+
+            @Override
+            public void onPostExecute(JSONArray addons) {
+                if (addons == null || addons.length() == 0) {
+                    hide();
+                    return;
+                }
 
-                            for (int i = 0; i < array.length(); i++) {
-                                JSONObject jsonobj = array.getJSONObject(i);
-                                String name = jsonobj.getString("name");
-                                String version = jsonobj.getString("version");
-                                String text = name + " " + version;
-
-                                SpannableString spannable = new SpannableString(text);
-                                spannable.setSpan(sSubTitleSpan, name.length() + 1, text.length(), 0);
+                try {
+                    for (int i = 0; i < addons.length(); i++) {
+                        View addonView = createAddonView(addons.getJSONObject(i), getItemsContainer());
+                        addItem(addonView);
+                    }
+                } catch (JSONException e) {
+                    Log.e(LOGTAG, "Error reading JSON", e);
+                    return;
+                }
 
-                                final TextView row = (TextView) LayoutInflater.from(mContext).inflate(R.layout.abouthome_addon_row, getItemsContainer(), false);
-                                row.setText(spannable, TextView.BufferType.SPANNABLE);
-
-                                Drawable drawable = mContext.getResources().getDrawable(R.drawable.ic_addons_empty);
-                                drawable.setBounds(sIconBounds);
-                                row.setCompoundDrawables(drawable, null, null, null);
-
-                                String iconUrl = jsonobj.getString("iconURL");
-                                String pageUrl = getPageUrlFromIconUrl(iconUrl);
+                show();
+            }
+        }.execute();
+    }
 
-                                final String homepageUrl = jsonobj.getString("homepageURL");
-                                row.setOnClickListener(new View.OnClickListener() {
-                                    @Override
-                                    public void onClick(View v) {
-                                        if (mUriLoadListener != null)
-                                            mUriLoadListener.onAboutHomeUriLoad(homepageUrl);
-                                    }
-                                });
-                                row.setOnKeyListener(GamepadUtils.getClickDispatcher());
+    View createAddonView(JSONObject addonJSON, ViewGroup parent) throws JSONException {
+        String name = addonJSON.getString("name");
+        String version = addonJSON.getString("version");
+        String text = name + " " + version;
+
+        SpannableString spannable = new SpannableString(text);
+        spannable.setSpan(sSubTitleSpan, name.length() + 1, text.length(), 0);
+
+        final TextView row = (TextView) LayoutInflater.from(mContext).inflate(R.layout.abouthome_addon_row, getItemsContainer(), false);
+        row.setText(spannable, TextView.BufferType.SPANNABLE);
 
-                                Favicons favicons = Favicons.getInstance();
-                                favicons.loadFavicon(pageUrl, iconUrl, true,
-                                            new Favicons.OnFaviconLoadedListener() {
-                                    @Override
-                                    public void onFaviconLoaded(String url, Bitmap favicon) {
-                                        if (favicon != null) {
-                                            Drawable drawable = new BitmapDrawable(favicon);
-                                            drawable.setBounds(sIconBounds);
-                                            row.setCompoundDrawables(drawable, null, null, null);
-                                        }
-                                    }
-                                });
+        Drawable drawable = mContext.getResources().getDrawable(R.drawable.ic_addons_empty);
+        drawable.setBounds(sIconBounds);
+        row.setCompoundDrawables(drawable, null, null, null);
+
+        String iconUrl = addonJSON.getString("iconURL");
+        String pageUrl = getPageUrlFromIconUrl(iconUrl);
 
-                                addItem(row);
-                            }
-
-                            show();
-                        } catch (JSONException e) {
-                            Log.i(LOGTAG, "error reading json file", e);
-                        }
-                    }
-                });
+        final String homepageUrl = addonJSON.getString("homepageURL");
+        row.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mUriLoadListener != null)
+                    mUriLoadListener.onAboutHomeUriLoad(homepageUrl);
             }
         });
+        row.setOnKeyListener(GamepadUtils.getClickDispatcher());
+
+        Favicons favicons = Favicons.getInstance();
+        favicons.loadFavicon(pageUrl, iconUrl, true,
+                new Favicons.OnFaviconLoadedListener() {
+            @Override
+            public void onFaviconLoaded(String url, Bitmap favicon) {
+                if (favicon != null) {
+                    Drawable drawable = new BitmapDrawable(favicon);
+                    drawable.setBounds(sIconBounds);
+                    row.setCompoundDrawables(drawable, null, null, null);
+                }
+            }
+        });
+
+        return row;
     }
 }
--- a/mobile/android/base/widget/LastTabsSection.java
+++ b/mobile/android/base/widget/LastTabsSection.java
@@ -7,108 +7,135 @@ package org.mozilla.gecko.widget;
 
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SessionParser;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UiAsyncTask;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import java.util.ArrayList;
 
 public class LastTabsSection extends AboutHomeSection {
     private Context mContext;
 
+    private class TabInfo {
+        final String url;
+        final String title;
+        final Bitmap favicon;
+
+        TabInfo(String url, String title, Bitmap favicon) {
+            this.url = url;
+            this.title = title;
+            this.favicon = favicon;
+        }
+    }
+
     public LastTabsSection(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
     }
 
     public void readLastTabs() {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        new UiAsyncTask<Void, Void, ArrayList<TabInfo>>(ThreadUtils.getBackgroundHandler()) {
             @Override
-            public void run() {
+            protected ArrayList<TabInfo> doInBackground(Void... params) {
                 String jsonString = GeckoProfile.get(mContext).readSessionFile(true);
                 if (jsonString == null) {
                     // no previous session data
-                    return;
+                    return null;
                 }
 
-                final ArrayList<String> lastTabUrlsList = new ArrayList<String>();
+                final ArrayList<TabInfo> lastTabs = new ArrayList<TabInfo>();
                 new SessionParser() {
                     @Override
                     public void onTabRead(final SessionTab tab) {
                         final String url = tab.getSelectedUrl();
                         // don't show last tabs for about:home
                         if (url.equals("about:home")) {
                             return;
                         }
 
                         ContentResolver resolver = mContext.getContentResolver();
                         final Bitmap favicon = BrowserDB.getFaviconForUrl(resolver, url);
-                        lastTabUrlsList.add(url);
-
-                        LastTabsSection.this.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                View container = LayoutInflater.from(mContext).inflate(R.layout.abouthome_last_tabs_row, getItemsContainer(), false);
-                                ((TextView) container.findViewById(R.id.last_tab_title)).setText(tab.getSelectedTitle());
-                                ((TextView) container.findViewById(R.id.last_tab_url)).setText(tab.getSelectedUrl());
-                                if (favicon != null) {
-                                    ((ImageView) container.findViewById(R.id.last_tab_favicon)).setImageBitmap(favicon);
-                                }
-
-                                container.setOnClickListener(new View.OnClickListener() {
-                                    @Override
-                                    public void onClick(View v) {
-                                        int flags = Tabs.LOADURL_NEW_TAB;
-                                        if (Tabs.getInstance().getSelectedTab().isPrivate())
-                                            flags |= Tabs.LOADURL_PRIVATE;
-                                        Tabs.getInstance().loadUrl(url, flags);
-                                    }
-                                });
-                                container.setOnKeyListener(GamepadUtils.getClickDispatcher());
-
-                                addItem(container);
-                            }
-                        });
+                        final String title = tab.getSelectedTitle();
+                        lastTabs.add(new TabInfo(url, title, favicon));
                     }
                 }.parse(jsonString);
 
-                final int nu = lastTabUrlsList.size();
-                if (nu >= 1) {
-                    post(new Runnable() {
+                return lastTabs;
+            }
+
+            @Override
+            public void onPostExecute(final ArrayList<TabInfo> lastTabs) {
+                if (lastTabs == null) {
+                    return;
+                }
+
+                for (TabInfo tab : lastTabs) {
+                    final View tabView = createTabView(tab, getItemsContainer());
+                    addItem(tabView);
+                }
+
+                // If we have at least one tab from last time, show the
+                // container view.
+                final int numTabs = lastTabs.size();
+                if (numTabs > 1) {
+                    // If we have more than one tab from last time, show the
+                    // "Open all tabs" button
+                    showMoreText();
+                    setOnMoreTextClickListener(new View.OnClickListener() {
                         @Override
-                        public void run() {
-                            if (nu > 1) {
-                                showMoreText();
-                                setOnMoreTextClickListener(new View.OnClickListener() {
-                                    @Override
-                                    public void onClick(View v) {
-                                        int flags = Tabs.LOADURL_NEW_TAB;
-                                        if (Tabs.getInstance().getSelectedTab().isPrivate())
-                                            flags |= Tabs.LOADURL_PRIVATE;
-                                        for (String url : lastTabUrlsList) {
-                                            Tabs.getInstance().loadUrl(url, flags);
-                                        }
-                                    }
-                                });
-                            } else if (nu == 1) {
-                                hideMoreText();
+                        public void onClick(View v) {
+                            int flags = Tabs.LOADURL_NEW_TAB;
+                            if (Tabs.getInstance().getSelectedTab().isPrivate())
+                                flags |= Tabs.LOADURL_PRIVATE;
+                            for (TabInfo tab : lastTabs) {
+                                Tabs.getInstance().loadUrl(tab.url, flags);
                             }
-                            show();
                         }
                     });
+                    show();
+                } else if (numTabs == 1) {
+                    hideMoreText();
+                    show();
                 }
             }
+        }.execute();
+    }
+
+    View createTabView(final TabInfo tab, ViewGroup parent) {
+        final String url = tab.url;
+        final Bitmap favicon = tab.favicon;
+
+        View tabView = LayoutInflater.from(mContext).inflate(R.layout.abouthome_last_tabs_row, parent, false);
+        ((TextView) tabView.findViewById(R.id.last_tab_title)).setText(tab.title);
+        ((TextView) tabView.findViewById(R.id.last_tab_url)).setText(url);
+        if (favicon != null) {
+            ((ImageView) tabView.findViewById(R.id.last_tab_favicon)).setImageBitmap(favicon);
+        }
+
+        tabView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                int flags = Tabs.LOADURL_NEW_TAB;
+                if (Tabs.getInstance().getSelectedTab().isPrivate())
+                    flags |= Tabs.LOADURL_PRIVATE;
+                Tabs.getInstance().loadUrl(url, flags);
+            }
         });
+        tabView.setOnKeyListener(GamepadUtils.getClickDispatcher());
+
+        return tabView;
     }
 }
--- a/mobile/android/base/widget/PromoBox.java
+++ b/mobile/android/base/widget/PromoBox.java
@@ -106,40 +106,16 @@ public class PromoBox extends TextView i
 
         mContext = context;
         setOnClickListener(this);
 
         mTypes = new ArrayList<Type>();
         mTypes.add(new SyncType(R.string.abouthome_about_sync,
                             R.string.abouthome_sync_bold_name,
                             R.drawable.abouthome_promo_logo_sync));
-
-        mTypes.add(new Type(R.string.abouthome_about_apps,
-                            R.string.abouthome_apps_bold_name,
-                            R.drawable.abouthome_promo_logo_apps) {
-            @Override
-            public boolean canShow() {
-                final ContentResolver resolver = mContext.getContentResolver();
-                return !BrowserDB.isVisited(resolver, "https://marketplace.firefox.com/");
-            }
-            @Override
-            public void onClick(View v) {
-                Tabs.getInstance().loadUrl("https://marketplace.firefox.com/", Tabs.LOADURL_NEW_TAB);
-
-                // this isn't as good as being notified whenever this url is added to the database,
-                // if the user visits the marketplace through some other means, we'll have to wait
-                // until we are refreshed or restarted to get the correct value
-                v.postDelayed(new Runnable() {
-                    @Override
-                    public void run() {
-                        showRandomPromo();
-                    }
-                }, 5000);
-            }
-        });
     }
 
     @Override
     public void onClick(View v) {
         if (mType != null)
             mType.onClick(v);
     }
 
--- a/mobile/android/base/widget/RemoteTabsSection.java
+++ b/mobile/android/base/widget/RemoteTabsSection.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.widget;
 
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.TabsAccessor;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UiAsyncTask;
 
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.TextView;
 
@@ -51,32 +52,32 @@ public class RemoteTabsSection extends A
                 if (Tabs.getInstance().getSelectedTab().isPrivate())
                     flags |= Tabs.LOADURL_PRIVATE;
                 Tabs.getInstance().loadUrl((String) v.getTag(), flags);
             }
         };
     }
 
     public void loadRemoteTabs() {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        new UiAsyncTask<Void, Void, Boolean>(ThreadUtils.getBackgroundHandler()) {
             @Override
-            public void run() {
+            public Boolean doInBackground(Void... params) {
                 if (!SyncAccounts.syncAccountsExist(mActivity)) {
-                    post(new Runnable() {
-                        @Override
-                        public void run() {
-                            hide();
-                        }
-                    });
-                    return;
+                    return false;
                 }
+                TabsAccessor.getTabs(getContext(), NUMBER_OF_REMOTE_TABS, RemoteTabsSection.this);
+                return true;
+            }
 
-                TabsAccessor.getTabs(getContext(), NUMBER_OF_REMOTE_TABS, RemoteTabsSection.this);
+            public void onPostExecute(Boolean syncAccountsExist) {
+                if (!syncAccountsExist) {
+                    hide();
+                }
             }
-        });
+        }.execute();
     }
 
     @Override
     public void onQueryTabsComplete(List<TabsAccessor.RemoteTab> tabsList) {
         ArrayList<TabsAccessor.RemoteTab> tabs = new ArrayList<TabsAccessor.RemoteTab> (tabsList);
         if (tabs == null || tabs.size() == 0) {
             hide();
             return;
--- a/mobile/android/base/widget/TopSitesView.java
+++ b/mobile/android/base/widget/TopSitesView.java
@@ -189,56 +189,53 @@ public class TopSitesView extends GridVi
         int w = getColumnWidth(measuredWidth);
         ThumbnailHelper.getInstance().setThumbnailWidth(w);
         heightMeasureSpec = MeasureSpec.makeMeasureSpec((int)(w*ThumbnailHelper.THUMBNAIL_ASPECT_RATIO*numRows) + getPaddingTop() + getPaddingBottom(),
                                                              MeasureSpec.EXACTLY);
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
     public void loadTopSites() {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        final ContentResolver resolver = mContext.getContentResolver();
+        final Cursor oldCursor = (mTopSitesAdapter != null) ? mTopSitesAdapter.getCursor() : null;
+
+        new UiAsyncTask<Void, Void, Cursor>(ThreadUtils.getBackgroundHandler()) {
             @Override
-            public void run() {
-                final ContentResolver resolver = mContext.getContentResolver();
-
-                // Swap in the new cursor.
-                final Cursor oldCursor = (mTopSitesAdapter != null) ? mTopSitesAdapter.getCursor() : null;
-                final Cursor newCursor = BrowserDB.getTopSites(resolver, mNumberOfTopSites);
+            protected Cursor doInBackground(Void... params) {
+                return BrowserDB.getTopSites(resolver, mNumberOfTopSites);
+            }
 
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mTopSitesAdapter == null) {
-                            mTopSitesAdapter = new TopSitesCursorAdapter(mContext,
-                                                                         R.layout.abouthome_topsite_item,
-                                                                         newCursor,
-                                                                         new String[] { URLColumns.TITLE },
-                                                                         new int[] { R.id.title });
+            @Override
+            protected void onPostExecute(Cursor newCursor) {
+                if (mTopSitesAdapter == null) {
+                    mTopSitesAdapter = new TopSitesCursorAdapter(mContext,
+                                                                 R.layout.abouthome_topsite_item,
+                                                                 newCursor,
+                                                                 new String[] { URLColumns.TITLE },
+                                                                 new int[] { R.id.title });
 
-                            setAdapter(mTopSitesAdapter);
-                        } else {
-                            mTopSitesAdapter.changeCursor(newCursor);
-                        }
+                    setAdapter(mTopSitesAdapter);
+                } else {
+                    mTopSitesAdapter.changeCursor(newCursor);
+                }
 
-                        if (mTopSitesAdapter.getCount() > 0)
-                            loadTopSitesThumbnails(resolver);
+                if (mTopSitesAdapter.getCount() > 0)
+                    loadTopSitesThumbnails(resolver);
 
-                        // Free the old Cursor in the right thread now.
-                        if (oldCursor != null && !oldCursor.isClosed())
-                            oldCursor.close();
+                // Free the old Cursor in the right thread now.
+                if (oldCursor != null && !oldCursor.isClosed())
+                    oldCursor.close();
 
-                        // Even if AboutHome isn't necessarily entirely loaded if we
-                        // get here, for phones this is the part the user initially sees,
-                        // so it's the one we will care about for now.
-                        if (mLoadCompleteListener != null)
-                            mLoadCompleteListener.onAboutHomeLoadComplete();
-                    }
-                });
+                // Even if AboutHome isn't necessarily entirely loaded if we
+                // get here, for phones this is the part the user initially sees,
+                // so it's the one we will care about for now.
+                if (mLoadCompleteListener != null)
+                    mLoadCompleteListener.onAboutHomeLoadComplete();
             }
-        });
+        }.execute();
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
         if (mPendingThumbnails != null) {
             updateTopSitesThumbnails(mPendingThumbnails);
@@ -326,28 +323,33 @@ public class TopSitesView extends GridVi
             if (c != null)
                 c.close();
         }
 
         return thumbnails;
     }
 
     private void loadTopSitesThumbnails(final ContentResolver cr) {
-        final List<String> urls = getTopSitesUrls();
-        if (urls.size() == 0)
-            return;
-
         (new UiAsyncTask<Void, Void, Map<String, Bitmap> >(ThreadUtils.getBackgroundHandler()) {
             @Override
             public Map<String, Bitmap> doInBackground(Void... params) {
+                final List<String> urls = getTopSitesUrls();
+                if (urls.size() == 0) {
+                    return null;
+                }
+
                 return getThumbnailsFromCursor(BrowserDB.getThumbnailsForUrls(cr, urls));
             }
 
             @Override
             public void onPostExecute(Map<String, Bitmap> thumbnails) {
+                if (thumbnails == null) {
+                    return;
+                }
+
                 // If we're waiting for a layout to happen, the GridView may be
                 // stale, so store the pending thumbnails here. They will be
                 // shown on the next layout pass.
                 if (isLayoutRequested()) {
                     mPendingThumbnails = thumbnails;
                 } else {
                     updateTopSitesThumbnails(thumbnails);
                 }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -3457,16 +3457,18 @@ Tab.prototype = {
 
     if (aMetadata.defaultZoom > 0)
       aMetadata.defaultZoom *= scaleRatio;
     if (aMetadata.minZoom > 0)
       aMetadata.minZoom *= scaleRatio;
     if (aMetadata.maxZoom > 0)
       aMetadata.maxZoom *= scaleRatio;
 
+    aMetadata.isRTL = this.browser.contentDocument.documentElement.dir == "rtl";
+
     ViewportHandler.setMetadataForDocument(this.browser.contentDocument, aMetadata);
     this.updateViewportSize(gScreenWidth, aInitialLoad);
     this.sendViewportMetadata();
   },
 
   /** Update viewport when the metadata or the window size changes. */
   updateViewportSize: function updateViewportSize(aOldScreenWidth, aInitialLoad) {
     // When this function gets called on window resize, we must execute
@@ -3585,16 +3587,17 @@ Tab.prototype = {
   sendViewportMetadata: function sendViewportMetadata() {
     let metadata = this.metadata;
     sendMessageToJava({
       type: "Tab:ViewportMetadata",
       allowZoom: metadata.allowZoom,
       defaultZoom: metadata.defaultZoom || metadata.scaleRatio,
       minZoom: metadata.minZoom || 0,
       maxZoom: metadata.maxZoom || 0,
+      isRTL: metadata.isRTL,
       tabID: this.id
     });
   },
 
   setBrowserSize: function(aWidth, aHeight) {
     if (fuzzyEquals(this.browserWidth, aWidth) && fuzzyEquals(this.browserHeight, aHeight)) {
       return;
     }
@@ -5106,25 +5109,28 @@ var ViewportHandler = {
     maxScale = this.clamp(maxScale, minScale, kViewportMaxScale);
 
     if (autoSize) {
       // If initial scale is 1.0 and width is not set, assume width=device-width
       autoSize = (widthStr == "device-width" ||
                   (!widthStr && (heightStr == "device-height" || scale == 1.0)));
     }
 
+    let isRTL = aWindow.document.documentElement.dir == "rtl";
+
     return new ViewportMetadata({
       defaultZoom: scale,
       minZoom: minScale,
       maxZoom: maxScale,
       width: width,
       height: height,
       autoSize: autoSize,
       allowZoom: allowZoom,
-      isSpecified: hasMetaViewport
+      isSpecified: hasMetaViewport,
+      isRTL: isRTL
     });
   },
 
   clamp: function(num, min, max) {
     return Math.max(min, Math.min(max, num));
   },
 
   // The device-pixel-to-CSS-px ratio used to adjust meta viewport values.
@@ -5186,29 +5192,31 @@ function ViewportMetadata(aMetadata = {}
   this.height = ("height" in aMetadata) ? aMetadata.height : 0;
   this.defaultZoom = ("defaultZoom" in aMetadata) ? aMetadata.defaultZoom : 0;
   this.minZoom = ("minZoom" in aMetadata) ? aMetadata.minZoom : 0;
   this.maxZoom = ("maxZoom" in aMetadata) ? aMetadata.maxZoom : 0;
   this.autoSize = ("autoSize" in aMetadata) ? aMetadata.autoSize : false;
   this.allowZoom = ("allowZoom" in aMetadata) ? aMetadata.allowZoom : true;
   this.isSpecified = ("isSpecified" in aMetadata) ? aMetadata.isSpecified : false;
   this.scaleRatio = ViewportHandler.getScaleRatio();
+  this.isRTL = ("isRTL" in aMetadata) ? aMetadata.isRTL : false;
   Object.seal(this);
 }
 
 ViewportMetadata.prototype = {
   width: null,
   height: null,
   defaultZoom: null,
   minZoom: null,
   maxZoom: null,
   autoSize: null,
   allowZoom: null,
   isSpecified: null,
   scaleRatio: null,
+  isRTL: null,
 };
 
 
 /**
  * Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml
  */
 var PopupBlockerObserver = {
   onUpdatePageReport: function onUpdatePageReport(aEvent) {
--- a/mozglue/android/NSSBridge.cpp
+++ b/mozglue/android/NSSBridge.cpp
@@ -8,16 +8,23 @@
 #include "APKOpen.h"
 #ifdef ANDROID
 #include <jni.h>
 #include <android/log.h>
 #endif
 
 #include "ElfLoader.h"
 
+#ifdef MOZ_MEMORY
+// libc's free().
+extern "C" void __real_free(void *);
+#else
+#define __real_free(a) free(a)
+#endif
+
 #ifdef DEBUG
 #define LOG(x...) __android_log_print(ANDROID_LOG_INFO, "GeckoJNI", x)
 #else
 #define LOG(x...)
 #endif
 
 static bool initialized = false;
 
@@ -79,17 +86,18 @@ throwError(JNIEnv* jenv, const char * fu
     char *msg;
 
     PRErrorCode perr = f_PR_GetError();
     char * errString = f_PR_ErrorToString(perr, 0);
     asprintf(&msg, "%s returned error %d: %s\n", funcString, perr, errString);
     LOG("Throwing error: %s\n", msg);
 
     JNI_Throw(jenv, "java/lang/Exception", msg);
-    free(msg);
+    // msg is allocated by asprintf, it needs to be freed by libc.
+    __real_free(msg);
     LOG("Error thrown\n");
 }
 
 extern "C" NS_EXPORT jstring JNICALL
 Java_org_mozilla_gecko_NSSBridge_nativeEncrypt(JNIEnv* jenv, jclass,
                                                jstring jPath,
                                                jstring jValue)
 {
--- a/mozglue/android/SQLiteBridge.cpp
+++ b/mozglue/android/SQLiteBridge.cpp
@@ -6,16 +6,23 @@
 #include <stdio.h>
 #include <jni.h>
 #include <android/log.h>
 #include "dlfcn.h"
 #include "APKOpen.h"
 #include "ElfLoader.h"
 #include "SQLiteBridge.h"
 
+#ifdef MOZ_MEMORY
+// libc's free().
+extern "C" void __real_free(void *);
+#else
+#define __real_free(a) free(a)
+#endif
+
 #ifdef DEBUG
 #define LOG(x...) __android_log_print(ANDROID_LOG_INFO, "GeckoJNI", x)
 #else
 #define LOG(x...)
 #endif
 
 #define SQLITE_WRAPPER_INT(name) name ## _t f_ ## name;
 
@@ -145,17 +152,18 @@ Java_org_mozilla_gecko_sqlite_SQLiteBrid
 
     dbPath = jenv->GetStringUTFChars(jDb, NULL);
     rc = f_sqlite3_open(dbPath, &db);
     jenv->ReleaseStringUTFChars(jDb, dbPath);
     if (rc != SQLITE_OK) {
         asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db));
         LOG("Error in SQLiteBridge: %s\n", errorMsg);
         JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg);
-        free(errorMsg);
+        // errorMsg is allocated by asprintf, it needs to be freed by libc.
+        __real_free(errorMsg);
     } else {
       jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes);
     }
     f_sqlite3_close(db);
     return jCursor;
 }
 
 extern "C" NS_EXPORT jobject JNICALL
@@ -186,17 +194,18 @@ Java_org_mozilla_gecko_sqlite_SQLiteBrid
 
     dbPath = jenv->GetStringUTFChars(jDb, NULL);
     rc = f_sqlite3_open(dbPath, &db);
     jenv->ReleaseStringUTFChars(jDb, dbPath);
     if (rc != SQLITE_OK) {
         asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db));
         LOG("Error in SQLiteBridge: %s\n", errorMsg);
         JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg);
-        free(errorMsg);
+        // errorMsg is allocated by asprintf, it needs to be freed by libc.
+        __real_free(errorMsg);
     }
     return (jlong)db;
 }
 
 extern "C" NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_sqlite_SQLiteBridge_closeDatabase(JNIEnv* jenv, jclass,
                                                         jlong jDb)
 {
@@ -389,11 +398,12 @@ sqliteInternalCall(JNIEnv* jenv,
         goto error_close;
     }
 
     return jCursor;
 
 error_close:
     LOG("Error in SQLiteBridge: %s\n", errorMsg);
     JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg);
-    free(errorMsg);
+    // errorMsg is allocated by asprintf, it needs to be freed by libc.
+    __real_free(errorMsg);
     return jCursor;
 }
--- a/testing/mochitest/android.json
+++ b/testing/mochitest/android.json
@@ -114,16 +114,17 @@
  "content/media/test/test_media_selection.html": "",
  "content/media/test/test_playback.html": "",
  "content/media/test/test_seekLies.html": "TIMED_OUT",
  "content/media/test/test_seekable2.html": "",
  "content/media/test/test_too_many_elements.html": "bug 775227",
  "content/media/test/test_wave_data_s16.html": "TIMED_OUT",
  "content/media/test/test_wave_data_u8.html": "TIMED_OUT",
  "content/media/webspeech/synth/ipc/test/test_ipc.html": "bug 857673",
+ "content/media/webspeech/recognition/test/test_nested_eventloop.html": "",
  "content/smil/test/test_smilRepeatTiming.xhtml": "TIMED_OUT",
  "content/smil/test/test_smilExtDoc.xhtml": "",
  "content/xul/content/test/test_bug486990.xul": "TIMED_OUT",
  "docshell/test/navigation/test_bug13871.html": "RANDOM",
  "docshell/test/navigation/test_bug430723.html": "TIMED_OUT",
  "docshell/test/navigation/test_popup-navigates-children.html": "bug 783589",
  "docshell/test/navigation/test_sessionhistory.html": "RANDOM",
  "docshell/test/navigation/test_bug344861.html": "",
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -315,32 +315,57 @@ Tester.prototype = {
         // that all tests are done.
         if (window.gBrowser) {
           gBrowser.addTab();
           gBrowser.removeCurrentTab();
         }
 
         // Schedule GC and CC runs before finishing in order to detect
         // DOM windows leaked by our tests or the tested code.
-        Cu.schedulePreciseGC((function () {
-          let analyzer = new CCAnalyzer();
-          analyzer.run(function () {
-            for (let obj of analyzer.find("nsGlobalWindow ")) {
-              let m = obj.name.match(/^nsGlobalWindow #(\d+)/);
-              if (m && m[1] in this.openedWindows) {
-                let test = this.openedWindows[m[1]];
-                let msg = "leaked until shutdown [" + obj.name +
-                          " " + (this.openedURLs[m[1]] || "NULL") + "]";
-                test.addResult(new testResult(false, msg, "", false));
+
+        let checkForLeakedGlobalWindows = aCallback => {
+          Cu.schedulePreciseGC(() => {
+            let analyzer = new CCAnalyzer();
+            analyzer.run(() => {
+              let results = [];
+              for (let obj of analyzer.find("nsGlobalWindow ")) {
+                let m = obj.name.match(/^nsGlobalWindow #(\d+)/);
+                if (m && m[1] in this.openedWindows)
+                  results.push({ name: obj.name, url: m[1] });
               }
-            }
+              aCallback(results);
+            });
+          });
+        };
 
+        let reportLeaks = aResults => {
+          for (let result of aResults) {
+            let test = this.openedWindows[result.url];
+            let msg = "leaked until shutdown [" + result.name +
+                      " " + (this.openedURLs[result.url] || "NULL") + "]";
+            test.addResult(new testResult(false, msg, "", false));
+          }
+        };
+
+        checkForLeakedGlobalWindows(aResults => {
+          if (aResults.length == 0) {
             this.finish();
-          }.bind(this));
-        }).bind(this));
+            return;
+          }
+          // After the first check, if there are reported leaked windows, sleep
+          // for a while, to allow off-main-thread work to complete and free up
+          // main-thread objects.  Then check again.
+          setTimeout(() => {
+            checkForLeakedGlobalWindows(aResults => {
+              reportLeaks(aResults);
+              this.finish();
+            });
+          }, 1000);
+        });
+
         return;
       }
 
       this.currentTestIndex++;
       this.execTest();
     }).bind(this));
   },
 
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
+++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
@@ -85,16 +85,23 @@
 <!ENTITY cmd.enableAddon.label                "Enable">
 <!ENTITY cmd.enableAddon.accesskey            "E">
 <!ENTITY cmd.disableAddon.label               "Disable">
 <!ENTITY cmd.disableAddon.accesskey           "D">
 <!ENTITY cmd.enableTheme.label                "Wear Theme">
 <!ENTITY cmd.enableTheme.accesskey            "W">
 <!ENTITY cmd.disableTheme.label               "Stop Wearing Theme">
 <!ENTITY cmd.disableTheme.accesskey           "W">
+<!ENTITY cmd.askToActivate.label              "Ask to Activate">
+<!ENTITY cmd.askToActivate.tooltip            "Ask to use this add-on each time">
+<!ENTITY cmd.alwaysActivate.label             "Always Activate">
+<!ENTITY cmd.alwaysActivate.tooltip           "Always use this add-on">
+<!ENTITY cmd.neverActivate.label              "Never Activate">
+<!ENTITY cmd.neverActivate.tooltip            "Never use this add-on">
+<!ENTITY cmd.tristateMenu.tooltip             "Change when this add-on runs">
 <!ENTITY cmd.installAddon.label               "Install">
 <!ENTITY cmd.installAddon.accesskey           "I">
 <!ENTITY cmd.uninstallAddon.label             "Remove">
 <!ENTITY cmd.uninstallAddon.accesskey         "R">
 <!ENTITY cmd.showPreferencesWin.label         "Options">
 <!ENTITY cmd.showPreferencesWin.tooltip       "Change this add-on's options">
 <!ENTITY cmd.showPreferencesUnix.label        "Preferences">
 <!ENTITY cmd.showPreferencesUnix.tooltip      "Change this add-on's preferences">
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -2195,16 +2195,19 @@ this.AddonManager = {
   // Indicates that the Addon can be uninstalled.
   PERM_CAN_UNINSTALL: 1,
   // Indicates that the Addon can be enabled by the user.
   PERM_CAN_ENABLE: 2,
   // Indicates that the Addon can be disabled by the user.
   PERM_CAN_DISABLE: 4,
   // Indicates that the Addon can be upgraded.
   PERM_CAN_UPGRADE: 8,
+  // Indicates that the Addon can be set to be optionally enabled
+  // on a case-by-case basis.
+  PERM_CAN_ASK_TO_ACTIVATE: 16,
 
   // General descriptions of where items are installed.
   // Installed in this profile.
   SCOPE_PROFILE: 1,
   // Installed for all of this user's profiles.
   SCOPE_USER: 2,
   // Installed and owned by the application.
   SCOPE_APPLICATION: 4,
@@ -2212,16 +2215,20 @@ this.AddonManager = {
   SCOPE_SYSTEM: 8,
   // The combination of all scopes.
   SCOPE_ALL: 15,
 
   // 1-15 are different built-in views for the add-on type
   VIEW_TYPE_LIST: "list",
 
   TYPE_UI_HIDE_EMPTY: 16,
+  // Indicates that this add-on type supports the ask-to-activate state.
+  // That is, add-ons of this type can be set to be optionally enabled
+  // on a case-by-case basis.
+  TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32,
 
   // Constants for Addon.applyBackgroundUpdates.
   // Indicates that the Addon should not update automatically.
   AUTOUPDATE_DISABLE: 0,
   // Indicates that the Addon should update automatically only if
   // that's the global default.
   AUTOUPDATE_DEFAULT: 1,
   // Indicates that the Addon should update automatically.
@@ -2259,16 +2266,22 @@ this.AddonManager = {
   // an application change making an add-on incompatible. Doesn't include
   // add-ons that were pending being disabled the last time the application ran.
   STARTUP_CHANGE_DISABLED: "disabled",
   // Add-ons that were detected as enabled during startup, normally because of
   // an application change making an add-on compatible. Doesn't include
   // add-ons that were pending being enabled the last time the application ran.
   STARTUP_CHANGE_ENABLED: "enabled",
 
+  // Constants for the Addon.userDisabled property
+  // Indicates that the userDisabled state of this add-on is currently
+  // ask-to-activate. That is, it can be conditionally enabled on a
+  // case-by-case basis.
+  STATE_ASK_TO_ACTIVATE: "askToActivate",
+
 #ifdef MOZ_EM_DEBUG
   get __AddonManagerInternal__() {
     return AddonManagerInternal;
   },
 #endif
 
   getInstallForURL: function AM_getInstallForURL(aUrl, aCallback, aMimetype,
                                                  aHash, aName, aIcons,
--- a/toolkit/mozapps/extensions/PluginProvider.jsm
+++ b/toolkit/mozapps/extensions/PluginProvider.jsm
@@ -290,32 +290,68 @@ function PluginWrapper(aId, aName, aDesc
   this.__defineGetter__("name", function() aName);
   this.__defineGetter__("creator", function() null);
   this.__defineGetter__("description", function() safedesc);
   this.__defineGetter__("version", function() aTags[0].version);
   this.__defineGetter__("homepageURL", function() homepageURL);
 
   this.__defineGetter__("isActive", function() !aTags[0].blocklisted && !aTags[0].disabled);
   this.__defineGetter__("appDisabled", function() aTags[0].blocklisted);
-  this.__defineGetter__("userDisabled", function() aTags[0].disabled);
+
+  this.__defineGetter__("userDisabled", function() {
+    if (aTags[0].disabled)
+      return true;
+
+    if ((Services.prefs.getBoolPref("plugins.click_to_play") && aTags[0].clicktoplay) ||
+        this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE ||
+        this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE)
+      return AddonManager.STATE_ASK_TO_ACTIVATE;
+
+    return false;
+  });
+
   this.__defineSetter__("userDisabled", function(aVal) {
-    if (aTags[0].disabled == aVal)
-      return;
+    let previousVal = this.userDisabled;
+    if (aVal === previousVal)
+      return aVal;
 
     for (let tag of aTags) {
       if (aVal === true)
         tag.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
-      else
+      else if (aVal === false)
         tag.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+      else if (aVal == AddonManager.STATE_ASK_TO_ACTIVATE)
+        tag.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+    }
+
+    // If 'userDisabled' was 'true' and we're going to a state that's not
+    // that, we're enabling, so call those listeners.
+    if (previousVal === true && aVal !== true) {
+      AddonManagerPrivate.callAddonListeners("onEnabling", this, false);
+      AddonManagerPrivate.callAddonListeners("onEnabled", this);
     }
-    AddonManagerPrivate.callAddonListeners(aVal ? "onDisabling" : "onEnabling", this, false);
-    AddonManagerPrivate.callAddonListeners(aVal ? "onDisabled" : "onEnabled", this);
+
+    // If 'userDisabled' was not 'true' and we're going to a state where
+    // it is, we're disabling, so call those listeners.
+    if (previousVal !== true && aVal === true) {
+      AddonManagerPrivate.callAddonListeners("onDisabling", this, false);
+      AddonManagerPrivate.callAddonListeners("onDisabled", this);
+    }
+
+    // If the 'userDisabled' value involved AddonManager.STATE_ASK_TO_ACTIVATE,
+    // call the onPropertyChanged listeners.
+    if (previousVal == AddonManager.STATE_ASK_TO_ACTIVATE ||
+        aVal == AddonManager.STATE_ASK_TO_ACTIVATE) {
+      AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["userDisabled"]);
+    }
+
     return aVal;
   });
 
+
   this.__defineGetter__("blocklistState", function() {
     let bs = Cc["@mozilla.org/extensions/blocklist;1"].
              getService(Ci.nsIBlocklistService);
     return bs.getPluginBlocklistState(aTags[0]);
   });
 
   this.__defineGetter__("blocklistURL", function() {
     let bs = Cc["@mozilla.org/extensions/blocklist;1"].
@@ -409,20 +445,34 @@ function PluginWrapper(aId, aName, aDesc
 
   this.__defineGetter__("operationsRequiringRestart", function() {
     return AddonManager.OP_NEEDS_RESTART_NONE;
   });
 
   this.__defineGetter__("permissions", function() {
     let permissions = 0;
     if (!this.appDisabled) {
-      if (this.userDisabled)
+
+      if (this.userDisabled !== true)
+        permissions |= AddonManager.PERM_CAN_DISABLE;
+
+      let blocklistState = this.blocklistState;
+      let isCTPBlocklisted =
+        (blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE ||
+         blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);
+
+      if (this.userDisabled !== AddonManager.STATE_ASK_TO_ACTIVATE &&
+          (Services.prefs.getBoolPref("plugins.click_to_play") ||
+           isCTPBlocklisted)) {
+        permissions |= AddonManager.PERM_CAN_ASK_TO_ACTIVATE;
+      }
+
+      if (this.userDisabled !== false && !isCTPBlocklisted) {
         permissions |= AddonManager.PERM_CAN_ENABLE;
-      else
-        permissions |= AddonManager.PERM_CAN_DISABLE;
+      }
     }
     return permissions;
   });
 }
 
 PluginWrapper.prototype = {
   optionsType: AddonManager.OPTIONS_TYPE_INLINE_INFO,
   optionsURL: "chrome://mozapps/content/extensions/pluginPrefs.xul",
@@ -459,10 +509,11 @@ PluginWrapper.prototype = {
     if ("onUpdateFinished" in aListener)
       aListener.onUpdateFinished(this);
   }
 };
 
 AddonManagerPrivate.registerProvider(PluginProvider, [
   new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
                                     STRING_TYPE_NAME,
-                                    AddonManager.VIEW_TYPE_LIST, 6000)
+                                    AddonManager.VIEW_TYPE_LIST, 6000,
+                                    AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
 ]);
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -957,17 +957,19 @@ var gViewController = {
                      "", "chrome,centerscreen,modal", aAddon);
       }
     },
 
     cmd_enableItem: {
       isEnabled: function cmd_enableItem_isEnabled(aAddon) {
         if (!aAddon)
           return false;
-        return hasPermission(aAddon, "enable");
+        let addonType = AddonManager.addonTypes[aAddon.type];
+        return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+                hasPermission(aAddon, "enable"));
       },
       doCommand: function cmd_enableItem_doCommand(aAddon) {
         aAddon.userDisabled = false;
       },
       getTooltip: function cmd_enableItem_getTooltip(aAddon) {
         if (!aAddon)
           return "";
         if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE)
@@ -975,17 +977,19 @@ var gViewController = {
         return gStrings.ext.GetStringFromName("enableAddonTooltip");
       }
     },
 
     cmd_disableItem: {
       isEnabled: function cmd_disableItem_isEnabled(aAddon) {
         if (!aAddon)
           return false;
-        return hasPermission(aAddon, "disable");
+        let addonType = AddonManager.addonTypes[aAddon.type];
+        return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+                hasPermission(aAddon, "disable"));
       },
       doCommand: function cmd_disableItem_doCommand(aAddon) {
         aAddon.userDisabled = true;
       },
       getTooltip: function cmd_disableItem_getTooltip(aAddon) {
         if (!aAddon)
           return "";
         if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE)
@@ -1126,17 +1130,56 @@ var gViewController = {
       isEnabled: function cmd_contribute_isEnabled(aAddon) {
         if (!aAddon)
           return false;
         return ("contributionURL" in aAddon && aAddon.contributionURL);
       },
       doCommand: function cmd_contribute_doCommand(aAddon) {
         openURL(aAddon.contributionURL);
       }
-    }
+    },
+
+    cmd_askToActivateItem: {
+      isEnabled: function cmd_askToActivateItem_isEnabled(aAddon) {
+        if (!aAddon)
+          return false;
+        let addonType = AddonManager.addonTypes[aAddon.type];
+        return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+                hasPermission(aAddon, "ask_to_activate"));
+      },
+      doCommand: function cmd_askToActivateItem_doCommand(aAddon) {
+        aAddon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;
+      }
+    },
+
+    cmd_alwaysActivateItem: {
+      isEnabled: function cmd_alwaysActivateItem_isEnabled(aAddon) {
+        if (!aAddon)
+          return false;
+        let addonType = AddonManager.addonTypes[aAddon.type];
+        return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+                hasPermission(aAddon, "enable"));
+      },
+      doCommand: function cmd_alwaysActivateItem_doCommand(aAddon) {
+        aAddon.userDisabled = false;
+      }
+    },
+
+    cmd_neverActivateItem: {
+      isEnabled: function cmd_neverActivateItem_isEnabled(aAddon) {
+        if (!aAddon)
+          return false;
+        let addonType = AddonManager.addonTypes[aAddon.type];
+        return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+                hasPermission(aAddon, "disable"));
+      },
+      doCommand: function cmd_neverActivateItem_doCommand(aAddon) {
+        aAddon.userDisabled = true;
+      }
+    },
   },
 
   supportsCommand: function gVC_supportsCommand(aCommand) {
     return (aCommand in this.commands);
   },
 
   isCommandEnabled: function gVC_isCommandEnabled(aCommand) {
     if (!this.supportsCommand(aCommand))
@@ -2849,16 +2892,37 @@ var gDetailView = {
         errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link");
         errorLink.href = this._addon.blocklistURL;
         errorLink.hidden = false;
       } else {
         this.node.removeAttribute("notification");
       }
     }
 
+    let menulist = document.getElementById("detail-tristate-menulist");
+    let addonType = AddonManager.addonTypes[this._addon.type];
+    if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE &&
+        (hasPermission(this._addon, "ask_to_activate") ||
+         hasPermission(this._addon, "enable") ||
+         hasPermission(this._addon, "disable"))) {
+      let askItem = document.getElementById("detail-ask-to-activate-menuitem");
+      let alwaysItem = document.getElementById("detail-always-activate-menuitem");
+      let neverItem = document.getElementById("detail-never-activate-menuitem");
+      if (this._addon.userDisabled === true) {
+        menulist.selectedItem = neverItem;
+      } else if (this._addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
+        menulist.selectedItem = askItem;
+      } else {
+        menulist.selectedItem = alwaysItem;
+      }
+      menulist.hidden = false;
+    } else {
+      menulist.hidden = true;
+    }
+
     this.node.setAttribute("active", this._addon.isActive);
   },
 
   clearLoading: function gDetailView_clearLoading() {
     if (this._loadingTimer) {
       clearTimeout(this._loadingTimer);
       this._loadingTimer = null;
     }
@@ -3022,17 +3086,18 @@ var gDetailView = {
 
   onPropertyChanged: function gDetailView_onPropertyChanged(aProperties) {
     if (aProperties.indexOf("applyBackgroundUpdates") != -1) {
       this._autoUpdate.value = this._addon.applyBackgroundUpdates;
       let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
       document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
     }
 
-    if (aProperties.indexOf("appDisabled") != -1)
+    if (aProperties.indexOf("appDisabled") != -1 ||
+        aProperties.indexOf("userDisabled") != -1)
       this.updateState();
   },
 
   onExternalInstall: function gDetailView_onExternalInstall(aAddon, aExistingAddon, aNeedsRestart) {
     // Only care about upgrades for the currently displayed add-on
     if (!aExistingAddon || aExistingAddon.id != this._addon.id)
       return;
 
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -871,16 +871,37 @@
                             label="&cmd.enableAddon.label;"
                             oncommand="document.getBindingParent(this).userDisabled = false;"/>
                 <xul:button anonid="disable-btn" class="addon-control disable"
                             label="&cmd.disableAddon.label;"
                             oncommand="document.getBindingParent(this).userDisabled = true;"/>
                 <xul:button anonid="remove-btn" class="addon-control remove"
                             label="&cmd.uninstallAddon.label;"
                             oncommand="document.getBindingParent(this).uninstall();"/>
+                <xul:menulist anonid="tristate-menulist"
+                              class="addon-control"
+                              tooltiptext="&cmd.tristateMenu.tooltip;">
+                  <xul:menupopup>
+                    <xul:menuitem anonid="ask-to-activate-menuitem"
+                                  class="addon-control"
+                                  label="&cmd.askToActivate.label;"
+                                  tooltiptext="&cmd.askToActivate.tooltip;"
+                                  oncommand="document.getBindingParent(this).userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;"/>
+                    <xul:menuitem anonid="always-activate-menuitem"
+                                  class="addon-control"
+                                  label="&cmd.alwaysActivate.label;"
+                                  tooltiptext="&cmd.alwaysActivate.tooltip;"
+                                  oncommand="document.getBindingParent(this).userDisabled = false;"/>
+                    <xul:menuitem anonid="never-activate-menuitem"
+                                  class="addon-control"
+                                  label="&cmd.neverActivate.label;"
+                                  tooltiptext="&cmd.neverActivate.tooltip;"
+                                  oncommand="document.getBindingParent(this).userDisabled = true;"/>
+                  </xul:menupopup>
+                </xul:menulist>
               </xul:hbox>
             </xul:vbox>
           </xul:hbox>
         </xul:vbox>
       </xul:hbox>
     </content>
 
     <implementation>
@@ -953,16 +974,32 @@
       <field name="_dateUpdated">
         document.getAnonymousElementByAttribute(this, "anonid",
                                                 "date-updated");
       </field>
       <field name="_description">
         document.getAnonymousElementByAttribute(this, "anonid",
                                                 "description");
       </field>
+      <field name="_tristateMenulist">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "tristate-menulist");
+      </field>
+      <field name="_askToActivateMenuitem">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "ask-to-activate-menuitem");
+      </field>
+      <field name="_alwaysActivateMenuitem">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "always-activate-menuitem");
+      </field>
+      <field name="_neverActivateMenuitem">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "never-activate-menuitem");
+      </field>
       <field name="_preferencesBtn">
         document.getAnonymousElementByAttribute(this, "anonid",
                                                 "preferences-btn");
       </field>
       <field name="_enableBtn">
         document.getAnonymousElementByAttribute(this, "anonid",
                                                 "enable-btn");
       </field>
@@ -1235,32 +1272,53 @@
             } else {
               this.removeAttribute("notification");
             }
           }
 
           this._preferencesBtn.hidden = (!this.mAddon.optionsURL) ||
                                         this.mAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO;
 
-          if (this.hasPermission("enable")) {
-            this._enableBtn.hidden = false;
-            let tooltip = gViewController.commands["cmd_enableItem"]
-                                         .getTooltip(this.mAddon);
-            this._enableBtn.setAttribute("tooltiptext", tooltip);
+          let addonType = AddonManager.addonTypes[this.mAddon.type];
+          if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE &&
+              (this.hasPermission("ask_to_activate") ||
+               this.hasPermission("enable") ||
+               this.hasPermission("disable"))) {
+            this._enableBtn.disabled = true;
+            this._disableBtn.disabled = true;
+            this._askToActivateMenuitem.disabled = !this.hasPermission("ask_to_activate");
+            this._alwaysActivateMenuitem.disabled = !this.hasPermission("enable");
+            this._neverActivateMenuitem.disabled = !this.hasPermission("disable");
+            if (this.mAddon.userDisabled === true) {
+              this._tristateMenulist.selectedItem = this._neverActivateMenuitem;
+            } else if (this.mAddon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
+              this._tristateMenulist.selectedItem = this._askToActivateMenuitem;
+            } else {
+              this._tristateMenulist.selectedItem = this._alwaysActivateMenuitem;
+            }
+            this._tristateMenulist.disabled = false;
           } else {
-            this._enableBtn.hidden = true;
-          }
+            this._tristateMenulist.disabled = true;
+            if (this.hasPermission("enable")) {
+              this._enableBtn.hidden = false;
+              let tooltip = gViewController.commands["cmd_enableItem"]
+                                           .getTooltip(this.mAddon);
+              this._enableBtn.setAttribute("tooltiptext", tooltip);
+            } else {
+              this._enableBtn.hidden = true;
+            }
 
-          if (this.hasPermission("disable")) {
-            this._disableBtn.hidden = false;
-            let tooltip = gViewController.commands["cmd_disableItem"]
-                                         .getTooltip(this.mAddon);
-            this._disableBtn.setAttribute("tooltiptext", tooltip);
-          } else {
-            this._disableBtn.hidden = true;
+            if (this.hasPermission("disable")) {
+              this._disableBtn.hidden = false;
+              let tooltip = gViewController.commands["cmd_disableItem"]
+                                           .getTooltip(this.mAddon);
+              this._disableBtn.setAttribute("tooltiptext", tooltip);
+            } else {
+              this._disableBtn.hidden = true;
+            }
           }
 
           if (this.hasPermission("uninstall")) {
             this._removeBtn.hidden = false;
             let tooltip = gViewController.commands["cmd_uninstallItem"]
                                          .getTooltip(this.mAddon);
             this._removeBtn.setAttribute("tooltiptext", tooltip);
           } else {
@@ -1508,17 +1566,18 @@
         <body><![CDATA[
           this._updateState();
         ]]></body>
       </method>
 
       <method name="onPropertyChanged">
         <parameter name="aProperties"/>
         <body><![CDATA[
-          if (aProperties.indexOf("appDisabled") != -1)
+          if (aProperties.indexOf("appDisabled") != -1 ||
+              aProperties.indexOf("userDisabled") != -1)
             this._updateState();
         ]]></body>
       </method>
 
       <method name="onNoUpdateAvailable">
         <body><![CDATA[
           this._showStatus("none");
         ]]></body>
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -105,16 +105,19 @@
     <command id="cmd_enableItem"/>
     <command id="cmd_disableItem"/>
     <command id="cmd_installItem"/>
     <command id="cmd_purchaseItem"/>
     <command id="cmd_uninstallItem"/>
     <command id="cmd_cancelUninstallItem"/>
     <command id="cmd_cancelOperation"/>
     <command id="cmd_contribute"/>
+    <command id="cmd_askToActivateItem"/>
+    <command id="cmd_alwaysActivateItem"/>
+    <command id="cmd_neverActivateItem"/>
   </commandset>
 
   <keyset>
     <key id="focusSearch" key="&search.commandkey;" modifiers="accel"
          oncommand="gHeader.focusSearchBox();"/>
   </keyset>
 
   <!-- main header -->
@@ -614,16 +617,37 @@
                             accesskey="&cmd.uninstallAddon.accesskey;"
                             command="cmd_uninstallItem"/>
                     <button id="detail-purchase-btn" class="addon-control purchase"
                             command="cmd_purchaseItem"/>
                     <button id="detail-install-btn" class="addon-control install"
                             label="&cmd.installAddon.label;"
                             accesskey="&cmd.installAddon.accesskey;"
                             command="cmd_installItem"/>
+                    <menulist id="detail-tristate-menulist"
+                              crop="none" sizetopopup="always"
+                              tooltiptext="&cmd.tristateMenu.tooltip;">
+                      <menupopup>
+                        <menuitem id="detail-ask-to-activate-menuitem"
+                                  class="addon-control"
+                                  label="&cmd.askToActivate.label;"
+                                  tooltiptext="&cmd.askToActivate.tooltip;"
+                                  command="cmd_askToActivateItem"/>
+                        <menuitem id="detail-always-activate-menuitem"
+                                  class="addon-control"
+                                  label="&cmd.alwaysActivate.label;"
+                                  tooltiptext="&cmd.alwaysActivate.tooltip;"
+                                  command="cmd_alwaysActivateItem"/>
+                        <menuitem id="detail-never-activate-menuitem"
+                                  class="addon-control"
+                                  label="&cmd.neverActivate.label;"
+                                  tooltiptext="&cmd.neverActivate.tooltip;"
+                                  command="cmd_neverActivateItem"/>
+                      </menupopup>
+                    </menulist>
                   </hbox>
                 </vbox>
               </hbox>
             </vbox>
             <spacer flex="1"/>
           </hbox>
         </scrollbox>
 
--- a/toolkit/mozapps/extensions/test/browser/Makefile.in
+++ b/toolkit/mozapps/extensions/test/browser/Makefile.in
@@ -56,16 +56,17 @@ MOCHITEST_BROWSER_MAIN = \
   browser_updateid.js \
   browser_purchase.js \
   browser_openDialog.js \
   browser_types.js \
   browser_inlinesettings.js \
   browser_inlinesettings_info.js \
   browser_tabsettings.js \
   browser_pluginprefs.js \
+  browser_CTP_plugins.js \
   $(NULL)
 
 MOCHITEST_BROWSER_SECONDARY = \
   head.js \
   browser_addonrepository_performance.js \
   browser_bug557956.js \
   browser_bug616841.js \
   browser_checkAddonCompatibility.js \
@@ -101,16 +102,17 @@ MOCHITEST_BROWSER_RESOURCES = \
   browser_purchase.xml \
   discovery.html \
   signed_hotfix.rdf \
   signed_hotfix.xpi \
   unsigned_hotfix.rdf \
   unsigned_hotfix.xpi \
   more_options.xul \
   options.xul \
+  plugin_test.html \
   redirect.sjs \
   releaseNotes.xhtml \
   $(NULL)
 
 MOCHITEST_BROWSER_FILES_PARTS = $(foreach s,MAIN SECONDARY RESOURCES,MOCHITEST_BROWSER_$(s))
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js
@@ -0,0 +1,197 @@
+/* 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/. */
+
+const gHttpTestRoot = "http://127.0.0.1:8888/" + RELATIVE_DIR + "/";
+let gManagerWindow;
+let gTestPluginId;
+let gPluginBrowser;
+
+function test() {
+  waitForExplicitFinish();
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  let pluginTag = getTestPluginTag();
+  pluginTag.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+  open_manager("addons://list/plugin", part1);
+}
+
+function part1(aWindow) {
+  gManagerWindow = aWindow;
+  AddonManager.getAddonsByTypes(["plugin"], part2);
+}
+
+function part2(aPlugins) {
+  for (let plugin of aPlugins) {
+    if (plugin.name == "Test Plug-in") {
+      gTestPluginId = plugin.id;
+      break;
+    }
+  }
+  ok(gTestPluginId, "part2: Test Plug-in should exist");
+  AddonManager.getAddonByID(gTestPluginId, part3);
+}
+
+function part3(aTestPlugin) {
+  let pluginEl = get_addon_element(gManagerWindow, gTestPluginId);
+  pluginEl.parentNode.ensureElementIsVisible(pluginEl);
+  let enableButton = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "enable-btn");
+  is_element_hidden(enableButton, "part3: enable button should not be visible");
+  let disableButton = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "enable-btn");
+  is_element_hidden(disableButton, "part3: disable button should not be visible");
+  let menu = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "tristate-menulist");
+  is_element_visible(menu, "part3: tristate menu should be visible");
+  let askToActivateItem = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "ask-to-activate-menuitem");
+  is(menu.selectedItem, askToActivateItem, "part3: tristate menu should have 'Ask To Activate' selected");
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gPluginBrowser = gBrowser.selectedBrowser;
+  gPluginBrowser.addEventListener("PluginBindingAttached", part4, true, true);
+  gPluginBrowser.contentWindow.location = gHttpTestRoot + "plugin_test.html";
+}
+
+function part4() {
+  ok(PopupNotifications.getNotification("click-to-play-plugins", gPluginBrowser), "part4: should have a click-to-play notification");
+  gPluginBrowser.removeEventListener("PluginBindingAttached", part4);
+  gBrowser.removeCurrentTab();
+
+  let pluginEl = get_addon_element(gManagerWindow, gTestPluginId);
+  let menu = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "tristate-menulist");
+  let alwaysActivateItem = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "always-activate-menuitem");
+  menu.selectedItem = alwaysActivateItem;
+  alwaysActivateItem.doCommand();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gPluginBrowser = gBrowser.selectedBrowser;
+  gPluginBrowser.addEventListener("load", part5, true);
+  gPluginBrowser.contentWindow.location = gHttpTestRoot + "plugin_test.html";
+}
+
+function part5() {
+  let testPlugin = gPluginBrowser.contentDocument.getElementById("test");
+  ok(testPlugin, "part5: should have a plugin element in the page");
+  let objLoadingContent = testPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  let condition = function() objLoadingContent.activated;
+  waitForCondition(condition, part6, "part5: waited too long for plugin to activate");
+}
+
+function part6() {
+  let testPlugin = gPluginBrowser.contentDocument.getElementById("test");
+  ok(testPlugin, "part6: should have a plugin element in the page");
+  let objLoadingContent = testPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "part6: plugin should be activated");
+  gPluginBrowser.removeEventListener("load", part5);
+  gBrowser.removeCurrentTab();
+
+  let pluginEl = get_addon_element(gManagerWindow, gTestPluginId);
+  let menu = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "tristate-menulist");
+  let neverActivateItem = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "never-activate-menuitem");
+  menu.selectedItem = neverActivateItem;
+  neverActivateItem.doCommand();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gPluginBrowser = gBrowser.selectedBrowser;
+  gPluginBrowser.addEventListener("PluginBindingAttached", part7, true, true);
+  gPluginBrowser.contentWindow.location = gHttpTestRoot + "plugin_test.html";
+}
+
+function part7() {
+  ok(!PopupNotifications.getNotification("click-to-play-plugins", gPluginBrowser), "part7: should not have a click-to-play notification");
+  let testPlugin = gPluginBrowser.contentDocument.getElementById("test");
+  ok(testPlugin, "part7: should have a plugin element in the page");
+  let objLoadingContent = testPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(!objLoadingContent.activated, "part7: plugin should not be activated");
+
+  gPluginBrowser.removeEventListener("PluginBindingAttached", part7);
+  gBrowser.removeCurrentTab();
+
+  let pluginEl = get_addon_element(gManagerWindow, gTestPluginId);
+  let details = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "details-btn");
+  is_element_visible(details, "part7: details link should be visible");
+  EventUtils.synthesizeMouseAtCenter(details, {}, gManagerWindow);
+  wait_for_view_load(gManagerWindow, part8);
+}
+
+function part8() {
+  let enableButton = gManagerWindow.document.getElementById("detail-enable-btn");
+  is_element_hidden(enableButton, "part8: detail enable button should be hidden");
+  let disableButton = gManagerWindow.document.getElementById("detail-disable-btn");
+  is_element_hidden(disableButton, "part8: detail disable button should be hidden");
+  let menu = gManagerWindow.document.getElementById("detail-tristate-menulist");
+  is_element_visible(menu, "part8: detail tristate menu should be visible");
+  let neverActivateItem = gManagerWindow.document.getElementById("detail-never-activate-menuitem")
+  is(menu.selectedItem, neverActivateItem, "part8: tristate menu should have 'Never Activate' selected");
+
+  let alwaysActivateItem = gManagerWindow.document.getElementById("detail-always-activate-menuitem");
+  menu.selectedItem = alwaysActivateItem;
+  alwaysActivateItem.doCommand();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gPluginBrowser = gBrowser.selectedBrowser;
+  gPluginBrowser.addEventListener("load", part9, true);
+  gPluginBrowser.contentWindow.location = gHttpTestRoot + "plugin_test.html";
+}
+
+function part9() {
+  let testPlugin = gPluginBrowser.contentDocument.getElementById("test");
+  ok(testPlugin, "part9: should have a plugin element in the page");
+  let objLoadingContent = testPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  let condition = function() objLoadingContent.activated;
+  waitForCondition(condition, part10, "part9: waited too long for plugin to activate");
+}
+
+function part10() {
+  let testPlugin = gPluginBrowser.contentDocument.getElementById("test");
+  ok(testPlugin, "part10: should have a plugin element in the page");
+  let objLoadingContent = testPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "part10: plugin should be activated");
+  gPluginBrowser.removeEventListener("load", part9);
+  gBrowser.removeCurrentTab();
+
+  let menu = gManagerWindow.document.getElementById("detail-tristate-menulist");
+  let askToActivateItem = gManagerWindow.document.getElementById("detail-ask-to-activate-menuitem")
+  menu.selectedItem = askToActivateItem;
+  askToActivateItem.doCommand();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gPluginBrowser = gBrowser.selectedBrowser;
+  gPluginBrowser.addEventListener("PluginBindingAttached", part11, true, true);
+  gPluginBrowser.contentWindow.location = gHttpTestRoot + "plugin_test.html";
+}
+
+function part11() {
+  ok(PopupNotifications.getNotification("click-to-play-plugins", gPluginBrowser), "part11: should have a click-to-play notification");
+  gPluginBrowser.removeEventListener("PluginBindingAttached", part11);
+  gBrowser.removeCurrentTab();
+
+  let pluginTag = getTestPluginTag();
+  pluginTag.blocklisted = true; // causes appDisabled to be set
+  close_manager(gManagerWindow, function() {
+    open_manager("addons://list/plugin", part12);
+  });
+}
+
+function part12(aWindow) {
+  gManagerWindow = aWindow;
+  let pluginEl = get_addon_element(gManagerWindow, gTestPluginId);
+  pluginEl.parentNode.ensureElementIsVisible(pluginEl);
+  let menu = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "tristate-menulist");
+  is_element_hidden(menu, "part12: tristate menu should be hidden");
+
+  let details = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "details-btn");
+  EventUtils.synthesizeMouseAtCenter(details, {}, gManagerWindow);
+  wait_for_view_load(gManagerWindow, part13);
+}
+
+function part13() {
+  let menu = gManagerWindow.document.getElementById("detail-tristate-menulist");
+  is_element_hidden(menu, "part13: detail tristate menu should be hidden");
+
+  let pluginTag = getTestPluginTag();
+  pluginTag.blocklisted = false;
+  run_next_test();
+}
+
+function end_test() {
+  Services.prefs.clearUserPref("plugins.click_to_play");
+  let pluginTag = getTestPluginTag();
+  pluginTag.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+  close_manager(gManagerWindow, function() {
+    finish();
+  });
+}
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -1229,8 +1229,35 @@ MockInstall.prototype = {
         ok(false, "Test listener threw exception: " + e);
       }
     }
 
     return result;
   }
 };
 
+function waitForCondition(condition, nextTest, errorMsg) {
+  let tries = 0;
+  let interval = setInterval(function() {
+    if (tries >= 30) {
+      ok(false, errorMsg);
+      moveOn();
+    }
+    if (condition()) {
+      moveOn();
+    }
+    tries++;
+  }, 100);
+  let moveOn = function() { clearInterval(interval); nextTest(); };
+}
+
+function getTestPluginTag() {
+  let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+  let tags = ph.getPluginTags();
+
+  // Find the test plugin
+  for (let i = 0; i < tags.length; i++) {
+    if (tags[i].name == "Test Plug-in")
+      return tags[i];
+  }
+  ok(false, "Unable to find plugin");
+  return null;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/plugin_test.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"></head>
+<body>
+<object id="test" width=200 height=200 type="application/x-test"></object>
+</body>
+</html>
--- a/toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
@@ -135,18 +135,23 @@ function test_disable_blocklist() {
   gNextTest = null;
   Services.prefs.setBoolPref("extensions.blocklist.enabled", false);
   blocklistState = gBlocklistService.getPluginBlocklistState(plugin, "1", "1.9");
   do_check_neq(blocklistState, Components.interfaces.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE);
   do_check_neq(blocklistState, Components.interfaces.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);
   do_check_false(gPluginHost.isPluginClickToPlayForType("application/x-test"));
 
   // it should still be possible to make a plugin click-to-play via the pref
+  // and setting that plugin's enabled state to click-to-play
   Services.prefs.setBoolPref("plugins.click_to_play", true);
+  let previousEnabledState = plugin.enabledState;
+  plugin.enabledState = Components.interfaces.nsIPluginTag.STATE_CLICKTOPLAY;
   do_check_true(gPluginHost.isPluginClickToPlayForType("application/x-test"));
+  // clean up plugin state
+  plugin.enabledState = previousEnabledState;
 
   gServer.stop(do_test_finished);
 }
 
 // Observe "blocklist-updated" so we know when to advance to the next test
 function observer() {
   if (gNextTest)
     do_execute_soon(gNextTest);
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -2104,24 +2104,25 @@ AndroidBridge::SetPageRect(const gfx::Re
         return;
 
     client->SetPageRect(aCssPageRect);
 }
 
 void
 AndroidBridge::SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
                                 nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
-                                gfx::Margin& aFixedLayerMargins)
+                                gfx::Margin& aFixedLayerMargins, float& aOffsetX, float& aOffsetY)
 {
     AndroidGeckoLayerClient *client = mLayerClient;
     if (!client)
         return;
 
     client->SyncViewportInfo(aDisplayPort, aDisplayResolution, aLayersUpdated,
-                             aScrollOffset, aScaleX, aScaleY, aFixedLayerMargins);
+                             aScrollOffset, aScaleX, aScaleY, aFixedLayerMargins,
+                             aOffsetX, aOffsetY);
 }
 
 AndroidBridge::AndroidBridge()
   : mLayerClient(NULL)
 {
 }
 
 AndroidBridge::~AndroidBridge()
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -337,17 +337,17 @@ public:
     void GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo);
     void EnableNetworkNotifications();
     void DisableNetworkNotifications();
 
     void SetFirstPaintViewport(const nsIntPoint& aOffset, float aZoom, const nsIntRect& aPageRect, const gfx::Rect& aCssPageRect);
     void SetPageRect(const gfx::Rect& aCssPageRect);
     void SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
                           nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
-                          gfx::Margin& aFixedLayerMargins);
+                          gfx::Margin& aFixedLayerMargins, float& aOffsetX, float& aOffsetY);
 
     void AddPluginView(jobject view, const gfxRect& rect, bool isFullScreen);
     void RemovePluginView(jobject view, bool isFullScreen);
 
     // These methods don't use a ScreenOrientation because it's an
     // enum and that would require including the header which requires
     // include IPC headers which requires including basictypes.h which
     // requires a lot of changes...
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -107,16 +107,18 @@ jmethodID AndroidLayerRendererFrame::jEn
 jclass AndroidViewTransform::jViewTransformClass = 0;
 jfieldID AndroidViewTransform::jXField = 0;
 jfieldID AndroidViewTransform::jYField = 0;
 jfieldID AndroidViewTransform::jScaleField = 0;
 jfieldID AndroidViewTransform::jFixedLayerMarginLeft = 0;
 jfieldID AndroidViewTransform::jFixedLayerMarginTop = 0;
 jfieldID AndroidViewTransform::jFixedLayerMarginRight = 0;
 jfieldID AndroidViewTransform::jFixedLayerMarginBottom = 0;
+jfieldID AndroidViewTransform::jOffsetXField = 0;
+jfieldID AndroidViewTransform::jOffsetYField = 0;
 
 jclass AndroidProgressiveUpdateData::jProgressiveUpdateDataClass = 0;
 jfieldID AndroidProgressiveUpdateData::jXField = 0;
 jfieldID AndroidProgressiveUpdateData::jYField = 0;
 jfieldID AndroidProgressiveUpdateData::jWidthField = 0;
 jfieldID AndroidProgressiveUpdateData::jHeightField = 0;
 jfieldID AndroidProgressiveUpdateData::jScaleField = 0;
 jfieldID AndroidProgressiveUpdateData::jShouldAbortField = 0;
@@ -384,16 +386,18 @@ AndroidViewTransform::InitViewTransformC
 
     jXField = getField("x", "F");
     jYField = getField("y", "F");
     jScaleField = getField("scale", "F");
     jFixedLayerMarginLeft = getField("fixedLayerMarginLeft", "F");
     jFixedLayerMarginTop = getField("fixedLayerMarginTop", "F");
     jFixedLayerMarginRight = getField("fixedLayerMarginRight", "F");
     jFixedLayerMarginBottom = getField("fixedLayerMarginBottom", "F");
+    jOffsetXField = getField("offsetX", "F");
+    jOffsetYField = getField("offsetY", "F");
 }
 
 void
 AndroidProgressiveUpdateData::InitProgressiveUpdateDataClass(JNIEnv *jEnv)
 {
     initInit();
 
     jProgressiveUpdateDataClass = getClassGlobalRef("org/mozilla/gecko/gfx/ProgressiveUpdateData");
@@ -819,17 +823,17 @@ AndroidGeckoLayerClient::SetPageRect(con
     AutoLocalJNIFrame jniFrame(env, 0);
     return env->CallVoidMethod(wrapped_obj, jSetPageRect,
                                aCssPageRect.x, aCssPageRect.y, aCssPageRect.XMost(), aCssPageRect.YMost());
 }
 
 void
 AndroidGeckoLayerClient::SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
                                           nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
-                                          gfx::Margin& aFixedLayerMargins)
+                                          gfx::Margin& aFixedLayerMargins, float& aOffsetX, float& aOffsetY)
 {
     NS_ASSERTION(!isNull(), "SyncViewportInfo called on null layer client!");
     JNIEnv *env = GetJNIForThread();    // this is called on the compositor thread
     if (!env)
         return;
 
     AutoLocalJNIFrame jniFrame(env);
 
@@ -843,16 +847,19 @@ AndroidGeckoLayerClient::SyncViewportInf
     NS_ABORT_IF_FALSE(viewTransformJObj, "No view transform object!");
 
     AndroidViewTransform viewTransform;
     viewTransform.Init(viewTransformJObj);
 
     aScrollOffset = nsIntPoint(viewTransform.GetX(env), viewTransform.GetY(env));
     aScaleX = aScaleY = viewTransform.GetScale(env);
     viewTransform.GetFixedLayerMargins(env, aFixedLayerMargins);
+
+    aOffsetX = viewTransform.GetOffsetX(env);
+    aOffsetY = viewTransform.GetOffsetY(env);
 }
 
 bool
 AndroidGeckoLayerClient::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent,
                                                    const gfx::Rect& aDisplayPort,
                                                    float aDisplayResolution,
                                                    bool aDrawingCritical,
                                                    gfx::Rect& aViewport,
@@ -1090,16 +1097,32 @@ AndroidViewTransform::GetFixedLayerMargi
 
     aFixedLayerMargins.top = env->GetFloatField(wrapped_obj, jFixedLayerMarginTop);
     aFixedLayerMargins.right = env->GetFloatField(wrapped_obj, jFixedLayerMarginRight);
     aFixedLayerMargins.bottom = env->GetFloatField(wrapped_obj, jFixedLayerMarginBottom);
     aFixedLayerMargins.left = env->GetFloatField(wrapped_obj, jFixedLayerMarginLeft);
 }
 
 float
+AndroidViewTransform::GetOffsetX(JNIEnv *env)
+{
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jOffsetXField);
+}
+
+float
+AndroidViewTransform::GetOffsetY(JNIEnv *env)
+{
+    if (!env)
+        return 0.0f;
+    return env->GetFloatField(wrapped_obj, jOffsetYField);
+}
+
+float
 AndroidProg